上文介绍了 Zuul 的基本使用与路由功能, 本文接着介绍 Zuul 的核心概念 -- Zuul 过滤器 (filter).
Zuul 的功能基本通过 Zuul 过滤器来实现 (类比于 Struts 的拦截器, 只是 Struts 拦截器用到责任链模式, Zuul 则是通过 FilterProcessor 来控制执行), 在不同的阶段, 通过不同类型的过滤器来实现相应的功能.
Zuul 过滤器
过滤器类型
zuul 的过滤器根据对 HTTP 请求的不同处理阶段包括如下四种类型
pre : 在请求转发到后端目标服务之前执行, 一般用于请求认证, 确定路由地址, 日志记录等
route : 转发请求, 使用 Apache HttpClient 或 Ribbon 来构造对目标服务的请求
post : 在目标服务返回结果后对结果进行处理, 比如添加响应头, 收集统计性能数据等
error : 在请求处理的整个流程中如果出现错误, 则会触发 error 过滤器执行, 对错误进行处理
客户端请求经过 zuul 过滤器处理的流程如下图
zuul 使用 RequestContext 来在过滤器之间传递数据, 数据存于每个 request 的 ThreadLocal, 包含请求路由到哪里, 错误, HttpServletRequest,HttpServletResponse 等这些数据都存储于 RequestContext 中. RequestContext 扩展了 ConcurrentHashMap, 所以我们可以根据需要将信息存于 context 中进行传递.
@EnableZuulProxy vs @EnableZuulServer
zuul 提供了两个注解 @EnableZuulProxy, @EnableZuulServer, 来启用不同的过滤器集合.@EnableZuulProxy 启用的过滤器 是 @EnableZuulServer 的超集, 它包含了 @EnableZuulServer 的所有过滤器, proxy 主要多了一些提供路由功能的过滤器 (可见 @EnableZuulServer 不提供路由功能, 作为 server 模式而不是代理模式运行)
@EnableZuulServer 注解启用的过滤器包括
filter 类型 | 实现类 | filter 顺序值 | 功能说明 |
---|---|---|---|
pre | ServletDetectionFilter | -3 | 检测请求是否通过 Spring Dispatcher,并在 RequestContext 中添加一个 key 为 isDispatcherServletRequest, 值为 true(不通过则为 false)的属性 |
pre | FormBodyWrapperFilter | -1 | 解析 Form data,为请求的下游进行重新编码 |
pre | DebugFilter | 1 | 如果请求参数设置了 debug,则会将 RequestContext.setDebugRouting() ,RequestContext.setDebugRequest() 设置为 ture |
route | SendForwardFilter | 500 | 使用 RequestDispatch servlet 来转发请求,转发地址存于 RequestContext 中 key 为 FilterConstants.FORWARD_TO_KEY 的属性中,对于转发到当前应用的接口比较有用 |
post | SendResponseFilter | 1000 | 将代理请求的响应内容写到当前的响应中 |
error | SendErrorFilter | 0 | 如果 RequestContext.getThrowable() 不为空,则会转发到 / error,可以通过 error.path 来改变默认的转发路径 / error |
@EnableZuulProxy 除了上面的过滤器, 还包含如下过滤器
filter 类型 | 实现类 | filter 顺序值 | 功能说明 |
---|---|---|---|
pre | PreDecorationFilter | 5 | 确定路由到哪里,如何路由,依赖提供的 RouteLocator,同时也为下游请求设置多个与 proxy 相关的 header |
route | RibbonRoutingFilter | 10 | 使用 ribbon,hystrix,以及内嵌的 http client 来发送请求,可在 RequestContext 中通过 FilterConstants.SERVICE_ID_KEY 来找到路由 Service 的 ID |
route | SimpleHostRoutingFilter | 100 | 使用 Apache httpClient 来发送请求到一个预先确定的 url,可通过 RequestContext.getRouteHost() 来获取 urls |
由上可见 @EnableZuulServer 注解并不包含往后端服务负载均衡地路由请求的代理功能,@EnableZuulProxy 的 PreDecorationFilter,RibbonRoutingFilter 过滤器才能担当此任. PreDecorationFilter 通过提供的 DiscoveryClientRouteLocator 从 DiscoveryClient(如 Eureka) 与属性文件中加载路由定义, 为每个 serviceId 创建一个 route, 新服务添加进来, 路由也会动态刷新. 路由确定了, 在 RibbonRoutingFilter 中通过 ribbon 与 hystrix 结合来向后端目标服务发起请求, 并进行负载均衡. 过滤器的顺序值表示在同类型过滤器中的执行顺序, 值越小越先执行.
自定义 Zuul 过滤器
自定义的 zuul 过滤器与框架自带过滤器类似, 包括四部分
过滤器类型, 包括 pre, route, post
过滤器顺序, 定义在同类型过滤器中的执行顺序, 数值越小越先执行
是否执行过滤, 通过一些条件判断来确定是否执行该过滤器
过滤器执行体, 定义具体执行的操作
比如我们需要在 Http 请求头中设置一个值, 供请求链路的下游环节访问, 则可以自定义一个过滤器如下,
- @Component
- public class ReqIdPreFilter extends ZuulFilter {
- @Override
- public String filterType() {
- return FilterConstants.PRE_TYPE;
- }
- @Override
- public int filterOrder() {
- return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1; // 在 PreDecorationFilter 过滤器之前执行
- }
- @Override
- public boolean shouldFilter() {
- return true;
- }
- @Override
- public Object run() throws ZuulException {
- RequestContext ctx = RequestContext.getCurrentContext();
- ctx.addZuulRequestHeader("reqId", UUID.randomUUID().toString());
- return null;
- }
- }
在请求的后续环节, 比如后端服务的 filter 或接口中, 则可直接从 HttpServletRequest 获取该 header 值, 如
- @GetMapping("hello/reqId")
- public String getReqId(HttpServletRequest request) {
- return "hello-service 返回:" + request.getHeader("reqId");
- }
Zuul 的错误处理
在 zuul 过滤器的生命周期中, 如果任何一个环节抛出异常, 则 error 过滤器会被执行, SendErrorFilter 只有当 RequestContext.getThrowable() 不为 null 时才会运行, 会设置 javax.servlet.error.* 属性到 request 中, 然后将请求转发到 spring boot 的 error page, 默认为 BasicErrorController 实现的 / error 接口. 有时候我们需要将返回响应格式进行统一, 而默认的 / error 接口实现可能不满足要求, 则可以自定义 / error 接口. 需要实现 ErrorController 接口以使默认的 BasicErrorController 失效.
- @RestController
- public class ZuulErrorController implements ErrorController {
- @RequestMapping("/error")
- public Map<String, String> error(HttpServletRequest request){
- Map<String, String> result = Maps.newHashMap();
- result.put("code", request.getAttribute("javax.servlet.error.status_code").toString());
- result.put("message", request.getAttribute("javax.servlet.error.message").toString());
- result.put("exception", request.getAttribute("javax.servlet.error.exception").toString());
- return result;
- }
- @Override
- public String getErrorPath() {
- return "/error";
- }
- }
Zuul 的服务降级
当调用服务出现超时或异常时, 在 zuul 侧可提供回调进行服务降级, 返回默认响应结果, 如
- @Component
- public class MyFallbackProvider implements FallbackProvider {
- @Override
- public String getRoute() {
- return null; // 指定这个回调针对的 route Id, 如果对所有 route, 则返回 * 或 null
- }
- @Override
- public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
- if (cause instanceof HystrixTimeoutException) {
- return response(HttpStatus.GATEWAY_TIMEOUT);
- } else {
- return response(HttpStatus.INTERNAL_SERVER_ERROR);
- }
- }
- private ClientHttpResponse response(final HttpStatus status) {
- return new ClientHttpResponse() {
- @Override
- public HttpStatus getStatusCode() throws IOException {
- return status;
- }
- @Override
- public int getRawStatusCode() throws IOException {
- return status.value();
- }
- @Override
- public String getStatusText() throws IOException {
- return status.getReasonPhrase();
- }
- @Override
- public void close() {
- }
- @Override
- public InputStream getBody() throws IOException {
- Map<String, String> result = Maps.newLinkedHashMap();
- result.put("code", "" + status.value());
- String msg = HttpStatus.GATEWAY_TIMEOUT == getStatusCode() ? "请求服务超时" : "服务器内部错误";
- result.put("message", msg);
- return new ByteArrayInputStream(new ObjectMapper().writeValueAsString(result).getBytes());
- }
- @Override
- public HttpHeaders getHeaders() {
- HttpHeaders headers = new HttpHeaders();
- headers.setContentType(MediaType.APPLICATION_JSON);
- return headers;
- }
- };
- }
- }
则当服务请求失败时, 统一返回如下格式的响应
- {
- "code": "500",
- "message": "服务器内部错误"
- }
总结
本文主要对 Zuul 过滤器相关内容及自定义使用进行了介绍, 同时对过滤器运行过程中异常的处理及服务调用失败的降级回调进行了简单说明. 出于篇幅, 开发过程中更具体的细节我们后续再继续探讨.
认真生活, 快乐分享
来源: https://www.cnblogs.com/spec-dog/p/12340902.html