目录
Zuul过滤器之Error过滤器处理zuul调用服务出现的异常示例
相关知识:
在SpringCloud微服务中,Zuul除了最常用的(动态)路由功能外,还有很多其他的功能,如过滤、认证、服务迁移、压力测试等等,今天主要介绍Zuul的过滤器。
默认情况下,Spring Cloud Zuul在请求路由时,会过滤掉请求头信息中的 一些敏感信息,防止它们被传递到下游的外部服务器。 默认的敏感头信息通过 zuul.sensitiveHeaders参数定义(可通过覆盖的方式来设置),包括Cookie、Set-Cookie、Authorization 三个属性。
注:可通过覆盖的方式来设置。如指向过滤掉Cookie、Set-Cookie,不想过滤掉Authorization ,那么只要需要
在application.properties文件中设置zuul.sensitiveHeaders = Cookie, Set-Cookie即可。
Zuul过滤器:
Zuul过滤器的使用方式:
使用方式很简单,继承ZuulFilter类,重写相应的方法即可(可详见本文末给出的使用示例)。
Zuul过滤器的类型:
-
pre:路由请求前过滤。
-
post:路由请求后(此时已经走完目标服务程序了)过滤。
-
route:路由请求时过滤。
-
error:当上述三种过滤器抛出异常时,会走error过滤。
注:如果目标服务抛出异常,并不会走error过滤器,error过滤器的“管辖范围”只是pre、post、route这三种过滤器。
Zuul过滤器的调用顺序(过滤优先级):
源码ZuulServlet类(相关截图):
上图是四种Zuul过滤器的总体过滤顺序,对于同一类型的过滤器,其过滤顺序是由filterOrder方法(继承ZuulFilter类时,需要重写此方法)的返回值决定的,filterOrder返回值越小,在同一类型种,越先过滤,此处源码可见FilterLoader类的以下部分:
Zuul调用目标服务的时机:
在Zuul的众多route类型的过滤器中,RibbonRoutingFilter过滤的run()方法里,实现了由Zuul路由到具体的服务:
Zuul过滤器简单使用示例(以pre过滤器为例):
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.util.RequestBodyUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Random;
/**
* 使用ZuulFilter实现 简单认证
*
* @author JustryDeng
* @date 2019/3/1 1:36
*/
@Component
@Slf4j
@PropertySource(value = {"classpath:/authe_info.properties"}, encoding="utf8")
public class PreFilter extends ZuulFilter {
@Value("${need-filter-uri}")
private String[] needFilterURIs;
/**
* 过滤类型
* 有:
* 【pre】路由请求前被调用过滤、
* 【post】后置过滤、
* 【error】错误过滤、
* 【route】路由请求时被调用
*
* @date 2019/3/19 14:49
*/
@Override
public String filterType() {
// 设置为 前置过滤
return "pre";
}
/**
* 设置过滤优先级
*
* 注:当有多个同类型(即;同filterType)的ZuulFilter的子类时,可使用此返回值指定过滤器优先级;值越小越先过滤
*
* @date 2019/3/19 14:55
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 设置 哪些请求 需要过滤
*
* @date 2019/3/19 14:55
*/
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
String uri = request.getRequestURI();
// 当为目标URI时,需要过滤
return Arrays.asList(needFilterURIs).contains(uri);
}
/**
* 实际逻辑逻辑
*
* 注:当RequestContext.setSendZuulResponse(false);时表示过滤失败,zuul不对其进行路由
*/
@Override
public Object run() {
// 获取请求上下文
RequestContext requestContext = RequestContext.getCurrentContext();
// 获取请求
HttpServletRequest request = requestContext.getRequest();
if (doFiltrate(request)) {
// 验证通过
return null;
}
// 如果验证不通过,那么过滤该请求,不往下级服务去转发请求,到此结束
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
requestContext.setResponseBody(HttpStatus.FORBIDDEN.getReasonPhrase());
requestContext.getResponse().setContentType("text/html;charset=UTF-8");
return null;
}
/**
* 进行过滤
*
* @param request
* 请求
* @return 是否通过
* @author JustryDeng
* @date 2019/3/1 2:13
*/
private boolean doFiltrate (HttpServletRequest request) {
try {
/// 获取请求头
String who = request.getHeader("Authorization");
log.info(" requestHeader param 【Authorization】 is -> {} !", who);
/// 向请求头中添加信息
// requestContext.addZuulRequestHeader("");
// 获取请求体
RequestBodyUtil requestBodyUtil = new RequestBodyUtil(request);
String requestBody = requestBodyUtil.getBody();
log.info(" got requestBady -> {}", requestBody);
// TODO 由于是测试代码,这里随机返回 成功、失败
return new Random().nextBoolean();
} catch (Exception e) {
log.error(" zull authe occur error !", e);
return false;
}
}
}
Zuul过滤器之Error过滤器处理zuul调用服务出现的异常示例:
场景说明:
当zuul通过eureka调用一个不可用、不存在、宕机了的服务时,可能就会直接返回类似于这样的不友好的画面:
我们可以通过编写一个异常过滤器来处理这种情况:
第一步:先编写一个Controller设置当出现这种情况时,返回的响应。
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* zuul异常处理 现场
*
*
* @author JustryDeng
* @date 2019/4/19 15:52
*/
@RestController
public class ErrorHandlerController {
/**
* zuul调用服务异常时,走此方法
*
* 注意:method的方法类别 最好全面一点,如本人这里将可能的请求方式全列出来了
*
* @return zuul其他服务异常时的响应
* @date 2019/4/19 16:51
*/
@RequestMapping(value = "/justrydeng/error",
method = {RequestMethod.GET, RequestMethod.HEAD,
RequestMethod.POST, RequestMethod.PUT,
RequestMethod.PATCH, RequestMethod.DELETE,
RequestMethod.OPTIONS, RequestMethod.TRACE})
public String errorHandler() {
// 设置当发生错误时,返回给前端的数据
return "zuul 调用服务时出错了!";
}
}
第二步:编写zuul异常处理过滤器,设置当异常时,将请求转发至第一步编写的方法的uri地址。
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
/**
* 异常过滤器
*
* @author JustryDeng
* @date 2019/3/1 1:36
*/
@Component
@Slf4j
public class ErrorFilter extends ZuulFilter {
@Override
public String filterType() {
// 设置为 错误过滤
return "error";
}
@Override
public int filterOrder() {
// 注意: 此处自定义的优先级 要高于SendErrorFilter的优先级,
// 由于SendErrorFilter的优先级是0, 由于值越小优先级越高,
// 所以这里的值要小于0
return -1;
}
@Override
public boolean shouldFilter() {
return true;
}
/**
* 重写run()方法
*
* 提示:此方法的编写 可 参考 SendErrorFilter的run方法;
*/
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
try {
Throwable throwable = ctx.getThrowable();
log.error("记录zuul调用服务时发生的异常信息", throwable);
// 设置请求转发到自定义的controller方法对应的uri地址
RequestDispatcher dispatcher = request.getRequestDispatcher("/justrydeng/error");
if (dispatcher != null) {
if (!ctx.getResponse().isCommitted()) {
// 一定要将【sendErrorFilter.ran】的值设置为true;这样就不会走SendErrorFilter过滤器了
// 提示:由于过滤器执行顺序的问题,如果走了SendErrorFilter的话,那么就会覆盖我们设置好了的响应信息
ctx.set("sendErrorFilter.ran", true);
dispatcher.forward(request, ctx.getResponse());
}
}
} catch (Exception ex) {
// 若到这一步, 可选择邮件告警等
log.error("系统异常", ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
}
测试一下:
我们利用zuul调用一个被关闭了的服务,此时响应就变为了:
由此可见:zuul调用服务出错时的过滤器编写成功!