Spring Cloud Zuul sophisticated

Previous Zuul then continue to speak, built on a simple Zuul environment explains how to use herein, this Network access will be posted out some common configuration. We'll end the analysis with the look Zuul source, sublimation about style Benpian!

Ignore all or some of the micro-micro-service service

By default, as long as the introduction of zuul, it will automatically configure a default route, but there are times when we may not want the default routing configuration rules, would like to define themselves, ignoring all micro-services write back :( *):

zuul:  
  ignored-services: "*"  

:( ignore certain micro-services directly write the name of the micro-services => spring.application.name can be understood as a value of more good to have separated)

zuul:  
  ignored-services: product-provider,product-consumer-8201  

Ignore all for the service, only the route specified micro-services

zuul:
  # 排除所有的服务,只由指定的服务进行路由
  ignored-services: "*"
  routes:
    eureka-client:
      path: /client1/**
      serviceId: eureka-client

Access to the machine through a specific path and url

Sometimes when we test requires access to a specific machine and do not want to load balancing or other machines need access to a machine on a third party:

zuul:  
  routes:  
    product-provider:  
      path: /product/**  
      url: http://localhost:8202/  

note:

  1. product-provider value can easily write, even a non-existent value;
  2. This access will not be as HystrixCommand be accessed;
  3. url which can not write more url

Sensitive transfer head (such as Cookie, etc.) Global and a widget service provided

Sometimes we may want to pass upstream micro-services Some of the downstream service request to the head, such as Token, Cookie equivalent, by default, zuul will not Cookie, Set-Cookie, Authorization three first passed to downstream services, if you need to pass , you need to ignore these sensitive head.

zuul:
  #所有服务路径前统一加上前缀
  prefix: /api
  # 排除某些路由, 支持正则表达式
  ignored-patterns:
    - /**/modify/pwd
  # 排除服务
  ignored-services: user-center
  routes:
    eureka-client:
      path: /client1/**
      serviceId: eureka-client
      sensitiveHeaders:  #当前这个路由的header为空
  sensitiveHeaders: Cookie,Set-cookie #全局路由都带这些header

On Zuul source

Open Zuul is very simple, add annotations Zuul open on startup class:

@EnableZuulProxy
/**
 * Sets up a Zuul server endpoint and installs some reverse proxy filters in it, so it can
 * forward requests to backend servers. The backends can be registered manually through
 * configuration or via DiscoveryClient.
 *
 * @see EnableZuulServer for how to get a Zuul server without any proxying
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Biju Kunjummen
 */
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}

There is a saying on the above comment: EnableZuulServer is not using a proxy function to get Zuul server. Zuul open gateway in two ways:

  • @EnableZuulServer: common gateway supports only basic route and filter;
  • @EnableZuulProxy: Service Discovery and fuse with the switch, is EnableZuulServer enhanced version, with a reverse proxy functionality.

In simple terms, @ EnableZuulProxy understood as an enhanced version of @EnableZuulServer when Zuul and Eureka, Ribbon and other components used in conjunction with, we use @EnableZuulProxy.

Then look EnableZuulProxy, cited in the first class ZuulProxyMarkerConfiguration, the effect is on ZuulProxyAutoConfiguration ZuulProxyAutoConfigurationmarker.

ZuulProxyAutoConfiguration inherited ZuulServerAutoConfiguration, it is ZuulServerAutoConfiguration superset. This class has injected DiscoveryClient, RibbonCommandFactoryConfiguration used as load balancing related. He injected some filters columns, such as PreDecorationFilter, RibbonRoutingFilter, SimpleHostRoutingFilter.

ZuulServerAutoConfiguration more important:

@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);
    }

  /**
  * Zuul创建的一个Controller,用于将请求交由ZuulServlet处理
  *
  */
    @Bean
    public ZuulController zuulController() {
        return new ZuulController();
    }

  /**
  * 会添加到SpringMvc的HandlerMapping链中,
  *只有选择了ZuulHandlerMapping的请求才能出发到Zuul的后续流程
  *
  */
    @Bean
    public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
        ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
        mapping.setErrorController(this.errorController);
        mapping.setCorsConfigurations(getCorsConfigurations());
        return mapping;
    }

/**
  * ZuulServlet是整个流程的核心
  *
  *
  */
    @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;
    }

    
    ......
    ......
    ......
    
}

It defines several core objects in ZuulServerAutoConfiguration in:

  • ZuulController: Default Controller for all routes;
  • ZuulHandlerMapping: Zuul route Mapping mapper;
  • ZuulServlet: Inherited from the HttpServlet, filtering logic beginning from here, to this end.

ZuulServlet is the core of the entire process, the request procedure substantially as follows:

Zuulservlet When receiving the request, creates a ZuulRunner object are initialized RequestContext: ServletRequest ServletResponse objects and store request, and the request for all current Zuulfilter chain sharing;

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 polling thermally loaded;

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.

ZuulServlet control the flow of all url, we look at what it does work:

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();
            }


        }
    }

}

ZuulServlet inherited HttpServlet, the main role is to intercept the HTTP requests do corresponding processing. Look achieve direct implementation service () in:

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

    //这里从RequestContext中取出当前线程中封装好的对象然后在该对象上打上zuul处理的标记
    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();
  }
}

The first sentence of the init method:

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));
}

Here called a RequestContext class will be saved into HttpServletRequest, and RequestContext class itself is special:

public class RequestContext extends ConcurrentHashMap<String, Object> {

  rotected 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 static RequestContext getCurrentContext() {
    if (testContext != null) return testContext;

    RequestContext context = threadLocal.get();
    return context;
  }
  
}

It is itself a Map, needs to be noted that the use of the new object is not directly a new object, but calls getCurrentContext () method, which is reflected in the return generated new RequestContext way threadLocal package to create objects . Make sure that each RequestContext created only valid for the current thread, that is, within the current thread, getCurrentContext () method is to remove the same RequestContext object.

Continue back to the service () method, to get a good package after the RequestContext method, enter the following four route in the previous section we have already talked about this four route belong Filter life cycle, where to complete the requested filtering, forwarding, post-processing logic. After the route is complete, the final finally call the method RequestContext.getCurrentContext().unset()method, since the use of threadLocal, is bound to be finished using clear, or is likely to memory leaks.

Rest for a while:

Analysis to ZuulServlet, I wonder if you find the core of Zuul. For Zuul realize the function of the gateway is actually around HttpServlet get ServletRequest, the request to do the filtering operation, get ServletResponse to return the results do post-processing operations. HttpServlet is a single-instance multi-threaded processing model, if there is a request for a time-consuming, so the thread will block until the process is completed successfully before the end of the return. If many such requests, the pressure on the server where Zuul is not small.

How to deal with a request Zuul

The above results have been analyzed based on Servlet Zuul is a logic to do it, down with it becomes easy. How to handle the request SpringMVC is it? We should all be familiar with, the browser sends a request to reach the server, the first to reach the DispatcherServlet, Servlet containers will be requested to HandlerMapping, find Controller access path and approach a correspondence relationship, and then handed over to HandlerAdapter routed to the real processing logic to be processed.

I posted above ZuulServerAutoConfiguration # ZuulHandlerMapping, defined ZuulHandlerMapping bean object.

public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {

}

ZuulHandlerMapping own inherited AbstractUrlHandlerMapping, that is, to find the corresponding processor via url. Analyzing core logic lookupHandler method:

@Override
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
  if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
    return null;
  }
  //判断urlPath是否被忽略,如果忽略则返回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);
}


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) {
      //调用父类,注册处理器,这里所有路径的处理器都是ZuulController
      registerHandler(route.getFullPath(), this.zuul);
    }
  }
}

When the whole logic is required to specify when the route is loaded for each routing processor, because Zuul is not responsible for logic processing, so it does not correspond to the Controller can be used, how to do it, registered processor, using ZuulController, Shi Controllersubclass, the corresponding adapters are SimpleControllerHandlerAdapter, also said that each routing rules are common processor ZuulController, this processor will eventually call ZuulServletafter zuul defined and custom interceptors.

Another one above:

Collection<Route> routes = this.routeLocator.getRoutes();

RouteLocator role of routing locator, which look at the implementation class that:

  • SimpleRouteLocator: The main load routing rule configuration file;
  • DiscoveryClientRouteLocator: routing locator service discovery, such as the center to register Eureka, Consul get the service name and the like, in such a manner /服务名称/**mapped into the routing rule;
  • CompositeRouteLocator: Compound locator routing, all routing integrated main locator (e.g., Locator route profile, service discovery locator, custom routing positioners, etc.) to route positioning;
  • RefreshableRouteLocator: Route refresh, only to achieve routing locator for this interface to be refreshed.

Look routing locator from the implementation class is to distinguish between the role of the currently loaded routes where registered. These achieve the above class implements the Ordered class load order in accordance with the getOrder()value of a given size.

So far we've Zuul core routing part of the line and again loaded from Spring MVC Servlet process to start, to customize ZuulServlet be processed further using various Filter Zuul defined logic to do the filtering. Principle is very simple, it is important thoughts. As a gateway, it is very important service, whether you think this implementation elegance, whether there are other ways to achieve it? If you how would you implement a gateway, you can ponder these questions slowly.

Guess you like

Origin www.cnblogs.com/rickiyang/p/12080755.html