>>>> 😜😜😜 Github: 👉 github.com/black-ant CASE Backup: 👉 gitee.com/antblack/ca…

A preface.

In this chapter, we will learn the main process of Zuul. The main process diagram is as follows:

Use cases

Basic routing rules can be implemented through configuration files

zuul:
  routes:
    test:
      url: http:/ / 127.0.0.1:8086 /
Copy the code

3. Comb the source code

3.1 Configuration Loading and Initialization

Here’s a look at how configuration is loaded into the system, starting with the autowiring classes:

Zuul automatic assembly class is ZuulServerAutoConfiguration, it exists the following properties:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass({ ZuulServlet.class, ZuulServletFilter.class })
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)


// The injected classes include the following beans:
- HasFeatures : 
- CompositeRouteLocator :
- SimpleRouteLocator : 
- ZuulController : 
- ZuulHandlerMapping : 
- ApplicationListener :
- ServletRegistrationBean : 

// Filter Bean list:
- ServletDetectionFilter :
- FormBodyWrapperFilter :
- DebugFilter :
- Servlet30WrapperFilter : 
- SendResponseFilter :
- SendErrorFilter : 
- SendForwardFilter : 


// There are three configurations:
- ZuulFilterConfiguration :
- ZuulCounterFactoryConfiguration :
- ZuulMetricsConfiguration

Copy the code

The core processing class is ZuulRefreshListener, which inherits from ApplicationListener and is called when the Application is created. ZuulHandlerMapping is called

// Step 1: reset call entry
private void reset(a) {
   // ((RefreshableRouteLocator) this.routeLocator).refresh();
   this.zuulHandlerMapping.setDirty(true);
}

// Step 2 : 刷新 routeLocator
for (RouteLocator locator : routeLocators) {
   if (locator instanceofRefreshableRouteLocator) { ((RefreshableRouteLocator) locator).refresh(); }}// Step 3 : 添加 routes
protected void doRefresh(a) {
   this.routes.set(locateRoutes());
}

// Step 4: Create routes
protected LinkedHashMap<String, ZuulRoute> locateRoutes(a) {
   LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
   
   // Call super local to get the configuration from Properties
   routesMap.putAll(super.locateRoutes());
   
   if (this.discovery ! =null) {
      Map<String, ZuulRoute> staticServices = new LinkedHashMap<>();
      
      
      for (ZuulRoute route : routesMap.values()) {
          // Load the configuration of the Properties file into the Map
         String serviceId = route.getServiceId();
         staticServices.put(serviceId, route);
      }
      // Pull associations from the registry through DiscoveryClient
      List<String> services = this.discovery.getServices();
      
      Get the ignore list from Proerties
      String[] ignored = this.properties.getIgnoredServices().toArray(new String[0]);
      for (String serviceId : services) {
      
         // Loop through all services to generate corresponding keys
          String key = "/" + mapRouteToService(serviceId) + "/ * *";
         
         // Two things can happen:
         // one: If a URL already exists and no URL exists, set location for it
         if (staticServices.containsKey(serviceId)&& staticServices.get(serviceId).getUrl() == null) {
            ZuulRoute staticRoute = staticServices.get(serviceId);
            if (!StringUtils.hasText(staticRoute.getLocation())) {
               staticRoute.setLocation(serviceId);
            }
         }
         
         // Two: if it is not in the ignore queue and does not exist before
         if(! PatternMatchUtils.simpleMatch(ignored, serviceId)&& ! routesMap.containsKey(key)) { routesMap.put(key,newZuulRoute(key, serviceId)); }}}// Additional flow: If DEFAULT_ROUTE exists, routesMap overrides DEFAULT_ROUTE
   if(routesMap.get(DEFAULT_ROUTE) ! =null) {
      ZuulRoute defaultRoute = routesMap.get(DEFAULT_ROUTE);
      routesMap.remove(DEFAULT_ROUTE);
      routesMap.put(DEFAULT_ROUTE, defaultRoute);
   }
   

   LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
   for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
      String path = entry.getKey();
       // The main logic is to build the URL, adding the/and prefix to it
      values.put(path, entry.getValue());
   }
   // Finally returns the object
   return values;
}

// Step 4-1 L
protected Map<String, ZuulRoute> locateRoutes(a) {
   LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
   
   // Build the routeMap by getting the list of routes from properties
   for (ZuulRoute route : this.properties.getRoutes().values()) {
      routesMap.put(route.getPath(), route);
   }
   return routesMap;
}


Copy the code

At this point, the Route scan is complete and the processing logic starts

3.2 Request interception

Zuul requests are also processed through servlets, which were generated in the previous auto-assembly. The main entry is: ZuulController, which is initiated by MVC. The main flow is as follows:

Step 1: Intercept the entry point

public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
   try {
      // The parent class is actually called, and the parent class continues to call the Service, to ZuulServlet
      return super.handleRequestInternal(request, response);
   }
   finally{ RequestContext.getCurrentContext().unset(); }}Copy the code

Step 2: ZuulServlet handles the request

After masking the associated try-catch, the main methods left are as follows:


public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {

    ZuulRunner. Init (servletRequest, servletResponse)
    init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

    // Set the container identifier to zuul engine -> put("zuulEngineRan", true);
    RequestContext context = RequestContext.getCurrentContext();
    context.setZuulEngineRan();

    // Step 3: route preprocessing, mainly filter processing
    preRoute();
    
    // Step 4: route route
    route();

    // Step 5: post processing, return Response
    postRoute();

}
Copy the code

Step 3: General filter is used for processing

The above three, four and five are finally the implementation of Filter, which is also the core logic of Zuul. The whole core code is processed through Filter: FilterProcessor. GetInstance (). The route (), the difference is three different entry will be introduced to three different type of processing – > pre/route/post

// C- FilterProcessor
public Object runFilters(String sType) throws Throwable {

    boolean bResult = false;
    
    // Get all zuulFilters of the current type
    // There are multiple types of type: pre/route/post, which means we have three entry points
    List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
    
    // Step 2: Loop through the list of all filters and initiate ZuulFilter Process for processing
    Object result = processZuulFilter(zuulFilter);
    
    return bResult;
}


// Initiate a Filter call
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {

    RequestContext ctx = RequestContext.getCurrentContext();
    boolean bDebug = ctx.debugRouting();
    final String metricPrefix = "zuul.filter-";
    long execTime = 0;
    String filterName = "";
    try {
        // Time 1: records the start Time
        long ltime = System.currentTimeMillis();
        filterName = filter.getClass().getSimpleName();
        
        RequestContext copy = null;
        
        // Prepare to return an object to accept an exception and a normal result
        Object o = null;
        Throwable t = null;

        / / core!!!!! Call Filter for processing
        // Note that since forward and so on are processed by filter, there is state and time
        ZuulFilterResult result = filter.runFilter();
        ExecutionStatus s = result.getStatus();
        
        // Time 2: record the end Time
        execTime = System.currentTimeMillis() - ltime;
        
        switch (s) {
            case FAILED:
                // Call Exception, get Exception for processing
                t = result.getException();
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                break;
            case SUCCESS:
                // If the call succeeds, result is obtained
                o = result.getResult();
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
                break;
            default:
                break;
        }
        
        if(t ! =null) throw t;

        usageNotifier.notify(filter, s);
        return o;

    } catch (Throwable e) {
        // Exception handling builds ZuulException to return, meaning the Exception can be caught for processing}}Copy the code

Step 4 : 执行 FIlter

public ZuulFilterResult runFilter(a) {

    ZuulFilterResult zr = new ZuulFilterResult();
    
    // Check whether filter is enabled and needs to be filtered
    if(! isFilterDisabled()) {if (shouldFilter()) {
            // Prepare the exception object
            Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
            try {
                // run executes the filter logic, which is implemented by the actual filter, and builds the return body
                Object res = run();
                zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
            } catch (Throwable e) {
            
                // Here is the build FAILED state, and is blocked externally
                t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
                zr = new ZuulFilterResult(ExecutionStatus.FAILED);
                zr.setException(e);
            } finally{ t.stopAndLog(); }}else {
            zr = newZuulFilterResult(ExecutionStatus.SKIPPED); }}return zr;
}
Copy the code

Added: preRoute associated Filter

org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter com.gang.zuul.service.demo.config.DefaultZuulFilter org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter

3.3 Redirection of Requests

The core second step in ZuulServlet’s service is to initiate a redirection. Again, there are multiple filters for processing, including:

  • RibbonRoutingFilter: Ribbon load balancing
  • SimpleHostRoutingFilter: The base Route class
  • SendForwardFilter: redirects forwarding
public Object run(a) {
   // Step 1: Get the request object from the Context
   RequestContext context = RequestContext.getCurrentContext();
   HttpServletRequest request = context.getRequest();
   
   // Step 2: Get the Header and Param attributes
   MultiValueMap<String, String> headers = this.helper.buildZuulRequestHeaders(request);
   MultiValueMap<String, String> params = this.helper.buildZuulRequestQueryParams(request);
   String verb = getVerb(request);
   
   
   // Step 3: Get the input stream from request. PS: Stream is an object that can usually only be retrieved once
   InputStream requestEntity = getRequestBody(request);
   if (getContentLength(request) < 0) {
      context.setChunkedRequestBody();
   }

   // Step 4: build request URL -> /demo/start/get
   String uri = this.helper.buildZuulRequestURI(request);
   this.helper.addIgnoredHeaders();

   try {
       // Step 5: Initiate the remote call
      CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
            headers, params, requestEntity);
            
      // Step 6: Put Response in the container
      // RequestContext.getCurrentContext().set("zuulResponse", response);
      // this.helper.setResponse
      setResponse(response);
   }
   catch (Exception ex) {
      throw new ZuulRuntimeException(handleException(ex));
   }
   return null;
}
Copy the code

Supplementary request segment:

Processing of the previous part of the forward

// 1. Build host
URL host = RequestContext.getCurrentContext().getRouteHost();
HttpHost httpHost = getHttpHost(host);

// 2. Build InputStreamEntity
new InputStreamEntity(requestEntity, contentLength,contentType);

// 3. Build HttpRequest
buildHttpRequest(verb, uri, entity, headers, params,request);

// 4. HttpClient initiates a remote call
httpclient.execute(httpHost, httpRequest)
Copy the code

3.4 Returning a Route Result

It is mainly implemented through the SendResponseFilter class. The core property is private ThreadLocal

buffers, which includes two main steps:
[]>

  • AddResponseHeaders () : Writes the ResponseHeader
  • WriteResponse () : Write ResponseBody
private void addResponseHeaders(a) {

   RequestContext context = RequestContext.getCurrentContext();
   HttpServletResponse servletResponse = context.getResponse();
   
   // If DebugHeader is enabled, the corresponding Header is added
   // public static final String X_ZUUL_DEBUG_HEADER = "X-Zuul-Debug-Header";
   // Get context.get(ROUTING_DEBUG_KEY), add to the bottom after the for loop
   // servletResponse.addHeader(X_ZUUL_DEBUG_HEADER, debugHeader.toString());


   // Iterate over ZuulResponseHeaders in the container and add it to the servletResponse
   List<Pair<String, String>> zuulResponseHeaders = context.getZuulResponseHeaders();
   if(zuulResponseHeaders ! =null) {
      for (Pair<String, String> it : zuulResponseHeaders) {
         if(! ZuulHeaders.CONTENT_ENCODING.equalsIgnoreCase(it.first())) { servletResponse.addHeader(it.first(), it.second()); }}}/ / if you need to add LengthHeader, context getOriginContentLength () + servletResponse. SetContentLength
   // TODO: omit details
}
Copy the code

Write Response main logic

private void writeResponse(a) throws Exception {

   RequestContext context = RequestContext.getCurrentContext();
   // there is no body to send
   if (context.getResponseBody() == null
         && context.getResponseDataStream() == null) {
      return;
   }
   
   // Get response from the container and set CharacterEncoding
   HttpServletResponse servletResponse = context.getResponse();
   if (servletResponse.getCharacterEncoding() == null) { // only set if not set
      servletResponse.setCharacterEncoding("UTF-8");
   }

   // Core: Output via stream
   String servletResponseContentEncoding = getResponseContentEncoding(context);
   OutputStream outStream = servletResponse.getOutputStream();
   
   InputStream is = null;
   try {
      // Distinguish between byte streams and ZIP streams
      if(context.getResponseBody() ! =null) {
         String body = context.getResponseBody();
         is = new ByteArrayInputStream(
               body.getBytes(servletResponse.getCharacterEncoding()));
      }
      else {
         is = context.getResponseDataStream();
         if(is ! =null && context.getResponseGZipped()) {
            // Unpack the stream before sending it to the client to summarize the gzip stream
            if (isGzipRequested(context)) {
               servletResponseContentEncoding = "gzip";
            }
            else {
               servletResponseContentEncoding = null; is = handleGzipStream(is); }}}// Set the header, encoding type to reponse
      if(servletResponseContentEncoding ! =null) {
         servletResponse.setHeader(ZuulHeaders.CONTENT_ENCODING,servletResponseContentEncoding);
      }

      if(is ! =null) {
         // Write response to returnwriteResponse(is, outStream); }}finally {
       // Close the stream- is.close(); - buffers.remove(); - (Closeable) zuulResponse).close(); }}Copy the code

conclusion

In general, Zuul’s core is Filter interception, retrieving the Request from the Request and forwarding it through the route. During this process, various complex processing is carried out. Overall, the structure is very clear and simple

Some filters, such as load balancing, are not in-depth this time, and we will look at them in detail later