SpringCloud-Zuul source code analysis and transformation route

When using SpringCloud ready to use as a gateway Zuul micro services, default routing Zuul are mainly two, one is designated static routing directly in the configuration file, the other is automatically match the service name registered in accordance with Eureka. For example, if there is a service called service1 through   will be able to access this service. But I expected this and demand still some gaps. There are many methods to achieve about dynamic routing online, is not generally thought to take a registered service information from Eureka pull, but in its own database to maintain a routing table, routing time to read the database pull, to achieve automatic update. And I demand further, I want to expose the external gateway interface is a fixed url, such as  , and then specify the service you want to access the header information according to a service, rather than in url behind splice service name. I also do not want to put me into the Eureka service name directly exposed to the api, but rather to do the mapping layer, so that I can flexibly specify serivce name.

E.g:

Now look at the source code Zuul take a look at how to achieve this function.

We all know that on Springboot start classes plus a @EnableZuulProxy to enable Zuul. From this point to go see annotation:

@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}

It introduces ZuulProxyMarkerConfiguration this configuration, go look.

/**
 * Responsible for adding in a marker bean to trigger activation of
 * {@link ZuulProxyAutoConfiguration}.
 *
 * @author Biju Kunjummen
 */

@Configuration
public class ZuulProxyMarkerConfiguration {

    @Bean
    public Marker zuulProxyMarkerBean() {
        return new Marker();
    }

    class Marker {

    }

}

See the explanatory notes this is used to activate ZuulProxyAutoConfiguration, look at this class

@Configuration
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
        RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
        RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
        HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
    //...
}

This configuration registered under a lot of components, but temporarily do not look at it inherits from ZuulServerAutoConfiguration, look at this class:

@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass({ ZuulServlet.class, ZuulServletFilter.class })
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
// Make sure to get the ServerProperties from the same place as a normal web app would
// FIXME @Import(ServerPropertiesAutoConfiguration.class)
public class ZuulServerAutoConfiguration {

    @Autowired
    protected ZuulProperties zuulProperties;

    @Autowired
    protected ServerProperties server;

    @Autowired(required = false)
    private ErrorController errorController;

    private Map<String, CorsConfiguration> corsConfigurations;

    @Autowired(required = false)
    private List<WebMvcConfigurer> configurers = emptyList();

    @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.getServlet().getContextPath(),
                this.zuulProperties);
    }

    @Bean
    public ZuulController zuulController() {
        return new ZuulController();
    }

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

    protected final Map<String, CorsConfiguration> getCorsConfigurations() {
        if (this.corsConfigurations == null) {
            ZuulCorsRegistry registry = new ZuulCorsRegistry();
            this.configurers.forEach(configurer -> configurer.addCorsMappings(registry));
            this.corsConfigurations = registry.getCorsConfigurations();
        }
        return this.corsConfigurations;
    }

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

    @Bean
    @ConditionalOnMissingBean(name = "zuulServlet")
    @ConditionalOnProperty(name = "zuul.use-filter", havingValue = "false", matchIfMissing = true)
    public ServletRegistrationBean zuulServlet() {
        ServletRegistrationBean<ZuulServlet> 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;
    }

    @Bean
    @ConditionalOnMissingBean(name = "zuulServletFilter")
    @ConditionalOnProperty(name = "zuul.use-filter", havingValue = "true", matchIfMissing = false)
    public FilterRegistrationBean zuulServletFilter() {
        final FilterRegistrationBean<ZuulServletFilter> filterRegistration = new FilterRegistrationBean<>();
        filterRegistration.setUrlPatterns(
                Collections.singleton(this.zuulProperties.getServletPattern()));
        filterRegistration.setFilter(new ZuulServletFilter());
        filterRegistration.setOrder(Ordered.LOWEST_PRECEDENCE);
        // The whole point of exposing this servlet is to provide a route that doesn't
        // buffer requests.
        filterRegistration.addInitParameter("buffer-requests", "false");
        return filterRegistration;
    }

    // pre filters

    @Bean
    public ServletDetectionFilter servletDetectionFilter() {
        return new ServletDetectionFilter();
    }

    @Bean
    public FormBodyWrapperFilter formBodyWrapperFilter() {
        return new FormBodyWrapperFilter();
    }

    @Bean
    public DebugFilter debugFilter() {
        return new DebugFilter();
    }

    @Bean
    public Servlet30WrapperFilter servlet30WrapperFilter() {
        return new Servlet30WrapperFilter();
    }

    // post filters

    @Bean
    public SendResponseFilter sendResponseFilter(ZuulProperties properties) {
        return new SendResponseFilter(zuulProperties);
    }

    @Bean
    public SendErrorFilter sendErrorFilter() {
        return new SendErrorFilter();
    }

    @Bean
    public SendForwardFilter sendForwardFilter() {
        return new SendForwardFilter();
    }

    @Bean
    @ConditionalOnProperty("zuul.ribbon.eager-load.enabled")
    public ZuulRouteApplicationContextInitializer zuulRoutesApplicationContextInitiazer(
            SpringClientFactory springClientFactory) {
        return new ZuulRouteApplicationContextInitializer(springClientFactory,
                zuulProperties);
    }

    @Configuration
    protected static class ZuulFilterConfiguration {

        @Autowired
        private Map<String, ZuulFilter> filters;

        @Bean
        public ZuulFilterInitializer zuulFilterInitializer(CounterFactory counterFactory,
                TracerFactory tracerFactory) {
            FilterLoader filterLoader = FilterLoader.getInstance();
            FilterRegistry filterRegistry = FilterRegistry.instance();
            return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory,
                    filterLoader, filterRegistry);
        }

    }

    @Configuration
    @ConditionalOnClass(MeterRegistry.class)
    protected static class ZuulCounterFactoryConfiguration {

        @Bean
        @ConditionalOnBean(MeterRegistry.class)
        @ConditionalOnMissingBean(CounterFactory.class)
        public CounterFactory counterFactory(MeterRegistry meterRegistry) {
            return new DefaultCounterFactory(meterRegistry);
        }

    }

    @Configuration
    protected static class ZuulMetricsConfiguration {

        @Bean
        @ConditionalOnMissingClass("io.micrometer.core.instrument.MeterRegistry")
        @ConditionalOnMissingBean(CounterFactory.class)
        public CounterFactory counterFactory() {
            return new EmptyCounterFactory();
        }

        @ConditionalOnMissingBean(TracerFactory.class)
        @Bean
        public TracerFactory tracerFactory() {
            return new EmptyTracerFactory();
        }

    }

    private static class ZuulRefreshListener
            implements ApplicationListener<ApplicationEvent> {

        @Autowired
        private ZuulHandlerMapping zuulHandlerMapping;

        private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();

        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            if (event instanceof ContextRefreshedEvent
                    || event instanceof RefreshScopeRefreshedEvent
                    || event instanceof RoutesRefreshedEvent
                    || event instanceof InstanceRegisteredEvent) {
                reset();
            }
            else if (event instanceof ParentHeartbeatEvent) {
                ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
                resetIfNeeded(e.getValue());
            }
            else if (event instanceof HeartbeatEvent) {
                HeartbeatEvent e = (HeartbeatEvent) event;
                resetIfNeeded(e.getValue());
            }
        }

        private void resetIfNeeded(Object value) {
            if (this.heartbeatMonitor.update(value)) {
                reset();
            }
        }

        private void reset() {
            this.zuulHandlerMapping.setDirty(true);
        }

    }

    private static class ZuulCorsRegistry extends CorsRegistry {

        @Override
        protected Map<String, CorsConfiguration> getCorsConfigurations() {
            return super.getCorsConfigurations();
        }

    }

}

This configuration class registered a lot of bean:

  • SimpleRouteLocator: Default routing locators, is responsible for maintaining the routing configuration in the configuration file.
  • DiscoveryClientRouteLocator: inherited from SimpleRouteLocator, the class will be in the configuration file to configure static routing and service discovery (such as eureka) routing information to merge relied mainly routed to specific services.
  • CompositeRouteLocator: a combination routing locator, look into the reference you know will save more RouteLocator, in fact, only during construction include a DiscoveryClientRouteLocator.
  • ZuulController: a Controller Zuul created to deal with the request referred to ZuulServlet.
  • ZuulHandlerMapping: This will be added to HandlerMapping chain SpringMvc, only selected ZuulHandlerMapping request to start the subsequent process of Zuul.

There are some other Filter, not one by one to read.

Wherein, ZuulServlet is the core of the whole process, is the process of requesting such a specific, when Zuulservlet receives a request, it creates a ZuulRunner object are initialized RequestContext: As some of the data stored in the entire request, and all Zuulfilter shared. ZuulRunner There is also a FilterProcessor, FilterProcessor as perform all Zuulfilter manager. FilterProcessor obtained from the zuulfilter filterloader, and is loaded zuulfilter filterFileManager, and supports groovy heat load, using a round-robin fashion thermal loading. With After these filter, zuulservelet Pre performed first type of filter, and then perform the route type of filter, the final implementation of the post is the type of filter, if there is an error when executing these filters error type will be executed filter. After the implementation of these filters, the final results of the request back to the client. RequestContext that will always follow the whole context object request cycle, there are between filters what information needs to be passed to set some values ​​into the line.

There is the example of FIG help understand this:

 

 

ZuulServlet of service method:

@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 {
                //执行pre阶段的filters
                preRoute();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                //执行route阶段的filters
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                //执行post阶段的filters
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }

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

Incidentally may explain ZuulFilter, he comprises four basic characteristics: the type of execution order, performs filtering condition, the specific operation.

String filterType();

int filterOrder();

boolean shouldFilter();

Object run();

For their meanings and functions are summarized as follows:

  • filterType: This function returns a string requires to represent the type of the filter, and the various stages of this type is defined in the HTTP request process. In Zuul default filter is defined four different types of life cycle, as follows:
  • pre: it can be called before the request is routed.
  • routing: routing request time is called.
  • post: is called after the routing and error filters.
  • error: is called when an error occurred while processing the request.
  • filterOrder: the filter is defined by the value of int execution order, the smaller the value the higher the priority.
  • shouldFilter:返回一个boolean类型来判断该过滤器是否要执行。我们可以通过此方法来指定过滤器的有效范围。
  • run:过滤器的具体逻辑。在该函数中,我们可以实现自定义的过滤逻辑,来确定是否要拦截当前的请求,不对其进行后续的路由,或是在请求路由返回结果之后,对处理结果做一些加工等。

下图源自Zuul的官方WIKI中关于请求生命周期的图解,它描述了一个HTTP请求到达API网关之后,如何在各个不同类型的过滤器之间流转的详细过程。

 

 

Zuul默认实现了一批过滤器,如下:

|过滤器 |order |描述 |类型 |:---|:---:|:---:|---:| |ServletDetectionFilter| -3| 检测请求是用 DispatcherServlet还是 ZuulServlet| pre| |Servlet30WrapperFilter| -2| 在Servlet 3.0 下,包装 requests| pre| |FormBodyWrapperFilter| -1| 解析表单数据| pre| |SendErrorFilter| 0| 如果中途出现错误| error| |DebugFilter| 1| 设置请求过程是否开启debug| pre| |PreDecorationFilter| 5| 根据uri决定调用哪一个route过滤器| pre| |RibbonRoutingFilter| 10| 如果写配置的时候用ServiceId则用这个route过滤器,该过滤器可以用Ribbon 做负载均衡,用hystrix做熔断| route| |SimpleHostRoutingFilter| 100| 如果写配置的时候用url则用这个route过滤| route| |SendForwardFilter| 500| 用RequestDispatcher请求转发| route| |SendResponseFilter| 1000| 用RequestDispatcher请求转发| post|

回到我的需求,我不需要静态配置,所有请求都是调用在eureka注册的服务,所以每次请求都要在route阶段转到RibbonRoutingFilter,由它使用Ribbon向其它服务发起请求,因此看一下这个类的shouldFilter()方法:

@Override
public boolean shouldFilter() {
    RequestContext ctx = RequestContext.getCurrentContext();
    return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
            && ctx.sendZuulResponse());
}

原来进入这个Filter的条件是RequestContext中getRouteHost为空且ctx.get(SERVICE_ID_KEY)不为空,即serviceId有值! 那么Zuul在默认情况下是怎么选择route阶段的Filter的呢?看到上面的pre阶段有一个PreDecorationFilter,这个类主要就是根据uri来给RequestContext添加不同的内容来控制之后走哪个route过滤器。 看下它的Run方法:

@Override
public Object run() {
    RequestContext ctx = RequestContext.getCurrentContext();
    final String requestURI = this.urlPathHelper
            .getPathWithinApplication(ctx.getRequest());
    //已经包含的路由配置里是否有能匹配到的route
    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());
            }
            //根据各种情况设置context
            //http:开头的
            if (location.startsWith(HTTP_SCHEME + ":")
                    || location.startsWith(HTTPS_SCHEME + ":")) {
                ctx.setRouteHost(getUrl(location));
                ctx.addOriginResponseHeader(SERVICE_HEADER, location);
            }
            //forward:开头的
            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;
            }
            //这里设置了serviceId,走Ribbon
            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);
        //都不满足的话,设置一个forward.to,走SendForwardFilter
        ctx.set(FORWARD_TO_KEY, forwardURI);
    }
    return null;
}

情况比较复杂,实际根据我的需求,我只要让route阶段时候使用RibbonRoutingFilter,因此我只要保证进入route阶段时RequestContext里包含对应服务的serviceId就行了。 我可以在pre阶段将请求头内的service转化为所需要的服务serviceId,设置到context内,同时移除context中其它有影响的值就行了。

听上去挺简单的,我们自定义一个pre阶段的Filter。

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

public class HeaderPreDecorationFilter extends ZuulFilter {

    private static final Log log = LogFactory.getLog(HeaderPreDecorationFilter.class);
    private RouteLocator routeLocator;
    private UrlPathHelper urlPathHelper = new UrlPathHelper();

    private Map<String, Service> serviceMap = new HashMap();

    public HeaderPreDecorationFilter(RouteLocator routeLocator, String dispatcherServletPath) {
        this.routeLocator = routeLocator;
        //举个小例子,假如我在后端有一个名为platform-server的服务,服务内有一个/mwd/client/test的接口
        serviceMap.put("mwd.service.test", new Service("platform-server", "/mwd/client/test"));
    }

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

    public int filterOrder() {
        return 6;
    }

    public boolean shouldFilter() {
        return true;
    }

    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
        //取得头信息
        String serviceName = request.getHeader("service");
        //获取头信息映射成对应的服务信息
        Service service = serviceMap.get(serviceName);
        String serviceURI = service.getServiceId() + service.getPath();
        //TODO 判断服务是否存在,可以做额外异常处理
        Route route = this.routeLocator.getMatchingRoute("/" + serviceURI);
        //设置context
        ctx.set("serviceId", service.getServiceId());
        ctx.put("requestURI", service.getPath());
        ctx.put("proxy", service.getServiceId());
        ctx.put("retryable", false);
//        ctx.remove("forward.to");  
        log.info(String.format("send %s request to %s", request.getMethod(), request.getRequestURL().toString()));
        return null;
    }

    class Service {

        public Service(String serviceId, String path) {
            this.serviceId = serviceId;
            this.path = path;
        }

        String serviceId;
        String path;

        public String getServiceId() {
            return serviceId;
        }

        public void setServiceId(String serviceId) {
            this.serviceId = serviceId;
        }

        public String getPath() {
            return path;
        }

        public void setPath(String path) {
            this.path = path;
        }
    }
}

然后可以将之前的PreDecorationFilter禁用,以免它对RequestContext的操作影响我们,例如,如果没有匹配到任何规则,它会在RequestContext中添加一个forward.to 这个key会调用post阶段的SendForwardFilter导致报错。

在配置文件设置zuul.PreDecorationFilter.pre.disable=true即可。

现在将这个类纳入spring容器中,写法可以参照ZuulProxyAutoConfiguration中其它Filter的实例化方式,我们也做一个自己的配置类:

@Configuration
@EnableConfigurationProperties({ZuulProperties.class})
public class Config {

    @Autowired
    protected ZuulProperties zuulProperties;
    @Autowired
    protected ServerProperties server;

    @Bean
    public HeaderPreDecorationFilter geaderPreDecorationFilter(RouteLocator routeLocator) {
        return new HeaderPreDecorationFilter(routeLocator, this.server.getServlet().getContextPath());
    }
}

这样每次请求进来后,在pre阶段会去取service头信息,然后匹配成对应的serviceId(取不到或者匹配不到自然就报错了),在route阶段就直接触发RibbonRoutingFilter调用服务返回了!

现在还剩一个网关入口的问题,我是想让所有的请求走一个固定的url,先试着直接访问一下:localhost:8080/gateway ,直接报404了。很正常,我们还没有做这个url path的映射! SpringMvc的DispatcherServlet没有查到这个path的处理方法自然报404了!怎样才能让gateway这个路由进入zuul中呢?

我们记得在上面Zuul的配置类中有一个ZuulHandlerMapping, 当一个请求进入SpringMvc的DispatchServlet后,会根据路由看能否匹配到ZuulHandlerMapping,匹配成功才会走zuul后续的流程。

以下是DispatcherServlet中doDispatch方法的代码:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        try {
            ModelAndView mv = null;
            Object dispatchException = null;

            try {
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }
                //这里选择ZuulHandlerMapping,如果路由匹配成功,会返回包含ZuulController的ha
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

                // ... 省略代码

                //从这里进入调用ZuulController
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                this.applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var20) {
                dispatchException = var20;
            } catch (Throwable var21) {
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }

            this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
        } catch (Exception var22) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
        } catch (Throwable var23) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
        }

    } finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else if (multipartRequestParsed) {
            this.cleanupMultipart(processedRequest);
        }

    }
}

那么怎样才能让请求进入ZuulHandlerMapping呢,看下DispatchServlet中的的这个方法:

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        Iterator var2 = this.handlerMappings.iterator();
        //按顺序遍历所有的HandlerMapping,直到取得一个
        while(var2.hasNext()) {
            HandlerMapping mapping = (HandlerMapping)var2.next();
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }

    return null;
}

我们需要ZuulHandlerMapping在mapping.getHandler的时候返回非空。研究下ZuulHandlerMapping,看下它的结构先:

 

 

ZuulHandlerMapping继承了AbstractUrlHandlerMapping,AbstractUrlHandlerMapping又继承自AbstractHandlerMapping。在上面的方法中调用ZuulHandlerMapping的mapping.getHandler(request)的时候 实际会调用到AbstractHandlerMapping的getHandlerInternal(request),再进入ZuulHandlerMapping的lookupHandler(String urlPath, HttpServletRequest request)这个方法。

看下这个方法:

@Override
protected Object lookupHandler(String urlPath, HttpServletRequest request)
        throws Exception {
    if (this.errorController != null
            && urlPath.equals(this.errorController.getErrorPath())) {
        return null;
    }
    if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) {
        return null;
    }
    RequestContext ctx = RequestContext.getCurrentContext();
    if (ctx.containsKey("forward.to")) {
        return null;
    }
    if (this.dirty) {
        synchronized (this) {
            if (this.dirty) {
                registerHandlers();
                this.dirty = false;
            }
        }
    }
    //实际会调用这里
    return super.lookupHandler(urlPath, request);
}

调用父类的AbstractUrlHandlerMapping.lookupHandler(urlPath, request)。

这个方法里代码比较多,其中的关键信息是:this.handlerMap.get(urlPath),也就是说我们输入的url path只要能从handlerMap里匹配到,就可以了! 现在需要看下ZuulHandlerMapping里的这个handlerMap是怎么维护的。类中有这么一个方法:

private void registerHandlers() {
    Collection<Route> routes = this.routeLocator.getRoutes();
    if (routes.isEmpty()) {
        this.logger.warn("No routes found from RouteLocator");
    }
    else {
        for (Route route : routes) {
            registerHandler(route.getFullPath(), this.zuul);
        }
    }
}

它会从routeLocator里取出所有的route,一个一个注册到handlerMap里。这样的话就简单了,我只要自己定义一个RouteLocator,把我想要的路由设置好,再让它自动被注册进去就行了吧!

定义一个GatewayRouteLocator:

public class GatewayRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {

    public final static Logger logger = LoggerFactory.getLogger(GatewayRouteLocator.class);

    public GatewayRouteLocator(String servletPath, ZuulProperties properties) {
        super(servletPath, properties);
    }
    public void refresh() {
        doRefresh();
    }

    @Override
    protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
        LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap<String, ZuulProperties.ZuulRoute>();
        routesMap.put("gateway", new ZuulProperties.ZuulRoute());
        return routesMap;
    }

    @Override
    public List<Route> getRoutes() {
        //假设我希望网关API为http://www.domain.com/gateway
        List<Route> values = new ArrayList<Route>();
        values.add(new Route("gateway1", "/gateway/", "/gateway", "", true, new HashSet<String>()));
        values.add(new Route("gateway2", "/gateway", "/gateway", "", true, new HashSet<String>()));
        return values;
    }
}

现在我要将这个类也实例化到spring容器中。

观察下ZuulProxyAutoConfiguration中的RouteLocator是怎么实例化的,照葫芦画瓢弄一下,把这个类也添加到配置类里:

@Configuration
@EnableConfigurationProperties({ZuulProperties.class})
public class Config {

    @Autowired
    protected ZuulProperties zuulProperties;
    @Autowired
    protected ServerProperties server;

    @Bean
    public GatewayRouteLocator gatewayRouteLocator() {
        return new GatewayRouteLocator(this.server.getServlet().getContextPath(), zuulProperties);
    }

    @Bean
    public HeaderPreDecorationFilter geaderPreDecorationFilter(RouteLocator routeLocator) {
        return new HeaderPreDecorationFilter(routeLocator, this.server.getServlet().getContextPath());
    }
}

好了!这样每次输入 的时候,DispatchServlet就会为我们匹配到ZuulHandlerMapping,进而往下走到ZuulController中了。

再看下ZuulController的代码:

ZuulController:

public class ZuulController extends ServletWrappingController {

    public ZuulController() {
        //在这里已经设置了ZuulServlet
        setServletClass(ZuulServlet.class);
        setServletName("zuul");
        setSupportedMethods((String[]) null); // Allow all
    }

    @Override
    public ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        try {
            //在这里面会调用ZuulServlet的service方法
            return super.handleRequestInternal(request, response);
        }
        finally {
            // @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
            RequestContext.getCurrentContext().unset();
        }
    }

}

就是将Request送入ZuulServlet,这样就跟上面的流程衔接上了!

总结一下,一次请求流程为 DispatcherServlet->ZuulHandlerMapping->ZuulController->ZuulServlet->ZuulRunner-> FilterProcessor->ZuulFilter->PreDecorationFilter(替换为自定义的HeaderPreDecorationFilter)->RibbonRoutingFilter

至此,对Zuul的改造就完成了!现在我对外暴露一个统一的api:,所有的服务都从这里调用,同时通过传入一个service的头信息来指定调用具体 的服务,服务列表可以维护在其它地方动态刷新,这样就不会将serviceName暴露出去了!

Guess you like

Origin www.cnblogs.com/likui360/p/11617816.html