Zuul源码分析(2)Filter分析

前言

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

ZuulFilters运行流程图

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

Sample

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发起请求。
    Sample
  • 下面我们就来具体的看一下这三个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);
    }