前言

  • 上一章节已经介绍了使用@EnableZuulServer注解会开启 ZuulProxyAutoConfiguration自动注册功能,这个类会自动注册Zuul服务启动所需要的Bean,因为我们这里是网关服务,所以是需要接受外部应用的Http请求的

  • 回顾ZuulProxyAutoConfiguration 的父类ZuulServerAutoConfiguration,从下面可以看到是注册了ZuulControllerZuulHandlerMappingZuulServlet三个Bean,所以我们可以猜测入口应该是Spring MVC DispatcherServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/**
* @author Spencer Gibb
* @author Dave Syer
* @author Biju Kunjummen
*/
@Configuration // 声明是配置类
@EnableConfigurationProperties({ ZuulProperties.class }) // 激活 zuul配置
@ConditionalOnClass(ZuulServlet.class) // 条件1 存在ZuulServlet.class
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class) // 条件2 存在ZuulServerMarkerConfiguration.Marker.class bean, 即应用使用@EnableZuulServer注解
// Make sure to get the ServerProperties from the same place as a normal web app would
@Import(ServerPropertiesAutoConfiguration.class) // 配置ServerProperties实例
public class ZuulServerAutoConfiguration {

@Autowired
protected ZuulProperties zuulProperties;

@Autowired
protected ServerProperties server;

@Autowired(required = false)
private ErrorController errorController;

@Bean
public HasFeatures zuulFeature() {
return HasFeatures.namedFeature("Zuul (Simple)", ZuulServerAutoConfiguration.class);
}

@Bean
@Primary
public CompositeRouteLocator primaryRouteLocator(
Collection<RouteLocator> routeLocators) {
return new CompositeRouteLocator(routeLocators);
}

@Bean
@ConditionalOnMissingBean(SimpleRouteLocator.class)
public SimpleRouteLocator simpleRouteLocator() {
return new SimpleRouteLocator(this.server.getServletPrefix(),
this.zuulProperties);
}

/**
* zuulController, 包装了一个ZuulServlet类型的servlet, 实现对ZuulServlet类型的servlet的初始化.
*
* @return
*/
@Bean
public ZuulController zuulController() {
return new ZuulController();
}

@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
return mapping;
}

@Bean
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
return new ZuulRefreshListener();
}

@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
public ServletRegistrationBean zuulServlet() {
ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(),
this.zuulProperties.getServletPattern());
// The whole point of exposing this servlet is to provide a route that doesn't
// buffer requests.
servlet.addInitParameter("buffer-requests", "false");
return servlet;
}

...

解析

  • 查看源码是怎样执行调用的可以在代码里打好断点,观察其执行链,第六章节已经介绍了Zuul的一个简单例子,我们可以在自己定义的Filterrun()方法里打好断点,只要没配置错误,这里是一定会执行的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
@Slf4j
@Component
public class AuthenticationFilter extends ZuulFilter {

@Autowired
private SsoClient ssoClient;

private Pattern p = Pattern.compile("/*/pub/*");

@Override
public Object run() {
ResponseMO resMO = new ResponseMO();
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();

String relativeURL = extractRelativePath(request);
String token = request.getHeader(WebConstants.TOKEN_HEADER);
if (p.matcher(relativeURL).find()) {
return null;
}
log.info(">> 鉴权开始[{}]",relativeURL);
ResponseMO resModel = null;

if (relativeURL.startsWith(ApplicationConstants.APPLICATION_ZUUL) ||
relativeURL.startsWith(ApplicationConstants.APPLICATION_USER) ||
relativeURL.startsWith(ApplicationConstants.APPLICATION_SSO)) {
// 调用sso服务鉴权
resModel = ssoClient.checkToken(new TokenMO(token));
} else {
// 其他服务不对其进行路由
authorizationFailed(relativeURL, ctx, resMO);
return null;
}
if (resModel.getCode() != ResponseMO.RESPONSE_CODE_SUCCESS) {
// 鉴权失败不对其进行路由
authorizationFailed(relativeURL, ctx, resMO);
return null;
}
// 从jwt解析后的userId
ctx.addZuulRequestHeader("userId", "reUserId");
log.info("<< 鉴权通过[{}]] ", relativeURL);
return null;
}

/**
* 返回一个boolean值来判断该过滤器是否要执行,true表示执行,false表示不执行
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}

/**
* gives the order in which this filter will be executed, relative to other
* filters
* @return
*/
@Override
public int filterOrder() {
// TODO Auto-generated method stub
return 0;
}

@Override
public String filterType() {
return "pre";
}

/**
* 鉴权失败
* @param relativeURL
* @param ctx
* @param resMO
*/
private void authorizationFailed (String relativeURL, RequestContext ctx, ResponseMO resMO) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
resMO.setAnonymous();
String resBody = convertToString(resMO);
ctx.setResponseBody(resBody);
log.info("<< 鉴权失败[{}]",relativeURL);
}

private String convertToString(ResponseMO resMO) {
String result = "";
ObjectMapper mapper = new ObjectMapper();
try {
result = mapper.writeValueAsString(resMO);
} catch (JsonProcessingException e) {
log.error(e.getMessage());
}
return result;
}

/**
* 获取相对访问路径
* @param request
* @return
*/
private String extractRelativePath(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return requestURI;
}
}
  • 下图是其方法调用链路图,可以看到入口是Spring MVCDispatcherServlet,然后就是doDispatch到了ZuulController上,ZuulController又转发到了ZuulServletservice方法

  • 根据上图可以梳理出大致的执行流程
    • 1、内置tomcat容器接受Http请求
    • 2、进入DispatcherServlet进行doDispatch请求转发
    • 3、转发到ZuulController上,执行其handleRequest()方法
    • 4、然后转发到ZuulServlet上的service()方法上,这个是个HttpServlet,这里会执行一系列的拦截器

1、ZuulController

  • 我们平常开发使用 Spring MVC一般都是通过@Controller注解的形式来定义其执行方法,Spring也提供通过实现接口的形式来定义其执行方法,下面的ZuulController就是这个例子,可以看到这个类十分简单,就只有主体方法handleRequest(),此方法是定义在Controller接口上

  • 那是DispatcherServlet是怎样找到ZuulController这个执行类的呢,可以看到ZuulServerAutoConfiguration是注册了ZuulControllerZuulHandlerMapping这两个beanZuulHandlerMapping和我们平常使用的RequestMappingHandlerMapping都是继承HandlerMapping接口,这个接口是定义请求与具体执行者的映射关系,所以DispatcherServlet就能发现ZuulController这个执行类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* zuulController, 包装了一个ZuulServlet类型的servlet, 实现对ZuulServlet类型的servlet的初始化.
*
* @return
*/
@Bean
public ZuulController zuulController() {
return new ZuulController();
}

@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
return mapping;
}

org.springframework.web.servlet.mvc.Controller#handleRequest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface Controller {

/**
* Process the request and return a ModelAndView object which the DispatcherServlet
* will render. A {@code null} return value is not an error: it indicates that
* this object completed request processing itself and that there is therefore no
* ModelAndView to render.
* @param request current HTTP request
* @param response current HTTP response
* @return a ModelAndView to render, or {@code null} if handled directly
* @throws Exception in case of errors
*/
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;

}

org.springframework.cloud.netflix.zuul.web.ZuulController#handleRequest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @author Spencer Gibb
*/
public class ZuulController extends ServletWrappingController {

public ZuulController() {
setServletClass(ZuulServlet.class);
setServletName("zuul");
setSupportedMethods((String[]) null); // Allow all
}

@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
// We don't care about the other features of the base class, just want to
// handle the request
return super.handleRequestInternal(request, response);
}
finally {
// @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
RequestContext.getCurrentContext().unset();
}
}

}
  • 查看ZuulController的构造函数里面setServletClass(ZuulServlet.class)可以看到是设置了父类ServletWrappingControllerservletClassZuulServlet.class

    • 看看父类ServletWrappingController

      • 代码如下,可以看到成员变量是记录了ServletnameClass对象,Servlet servletInstance是在afterPropertiesSet()赋值的,这个函数是Spring的钩子函数
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        public class ServletWrappingController extends AbstractController
        implements BeanNameAware, InitializingBean, DisposableBean {

        private Class<? extends Servlet> servletClass;

        private String servletName;

        private Properties initParameters = new Properties();

        private String beanName;

        private Servlet servletInstance;


        public ServletWrappingController() {
        super(false);
        }


        /**
        * Set the class of the servlet to wrap.
        * Needs to implement {@code javax.servlet.Servlet}.
        * @see javax.servlet.Servlet
        */
        public void setServletClass(Class<? extends Servlet> servletClass) {
        this.servletClass = servletClass;
        }

        /**
        * Set the name of the servlet to wrap.
        * Default is the bean name of this controller.
        */
        public void setServletName(String servletName) {
        this.servletName = servletName;
        }

        /**
        * Specify init parameters for the servlet to wrap,
        * as name-value pairs.
        */
        public void setInitParameters(Properties initParameters) {
        this.initParameters = initParameters;
        }

        @Override
        public void setBeanName(String name) {
        this.beanName = name;
        }


        /**
        * Initialize the wrapped Servlet instance.
        * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
        */
        @Override
        public void afterPropertiesSet() throws Exception {
        if (this.servletClass == null) {
        throw new IllegalArgumentException("'servletClass' is required");
        }
        if (this.servletName == null) {
        this.servletName = this.beanName;
        }
        this.servletInstance = this.servletClass.newInstance();
        this.servletInstance.init(new DelegatingServletConfig());
        }


        /**
        * Invoke the wrapped Servlet instance.
        * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
        */
        @Override
        protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
        throws Exception {

        this.servletInstance.service(request, response);
        return null;
        }
        ...
    • 进入ZuulControllerhandleRequest()方法,可以看到就一个入口super.handleRequestInternal(request, response);,进入此方法,可以看到实际上就是执行了ZuulServletservice()方法,Spring将一个Servlet包裹在一个Controller里面了

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      /**
      * 执行被包裹的Servlet
      *
      * Invoke the wrapped Servlet instance.
      * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
      */
      @Override
      protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
      throws Exception {

      this.servletInstance.service(request, response);
      return null;
      }

2、ZuulServlet

  • 先看代码,可以看到ZuulServlet就是个Servlet,所以我们关心他的service()方法,注意这个类是属于com.netflix.zuul包下的,不是Spring的类,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/**
* Core Zuul servlet which intializes and orchestrates zuulFilter execution
*
* @author Mikey Cohen
* Date: 12/23/11
* Time: 10:44 AM
*/
public class ZuulServlet extends HttpServlet {

private static final long serialVersionUID = -3374242278843351500L;
private ZuulRunner zuulRunner;


@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);

String bufferReqsStr = config.getInitParameter("buffer-requests");
boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;

zuulRunner = new ZuulRunner(bufferReqs);
}

@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

// Marks this request as having passed through the "Zuul engine", as opposed to servlets
// explicitly bound in web.xml, for which requests will not have the same data attached
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();

try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}

} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}

/**
* executes "post" ZuulFilters
*
* @throws ZuulException
*/
void postRoute() throws ZuulException {
zuulRunner.postRoute();
}

/**
* executes "route" filters
*
* @throws ZuulException
*/
void route() throws ZuulException {
zuulRunner.route();
}

/**
* executes "pre" filters
*
* @throws ZuulException
*/
void preRoute() throws ZuulException {
zuulRunner.preRoute();
}

/**
* initializes request
*
* @param servletRequest
* @param servletResponse
*/
void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
zuulRunner.init(servletRequest, servletResponse);
}

/**
* sets error context info and executes "error" filters
*
* @param e
*/
void error(ZuulException e) {
RequestContext.getCurrentContext().setThrowable(e);
zuulRunner.error();
}

@RunWith(MockitoJUnitRunner.class)
public static class UnitTest {

@Mock
HttpServletRequest servletRequest;
@Mock
HttpServletResponseWrapper servletResponse;
@Mock
FilterProcessor processor;
@Mock
PrintWriter writer;

@Before
public void before() {
MockitoAnnotations.initMocks(this);
}

@Test
public void testProcessZuulFilter() {

ZuulServlet zuulServlet = new ZuulServlet();
zuulServlet = spy(zuulServlet);
RequestContext context = spy(RequestContext.getCurrentContext());


try {
FilterProcessor.setProcessor(processor);
RequestContext.testSetCurrentContext(context);
when(servletResponse.getWriter()).thenReturn(writer);

zuulServlet.init(servletRequest, servletResponse);
verify(zuulServlet, times(1)).init(servletRequest, servletResponse);
assertTrue(RequestContext.getCurrentContext().getRequest() instanceof HttpServletRequestWrapper);
assertTrue(RequestContext.getCurrentContext().getResponse() instanceof HttpServletResponseWrapper);

zuulServlet.preRoute();
verify(processor, times(1)).preRoute();

zuulServlet.postRoute();
verify(processor, times(1)).postRoute();
// verify(context, times(1)).unset();

zuulServlet.route();
verify(processor, times(1)).route();
RequestContext.testSetCurrentContext(null);

} catch (Exception e) {
e.printStackTrace();
}


}
}
}
  • 关注service()方法,可以说这里是zuul的核心方法,看到这里的代码再来理解之前章节截的图就十分形象了,可以看到这里主要逻辑就是执行filter了,可以发现preRoute()route()都是跳转到ZuulRunner zuulRunner里对应的方法执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
try {
this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();

try {
// 执行 pre filter
this.preRoute();
} catch (ZuulException var12) {
// 发生异常 执行error 及 post filter
this.error(var12);
this.postRoute();
return;
}

try {
// 执行 routing filter
this.route();
} catch (ZuulException var13) {
// 发生异常 执行error 及 post filter
this.error(var13);
this.postRoute();
return;
}

try {
// 执行 post filter
this.postRoute();
} catch (ZuulException var11) {
this.error(var11);
}
} catch (Throwable var14) {
this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
  • 我们现在来调试service()方法

    • 先来看第一行this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);

      • 代码

        1
        2
        3
        void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
        zuulRunner.init(servletRequest, servletResponse);
        }
      • 跳转到zuulRunner.init()方法,可以看到下面使用了构造了一个RequestContext,并设置HttpServlet request and HttpResponse,不出所外这个类就是ThreadLocal来实现的

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        /**
        * sets HttpServlet request and HttpResponse
        *
        * @param servletRequest
        * @param servletResponse
        */
        public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {

        RequestContext ctx = RequestContext.getCurrentContext();
        if (bufferRequests) {
        ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
        } else {
        ctx.setRequest(servletRequest);
        }

        ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
        }
      • 查看RequestContext类,查看本地变量可以发现ThreadLocal<? extends RequestContext> threadLocal,而且这个类继承了ConcurrentHashMap所以这个类应该是存放每次请求的各种参数的,使用ThreadLocal变量来达到线程隔离的效果

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        /**
        * The Request Context holds request, response, state information and data for ZuulFilters to access and share.
        * The RequestContext lives for the duration of the request and is ThreadLocal.
        * extensions of RequestContext can be substituted by setting the contextClass.
        * Most methods here are convenience wrapper methods; the RequestContext is an extension of a ConcurrentHashMap
        *
        * @author Mikey Cohen
        * Date: 10/13/11
        * Time: 10:21 AM
        */
        public class RequestContext extends ConcurrentHashMap<String, Object> {

        private static final Logger LOG = LoggerFactory.getLogger(RequestContext.class);

        protected static Class<? extends RequestContext> contextClass = RequestContext.class;

        private static RequestContext testContext = null;

        protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
        @Override
        protected RequestContext initialValue() {
        try {
        return contextClass.newInstance();
        } catch (Throwable e) {
        throw new RuntimeException(e);
        }
        }
        };


        public RequestContext() {
        super();
        }
    • 看完第一行我们知道构造了一个RequestContext,再来回去看第二三行代码

      • 可以看到重新获取了一下RequestContext,context.setZuulEngineRan();用于标记这个请求是Zuul engine
        1
        2
        3
        4
        // Marks this request as having passed through the "Zuul engine", as opposed to servlets
        // explicitly bound in web.xml, for which requests will not have the same data attached
        RequestContext context = RequestContext.getCurrentContext();
        context.setZuulEngineRan();
    • 下面就是执行各种Route

      2.1、preRoute()
  • 先来看preRoute(),这个filters是最先执行的

1
2
3
void preRoute() throws ZuulException {
zuulRunner.preRoute();
}
  • 进入com.netflix.zuul.ZuulRunner#preRoute(),可以看到又包装了一个FilterProcessor
1
2
3
public void preRoute() throws ZuulException {
FilterProcessor.getInstance().preRoute();
}
  • 查看FilterProcessor类,这个类是执行filters的核心类,可以看到这个类的使用是用了单例模式

    • 代码
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      /**
      * This the the core class to execute filters.
      *
      * @author Mikey Cohen
      * Date: 10/24/11
      * Time: 12:47 PM
      */
      public class FilterProcessor {

      static FilterProcessor INSTANCE = new FilterProcessor();
      protected static final Logger logger = LoggerFactory.getLogger(FilterProcessor.class);

      private FilterUsageNotifier usageNotifier;


      public FilterProcessor() {
      usageNotifier = new BasicFilterUsageNotifier();
      }

      /**
      * @return the singleton FilterProcessor
      */
      public static FilterProcessor getInstance() {
      return INSTANCE;
      }

      /**
      * sets a singleton processor in case of a need to override default behavior
      *
      * @param processor
      */
      public static void setProcessor(FilterProcessor processor) {
      INSTANCE = processor;
      }
  • 进入com.netflix.zuul.FilterProcessor#preRoute(),看注释可以看到本方法是在请求路由之前执行所有的"pre" filters,可以看到得到List<ZuulFilter> list然后for循环执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* runs all "pre" filters. These filters are run before routing to the orgin.
*
* @throws ZuulException
*/
public void preRoute() throws ZuulException {
try {
runFilters("pre");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
}
}
/**
* runs all filters of the filterType sType/ Use this method within filters to run custom filters by type
*
* @param sType the filterType.
* @return
* @throws Throwable throws up an arbitrary exception
*/
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
ZuulFilter zuulFilter = list.get(i);
// 执行ZuulFilter
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}

List list 结果

  • 上面代码可以看到已经筛选出上图这些"pre" filters

    • 这些"pre" filters 也有我们自己定义的AuthenticationFilter
    • 可以看到ServletDetectionFilter是最先执行的filter,因为filterOrder()是最小,这个filter用于标识请求是否是DispatcherServletRequest
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      public class ServletDetectionFilter extends ZuulFilter {

      public ServletDetectionFilter() {
      }

      @Override
      public String filterType() {
      return PRE_TYPE;
      }

      /**
      * Must run before other filters that rely on the difference between
      * DispatcherServlet and ZuulServlet.
      */
      @Override
      public int filterOrder() {
      return SERVLET_DETECTION_FILTER_ORDER;
      }

      @Override
      public boolean shouldFilter() {
      return true;
      }

      @Override
      public Object run() {
      RequestContext ctx = RequestContext.getCurrentContext();
      HttpServletRequest request = ctx.getRequest();
      if (!(request instanceof HttpServletRequestWrapper)
      && isDispatcherServletRequest(request)) {
      ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, true);
      } else {
      ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, false);
      }

      return null;
      }

      private boolean isDispatcherServletRequest(HttpServletRequest request) {
      return request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null;
      }

      }
  • 进入Object result = processZuulFilter(zuulFilter) 查看ZuulFilter执行逻辑

    • com.netflix.zuul.FilterProcessor#processZuulFilter

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      /**
      * Processes an individual ZuulFilter. This method adds Debug information. Any uncaught Thowables are caught by this method and converted to a ZuulException with a 500 status code.
      *
      * @param filter
      * @return the return value for that filter
      * @throws ZuulException
      */
      public Object processZuulFilter(ZuulFilter filter) throws ZuulException {

      RequestContext ctx = RequestContext.getCurrentContext();
      boolean bDebug = ctx.debugRouting();
      final String metricPrefix = "zuul.filter-";
      long execTime = 0;
      String filterName = "";
      try {
      long ltime = System.currentTimeMillis();
      filterName = filter.getClass().getSimpleName();

      RequestContext copy = null;
      Object o = null;
      Throwable t = null;

      if (bDebug) {
      Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);
      copy = ctx.copy();
      }
      // 执行方法
      ZuulFilterResult result = filter.runFilter();
      ExecutionStatus s = result.getStatus();
      execTime = System.currentTimeMillis() - ltime;

      switch (s) {
      case FAILED:
      t = result.getException();
      ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
      break;
      case SUCCESS:
      o = result.getResult();
      ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
      if (bDebug) {
      Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
      Debug.compareContextState(filterName, copy);
      }
      break;
      default:
      break;
      }

      if (t != null) throw t;

      usageNotifier.notify(filter, s);
      return o;

      } catch (Throwable e) {
      if (bDebug) {
      Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + e.getMessage());
      }
      usageNotifier.notify(filter, ExecutionStatus.FAILED);
      if (e instanceof ZuulException) {
      throw (ZuulException) e;
      } else {
      ZuulException ex = new ZuulException(e, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
      ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
      throw ex;
      }
      }
      }
    • 进入ZuulFilterResult result = filter.runFilter();可以看到是直接调用了run()方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      public ZuulFilterResult runFilter() {
      ZuulFilterResult zr = new ZuulFilterResult();
      if (!isFilterDisabled()) {
      if (shouldFilter()) {
      Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
      try {
      // 执行run方法
      Object res = run();
      zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
      } catch (Throwable e) {
      t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
      zr = new ZuulFilterResult(ExecutionStatus.FAILED);
      zr.setException(e);
      } finally {
      t.stopAndLog();
      }
      } else {
      zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
      }
      }
      return zr;
      }
2.2、route()
  • preRoute()执行完成之后就是执行route()了,我们进入com.netflix.zuul.ZuulRunner#route(),可以看到这里和preRoute()方法执行一样也是执行了runFilters()方法,只不过是用参数进行区分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* executes "route" filterType ZuulFilters
*
* @throws ZuulException
*/
public void route() throws ZuulException {
FilterProcessor.getInstance().route();
}

// com.netflix.zuul.FilterProcessor#route
public void route() throws ZuulException {
try {
runFilters("route");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());
}
}

List list

  • 查看上图可以发现默认是有三个routing filter,我们这里关注的是RibbonRoutingFilter,这里是进行负载均衡路由转发的操作

    • 进入processZuulFilter(ZuulFilter filter)方法,查看RequestContext变量已经发现有一些关键信息了,这些信息是pre filter添加上去的,为路由转发为准备
    • 进入RibbonRoutingFilterrun()方法,可以看到是封装了一个Ribbon请求,执行请求,设置请求结果

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      @Override
      public Object run() {
      RequestContext context = RequestContext.getCurrentContext();
      this.helper.addIgnoredHeaders();
      try {
      // 根据RequestContext封装为一个Ribbon请求命名对象,里面有请求链接及请求参数
      RibbonCommandContext commandContext = buildCommandContext(context);
      // 执行请求
      ClientHttpResponse response = forward(commandContext);
      // 设置请求结果
      setResponse(response);
      return response;
      }
      catch (ZuulException ex) {
      throw new ZuulRuntimeException(ex);
      }
      catch (Exception ex) {
      throw new ZuulRuntimeException(ex);
      }
      }
    • 进入forward(commandContext)方法,command.execute();就是通过服务名来找出具体可以接受服务的ipport,然后请求执行,这里涉及到从注册中心获取服务ipport,负载均衡处理,断路器处理

    • 最终结果会放在ClientHttpResponse

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
      Map<String, Object> info = this.helper.debug(context.getMethod(),
      context.getUri(), context.getHeaders(), context.getParams(),
      context.getRequestEntity());
      // 创建请求
      RibbonCommand command = this.ribbonCommandFactory.create(context);
      try {
      // 执行请求
      ClientHttpResponse response = command.execute();
      this.helper.appendDebug(info, response.getRawStatusCode(), response.getHeaders());
      return response;
      }
      catch (HystrixRuntimeException ex) {
      return handleException(info, ex);
      }
      }
    • 进入this.ribbonCommandFactory.create(context);,下图是获取了RibbonLoadBalancingHttpClient,查看参数可以看到一些关键信息,比如链接超时时间

2.3、postRoute()
  • 进入com.netflix.zuul.ZuulRunner#postRoute() 与上面同理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void postRoute() throws ZuulException {
FilterProcessor.getInstance().postRoute();
}

// com.netflix.zuul.FilterProcessor#postRoute
public void postRoute() throws ZuulException {
try {
runFilters("post");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + e.getClass().getName());
}
}
2.4、error()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void error() {
try {
runFilters("error");
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}

// com.netflix.zuul.FilterProcessor#error
public void error() {
try {
runFilters("error");
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}

其他

总结

  • ZuulControllerSpringCloud Zuul的统一入口,因为要和Spring联系起来,所以这里遵循的Spring MVC DispatcherServlet的模式,这个ZuulControllercom.netflix.zuul包下的ZuulServlet整合起来,实际请求是跳转到ZuulServlet来处理的
  • Zuul组件的核心是一系列的过滤器filters,通过一系列的filters流式处理,按照阶段分为preroutingposterror四种类型的filter,在流式处理过程中使用RequestContext保存整个请求需要的参数及结果

参考