Spring zuul Quick Start Practice--Service Forwarding Implementation Resolution

Posted by madspof on Mon, 08 Jun 2020 02:10:43 +0200

As one of springCloud's bucket components, zuul has an indispensable component.As a normal java API gateway, it has its own benefits:

Avoid exposing internal information to the outside;
Unified Service End Application Entry;
Adding an additional security layer to the microservice;
Support hybrid communication protocols;
Reduce the complexity of building microservices;
Micro-service simulation and virtualization;




zuul has basically been handled by springCloud as an out-of-the-box component, so basically all you need to do is add the dependencies and some necessary configurations, and the gateway will run.(This surface looks similar to the nginx reverse proxy part)

Let's try it out quickly!

 

1. Basic Practice Steps for zuul Pit Entry

1.1. Introducing pom dependency

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    
    <modelVersion>4.0.0</modelVersion>
    <groupId>zuul-test</groupId>
    <artifactId>com.youge</artifactId>
    <version>1.0</version>
    
    <!-- Introduce spingcloud Family bucket -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.RC2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <dependencies>
        <!-- Import Service Gateway zuul -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
    </dependencies>

That's all the maven dependencies of our entire demo. It's very concise.That's what springboot is all about, moving everything behind the scenes to make your business simpler.

 

1.2. Write gateway entry class

The following is the entry class for the entire gateway, which is essentially two notes that react chemically.@EnableZuulProxy is the hero of this article, which opens gateway-related services.

@SpringBootApplication
@EnableZuulProxy
public class MyZuulGateway {
    // There is only one empty main Method
    public static void main(String[] args) {
        SpringApplication.run(MyZuulGateway.class, args);
    }
}

That's it!

 

1.3. Add Test Configuration Items

InApplication.propertiesAdd the following configuration to the configuration file, and use only one routing configuration to verify it!

server.port=9000
spring.application.name=my-zuul-gateway

#Local environment configures rules for zuul forwarding:
# Ignore all microservices and route only specified microservices
# Configure as follows to /sapi/** Path request, forwarded to http://127.0.0.1:8082/fileenc/ Up
zuul.ignored-services=*
zuul.routes.fileenc1.url=http://127.0.0.1:8082/fileenc/
zuul.routes.fileenc1.path=/sapi/**

If so, you can run the gateway. If you don't even have background services, it's OK. Just write one yourself.

@GetMapping("hello")
    public Object hello() {
        return "hello, world";
    }

  

1.4. Test Gateway

The whole gateway has been well done above. run can do it right away by accessing the gateway address in a direct browser:

http://localhost:9000/sapi/test/hello?a=1&b=22 .

If you see "hello, world", congratulations, zuul is in the pit.

 

2. How does zuul forward the request?

According to the above observation, zuul can basically meet our development needs, so what we need to do more in the future may be security related, business related, and optimizing related things.But before we do that, let's ask one more question: How does zuul forward requests to back-end services?

This is actually related to the architecture of zuul:

 

The core concept of zuul is: Filter. Runtime logically divides into several types of filters, each type of filter handles at different times!PRE: This filter is called before the request is routed; ROUTING: this filter routes the request to the microservice; POST: this filter is executed after the request is routed to the microservice; ERROR: this filter is executed when errors occur at other stages;

So, overall, its forwarding process goes through a series of filters before actually forwarding.

If you only want to know how it ultimately goes, you can go straight to the topic, and if you want to add your functionality, you need to write some filters for the corresponding life cycle.

This was meant to analyze how zuul handles requests, but in fact, once zuul is integrated into spring, it fits perfectly into a springmvc programming model.All requests to the gateway invoke ZuulController to receive the request, then go to the service to process it, and then to respond to such a process.

The whole ZuulController is very simple: it's a request delegation process!

// org.springframework.cloud.netflix.zuul.web.ZuulController
public class ZuulController extends ServletWrappingController {

    public ZuulController() {
        setServletClass(ZuulServlet.class);
        setServletName("zuul");
        setSupportedMethods((String[]) null); // Allow all
    }

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        try {
            // We don't care about the other features of the base class, just want to
            // handle the request
            return super.handleRequestInternal(request, response);
        }
        finally {
            // @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
            RequestContext.getCurrentContext().unset();
        }
    }

}
    // org.springframework.web.servlet.mvc.ServletWrappingController#handleRequestInternal
    /**
     * Invoke the wrapped Servlet instance.
     * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
     */
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
            throws Exception {

        Assert.state(this.servletInstance != null, "No Servlet instance");
        // this servletInstance yes ZuulServlet, Whole zuul The implementation framework is controlled by it
        this.servletInstance.service(request, response);
        return null;
    }
    // com.netflix.zuul.http.ZuulServlet#service
    @Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            // Initialize the request by zuulRunner Handle
            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
            // setZuulEngineRan An identifier is rotated: "zuulEngineRan", true
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                // Pre-filter
                preRoute();
            } catch (ZuulException e) {
                error(e);
                // Call post route directly to complete the request in case of an exception
                postRoute();
                return;
            }
            try {
                // Normal Routing Request Processing
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                // Normal post-routing processing
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }

        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            // Reset context for next use
            RequestContext.getCurrentContext().unset();
        }
    }

This is part of zuul's framework for handling common requests.The logic is clear and simple, with pre+forward+post processing.Let's explain a few key points:

 

2.1. Request Initialization

This part mainly connects external requests to the process of zuul, of course, the following implementations mainly use ThreadLocal to achieve context convergence.

// com.netflix.zuul.http.ZuulServlet#init
    /**
     * initializes request
     *
     * @param servletRequest
     * @param servletResponse
     */
    void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
        zuulRunner.init(servletRequest, servletResponse);
    }
    // com.netflix.zuul.ZuulRunner#init
    /**
     * sets HttpServlet request and HttpResponse
     *
     * @param servletRequest
     * @param servletResponse
     */
    public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
        // RequestContext Use ThreadLocal Save with value guaranteed
        // And RequestContext Inherited ConcurrentHashMap, Thread-safe operation guaranteed
        RequestContext ctx = RequestContext.getCurrentContext();
        if (bufferRequests) {
            ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
        } else {
            ctx.setRequest(servletRequest);
        }

        ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
    }

This is the initialization of a zuul request, simply set the request context for standby.

 

2.2. Pre-treatment filters

Pre-processing filters are mainly used to mark some request types, privilege validation, security filtering, and so on.It is an indispensable link.Do it yourself!Let's look at a general process:

// com.netflix.zuul.http.ZuulServlet#preRoute
    /**
     * executes "pre" filters
     *
     * @throws ZuulException
     */
    void preRoute() throws ZuulException {
        zuulRunner.preRoute();
    }
    // com.netflix.zuul.ZuulRunner#preRoute
    /**
     * executes "pre" filterType  ZuulFilters
     *
     * @throws ZuulException
     */
    public void preRoute() throws ZuulException {
        // FilterProcessor Is a singleton
        FilterProcessor.getInstance().preRoute();
    }
    // com.netflix.zuul.FilterProcessor#preRoute
    /**
     * runs all "pre" filters. These filters are run before routing to the orgin.
     *
     * @throws ZuulException
     */
    public void preRoute() throws ZuulException {
        try {
            // call Type by pre Filter
            runFilters("pre");
        } catch (ZuulException e) {
            throw e;
        } catch (Throwable e) {
            throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
        }
    }
    // com.netflix.zuul.FilterProcessor#runFilters
    /**
     * runs all filters of the filterType sType/ Use this method within filters to run custom filters by type
     *
     * @param sType the filterType.
     * @return
     * @throws Throwable throws up an arbitrary exception
     */
    public Object runFilters(String sType) throws Throwable {
        if (RequestContext.getCurrentContext().debugRouting()) {
            Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
        }
        boolean bResult = false;
        // adopt FilterLoader Get all registered as sType Filter
        // Deposit Filters The container of is also thread-safe naturally, for ConcurrentHashMap
        // - 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
        // - org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            for (int i = 0; i < list.size(); i++) {
                ZuulFilter zuulFilter = list.get(i);
                // Processing each in turn filter
                Object result = processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= ((Boolean) result);
                }
            }
        }
        return bResult;
    }
    // Get the corresponding filters
    // com.netflix.zuul.FilterLoader#getFiltersByType
    /**
     * Returns a list of filters by the filterType specified
     *
     * @param filterType
     * @return a List<ZuulFilter>
     */
    public List<ZuulFilter> getFiltersByType(String filterType) {

        List<ZuulFilter> list = hashFiltersByType.get(filterType);
        if (list != null) return list;

        list = new ArrayList<ZuulFilter>();

        Collection<ZuulFilter> filters = filterRegistry.getAllFilters();
        for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) {
            ZuulFilter filter = iterator.next();
            if (filter.filterType().equals(filterType)) {
                list.add(filter);
            }
        }
        Collections.sort(list); // sort by priority

        hashFiltersByType.putIfAbsent(filterType, list);
        return list;
    }

    // com.netflix.zuul.FilterProcessor#processZuulFilter
    /**
     * Processes an individual ZuulFilter. This method adds Debug information. Any uncaught Thowables are caught by this method and converted to a ZuulException with a 500 status code.
     *
     * @param filter
     * @return the return value for that filter
     * @throws ZuulException
     */
    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 {
            long ltime = System.currentTimeMillis();
            filterName = filter.getClass().getSimpleName();
            
            RequestContext copy = null;
            Object o = null;
            Throwable t = null;

            if (bDebug) {
                Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);
                copy = ctx.copy();
            }
            // Call each filter Of runFilter() Method, trigger filter Effect
            // If filter Disabled, not called zuul.ServletDetectionFilter.pre.disable=true, Represents Disabled pre
            // The specific implementation logic is determined by each filter Decision 
            ZuulFilterResult result = filter.runFilter();
            ExecutionStatus s = result.getStatus();
            execTime = System.currentTimeMillis() - ltime;

            switch (s) {
                case FAILED:
                    t = result.getException();
                    ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                    break;
                case SUCCESS:
                    o = result.getResult();
                    // Use StringBuilder Logging Request Processing Log
                    ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
                    if (bDebug) {
                        Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
                        Debug.compareContextState(filterName, copy);
                    }
                    break;
                default:
                    break;
            }
            // Throw whenever an exception occurs
            if (t != null) throw t;
            // Request Counter Increase
            usageNotifier.notify(filter, s);
            return o;

        } catch (Throwable e) {
            if (bDebug) {
                Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + e.getMessage());
            }
            usageNotifier.notify(filter, ExecutionStatus.FAILED);
            if (e instanceof ZuulException) {
                throw (ZuulException) e;
            } else {
                ZuulException ex = new ZuulException(e, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                throw ex;
            }
        }
    }
    // com.netflix.zuul.ZuulFilter#runFilter
    /**
     * runFilter checks !isFilterDisabled() and shouldFilter(). The run() method is invoked if both are true.
     *
     * @return the return from ZuulFilterResult
     */
    public ZuulFilterResult runFilter() {
        ZuulFilterResult zr = new ZuulFilterResult();
        // Disabled does not trigger a real call
        if (!isFilterDisabled()) {
            // shouldFilter() By each filter Decide, go back true Execute on time filter
            if (shouldFilter()) {
                Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
                try {
                    Object res = run();
                    zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
                } catch (Throwable e) {
                    t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
                    zr = new ZuulFilterResult(ExecutionStatus.FAILED);
                    zr.setException(e);
                } finally {
                    t.stopAndLog();
                }
            } else {
                // Skip identification
                zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
            }
        }
        return zr;
    }
    // org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter#run
    @Override
    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;
    }

This is a preFilter process:

Get all pre type filter s from the FilterLoader;
2. Call the runFilter() method of each filter in turn to trigger the filter;
3. Call shouldFilter() before calling to determine if the filter is useful for this request. Each filter implementation can get appropriate information from the context and make its own decisions.
4. Add 1 to the counter;
5. By default, there will be more than one filter to call, which is not enough to satisfy the business scenario before adding it yourself;



 

2.3. Normal Routing Processing

zuul's native job is to route forwards (forward or reverse agents) to routes as follows:

// com.netflix.zuul.http.ZuulServlet#route
    /**
     * executes "route" filters
     *
     * @throws ZuulException
     */
    void route() throws ZuulException {
        zuulRunner.route();
    }
    // com.netflix.zuul.ZuulRunner#route
    /**
     * executes "route" filterType  ZuulFilters
     *
     * @throws ZuulException
     */
    public void route() throws ZuulException {
        FilterProcessor.getInstance().route();
    }
    // com.netflix.zuul.FilterProcessor#route
    /**
     * Runs all "route" filters. These filters route calls to an origin.
     *
     * @throws ZuulException if an exception occurs.
     */
    public void route() throws ZuulException {
        try {
            // Again, get filter Type is route Of filters, Call handling is sufficient
            // - org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter
            // - org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter
            // - org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter
            runFilters("route");
        } catch (ZuulException e) {
            throw e;
        } catch (Throwable e) {
            throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());
        }
    }
    // Where, Ribbon Processing requires ribbon Introduction and configuration of components
    // org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter#shouldFilter
    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        // Determine if there is serviceId, And sendZuulResponse=true Will proceed ribbon Handle
        return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
                && ctx.sendZuulResponse());
    }
    
    //The following are the implementations of normal routing forwarding, which happens whenever the corresponding routing information is configured:
    // org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter#shouldFilter
    @Override
    public boolean shouldFilter() {
        return RequestContext.getCurrentContext().getRouteHost() != null
                && RequestContext.getCurrentContext().sendZuulResponse();
    }

    @Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();
        // step1. structure http Request Header Information
        HttpServletRequest request = context.getRequest();
        MultiValueMap<String, String> headers = this.helper
                .buildZuulRequestHeaders(request);
        // step2. structure params information, as: a=111&&b=222
        MultiValueMap<String, String> params = this.helper
                .buildZuulRequestQueryParams(request);
        // Get Request Type, GET,POST,PUT,DELETE
        String verb = getVerb(request);
        // step3. Build request body information, such as files
        InputStream requestEntity = getRequestBody(request);
        // Without Content-Length Field, then set chunkedRequestBody:true
        if (getContentLength(request) < 0) {
            context.setChunkedRequestBody();
        }
        // step4. Build to forward uri Address Information
        String uri = this.helper.buildZuulRequestURI(request);
        this.helper.addIgnoredHeaders();

        try {
            // step5. Request forwarded, waiting for response
            // How to forward the request is forward Processed in
            CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
                    headers, params, requestEntity);
            // Put the result in context for later filter Handle
            setResponse(response);
        }
        catch (Exception ex) {
            throw new ZuulRuntimeException(ex);
        }
        return null;
    }

    // step1. structure http Request Header Information
    // org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper#buildZuulRequestHeaders
    public MultiValueMap<String, String> buildZuulRequestHeaders(
            HttpServletRequest request) {
        RequestContext context = RequestContext.getCurrentContext();
        MultiValueMap<String, String> headers = new HttpHeaders();
        Enumeration<String> headerNames = request.getHeaderNames();
        // Get All header Information, restore to headers in
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                // Exclude some special header information
                if (isIncludedHeader(name)) {
                    Enumeration<String> values = request.getHeaders(name);
                    while (values.hasMoreElements()) {
                        String value = values.nextElement();
                        headers.add(name, value);
                    }
                }
            }
        }
        // Add new header information for this route forwarding
        Map<String, String> zuulRequestHeaders = context.getZuulRequestHeaders();
        for (String header : zuulRequestHeaders.keySet()) {
            headers.set(header, zuulRequestHeaders.get(header));
        }
        headers.set(HttpHeaders.ACCEPT_ENCODING, "gzip");
        return headers;
    }
    
    // step2. structure params information, as: a=111&&b=222
    // org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper#buildZuulRequestQueryParams
    public MultiValueMap<String, String> buildZuulRequestQueryParams(
            HttpServletRequest request) {
        // analysis getQueryString In a=111&b=222... information
        Map<String, List<String>> map = HTTPRequestUtils.getInstance().getQueryParams();
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        if (map == null) {
            return params;
        }
        for (String key : map.keySet()) {
            for (String value : map.get(key)) {
                params.add(key, value);
            }
        }
        return params;
    }
    // Resolve Request url In k=v&k2=v2 by map format
    // com.netflix.zuul.util.HTTPRequestUtils#getQueryParams
    /**
     * returns query params as a Map with String keys and Lists of Strings as values
     * @return
     */
    public Map<String, List<String>> getQueryParams() {

        Map<String, List<String>> qp = RequestContext.getCurrentContext().getRequestQueryParams();
        if (qp != null) return qp;

        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();

        qp = new LinkedHashMap<String, List<String>>();

        if (request.getQueryString() == null) return null;
        StringTokenizer st = new StringTokenizer(request.getQueryString(), "&");
        int i;

        while (st.hasMoreTokens()) {
            String s = st.nextToken();
            i = s.indexOf("=");
            if (i > 0 && s.length() >= i + 1) {
                String name = s.substring(0, i);
                String value = s.substring(i + 1);

                try {
                    name = URLDecoder.decode(name, "UTF-8");
                } catch (Exception e) {
                }
                try {
                    value = URLDecoder.decode(value, "UTF-8");
                } catch (Exception e) {
                }

                List<String> valueList = qp.get(name);
                if (valueList == null) {
                    valueList = new LinkedList<String>();
                    qp.put(name, valueList);
                }

                valueList.add(value);
            }
            else if (i == -1)
            {
                String name=s;
                String value="";
                try {
                    name = URLDecoder.decode(name, "UTF-8");
                } catch (Exception e) {
                }
               
                List<String> valueList = qp.get(name);
                if (valueList == null) {
                    valueList = new LinkedList<String>();
                    qp.put(name, valueList);
                }

                valueList.add(value);
                
            }
        }

        RequestContext.getCurrentContext().setRequestQueryParams(qp);
        return qp;
    }

    
    // step3. Build request body information, such as files
    // org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter#getRequestBody
    protected InputStream getRequestBody(HttpServletRequest request) {
        InputStream requestEntity = null;
        try {
            // Forward requestEntity Gets the input stream in, if not, directs servlet Get in
            requestEntity = (InputStream) RequestContext.getCurrentContext().get(REQUEST_ENTITY_KEY);
            if (requestEntity == null) {
                // towards HttpServletRequest Get the original input stream in
                requestEntity = request.getInputStream();
            }
        }
        catch (IOException ex) {
            log.error("error during getRequestBody", ex);
        }
        return requestEntity;
    }
    
    
    // step4. Build to forward uri Address Information
    // org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper#buildZuulRequestURI
    public String buildZuulRequestURI(HttpServletRequest request) {
        RequestContext context = RequestContext.getCurrentContext();
        // Original Request uri
        String uri = request.getRequestURI();
        // Requests after routing conversion uri
        String contextURI = (String) context.get(REQUEST_URI_KEY);
        if (contextURI != null) {
            try {
                // Prevent random code, urlencode Once
                uri = UriUtils.encodePath(contextURI, characterEncoding(request));
            }
            catch (Exception e) {
                log.debug(
                        "unable to encode uri path from context, falling back to uri from request",
                        e);
            }
        }
        return uri;
    }
    
    // step5. Request forwarded, waiting for response
    // How to forward the request is forward Processed in
    // org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter#forward
    private CloseableHttpResponse forward(CloseableHttpClient httpclient, String verb,
            String uri, HttpServletRequest request, MultiValueMap<String, String> headers,
            MultiValueMap<String, String> params, InputStream requestEntity)
            throws Exception {
        Map<String, Object> info = this.helper.debug(verb, uri, headers, params,
                requestEntity);
        // Configured routing address prefix
        URL host = RequestContext.getCurrentContext().getRouteHost();
        HttpHost httpHost = getHttpHost(host);
        // take out uri
        uri = StringUtils.cleanPath((host.getPath() + uri).replaceAll("/{2,}", "/"));
        long contentLength = getContentLength(request);

        ContentType contentType = null;

        if (request.getContentType() != null) {
            contentType = ContentType.parse(request.getContentType());
        }
        // Use InputStreamEntity encapsulation inputStream Request, the inputStream From socket Input stream after access
        // Follow-up httpclient When data is read, it provides the appropriate reading method
        InputStreamEntity entity = new InputStreamEntity(requestEntity, contentLength,
                contentType);
        // Building the data for this request is critical
        HttpRequest httpRequest = buildHttpRequest(verb, uri, entity, headers, params,
                request);
        try {
            log.debug(httpHost.getHostName() + " " + httpHost.getPort() + " "
                    + httpHost.getSchemeName());
            // Submitted to httpclient Component Execution http Request and return results
            CloseableHttpResponse zuulResponse = forwardRequest(httpclient, httpHost,
                    httpRequest);
            this.helper.appendDebug(info, zuulResponse.getStatusLine().getStatusCode(),
                    revertHeaders(zuulResponse.getAllHeaders()));
            return zuulResponse;
        }
        finally {
            // When HttpClient instance is no longer needed,
            // shut down the connection manager to ensure
            // immediate deallocation of all system resources
            // httpclient.getConnectionManager().shutdown();
        }
    }
    // org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter#buildHttpRequest
    protected HttpRequest buildHttpRequest(String verb, String uri,
            InputStreamEntity entity, MultiValueMap<String, String> headers,
            MultiValueMap<String, String> params, HttpServletRequest request) {
        HttpRequest httpRequest;
        String uriWithQueryString = uri + (this.forceOriginalQueryStringEncoding
                ? getEncodedQueryString(request) : this.helper.getQueryString(params));
        // Based on the different types of the original request, do the appropriate type of forwarding
        // The following request processing contains logic for requests such as file streams
        switch (verb.toUpperCase()) {
        case "POST":
            HttpPost httpPost = new HttpPost(uriWithQueryString);
            httpRequest = httpPost;
            httpPost.setEntity(entity);
            break;
        case "PUT":
            HttpPut httpPut = new HttpPut(uriWithQueryString);
            httpRequest = httpPut;
            httpPut.setEntity(entity);
            break;
        case "PATCH":
            HttpPatch httpPatch = new HttpPatch(uriWithQueryString);
            httpRequest = httpPatch;
            httpPatch.setEntity(entity);
            break;
        case "DELETE":
            BasicHttpEntityEnclosingRequest entityRequest = new BasicHttpEntityEnclosingRequest(
                    verb, uriWithQueryString);
            httpRequest = entityRequest;
            // DELETE Will do two steps
            entityRequest.setEntity(entity);
            break;
        default:
            // All but the above BasicHttpRequest Processing is OK
            httpRequest = new BasicHttpRequest(verb, uriWithQueryString);
            log.debug(uriWithQueryString);
        }
        // Unified set request header, will map Convert to BasicHeader
        httpRequest.setHeaders(convertHeaders(headers));
        return httpRequest;
    }
    // org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter#forwardRequest
    private CloseableHttpResponse forwardRequest(CloseableHttpClient httpclient,
            HttpHost httpHost, HttpRequest httpRequest) throws IOException {
        return httpclient.execute(httpHost, httpRequest);
    }

Visible the entire true forwarding process, which consists of several main steps:

1. Parse the http request header information and add your own part of the header information;
2. Resolve and retain request parameter information such as a=111 &&b=222;
3. Get original inputStream information, such as files;
4. Build uri address information to forward based on routing configuration;
5. Use the httpclient component to forward requests and wait for responses to be set up in response;



In fact, the true forwarding is still to make the appropriate judgment in turn, revert to the corresponding request, and forward to the back-end service.

The above is a common service forwarding implementation.Not much skill, but the most basic steps: receiving requests, parsing parameters, rebuilding requests, requesting backends, and getting results.

 

2.4. Rear filter

The post processor can do some response data to the client after requesting the server, including the output of normal data streams, the return of error information, and so on.For example, SendResponseFilter, SendErrorFilter...

// com.netflix.zuul.http.ZuulServlet#postRoute
    /**
     * executes "post" ZuulFilters
     *
     * @throws ZuulException
     */
    void postRoute() throws ZuulException {
        zuulRunner.postRoute();
    }
    
    // com.netflix.zuul.ZuulRunner#postRoute
    /**
     * executes "post" filterType  ZuulFilters
     *
     * @throws ZuulException
     */
    public void postRoute() throws ZuulException {
        FilterProcessor.getInstance().postRoute();
    }

    // com.netflix.zuul.FilterProcessor#postRoute
    /**
     * runs "post" filters which are called after "route" filters. ZuulExceptions from ZuulFilters are thrown.
     * Any other Throwables are caught and a ZuulException is thrown out with a 500 status code
     *
     * @throws ZuulException
     */
    public void postRoute() throws ZuulException {
        try {
            // Get type is post Of filter, call
            // Default to: org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter
            runFilters("post");
        } catch (ZuulException e) {
            throw e;
        } catch (Throwable e) {
            throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + e.getClass().getName());
        }
    }
    // org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter#shouldFilter
    @Override
    public boolean shouldFilter() {
        // Responsive data can be processed
        RequestContext context = RequestContext.getCurrentContext();
        return context.getThrowable() == null
                && (!context.getZuulResponseHeaders().isEmpty()
                    || context.getResponseDataStream() != null
                    || context.getResponseBody() != null);
    }
    // org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter#run
    @Override
    public Object run() {
        try {
            // Add to header information
            addResponseHeaders();
            // Output data stream to requester
            writeResponse();
        }
        catch (Exception ex) {
            ReflectionUtils.rethrowRuntimeException(ex);
        }
        return null;
    }
    // org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter#addResponseHeaders
    private void addResponseHeaders() {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletResponse servletResponse = context.getResponse();
        if (this.zuulProperties.isIncludeDebugHeader()) {
            @SuppressWarnings("unchecked")
            List<String> rd = (List<String>) context.get(ROUTING_DEBUG_KEY);
            if (rd != null) {
                StringBuilder debugHeader = new StringBuilder();
                for (String it : rd) {
                    debugHeader.append("[[[" + it + "]]]");
                }
                servletResponse.addHeader(X_ZUUL_DEBUG_HEADER, debugHeader.toString());
            }
        }
        // towards response Add in header
        List<Pair<String, String>> zuulResponseHeaders = context.getZuulResponseHeaders();
        if (zuulResponseHeaders != null) {
            for (Pair<String, String> it : zuulResponseHeaders) {
                servletResponse.addHeader(it.first(), it.second());
            }
        }
        if (includeContentLengthHeader(context)) {
            Long contentLength = context.getOriginContentLength();
            if(useServlet31) {
                servletResponse.setContentLengthLong(contentLength);
            } else {
                //Try and set some kind of content length if we can safely convert the Long to an int
                if (isLongSafe(contentLength)) {
                    servletResponse.setContentLength(contentLength.intValue());
                }
            }
        }
    }
    // org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter#writeResponse()
    private void writeResponse() throws Exception {
        RequestContext context = RequestContext.getCurrentContext();
        // there is no body to send
        if (context.getResponseBody() == null
                && context.getResponseDataStream() == null) {
            return;
        }
        HttpServletResponse servletResponse = context.getResponse();
        if (servletResponse.getCharacterEncoding() == null) { // only set if not set
            servletResponse.setCharacterEncoding("UTF-8");
        }
        
        OutputStream outStream = servletResponse.getOutputStream();
        InputStream is = null;
        try {
            if (context.getResponseBody() != null) {
                String body = context.getResponseBody();
                is = new ByteArrayInputStream(
                                body.getBytes(servletResponse.getCharacterEncoding()));
            }
            else {
                is = context.getResponseDataStream();
                if (is!=null && context.getResponseGZipped()) {
                    // if origin response is gzipped, and client has not requested gzip,
                    // decompress stream before sending to client
                    // else, stream gzip directly to client
                    if (isGzipRequested(context)) {
                        servletResponse.setHeader(ZuulHeaders.CONTENT_ENCODING, "gzip");
                    }
                    else {
                        is = handleGzipStream(is);
                    }
                }
            }
            
            if (is!=null) {
                writeResponse(is, outStream);
            }
        }
        finally {
            /**
            * We must ensure that the InputStream provided by our upstream pooling mechanism is ALWAYS closed
            * even in the case of wrapped streams, which are supplied by pooled sources such as Apache's
            * PoolingHttpClientConnectionManager. In that particular case, the underlying HTTP connection will
            * be returned back to the connection pool iif either close() is explicitly called, a read
            * error occurs, or the end of the underlying stream is reached. If, however a write error occurs, we will
            * end up leaking a connection from the pool without an explicit close()
            *
            * @author Johannes Edmeier
            */
            if (is != null) {
                try {
                    is.close();
                }
                catch (Exception ex) {
                    log.warn("Error while closing upstream input stream", ex);
                }
            }

            try {
                Object zuulResponse = context.get("zuulResponse");
                if (zuulResponse instanceof Closeable) {
                    ((Closeable) zuulResponse).close();
                }
                outStream.flush();
                // The container will close the stream for us
            }
            catch (IOException ex) {
                log.warn("Error while sending response to client: " + ex.getMessage());
            }
        }
    }
    // org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter#writeResponse
    private void writeResponse(InputStream zin, OutputStream out) throws Exception {
        // Default size 8192
        byte[] bytes = buffers.get();
        int bytesRead = -1;
        // Sequentially outputStream Write byte stream in
        while ((bytesRead = zin.read(bytes)) != -1) {
            out.write(bytes, 0, bytesRead);
        }
    }

Similarly, the output to the client is as simple as parsing the header information and putting the response write() into the client's socket.That is to complete the task.

Above, we have mainly looked at the processing of several very common filters to understand the running process of zuul. Of course, the main purpose is to analyze how zuul forwards requests.Basically, all filters above inherit the abstraction of ZuulFilter, which provides two important and unified methods: the isFilterDisabled() and shouldFilter() methods to control whether the filter is enabled or should be used, and to unify the returned results.

 

The overall implementation of zuul is also very simple and straightforward, based on the template approach model and the responsibility chain model and the single-case model.It's just that more flowers need to be played on their own.

 

3. Implement a business filter on your own

This is necessary to achieve a common framework.Of course, it must be simple enough as follows: a comment plus an inheritance implementation is sufficient!

// A note,@Component, Success spring bean assembly
// An inheritance, ZuulFilter, Use zuul Can be done in accordance with specifications filter Access
@Component
public class MyOneFilter extends ZuulFilter {

    private final UrlPathHelper urlPathHelper = new UrlPathHelper();

    @Autowired
    private ZuulProperties zuulProperties;

    @Autowired
    private RouteLocator routeLocator;

    public MyOneFilter() {
    }

    public MyOneFilter(ZuulProperties zuulProperties,
                       RouteLocator routeLocator) {
        this.routeLocator = routeLocator;
        this.zuulProperties = zuulProperties;
    }

    @Override
    public String filterType() {
        // Custom filter type, do you know why enumeration classes are not used?Hey
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        // Define the exit order of the filter, the younger the cow
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        // Is it possible to enable the current filter, Has the final say in your business rules
        return true;
    }

    @Override
    public Object run() {
        // If the filter conditions are met, you can do whatever you want. RequestContext There's everything you want
        RequestContext ctx = RequestContext.getCurrentContext();
        Route route = routeLocator.getMatchingRoute(
                urlPathHelper.getPathWithinApplication(ctx.getRequest()));
        System.out.println("in my one filter");
        return null;
    }

}

For other configurations, just check your own website!( https://www.springcloud.cc/spring-cloud-greenwich.html#_router_and_filter_zuul

Topics: Spring socket Java Nginx