Zuul-02丨Zuul执行流程

Posted by jiefang on December 31, 2020

Zuul执行流程

自动装配

EnableZuulProxy

1
2
3
4
5
6
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}

ZuulProxyMarkerConfiguration

1
2
3
4
5
6
7
8
9
10
@Configuration(proxyBeanMethods = false)
public class ZuulProxyMarkerConfiguration {
   @Bean
   public Marker zuulProxyMarkerBean() {
      return new Marker();
   }
   class Marker {

   }
}

ZuulProxyAutoConfiguration

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
@Configuration(proxyBeanMethods = false)
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
      RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
      RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
      HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
    // pre filters
	@Bean
	@ConditionalOnMissingBean(PreDecorationFilter.class)
	public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
			ProxyRequestHelper proxyRequestHelper) {
		return new PreDecorationFilter(routeLocator,
				this.server.getServlet().getContextPath(), this.zuulProperties,
				proxyRequestHelper);
	}

	// route filters
	@Bean
	@ConditionalOnMissingBean(RibbonRoutingFilter.class)
	public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
			RibbonCommandFactory<?> ribbonCommandFactory) {
		RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,
				this.requestCustomizers);
		return filter;
	}
}

核心类

ZuulServlet

原生的HttpServlet

ZuulServlet#init()

1
2
3
4
5
6
7
public class ZuulServlet extends HttpServlet {
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        String bufferReqsStr = config.getInitParameter("buffer-requests");
        boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true");
        this.zuulRunner = new ZuulRunner(bufferReqs);
    }

ZuulServlet#service()

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
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    try {
        this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
        RequestContext context = RequestContext.getCurrentContext();
        context.setZuulEngineRan();

        try {
            this.preRoute();
        } catch (ZuulException var13) {
            this.error(var13);
            this.postRoute();
            return;
        }

        try {
            this.route();
        } catch (ZuulException var12) {
            this.error(var12);
            this.postRoute();
            return;
        }

        try {
            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();
    }
}
ZuulRunner#init()
1
2
3
4
5
6
7
8
9
10
public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
    RequestContext ctx = RequestContext.getCurrentContext();
    if (this.bufferRequests) {
        ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
    } else {
        ctx.setRequest(servletRequest);
    }

    ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
}
RequestContext#getCurrentContext()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class RequestContext extends ConcurrentHashMap<String, Object> {
    protected static Class<? extends RequestContext> contextClass = RequestContext.class;
    private static RequestContext testContext = null;
    protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
        //ThreadLocal初始化时设置的RequestContext
        protected RequestContext initialValue() {
            try {
                return (RequestContext)RequestContext.contextClass.newInstance();
            } catch (Throwable var2) {
                throw new RuntimeException(var2);
            }
        }
    };
    //获取当前线程本地变量的RequestContext,没有则创建
    public static RequestContext getCurrentContext() {
        if (testContext != null) {
            return testContext;
        } else {
            RequestContext context = (RequestContext)threadLocal.get();
            return context;
        }
    }    
}

####

ZuulServletFilter

原生的Http的Filter。

PreDecorationFilter

解析URL,根据URL获取对应路由规则,设置路由规则至RequestContext

Route{id=’ServiceB’, fullPath=’/demo/ServiceB/user/sayHello/1’, path=’/ServiceB/user/sayHello/1’, location=’ServiceB’, prefix=’/demo’, retryable=false, sensitiveHeaders=[], customSensitiveHeaders=false, prefixStripped=true}

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 PreDecorationFilter extends ZuulFilter {
    	@Override
	public boolean shouldFilter() {
		RequestContext ctx = RequestContext.getCurrentContext();
		return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded
				&& !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined
	}
	@Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
        //根据request获取url
		final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
        //根据url匹配路由规则
		Route route = this.routeLocator.getMatchingRoute(requestURI);
		if (route != null) {
			String location = route.getLocation();
			if (location != null) {
				ctx.put(REQUEST_URI_KEY, route.getPath());
				ctx.put(PROXY_KEY, route.getId());
				if (!route.isCustomSensitiveHeaders()) {
					this.proxyRequestHelper.addIgnoredHeaders(
							this.properties.getSensitiveHeaders().toArray(new String[0]));
				}
				else {
					this.proxyRequestHelper.addIgnoredHeaders(
							route.getSensitiveHeaders().toArray(new String[0]));
				}

				if (route.getRetryable() != null) {
					ctx.put(RETRYABLE_KEY, route.getRetryable());
				}

				if (location.startsWith(HTTP_SCHEME + ":")
						|| location.startsWith(HTTPS_SCHEME + ":")) {
					ctx.setRouteHost(getUrl(location));
					ctx.addOriginResponseHeader(SERVICE_HEADER, location);
				}
				else if (location.startsWith(FORWARD_LOCATION_PREFIX)) {
					ctx.set(FORWARD_TO_KEY,
							StringUtils.cleanPath(
									location.substring(FORWARD_LOCATION_PREFIX.length())
											+ route.getPath()));
					ctx.setRouteHost(null);
					return null;
				}
				else {
					// set serviceId for use in filters.route.RibbonRequest
					ctx.set(SERVICE_ID_KEY, location);
					ctx.setRouteHost(null);
					ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
				}
				if (this.properties.isAddProxyHeaders()) {
					addProxyHeaders(ctx, route);
					String xforwardedfor = ctx.getRequest()
							.getHeader(X_FORWARDED_FOR_HEADER);
					String remoteAddr = ctx.getRequest().getRemoteAddr();
					if (xforwardedfor == null) {
						xforwardedfor = remoteAddr;
					}
					else if (!xforwardedfor.contains(remoteAddr)) { // Prevent duplicates
						xforwardedfor += ", " + remoteAddr;
					}
					ctx.addZuulRequestHeader(X_FORWARDED_FOR_HEADER, xforwardedfor);
				}
				if (this.properties.isAddHostHeader()) {
					ctx.addZuulRequestHeader(HttpHeaders.HOST,
							toHostHeader(ctx.getRequest()));
				}
			}
		}
		else {
			log.warn("No route found for uri: " + requestURI);
			String forwardURI = getForwardUri(requestURI);

			ctx.set(FORWARD_TO_KEY, forwardURI);
		}
		return null;
	}
}

RibbonRoutingFilter

根据Ribbon的LoadBalancer进行负载均衡,进行真正的请求分发。

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
public class RibbonRoutingFilter extends ZuulFilter {
    @Override
	public boolean shouldFilter() {
		RequestContext ctx = RequestContext.getCurrentContext();
		return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
				&& ctx.sendZuulResponse());
	}
	@Override
	public Object run() {
		RequestContext context = RequestContext.getCurrentContext();
		this.helper.addIgnoredHeaders();
		try {
			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);
		}
	}    
}

buildCommandContext()

组装RibbonCommandContext所需数据。

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
protected RibbonCommandContext buildCommandContext(RequestContext context) {
   HttpServletRequest request = context.getRequest();

   MultiValueMap<String, String> headers = this.helper
         .buildZuulRequestHeaders(request);
   MultiValueMap<String, String> params = this.helper
         .buildZuulRequestQueryParams(request);
   String verb = getVerb(request);
   InputStream requestEntity = getRequestBody(request);
   if (request.getContentLength() < 0 && !verb.equalsIgnoreCase("GET")) {
      context.setChunkedRequestBody();
   }

   String serviceId = (String) context.get(SERVICE_ID_KEY);
   Boolean retryable = (Boolean) context.get(RETRYABLE_KEY);
   Object loadBalancerKey = context.get(LOAD_BALANCER_KEY);

   String uri = this.helper.buildZuulRequestURI(request);

   // remove double slashes
   uri = uri.replace("//", "/");

   long contentLength = useServlet31 ? request.getContentLengthLong()
         : request.getContentLength();

   return new RibbonCommandContext(serviceId, verb, uri, retryable, headers, params,
         requestEntity, this.requestCustomizers, contentLength, loadBalancerKey);
}

forward()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
   Map<String, Object> info = this.helper.debug(context.getMethod(),
         context.getUri(), context.getHeaders(), context.getParams(),
         context.getRequestEntity());
   //创建HttpClientRibbonCommand,它是AbstractRibbonCommand的子类
   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);
   }

}

AbstractRibbonCommand

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
public abstract class AbstractRibbonCommand<LBC extends AbstractLoadBalancerAwareClient<RQ, RS>, RQ extends ClientRequest, RS extends HttpResponse>
		extends HystrixCommand<ClientHttpResponse> implements RibbonCommand {
    @Override
    protected ClientHttpResponse run() throws Exception {
       final RequestContext context = RequestContext.getCurrentContext();

       RQ request = createRequest();
       RS response;

       boolean retryableClient = this.client instanceof AbstractLoadBalancingClient
             && ((AbstractLoadBalancingClient) this.client)
                   .isClientRetryable((ContextAwareRequest) request);

       if (retryableClient) {
          response = this.client.execute(request, config);
       }
       else {
          //根据LoadBalancer选择一个实例发出Http请求
          response = this.client.executeWithLoadBalancer(request, config);
       }
       context.set("ribbonResponse", response);

       // Explicitly close the HttpResponse if the Hystrix command timed out to
       // release the underlying HTTP connection held by the response.
       //
       if (this.isResponseTimedOut()) {
          if (response != null) {
             response.close();
          }
       }
       return new RibbonHttpResponse(response);
    }
}