Spring Cloud Zuul Filter 和熔断
转一篇很不错的关于Spring Cloud Zuul 相关用法的文章,基本包含常用的一些场景,另外附上实际项目中的熔断、打印请求日志和登录验证的实例。
原文地址:https://www.cnblogs.com/shihaiming/p/8489006.html ,作者:https://www.cnblogs.com/shihaiming/
1.服务熔断
package com.ftk.hjs.zuul.server.hystrix; import com.alibaba.fastjson.JSON;
import com.ftk.hjs.common.WebConstants;
import com.ftk.hjs.common.common.Response;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UrlPathHelper; import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream; @Component
public class ServiceFallbackProvider implements FallbackProvider { private static final Logger logger = LoggerFactory.getLogger(ServiceFallbackProvider.class);
@Autowired
private RouteLocator routeLocator;
@Autowired
private UrlPathHelper urlPathHelper; //服务id,可以用* 或者 null 代表所有服务都过滤
@Override
public String getRoute() {
return null;
} @Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK; //请求网关成功了,所以是ok
} @Override
public int getRawStatusCode() throws IOException {
return HttpStatus.OK.value();
} @Override
public String getStatusText() throws IOException {
return HttpStatus.OK.getReasonPhrase();
} @Override
public void close() { } @Override
public InputStream getBody() throws IOException {
RequestContext ctx = RequestContext.getCurrentContext();
Route route = route(ctx.getRequest());
logger.error(" >>>触发zuul-server断溶;zuulServletContextPath={{}}", route.getLocation());
Response response = new Response(false);
response.setMessage(WebConstants.SYSTEM_ERROR_MESSAGE);
return new ByteArrayInputStream(JSON.toJSONString(response).getBytes("UTF-8")); //返回前端的内容
} @Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); //设置头
return httpHeaders;
}
};
} //核心逻辑,获取请求路径,利用RouteLocator返回路由信息
protected Route route(HttpServletRequest request) {
String requestURI = urlPathHelper.getPathWithinApplication(request);
return routeLocator.getMatchingRoute(requestURI);
} }
2.打印日志的拦截器
package com.ftk.hjs.zuul.server.filter; import com.alibaba.fastjson.JSONObject;
import com.ftk.framework.redis.clients.collections.builder.RedisStrutureBuilder;
import com.ftk.framework.redis.clients.collections.keyValueRedisStructure;
import com.ftk.hjs.common.WebConstants;
import com.ftk.hjs.common.constant.RedisConstants;
import com.ftk.hjs.common.data.LoginUserData;
import com.ftk.hjs.common.utils.StringUtil;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.util.StreamUtils; import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset; public class PrintRequestLogFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(PrintRequestLogFilter.class); @Override
public String filterType() {
return FilterConstants.POST_TYPE;//要打印返回信息,必须得用"post"
} @Override
public int filterOrder() {
return 1;
} @Override
public boolean shouldFilter() {
return true;
} @Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
InputStream in = request.getInputStream();
String reqBbody = StreamUtils.copyToString(in, Charset.forName("UTF-8")); String principal = request.getHeader(WebConstants.TOKEN_KEY); if (StringUtil.isNotBlank(principal)) {
String userToken = RedisConstants.UserNP.USER_TOKEN_KEY + principal;
keyValueRedisStructure<String> structure = RedisStrutureBuilder.ofKeyValue(String.class)
.withNameSpace(RedisConstants.UserNP.USER_NAMESPACE)
.withttl(RedisConstants.UserNP.USER_TOKEN_EXPIRE).build();
String token = structure.get(userToken);
if (StringUtil.isNotBlank(token)) {
LoginUserData userData = JSONObject.parseObject(token, LoginUserData.class);
log.info("request token:{} , userNum:{} , mobilePhone:{} , channelNum:{}", principal, userData.getUserNum(), userData.getMobilePhone(), userData.getChannelNum());
} }
log.info("request url:{} , requestUrl:{}", request.getMethod(), request.getRequestURL().toString()); if (reqBbody != null) {
log.info("request body:{}", reqBbody);
}
String outBody = ctx.getResponseBody();
if (outBody != null) {
log.info("response body:{}", outBody);
}
ctx.getResponse().setContentType("text/html;charset=UTF-8");
ctx.setResponseBody(outBody);
} catch (IOException e) {
log.error(e.getMessage(), e);
} return null;
} }
3.登录验证
package com.ftk.hjs.zuul.server.filter; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ftk.framework.redis.clients.collections.MapStructure;
import com.ftk.framework.redis.clients.collections.builder.RedisStrutureBuilder;
import com.ftk.framework.redis.clients.collections.keyValueRedisStructure;
import com.ftk.hjs.common.WebConstants;
import com.ftk.hjs.common.common.Request;
import com.ftk.hjs.common.common.Response;
import com.ftk.hjs.common.constant.ErrCodeConstants;
import com.ftk.hjs.common.constant.RedisConstants;
import com.ftk.hjs.common.data.LoginUserData;
import com.ftk.hjs.common.utils.StringUtil;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.http.ServletInputStreamWrapper;
import lombok.Data;
import lombok.ToString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StreamUtils;
import org.springframework.web.util.UrlPathHelper; import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.nio.charset.Charset;
import java.util.*; import static com.alibaba.fastjson.JSON.parseObject; /**
* 登录验证
* Created by Frank on 2016/12/8.
*/
public class LoginFilter extends ZuulFilter { private static Logger log = LoggerFactory.getLogger(LoginFilter.class); private final RouteLocator routeLocator;
private final UrlPathHelper urlPathHelper; private static List<String> oldServers = new ArrayList<>();
private static List<String> newServers = new ArrayList<>(); private List<String> excludeSuffixs = new ArrayList<>(); public LoginFilter(RouteLocator routeLocator, UrlPathHelper urlPathHelper) {
this.routeLocator = routeLocator;
this.urlPathHelper = urlPathHelper;
oldServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_FINANCIAL);
oldServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_COMMON);
oldServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_FUND);
oldServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_INSURANCE); newServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_BANK);
newServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_MESSAGE);
newServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_USER);
newServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_ROUT); excludeSuffixs.addAll(Arrays.asList(".png", ".js", ".css"));
} //核心逻辑,获取请求路径,利用RouteLocator返回路由信息
protected Route route(HttpServletRequest request) {
String requestURI = urlPathHelper.getPathWithinApplication(request);
return routeLocator.getMatchingRoute(requestURI);
} @Override
public String filterType() {
return "pre";
} @Override
public int filterOrder() {
return 0;
} @Override
public boolean shouldFilter() {
return true;
} @Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String userNum = null;
String body = "";
String principal = request.getHeader(WebConstants.TOKEN_KEY);
String osTypeKey = request.getHeader(WebConstants.OSTYPE_KEY);
String channelNum = request.getHeader(WebConstants.CHANNEL_NUM);
ctx.addZuulRequestHeader(WebConstants.OSTYPE_KEY, osTypeKey);
ctx.addZuulRequestHeader(WebConstants.CHANNEL_NUM, channelNum);
try {
body = StreamUtils.copyToString(request.getInputStream(), Charset.forName("UTF-8"));
// log.info(">>> zuul LoginFilter body={}", body);
if(StringUtil.isBlank(principal)){
principal = request.getParameter(WebConstants.TOKEN_KEY);
}
if (StringUtil.isNotBlank(principal)) {
String userToken = RedisConstants.UserNP.USER_TOKEN_KEY + principal;
//如果用户token为空,则肯定是没有登录
if (StringUtil.isBlank(userToken)) {
return null;
} keyValueRedisStructure<String> structure = RedisStrutureBuilder.ofKeyValue(String.class)
.withNameSpace(RedisConstants.UserNP.USER_NAMESPACE)
.withttl(RedisConstants.UserNP.USER_TOKEN_EXPIRE).build();
String token = structure.get(userToken);
if (StringUtil.isNotBlank(token)) {
LoginUserData userData = JSONObject.parseObject(token, LoginUserData.class);
if (userData != null) {
userNum = userData.getUserNum();
channelNum = userData.getChannelNum();
//延长用户token登录时间
structure.set(userToken, token);
ctx.addZuulRequestHeader(WebConstants.USER_NUM, userNum);
ctx.addZuulRequestHeader(WebConstants.CHANNEL_NUM, channelNum);
JSONObject jsonObject = new JSONObject();
if (!StringUtil.isEmpty(body)) {
jsonObject = JSONObject.parseObject(body);
}
jsonObject.put("userNum", userNum);
request.setAttribute("userNum", userNum);
final byte[] reqBodyBytes = jsonObject.toJSONString().getBytes();
ctx.setRequest(new HttpServletRequestWrapper(request) {
@Override
public ServletInputStream getInputStream() {
return new ServletInputStreamWrapper(reqBodyBytes);
} @Override
public int getContentLength() {
return reqBodyBytes.length;
} @Override
public long getContentLengthLong() {
return reqBodyBytes.length;
}
});
}
}
} // log.info(" >>> gateWay url={}, userTokenKey={}, userNum={}", request.getRequestURI(), principal, userNum); Route route = route(ctx.getRequest());
String requestURI = request.getRequestURI();
String zuulServletContextPath = route.getLocation().replace("-server", "");
//验证接口是否需要登录
MapStructure<Boolean> structure = RedisStrutureBuilder.ofMap(Boolean.class).withNameSpace(RedisConstants.SystemNP.SYSTEM_NAMESPACE).build();
Map<String, Boolean> serviceMethod = structure.get(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD.concat(zuulServletContextPath));
NeedLoginBean needLoginBean = adaptServiceMethod(requestURI, body, serviceMethod, zuulServletContextPath);
log.info(">>> zuul LoginFilter needLoginBean={}", needLoginBean);
//static 静态资源不进行接口验证 for (String suffix : excludeSuffixs) {
if (requestURI.endsWith(suffix)) {
return null;
}
} //选判断此接口是否存在zuul网关中
if (!needLoginBean.isHasMethod()) {
log.error(">>> 未知接口。requestType={}", requestURI);
ctx.setSendZuulResponse(false); //不进行路由
ctx.getResponse().setContentType("text/html;charset=UTF-8");
Response response = new Response(needLoginBean.getRequestURI(), false);
response.setMessage(WebConstants.SYSTEM_ERROR_MESSAGE);
ctx.setResponseBody(JSON.toJSONString(response));
return null;
}
boolean needLogin = needLoginBean.needLogin;
if (needLogin && StringUtil.isBlank(userNum)) {
log.error(">>> 当前接口需要登录,请先登录。requestType={}", requestURI);
ctx.setSendZuulResponse(false); //不进行路由
ctx.getResponse().setContentType("text/html;charset=UTF-8");
Response response = new Response(needLoginBean.getRequestURI(), false);
response.setErrorCode(ErrCodeConstants.NEED_LOGIN.getCode());
response.setMessage(ErrCodeConstants.NEED_LOGIN.getMessage());
ctx.setResponseBody(JSON.toJSONString(response));
}
} catch ( Exception e) {
log.error(e.getMessage(), e);
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
Response response = new Response(request.getRequestURI(), false);
response.setMessage(WebConstants.SYSTEM_ERROR_MESSAGE);
ctx.setResponseBody(JSON.toJSONString(response));
ctx.getResponse().setContentType("text/html;charset=UTF-8");
}
return null;
} public NeedLoginBean adaptServiceMethod(String requestURI, String req, Map<String, Boolean> serviceMethod, String zuulServletContextPath) {
NeedLoginBean needLoginBean = new NeedLoginBean();//兼容老的服务调用方式
if (oldServers.contains(zuulServletContextPath)) {
Request bizRequest = parseObject(req, Request.class);
needLoginBean.setRequestURI(bizRequest.getRequestType());
Boolean needLogin = serviceMethod.get(bizRequest.getRequestType());
if (needLogin == null) {
//false说明此接口不在网关注册接口范围内
needLoginBean.setHasMethod(false);
} else {
needLoginBean.setHasMethod(true);
needLoginBean.setNeedLogin(needLogin);
} } else if (newServers.contains(zuulServletContextPath)) {
needLoginBean.setRequestURI(requestURI);
//false说明此接口不在网关注册接口范围内
PathMatcher matcher = new AntPathMatcher();
Iterator it = serviceMethod.keySet().iterator();
while (it.hasNext()) {
String key = (String) it.next();
boolean result = matcher.match(key, requestURI);
if (result) {
needLoginBean.setHasMethod(true);
needLoginBean.setNeedLogin(serviceMethod.get(key));
break;
}
} } else {
throw new RuntimeException(" >>>请求接口不存在");
}
return needLoginBean;
} @ToString
@Data
private static class NeedLoginBean {
String requestURI;
boolean hasMethod;
boolean needLogin;
}
}
3.启动类
package com.ftk.hjs.zuul.server; import com.ftk.framework.redis.clients.collections.factory.RedisConfig;
import com.ftk.framework.redis.clients.collections.factory.RedisConnection;
import com.ftk.hjs.zuul.server.filter.LoginFilter;
import com.ftk.hjs.zuul.server.filter.PrintRequestLogFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.util.UrlPathHelper; import java.util.ArrayList;
import java.util.List; @SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
@SpringCloudApplication
@EnableFeignClients
@ComponentScan(basePackages = {"com.ftk.hjs","com.ftk.framework"})
public class ZuulServerLauncher implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(ZuulServerLauncher.class); @Autowired
private RedisConfig redisConfig; public static void main(String[] args) {
SpringApplication.run(ZuulServerLauncher.class, args);
} @Override
public void run(String... strings){
//初始化redis
// RedisConnection.init(redisConfig);
logger.info("ZuulServerLauncher has run !!! {} ", strings); } @Bean
public LoginFilter accessFilter(RouteLocator routeLocator) {
return new LoginFilter(routeLocator,new UrlPathHelper());
} @Bean
public PrintRequestLogFilter printRequestLogFilter() {
return new PrintRequestLogFilter();
} private CorsConfiguration addcorsConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
List<String> list = new ArrayList<>();
list.add("*");
corsConfiguration.setAllowedOrigins(list);
/*
// 请求常用的三种配置,*代表允许所有,当时你也可以自定义属性(比如header只能带什么,只能是post方式等等)
*/
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setMaxAge(3600L);
corsConfiguration.setAllowCredentials(true);
return corsConfiguration;
} @Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", addcorsConfig());
return new CorsFilter(source);
} }
最后,以上代码均为部分代码,参照转载文章的说明和实例即可实现自己的网关功能。
Spring Cloud Zuul Filter 和熔断的更多相关文章
- Spring Cloud Zuul网关 Filter、熔断、重试、高可用的使用方式。
时间过的很快,写springcloud(十):服务网关zuul初级篇还在半年前,现在已经是2018年了,我们继续探讨Zuul更高级的使用方式. 上篇文章主要介绍了Zuul网关使用模式,以及自动转发机制 ...
- SpringCloud---API网关服务---Spring Cloud Zuul
1.概述 1.1 微服务架构出现的问题 及 解决: 1.1.1 前言 每个微服务应用都提供对外的Restful API服务,它通过F5.Nginx等网络设备或工具软件实现对各个微服务的路由与负载 ...
- Spring Cloud Zuul API服务网关之请求路由
目录 一.Zuul 介绍 二.构建Spring Cloud Zuul网关 构建网关 请求路由 请求过滤 三.路由详解 一.Zuul 介绍 通过前几篇文章的介绍,我们了解了Spring Cloud ...
- 服务网关Spring Cloud Zuul
Spring Cloud Zuul 开发环境 idea 2019.1.2 jdk1.8.0_201 Spring Boot 2.1.9.RELEASE Spring Cloud Greenwich S ...
- Spring Cloud Zuul 添加 ZuulFilter
紧接着上篇随笔Spring Cloud Zuul写,添加过滤器,进行权限验证 1.添加过滤器 package com.dzpykj.filter; import java.io.IOException ...
- Spring Cloud Zuul 限流详解(附源码)(转)
在高并发的应用中,限流往往是一个绕不开的话题.本文详细探讨在Spring Cloud中如何实现限流. 在 Zuul 上实现限流是个不错的选择,只需要编写一个过滤器就可以了,关键在于如何实现限流的算法. ...
- Spring cloud Zuul网关异常处理
Spring cloud Zuul网关异常处理 一 异常测试: 1> 创建一个pre类型的过滤器,并在该过滤器的run方法实现中抛出一个异常.比如下面的实现,在run方法中调用的doSometh ...
- 第七章 API网关服务:Spring Cloud Zuul
API网关是一个更为智能的应用服务器, 它的定义类似于面向对象设计模式中的Facade模式, 它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤.它除了要实现 ...
- Spring Cloud Zuul实现IP访问控制
接着上篇文章 https://www.cnblogs.com/mxmbk/p/9569438.html IP访问限制和黑白名单如何做,需要解决以下几个问题: 1.如何识别正常访问和异常访问?(一段时间 ...
随机推荐
- centos7安装dockers
安装Docker我是虚拟机装的Centos7,linux 3.10 内核,docker官方说至少3.8以上,建议3.10以上(ubuntu下要linux内核3.8以上, RHEL/Centos 的内核 ...
- k8s配置文件模板
一,deployment Deployment为Pod和Replica Set下一代Replication Controller)提供声明式更新 1,配置示例 apiVersion: apps/v1 ...
- Linux不同机器文件挂载
由于此前发布项目应用时,需要对两台文件服务器进行文件挂载,所以才实际第一次接触到这个名词,但由于一直以来自己没有真正的去操作过,只是停留在一些理论层次,所以今天记录一下这个实现过程,以备后用. 使用设 ...
- HTML的状态码
HTML状态码的相关知识 ㈠:含义 HTTP状态码(英语:HTTP Status Code)是用以表示网页服务器超文本传输协议响应状态的3位数字代码. 也就是当浏览者访问一个网页时,浏览者的浏览器会向 ...
- jprofiler 监听远程java项目
1.下载.安装windows和linux版的jprofile.注意:若监控的是springboot.springcloud项目,切记本地和服务器上的jprofile要版本保持一致,本人亲自踩过坑. 官 ...
- codevs2370 小机房的树 x
2370 小机房的树 时间限制: 1 s 空间限制: 256000 KB 题目等级 : 钻石 Diamond 题目描述 Description 小机房有棵焕狗种的树,树上有N个节点,节点标号 ...
- ovs-vsctl patch 连接两个网桥
1.命令如下: ovs-vsctl add-port bridge-name port-name ovs-vsctl set interface port-name type=patch ovs-vs ...
- reactjs 的 css 模块化工具 styled-components 升级后 createGlobalStyle 废除,使用 createGlobalStyle 的方案
在 styled-components 升级到 4 版本后设置全局属性的 createGlobalStyle 这个 api 被废除,替代的 api 是 createGlobalStyle 与过去组织代 ...
- EL表达式里面不能直接使用list.size()得到长度,
在jsp页面中不能通过${list.size}取列表长度, 而是 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" pre ...
- C++入门经典-例6.10-将多维数组转换成一维数组
1:代码如下: // 6.10.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> usin ...