前言

前一篇文件中我们分析了zuul对Filter请求了不同的阶段划分了多个生命周期即FilterType。接下来我们继续分析每一个FilterType的具体的Filter有哪些,他们都干了什么。

ZuulFilters运行流程图

  • 前面我们分析完了zuul的一个生命周期,下面我们在来仔细的看一下每个生命周期具体使用到的Filter

Pre

  • 同样你可以在spring-cloud-starter-netflix-zuul-2.0.2.RELEASE包中找到我们的用到的代码片段。
  • 图中被圈起来的代码就是zuul默认激活的Filter,接下来我们分别看下他们都在干嘛。
  • 我们用debug的方式看看zuul激活的Pre的Filter
  • 可以看到我们一共有7个PreFilter,除开两个我们自定义的Filter剩下5个都是zuul自带的,下面我们挨个看一下这些Filer到底干了些嘛事情。
  • 一下我们只贴run()中的代码。

ServletDetectionFilter(顺序 SERVLET_DETECTION_FILTER_ORDER = -3)

  • 这个Filter主要是爬电请求是否来自于DispatcherServlet,并标记servlet的类型。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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;
}

Servlet30WrapperFilter(顺序 SERVLET_30_WRAPPER_FILTER_ORDER = -2)

  • 由于在zuul 1.2.2中存在一个错误,其中HttpServletRequestWrapper.getRequest返回一个包装请求而不是原始请求。这个Filter主要是目的是为了修复这个问题对HttpServletRequestWrapper进行包装,并覆盖 HttpServletRequestWrapper.getRequest.
 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
	@Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
		HttpServletRequest request = ctx.getRequest();
    // 如果是已经包装过的类
		if (request instanceof HttpServletRequestWrapper) {
      // 反射取得HttpServletRequest
			request = (HttpServletRequest) ReflectionUtils.getField(this.requestField,request);
      // 重新包装之后设置到ctx中
			ctx.setRequest(new Servlet30RequestWrapper(request));
		}
    // 这里其实就是使用了我们在前面Filter设置的IS_DISPATCHER_SERVLET_REQUEST_KEY的值。
		else if (RequestUtils.isDispatcherServletRequest()) {
			ctx.setRequest(new Servlet30RequestWrapper(request));
		}
		return null;
	}

  // Servlet30RequestWrapper.java 这个类其实很简单 就是修复zuul 1.2.2中存在一个错误
  class Servlet30RequestWrapper extends HttpServletRequestWrapper {
  	private HttpServletRequest request;

  	Servlet30RequestWrapper(HttpServletRequest request) {
  		super(request);
  		this.request = request;
  	}
  	@Override
  	public HttpServletRequest getRequest() {
  		return this.request;
  	}
  }

FormBodyWrapperFilter(顺序 FORM_BODY_WRAPPER_FILTER_ORDER = -1)

  • 该类主要是处理两种Content-Type,我们先看一下shouldFilter,
 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
@Override
public boolean shouldFilter() {
  RequestContext ctx = RequestContext.getCurrentContext();
  HttpServletRequest request = ctx.getRequest();
  String contentType = request.getContentType();
  // 如果contextType则不进行处理
  if (contentType == null) {
    return false;
  }
 // 这里很直观的可以看到主要是处理表单数据支持两种mediaType
 // 第一种是:APPLICATION_FORM_URLENCODED
 // 第二中是 DispatcherServlet && MULTIPART_FORM_DATA
  try {
    MediaType mediaType = MediaType.valueOf(contentType);
    return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)
        || (isDispatcherServletRequest(request) && MediaType.MULTIPART_FORM_DATA.includes(mediaType));
  }
  catch (InvalidMediaTypeException ex) {
    return false;
  }
}

public Object run() {
  RequestContext ctx = RequestContext.getCurrentContext();
  HttpServletRequest request = ctx.getRequest();
  FormBodyRequestWrapper wrapper = null;
  // 同理如果是被包装过的
  if (request instanceof HttpServletRequestWrapper) {
    // 反射取得HttpServletRequest
  	HttpServletRequest wrapped = (HttpServletRequest) ReflectionUtils.getField(this.requestField, request);
    //重新包装
  	wrapper = new FormBodyRequestWrapper(wrapped);
    // 反射覆盖request字段
  	ReflectionUtils.setField(this.requestField, request, wrapper);
    // 如果是通过`/zuul` 的servlet进来的请求
  	if (request instanceof ServletRequestWrapper) {
        // 反射覆盖request字段
  		ReflectionUtils.setField(this.servletRequestField, request, wrapper);
  	}
}
// 如果没有被包装过,直接包装一下即可
else {
	wrapper = new FormBodyRequestWrapper(request);
	ctx.setRequest(wrapper);
}
if (wrapper != null) {
  // 将content-type放置到ctx中。
	ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());
}
return null;
}
  • 这里的FormBodyRequestWrapper代码毕竟多,就不展开看了,有兴趣的老哥可以自己去翻阅。

DebugFilter(顺序 DEBUG_FILTER_ORDER = 1)

  • 这个类主要是判断用户是否携带debug参数,如果携带就设置一些标志位,就不展开了。

PreDecorationFilter(顺序 PRE_DECORATION_FILTER_ORDER = 5)

  • 这个类的代码行数相对较多,但是他确实只干两件事儿。设置serviceId,设置Header,都在为后面的具体route做准备。
 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
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
// 通过URI获取到对应的路由信息
//比如我们的请求是 mch-service/get 那么获取到的就是mch-servie为serviceId的路由
Route route = this.routeLocator.getMatchingRoute(requestURI);
// 如果匹配到有路由才进行处理
if (route != null) {
  // 获取到location,如果是静态路由配置为(http://xxx),如果是动态服务发现路由则是serviceId
	String location = route.getLocation();
	if (location != null) {
  // 添加path(/get)到ctx中方便后面直接使用
	ctx.put(REQUEST_URI_KEY, route.getPath());
  // 添加PROXY_KEY到ctx中方便后面直接使用
	ctx.put(PROXY_KEY, route.getId());
  // 如果你配置了相关路由信息的header 将在这里处理
	if (!route.isCustomSensitiveHeaders()) {
		this.proxyRequestHelper
				.addIgnoredHeaders(this.properties.getSensitiveHeaders().toArray(new String[0]));
	}
	else {
		this.proxyRequestHelper.addIgnoredHeaders(route.getSensitiveHeaders().toArray(new String[0]));
	}
  // 取得getRetryable并放入ctx
	if (route.getRetryable() != null) {
		ctx.put(RETRYABLE_KEY, route.getRetryable());
	}
  //判断是否是配置的静态路由(http://xx.xx.com/users),而非服务发现的服务
	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 {
		// 设置serviceID,并清空静态路由的Host
		ctx.set(SERVICE_ID_KEY, location);
		ctx.setRouteHost(null);
		ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
	}
	  //  ...
	}
}
else {
  // 由于我们会在自定义的PRE路由设置好白名单UrlList 所有这里几乎走不过来
	log.warn("No route found for uri: " + requestURI);
  ...
}
return null;
}

RouteFilter

  • 前置走了这么久,总算来到我们的具体请求发起阶段了,我们可以从下图看到至少两个信息。 第一个信息是zuul内置了3个RouteFilter, 第二个是Zuul默认支持使用apacheHttpClientokttp发起请求。
  • 下面我们就来具体的看一下这三个RouteFilter都在分别在做什么事情。

RibbonRoutingFilter(顺序 RIBBON_ROUTING_FILTER_ORDER = 10)

  • 我们分析的前置的Pre设置的相关信息都会在这里用到
  • 先看RibbonRoutingFilter.shouldFilter
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Override
public boolean shouldFilter() {
  RequestContext ctx = RequestContext.getCurrentContext();
  // 首先单独看看ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
  // 结合上一个Filter设置的信息,很直观的能想到如果是静态路由配置,Ribbon压根管不着,直接不支持处理。
  return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
  // 这里判断了ctx中的sendZuulResponse是否为true,该值默认为true,
  //可想而知如果我们不想走RibbonRoutingFilter,则在PreFilter中调用ctx.setSendZuulResponse(false)即可。
      && ctx.sendZuulResponse());
}
  • 在看具体的run方法,具体路由的操作。
 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
@Override
public Object run() {
	RequestContext context = RequestContext.getCurrentContext();
	this.helper.addIgnoredHeaders();
	try {
    // 这里buildCommandContext方法主要是获取前置的Pre设置到ctx中的相关参数,并构造一个RibbonCommandContext返回
		RibbonCommandContext commandContext = buildCommandContext(context);
    // forward内部其实也是委托给了ribbonCommandFactory的实现者去进行具体调用,
		ClientHttpResponse response = forward(commandContext);
    // 设置结果 其实就是将结果放到ctx(threadLocal中的zuulResponse属性)进行结果传递。
		setResponse(response);
		return response;
	}
	catch (ZuulException ex) {
		throw new ZuulRuntimeException(ex);
	}
	catch (Exception ex) {
		throw new ZuulRuntimeException(ex);
	}
}

protected RibbonCommandContext buildCommandContext(RequestContext context) {
  //...其他代码

  // 这里取出来serviceId -> mch-service
	String serviceId = (String) context.get(SERVICE_ID_KEY);
  // 这里取出来retryable
	Boolean retryable = (Boolean) context.get(RETRYABLE_KEY);
  // 该值并没有设置过,永远为null
	Object loadBalancerKey = context.get(LOAD_BALANCER_KEY);

  // 在此方法可以看成是String uri = (String) context.get(REQUEST_URI_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);
}

protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
...
// 将Context传入到具体的实现中进行包装,返回具体的包装好httpclient的cmmand,而这个command其实就HystrixExecutable接口。
// Zuul提供的默认实现有(HttpClientRibbonCommandFactory,OkHttpRibbonCommandFactory,RestClientRibbonCommandFactory)
RibbonCommand command = this.ribbonCommandFactory.create(context);
try {
  // 执行调用并获取到Htpp响应结果
  ClientHttpResponse response = command.execute();
  this.helper.appendDebug(info, response.getRawStatusCode(), response.getHeaders());
  return response;
}
catch (HystrixRuntimeException ex) {
  // 异常处理
  return handleException(info, ex);
}
}
// 可以看到这里只有http请求异常时,才会触发
protected ClientHttpResponse handleException(Map<String, Object> info,
  HystrixRuntimeException ex) throws ZuulException {
int statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
Throwable cause = ex;
String message = ex.getFailureType().toString();

ClientException clientException = findClientException(ex);
if (clientException == null) {
  clientException = findClientException(ex.getFallbackException());
}

if (clientException != null) {
  if (clientException
      .getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) {
    statusCode = HttpStatus.SERVICE_UNAVAILABLE.value();
  }
  cause = clientException;
  message = clientException.getErrorType().toString();
}
info.put("status", String.valueOf(statusCode));
// 包装了相关信息并向上抛出。
throw new ZuulException(cause, "Forwarding error", statusCode, message);
}


// 设置结果
protected void setResponse(ClientHttpResponse resp)
  throws ClientException, IOException {
RequestContext.getCurrentContext().set("zuulResponse", resp);
// 一下方法的调用会将返回的body的inputStream设置到ctx
// context.setResponseDataStream(entity); 方便后面的fiter使用
this.helper.setResponse(resp.getRawStatusCode(),
    resp.getBody() == null ? null : resp.getBody(), resp.getHeaders());
}
  • 这个地方比较复杂,如果你还没理清楚,建议debug配合我的注解跟几次就十分清晰了。

SimpleHostRoutingFilter (顺序 SIMPLE_HOST_ROUTING_FILTER_ORDER = 100)

  • 这里我不打算分析这个hostrouteFilter,因为在实际应用中我们的业务很少过静态路由设置。 我们这里看一下他会处理那些请求即可
1
2
3
4
5
6
@Override
public boolean shouldFilter() {
  // 从Ctx中获取Pre路由中这种的静态如有主机 如果有,并且需要进行发送响应到客户端
	return RequestContext.getCurrentContext().getRouteHost() != null
			&& RequestContext.getCurrentContext().sendZuulResponse();
}

SendForwardFilter (顺序 SIMPLE_HOST_ROUTING_FILTER_ORDER = 500)

  • 这个SendForwardFilter其实就调用了我们熟悉的HttpServletRquest.getRequestDispatcher.forward(ctx.getRequest(), ctx.getResponse())基本用不上。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Override
public Object run() {
  try {
    RequestContext ctx = RequestContext.getCurrentContext();
    String path = (String) ctx.get(FORWARD_TO_KEY);
    RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);
    if (dispatcher != null) {
      ctx.set(SEND_FORWARD_FILTER_RAN, true);
      if (!ctx.getResponse().isCommitted()) {
        // 熟悉的forward
        dispatcher.forward(ctx.getRequest(), ctx.getResponse());
        ctx.getResponse().flushBuffer();
      }
    }
  }
  catch (Exception ex) {
    ReflectionUtils.rethrowRuntimeException(ex);
  }
  return null;
}

PostFilter

  • 路由完成之后的结果会走到这里来。

LocationRewriteFilter (顺序 SEND_RESPONSE_FILTER_ORDER - 100 = 1000 - 100 = 900)

  • 主要处理3XX重定向请求,基本用不上,不多说。
1
2
3
4
5
6
7
@Override
public boolean shouldFilter() {
  RequestContext ctx = RequestContext.getCurrentContext();
  int statusCode = ctx.getResponseStatusCode();
  //这里主要是处理HTTP CODE
  return HttpStatus.valueOf(statusCode).is3xxRedirection();
}

SendResponseFilter(顺序 SEND_RESPONSE_FILTER_ORDER = 1000)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Override
public Object run() {
try {
  // 添加响应头信息
addResponseHeaders();
  // 写会结果到请求发起方
writeResponse();
}catch (Exception ex) {
  //异常向上抛出
	ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}

SendErrorFilter(顺序 SEND_ERROR_FILTER_ORDER = 0)

  • 在实际业务中我们一般采用直接继承该类的方式来轻松扩展它,完成业务相关异常的信息转换。
1
2
3
4
5
6
7
@Override
public boolean shouldFilter() {
  RequestContext ctx = RequestContext.getCurrentContext();
  // 由于异常会向上抛出的同时会设置到ctx中,这里统一全局处理掉网关抛出的异常。
  return ctx.getThrowable() != null
      && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
}