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