spring zuul转发问题

spring zuul项目中的RestController api成功调用,但是存在部分接口调用了PostFilter,但是其它接口不执行PostFilter

原因

zuul:
  prefix: /v1
  routes:
    new-platform-env:
      sensitiveHeaders: Access-Control-Allow-Origin,Access-Control-Allow-Methods
      path: /v1/**
      url: http://trade-application-gateway:8080
      stripPrefix: false
    new-platform-env2:
      sensitiveHeaders: Access-Control-Allow-Origin,Access-Control-Allow-Methods
      path: /v2/**
      url: http://trade-application-gateway:8080
      stripPrefix: false

出现上述问题的原因是在spring zuul配置中,zuul的前缀为v1,如果routes中配置了以/v1为前缀的路由,则会出现该问题

分析

  • DispatcherServlet.doDispatch()中getHandler(processedRequest);会循环handlerMappings和request进行判断
    • handlerMappings 是 HandlerMapping实例集合,HandlerMapping是一个用于实现请求和处理类映射的接口
    • handlerMapping存在以下实现
      • SimpleUrlHandlerMapping
      • ZuulHandlerMapping
      • EndpointHandlerMapping
      • RequestMappingHandlerMapping
      • BeanNameUrlHandlerMapping
      • SimpleUrlHandlerMapping
      • WebMvcConfigurationSupport$EmptyHandlerMapping
      • WebMvcConfigurationSupport$EmptyHandlerMapping
      • WebMvcAutoConfiguration$WelcomePageHandlerMapping
    • handlerMapping中存在方法getHandler(HttpServletRequest request),该方法通过request匹配对应的HandlerExecutionChain,getHandler()将匹配到的第一个返回
    • route路由是通过ZuulHandlerMapping匹配到的,而普通的RestController接口是通过RequestMappingHandlerMapping匹配到的HandlerExecutionChain
  • AbstractHandlerMapping.getHandler(HttpServletRequest request)中会根据request匹配对应的HandlerExecutionChain
    • 在getHandler()方法中,会调用getHandlerInternal(request);,此处使用模板模式,该方法是一个抽象方法;ZuulHandlerMapping是调用的AbstractUrlHandlerMapping,而RequestMappingHandlerMapping是调用的AbstractHandlerMethodMapping的getHandlerInternal(request);方法
  • ZuulHandlerMapping.getHandlerInternal(request)的执行过程
    • ZuulHandlerMapping通过AbstractUrlHandlerMapping.getHandlerInternal(request)最终会调用ZuulHandlerMapping.lookupHandler(String urlPath, HttpServletRequest request)
    • 在AbstractUrlHandlerMapping存在全局变量handlerMap
    • 在handlerMap中保存有Zuul配置的路由映射,见图1
    • 如图1所示,由于zuul前端及route前端均配置的是v1,但是却只展示了一个v1,故可匹配上
    • 然后将匹配到的handler返回
      WX20200625-213423@2x图1
  • RequestMappingHandlerMapping.getHandlerInternal(request)的执行过程
    • 在该方法中会调用lookupHandlerMethod(lookupPath, request)
    • 在lookupHandlerMethod方法中调用this.mappingRegistry.getMappingsByUrl(lookupPath);该方法会获取请求url匹配的注册映射请求http信息,如http method,param等
    • 最终会匹配到对应的HandlerMethod,在该实例中封装有执行的类及方法等信息
  • 在上述不同HandlerMapping返回handler后,会通过handler生成对应的HandlerExecutionChain
  • 然后会执行对应的handler,即v1/**对应的是ZuulController,然后执行ZuulServlet.service()方法;而test无对应的route,则执行对应的Controller
  • ZuulServlet.service()方法中,会执行pre,route及post等过滤器
    • 在route时,由于实际并没有对应的/v1/**路由,故在类PreDecorationFilter中会提示No route found for uri: /v1/test,并且会在ctx中set forward.to
    • 然后会重新DispatcherServlet,这次由于在ctx.containsKey("forward.to")为true,所以在ZuulHandlerMapping.lookupHandler时直接返回null,故下一次直接走RestController api

值得注意的是,如果请求是/v1/test,实际的执行过程为filter->ZuulServlet->PreFilter->发现无匹配的路由,重新分发->filter->controller->filter->响应数据->postfilter->zuulServlet->filter
所以在postfilter中是无法获取到响应状态及body的,并且在postfilter中write message,是无法真正响应到客户端的