The first impression of Zuul
is usually like this: it includes two functions: routing
and filtering
requests. The routing function is responsible for forwarding external requests to specific microservice instances, which is the basis for achieving a unified entrance to external access. The filter function is responsible for intervening in the processing of requests, and is the basis for implementing functions such as request verification and service aggregation. However, in reality, when the routing function is running, its routing mapping and request forwarding are completed by several different filters. Among them, the route mapping is mainly completed by a PRE
type filter, which matches the request path with the configured routing rule to find the destination address that needs to be forwarded. The part of the request forwarding is completed by the Route
type filter, which forwards the routing address obtained by the PRE
type filter. Therefore, the filter can be said to be the most important core component of Zuul
‘s API gateway. Every request entering Zuul
will go through a series of filter processing chains to get the request response and return it to the client.
Filter Introduction
1. Filter characteristics
The key features of Zuul
filters are:
- Type: Defines when to be executed during request execution;
- Execution Order: When there are multiple filters, it is used to indicate the order of execution. The smaller the value, the earlier the execution;
- Criteria: the conditions under which the filter will be triggered;
- Action: The specific action.
Filters do not communicate directly, but share information through RequestContext
, which is thread-safe
.
Corresponding to the characteristics of the Zuul
filter above, the methods we need to implement when implementing a custom filter are:
1 | public class PreTypeZuulFilter extends ZuulFilter { |
among them:
- The filterType() method is the type of the filter;
- The filterOrder() method returns the execution order;
- The shouldFilter() method is to determine whether the filter needs to be executed;
- run() is the specific filtering action to be performed.
2. Filter type
Zuul
defines four standard filter types, which correspond to the typical life cycle of a request.
- PRE filter: called before the request is routed, it can be used to implement authentication, select the requested microservice in the cluster, record debugging information, etc;
- ROUTING filter: called when routing requests;
- POST filter: It is executed after routing to the microservice, and can be used to add standard HTTP headers to the response, collect statistics and indicators, send the response from the microservice to the client, etc;
- ERROR filter: Called when an error occurs while processing the request.
The types of Zuul
filters are actually the life cycle of Zuul
filters. Use the following diagram to understand their execution process.
In addition to the 4 default filter types given above, Zuul
allows us to create custom filter types. For example, we can customize a STATIC type filter to generate a response directly in Zuul
without forwarding the request to the microservices on the backend.
3. Custom filter example code
Take a look at a few examples given by the official:
PRE type example
1 | public class QueryParamPreFilter extends ZuulFilter { |
This example obtains the serviceID
to be forwarded from the request parameter service
. Of course, it’s not recommended, here is just an example.
ROUTE type example
1 | public class OkHttpRoutingFilter extends ZuulFilter { |
This example converts an HTTP request to an OkHttp3 request, and converts the server’s return into a servlet’s response.
POST type example
1 | public class AddResponseHeaderFilter extends ZuulFilter { |
This example is just adding a randomly generated X-Foo to the returned header.
4. Disable filter
Only need to configure the filter to be disabled in application.properties
(or yml), the format is:
1 | zuul. [Filter-name]. [Filter-type] .disable = true |
Such as:
1 | zuul.FormBodyWrapperFilter.pre.disable=true |
5. A little supplement about Zuul filter Error
When Zuul
throws an exception during execution, the error filter is executed. SendErrorFilter
will only execute if RequestContext.getThrowable()
is not empty. It sets the error information into the requested javax.servlet.error.*
properties and forwards to Spring Boot’s error page.
The specific class implemented by Zuul
filters is ZuulServletFilter
, whose core code is as follows:
1 |
|
It can be seen from this code that error can be executed after catching exceptions in all stages, but if an exception occurs in the post stage and is handled by error, it will not be returned to the post stage for execution, which means that there should be no exceptions in the post stage. Because once there is an exception, other post filters behind this filter will no longer be executed.
A simple method for global exception handling is: Add a filter of type error and write the error information to the RequestContext
so that SendErrorFilter
can get the error information. Code show as below:
1 | public class GlobalErrorFilter extends ZuulFilter { |
@EnableZuulServer VS @EnableZuulProxy
Zuul
provides us with two main application annotations: @EnableZuulServer
and @EnableZuulProxy
, where @EnableZuulProxy
contains the functionality of @EnableZuulServer
, and @EnableCircuitBreaker
and @EnableDiscoveryClient
are also added. When we need to run a Zuul
service without proxy function, or selectively switch some proxy functions, then we need to use @EnableZuulServer
instead of @EnableZuulProxy
. At this time we can add any ZuulFilter
type entity class. They will be automatically loaded, which is the same as the previous article using @EnableZuulProxy
, but it will not automatically load any proxy filters.
1 @EnableZuulServer’s default filter
When we use @EnableZuulServer
, the filters loaded by default are:
PRE type filter
ServletDetectionFilter
This filter is executed first. It is mainly used to check whether the current request is processed through Spring’sDispatcherServlet
or processed throughZuulServlet
. The result is stored inisDispatcherServletRequest
, and the value type is Boolean.FormBodyWrapperFilter
The purpose of this filter is to wrap the request body that meets the requirements into aFormBodyRequestWrapper
object for subsequent processing.DebugFilter
PRE type filter. When the debug parameter is set in the request parameter, this filter will setRequestContext.setDebugRouting()
andRequestContext.setDebugRequest()
to true in the current request context, so that subsequent filters can define some debug information based on these two parameters. When there is a problem in the production environment, we can add the parameter to print debugging information in the background to help us analyze the problem. For the name of the debug parameter in the request, we can customize it throughzuul.debug.parameter
.
Route type filter
- SendForwardFilter
This filter only processes requests with theforward.to
(FilterConstants.FORWARD_TO_KEY) parameter in the request context. That is, the local forward of the forward in our routing rule is processed.
POST type filter
- SendResponseFilter
The filter is to encapsulate the response returned by the proxy request, and then send it back to the requester as the corresponding request.
ERROR type filter
- SendErrorFilter
The filter is to determine if there is any exception information in the current request context (RequestContext.getThrowable ()
is not empty), and if there is, it is forwarded to the/error
page by default. We can also customize the error page by settingerror.path
.
2 @EnableZuulProxy’s default filter
@EnableZuulProxy
adds the following filters to the above:
PRE type filter
- PreDecorationFilter
This filter determines the route to the route and how to route based on the providedRouteLocator
. The router can also set various proxy-related headers for back-end requests.
ROUTE type filter
RibbonRoutingFilter
This filter will process the request withserviceId
(can be obtained throughRequestContext.getCurrentContext().Get ("serviceId"))
in the context, useRibbon
,Hystrix
and pluggable HTTP client to send the request, and return the result of the request. That is to say thatRibbon
andHystrix
only take effect when we use theserviceId
to configure routing rules.SimpleHostRoutingFilter
When this filter detects that therouteHost
parameter (available throughRequestContext.getRouteHost()
) is set, it will send a request to the specified URL through Apache HttpClient. At this point, the request is not wrapped withHystrix
commands, so this type of request does not have thread isolation and circuit breaker protection.