Routing in an integral part of a microservice architecture. For example, /
may be mapped to your web application, /api/users
is mapped to the user service and /api/shop
is mapped to the shop service. Zuul is a JVM based router and server side load balancer by Netflix.
Netflix uses Zuul for the following:
Zuul’s rule engine allows rules and filters to be written in essentially any JVM language, with built in support for Java and Groovy.
Note | |
---|---|
The configuration property |
Note | |
---|---|
Default Hystrix isolation pattern (ExecutionIsolationStrategy) for all routes is SEMAPHORE. |
To include Zuul in your project use the starter with group org.springframework.cloud
and artifact id spring-cloud-starter-netflix-zuul
. See the Spring Cloud Project page
for details on setting up your build system with the current Spring Cloud Release Train.
Spring Cloud has created an embedded Zuul proxy to ease the development of a very common use case where a UI application wants to proxy calls to one or more back end services. This feature is useful for a user interface to proxy to the backend services it requires, avoiding the need to manage CORS and authentication concerns independently for all the backends.
To enable it, annotate a Spring Boot main class with
@EnableZuulProxy
, and this forwards local calls to the appropriate
service. By convention, a service with the ID "users", will
receive requests from the proxy located at /users
(with the prefix
stripped). The proxy uses Ribbon to locate an instance to forward to
via discovery, and all requests are executed in a
hystrix command, so
failures will show up in Hystrix metrics, and once the circuit is open
the proxy will not try to contact the service.
Note | |
---|---|
the Zuul starter does not include a discovery client, so for routes based on service IDs you need to provide one of those on the classpath as well (e.g. Eureka is one choice). |
To skip having a service automatically added, set
zuul.ignored-services
to a list of service id patterns. If a service
matches a pattern that is ignored, but also included in the explicitly
configured routes map, then it will be unignored. Example:
application.yml.
zuul: ignoredServices: '*' routes: users: /myusers/**
In this example, all services are ignored except "users".
To augment or change the proxy routes, you can add external configuration like the following:
application.yml.
zuul: routes: users: /myusers/**
This means that http calls to "/myusers" get forwarded to the "users" service (for example "/myusers/101" is forwarded to "/101").
To get more fine-grained control over a route you can specify the path and the serviceId independently:
application.yml.
zuul: routes: users: path: /myusers/** serviceId: users_service
This means that http calls to "/myusers" get forwarded to the "users_service" service. The route has to have a "path" which can be specified as an ant-style pattern, so "/myusers/*" only matches one level, but "/myusers/**" matches hierarchically.
The location of the backend can be specified as either a "serviceId" (for a service from discovery) or a "url" (for a physical location), e.g.
application.yml.
zuul: routes: users: path: /myusers/** url: https://example.com/users_service
These simple url-routes don’t get executed as a HystrixCommand
nor do they loadbalance multiple URLs with Ribbon.
To achieve this, you can specify a serviceId
with a static list of servers:
application.yml.
zuul: routes: echo: path: /myusers/** serviceId: myusers-service stripPrefix: true hystrix: command: myusers-service: execution: isolation: thread: timeoutInMilliseconds: ... myusers-service: ribbon: NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList ListOfServers: https://example1.com,http://example2.com ConnectTimeout: 1000 ReadTimeout: 3000 MaxTotalHttpConnections: 500 MaxConnectionsPerHost: 100
Another method is specifiying a service-route and configure a Ribbon client for the serviceId (this requires disabling Eureka support in Ribbon: see above for more information), e.g.
application.yml.
zuul: routes: users: path: /myusers/** serviceId: users ribbon: eureka: enabled: false users: ribbon: listOfServers: example.com,google.com
You can provide convention between serviceId and routes using regexmapper. It uses regular expression named groups to extract variables from serviceId and inject them into a route pattern.
ApplicationConfiguration.java.
@Bean public PatternServiceRouteMapper serviceRouteMapper() { return new PatternServiceRouteMapper( "(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); }
This means that a serviceId "myusers-v1" will be mapped to route "/v1/myusers/**". Any regular expression is accepted but all named groups must be present in both servicePattern and routePattern. If servicePattern does not match a serviceId, the default behavior is used. In the example above, a serviceId "myusers" will be mapped to route "/myusers/**" (no version detected) This feature is disabled by default and only applies to discovered services.
To add a prefix to all mappings, set zuul.prefix
to a value, such as
/api
. The proxy prefix is stripped from the request before the
request is forwarded by default (switch this behaviour off with
zuul.stripPrefix=false
). You can also switch off the stripping of
the service-specific prefix from individual routes, e.g.
application.yml.
zuul: routes: users: path: /myusers/** stripPrefix: false
Note | |
---|---|
|
In this example, requests to "/myusers/101" will be forwarded to "/myusers/101" on the "users" service.
The zuul.routes
entries actually bind to an object of type ZuulProperties
. If you
look at the properties of that object you will see that it also has a "retryable" flag.
Set that flag to "true" to have the Ribbon client automatically retry failed requests
(and if you need to you can modify the parameters of the retry operations using
the Ribbon client configuration).
The X-Forwarded-Host
header is added to the forwarded requests by
default. To turn it off set zuul.addProxyHeaders = false
. The
prefix path is stripped by default, and the request to the backend
picks up a header "X-Forwarded-Prefix" ("/myusers" in the examples
above).
An application with @EnableZuulProxy
could act as a standalone
server if you set a default route ("/"), for example zuul.route.home:
/
would route all traffic (i.e. "/**") to the "home" service.
If more fine-grained ignoring is needed, you can specify specific patterns to ignore. These patterns are evaluated at the start of the route location process, which means prefixes should be included in the pattern to warrant a match. Ignored patterns span all services and supersede any other route specification.
application.yml.
zuul: ignoredPatterns: /**/admin/** routes: users: /myusers/**
This means that all calls such as "/myusers/101" will be forwarded to "/101" on the "users" service. But calls including "/admin/" will not resolve.
Warning | |
---|---|
If you need your routes to have their order preserved you need to use a YAML file as the ordering will be lost using a properties file. For example: |
application.yml.
zuul: routes: users: path: /myusers/** legacy: path: /**
If you were to use a properties file, the legacy
path may end up in front of the users
path rendering the users
path unreachable.
The default HTTP client used by zuul is now backed by the Apache HTTP Client instead of the
deprecated Ribbon RestClient
. To use RestClient
or to use the okhttp3.OkHttpClient
set
ribbon.restclient.enabled=true
or ribbon.okhttp.enabled=true
respectively. If you would
like to customize the Apache HTTP client or the OK HTTP client provide a bean of type
ClosableHttpClient
or OkHttpClient
.
It’s OK to share headers between services in the same system, but you probably don’t want sensitive headers leaking downstream into external servers. You can specify a list of ignored headers as part of the route configuration. Cookies play a special role because they have well-defined semantics in browsers, and they are always to be treated as sensitive. If the consumer of your proxy is a browser, then cookies for downstream services also cause problems for the user because they all get jumbled up (all downstream services look like they come from the same place).
If you are careful with the design of your services, for example if only one of the downstream services sets cookies, then you might be able to let them flow from the backend all the way up to the caller. Also, if your proxy sets cookies and all your back end services are part of the same system, it can be natural to simply share them (and for instance use Spring Session to link them up to some shared state). Other than that, any cookies that get set by downstream services are likely to be not very useful to the caller, so it is recommended that you make (at least) "Set-Cookie" and "Cookie" into sensitive headers for routes that are not part of your domain. Even for routes that are part of your domain, try to think carefully about what it means before allowing cookies to flow between them and the proxy.
The sensitive headers can be configured as a comma-separated list per route, e.g.
application.yml.
zuul: routes: users: path: /myusers/** sensitiveHeaders: Cookie,Set-Cookie,Authorization url: https://downstream
Note | |
---|---|
this is the default value for |
The sensitiveHeaders
are a blacklist and the default is not empty,
so to make Zuul send all headers (except the "ignored" ones) you would
have to explicitly set it to the empty list. This is necessary if you
want to pass cookie or authorization headers to your back end. Example:
application.yml.
zuul: routes: users: path: /myusers/** sensitiveHeaders: url: https://downstream
Sensitive headers can also be set globally by setting zuul.sensitiveHeaders
. If sensitiveHeaders
is set on a route, this will override the global sensitiveHeaders
setting.
In addition to the per-route sensitive headers, you can set a global
value for zuul.ignoredHeaders
for values that should be discarded
(both request and response) during interactions with downstream
services. By default these are empty, if Spring Security is not on the
classpath, and otherwise they are initialized to a set of well-known
"security" headers (e.g. involving caching) as specified by Spring
Security. The assumption in this case is that the downstream services
might add these headers too, and we want the values from the proxy.
To not discard these well known security headers in case Spring Security is on the classpath you can set zuul.ignoreSecurityHeaders
to false
. This can be useful if you disabled the HTTP Security response headers in Spring Security and want the values provided by downstream services
If you are using @EnableZuulProxy
with the Spring Boot Actuator you
will enable (by default) two additional endpoints:
A GET to the routes endpoint at /routes
will return a list of the mapped
routes:
GET /routes.
{ /stores/**: "http://localhost:8081" }
Additional route details can be requested by adding the ?format=details
query
string to /routes
. This will produce the following output:
GET /routes/details.
{ "/stores/**": { "id": "stores", "fullPath": "/stores/**", "location": "http://localhost:8081", "path": "/**", "prefix": "/stores", "retryable": false, "customSensitiveHeaders": false, "prefixStripped": true } }
A POST will force a refresh of the existing routes (e.g. in
case there have been changes in the service catalog). You can disable
this endpoint by setting endpoints.routes.enabled
to false
.
Note | |
---|---|
the routes should respond automatically to changes in the service catalog, but the POST to /routes is a way to force the change to happen immediately. |
A common pattern when migrating an existing application or API is to "strangle" old endpoints, slowly replacing them with different implementations. The Zuul proxy is a useful tool for this because you can use it to handle all traffic from clients of the old endpoints, but redirect some of the requests to new ones.
Example configuration:
application.yml.
zuul: routes: first: path: /first/** url: https://first.example.com second: path: /second/** url: forward:/second third: path: /third/** url: forward:/3rd legacy: path: /** url: https://legacy.example.com
In this example we are strangling the "legacy" app which is mapped to
all requests that do not match one of the other patterns. Paths in
/first/**
have been extracted into a new service with an external
URL. And paths in /second/**
are forwarded so they can be handled
locally, e.g. with a normal Spring @RequestMapping
. Paths in
/third/**
are also forwarded, but with a different prefix
(i.e. /third/foo
is forwarded to /3rd/foo
).
Note | |
---|---|
The ignored patterns aren’t completely ignored, they just aren’t handled by the proxy (so they are also effectively forwarded locally). |
If you @EnableZuulProxy
you can use the proxy paths to
upload files and it should just work as long as the files
are small. For large files there is an alternative path
which bypasses the Spring DispatcherServlet
(to
avoid multipart processing) in "/zuul/*". I.e. if
zuul.routes.customers=/customers/**
then you can
POST large files to "/zuul/customers/*". The servlet
path is externalized via zuul.servletPath
. Extremely
large files will also require elevated timeout settings
if the proxy route takes you through a Ribbon load
balancer, e.g.
application.yml.
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000 ribbon: ConnectTimeout: 3000 ReadTimeout: 60000
Note that for streaming to work with large files, you need to use chunked encoding in the request (which some browsers do not do by default). E.g. on the command line:
$ curl -v -H "Transfer-Encoding: chunked" \ -F "[email protected]" localhost:9999/zuul/simple/file
When processing the incoming request, query params are decoded so they can be available for possible modifications in
Zuul filters. They are then re-encoded when building the backend request in the route filters. The result
can be different than the original input if it was encoded using Javascript’s encodeURIComponent()
method for example.
While this causes no issues in most cases, some web servers can be picky with the encoding of complex query string.
To force the original encoding of the query string, it is possible to pass a special flag to ZuulProperties
so
that the query string is taken as is with the HttpServletRequest::getQueryString
method :
application.yml.
zuul: forceOriginalQueryStringEncoding: true
Note: This special flag only works with SimpleHostRoutingFilter
and you loose the ability to easily override
query parameters with RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)
since
the query string is now fetched directly on the original HttpServletRequest
.
You can also run a Zuul server without the proxying, or switch on parts of the proxying platform selectively, if you
use @EnableZuulServer
(instead of @EnableZuulProxy
). Any beans that you add to the application of type ZuulFilter
will be installed automatically, as they are with @EnableZuulProxy
, but without any of the proxy filters being added
automatically.
In this case the routes into the Zuul server are still specified by configuring "zuul.routes.*", but there is no service discovery and no proxying, so the "serviceId" and "url" settings are ignored. For example:
application.yml.
zuul: routes: api: /api/**
maps all paths in "/api/**" to the Zuul filter chain.
Zuul for Spring Cloud comes with a number of ZuulFilter
beans enabled by default
in both proxy and server mode. See the zuul filters package for the
possible filters that are enabled. If you want to disable one, simply set
zuul.<SimpleClassName>.<filterType>.disable=true
. By convention, the package after
filters
is the Zuul filter type. For example to disable
org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter
set
zuul.SendResponseFilter.post.disable=true
.
When a circuit for a given route in Zuul is tripped you can provide a fallback response
by creating a bean of type ZuulFallbackProvider
. Within this bean you need to specify
the route ID the fallback is for and provide a ClientHttpResponse
to return
as a fallback. Here is a very simple ZuulFallbackProvider
implementation.
class MyFallbackProvider implements ZuulFallbackProvider { @Override public String getRoute() { return "customers"; } @Override public ClientHttpResponse fallbackResponse() { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } @Override public int getRawStatusCode() throws IOException { return 200; } @Override public String getStatusText() throws IOException { return "OK"; } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream("fallback".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }
And here is what the route configuration would look like.
zuul: routes: customers: /customers/**
If you would like to provide a default fallback for all routes than you can create a bean of
type ZuulFallbackProvider
and have the getRoute
method return *
or null
.
class MyFallbackProvider implements ZuulFallbackProvider { @Override public String getRoute() { return "*"; } @Override public ClientHttpResponse fallbackResponse() { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } @Override public int getRawStatusCode() throws IOException { return 200; } @Override public String getStatusText() throws IOException { return "OK"; } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream("fallback".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }
If you would like to choose the response based on the cause of the failure use FallbackProvider
which will replace ZuulFallbackProvder
in future versions.
class MyFallbackProvider implements FallbackProvider { @Override public String getRoute() { return "*"; } @Override public ClientHttpResponse fallbackResponse(final Throwable cause) { if (cause instanceof HystrixTimeoutException) { return response(HttpStatus.GATEWAY_TIMEOUT); } else { return fallbackResponse(); } } @Override public ClientHttpResponse fallbackResponse() { return response(HttpStatus.INTERNAL_SERVER_ERROR); } private ClientHttpResponse response(final HttpStatus status) { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return status; } @Override public int getRawStatusCode() throws IOException { return status.value(); } @Override public String getStatusText() throws IOException { return status.getReasonPhrase(); } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream("fallback".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }
If you want to configure the socket timeouts and read timeouts for requests proxied through Zuul there are two options based on your configuration.
If Zuul is using service discovery than you need to configure these timeouts via Ribbon properties,
ribbon.ReadTimeout
and ribbon.SocketTimeout
.
If you have configured Zuul routes by specifying URLs than you will need to use
zuul.host.connect-timeout-millis
and zuul.host.socket-timeout-millis
.
If Zuul is fronting a web application then there may be a need to re-write the Location
header when the web application redirects through a http status code of 3XX, otherwise the browser will end up redirecting to the web application’s url instead of the Zuul url.
A LocationRewriteFilter
Zuul filter can be configured to re-write the Location header to the Zuul’s url, it also adds back the stripped global and route specific prefixes. The filter can be added the following way via a Spring Configuration file:
import org.springframework.cloud.netflix.zuul.filters.post.LocationRewriteFilter; ... @Configuration @EnableZuulProxy public class ZuulConfig { @Bean public LocationRewriteFilter locationRewriteFilter() { return new LocationRewriteFilter(); } }
Warning | |
---|---|
Use this filter with caution though, the filter acts on the |
For a general overview of how Zuul works, please see the Zuul Wiki.
Zuul is implemented as a Servlet. For the general cases, Zuul is embedded into the Spring Dispatch mechanism. This allows Spring MVC to be in control of the routing. In this case, Zuul is configured to buffer requests. If there is a need to go through Zuul without buffering requests (e.g. for large file uploads), the Servlet is also installed outside of the Spring Dispatcher. By default, this is located at /zuul
. This path can be changed with the zuul.servlet-path
property.
To pass information between filters, Zuul uses a RequestContext
. Its data is held in a ThreadLocal
specific to each request. Information about where to route requests, errors and the actual HttpServletRequest
and HttpServletResponse
are stored there. The RequestContext
extends ConcurrentHashMap
, so anything can be stored in the context. FilterConstants
contains the keys that are used by the filters installed by Spring Cloud Netflix (more on these later).
Spring Cloud Netflix installs a number of filters based on which annotation was used to enable Zuul. @EnableZuulProxy
is a superset of @EnableZuulServer
. In other words, @EnableZuulProxy
contains all filters installed by @EnableZuulServer
. The additional filters in the "proxy" enable routing functionality. If you want a "blank" Zuul, you should use @EnableZuulServer
.
Creates a SimpleRouteLocator
that loads route definitions from Spring Boot configuration files.
The following filters are installed (as normal Spring Beans):
Pre filters:
ServletDetectionFilter
: Detects if the request is through the Spring Dispatcher. Sets boolean with key FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY
.FormBodyWrapperFilter
: Parses form data and reencodes it for downstream requests.DebugFilter
: if the debug
request parameter is set, this filter sets RequestContext.setDebugRouting()
and RequestContext.setDebugRequest()
to true.Route filters:
SendForwardFilter
: This filter forwards requests using the Servlet RequestDispatcher
. The forwarding location is stored in the RequestContext
attribute FilterConstants.FORWARD_TO_KEY
. This is useful for forwarding to endpoints in the current application.Post filters:
SendResponseFilter
: Writes responses from proxied requests to the current response.Error filters:
SendErrorFilter
: Forwards to /error (by default) if RequestContext.getThrowable()
is not null. The default forwarding path (/error
) can be changed by setting the error.path
property.Creates a DiscoveryClientRouteLocator
that loads route definitions from a DiscoveryClient
(like Eureka), as well as from properties. A route is created for each serviceId
from the DiscoveryClient
. As new services are added, the routes will be refreshed.
In addition to the filters described above, the following filters are installed (as normal Spring Beans):
Pre filters:
PreDecorationFilter
: This filter determines where and how to route based on the supplied RouteLocator
. It also sets various proxy-related headers for downstream requests.Route filters:
RibbonRoutingFilter
: This filter uses Ribbon, Hystrix and pluggable HTTP clients to send requests. Service ids are found in the RequestContext
attribute FilterConstants.SERVICE_ID_KEY
. This filter can use different HTTP clients. They are:
HttpClient
. This is the default client.OkHttpClient
v3. This is enabled by having the com.squareup.okhttp3:okhttp
library on the classpath and setting ribbon.okhttp.enabled=true
.ribbon.restclient.enabled=true
. This client has limitations, such as it doesn’t support the PATCH method, but also has built-in retry.SimpleHostRoutingFilter
: This filter sends requests to predetermined URLs via an Apache HttpClient. URLs are found in RequestContext.getRouteHost()
.Most of the following "How to Write" examples below are included Sample Zuul Filters project. There are also examples of manipulating the request or response body in that repository.
Pre filters are used to set up data in the RequestContext
for use in filters downstream. The main use case is to set information required for route filters.
public class QueryParamPreFilter extends ZuulFilter { @Override public int filterOrder() { return PRE_DECORATION_FILTER_ORDER - 1; // run before PreDecoration } @Override public String filterType() { return PRE_TYPE; } @Override public boolean shouldFilter() { RequestContext ctx = RequestContext.getCurrentContext(); return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded && !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined serviceId } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); if (request.getParameter("foo") != null) { // put the serviceId in `RequestContext` ctx.put(SERVICE_ID_KEY, request.getParameter("foo")); } return null; } }
The filter above populates SERVICE_ID_KEY
from the foo
request parameter. In reality, it’s not a good idea to do that kind of direct mapping, but the service id should be looked up from the value of foo
instead.
Now that SERVICE_ID_KEY
is populated, PreDecorationFilter
won’t run and RibbonRoutingFilter
will. If you wanted to route to a full URL instead, call ctx.setRouteHost(url)
instead.
To modify the path that routing filters will forward to, set the REQUEST_URI_KEY
.
Route filters are run after pre filters and are used to make requests to other services. Much of the work here is to translate request and response data to and from the client required model.
public class OkHttpRoutingFilter extends ZuulFilter { @Autowired private ProxyRequestHelper helper; @Override public String filterType() { return ROUTE_TYPE; } @Override public int filterOrder() { return SIMPLE_HOST_ROUTING_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return RequestContext.getCurrentContext().getRouteHost() != null && RequestContext.getCurrentContext().sendZuulResponse(); } @Override public Object run() { OkHttpClient httpClient = new OkHttpClient.Builder() // customize .build(); RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); String method = request.getMethod(); String uri = this.helper.buildZuulRequestURI(request); Headers.Builder headers = new Headers.Builder(); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); Enumeration<String> values = request.getHeaders(name); while (values.hasMoreElements()) { String value = values.nextElement(); headers.add(name, value); } } InputStream inputStream = request.getInputStream(); RequestBody requestBody = null; if (inputStream != null && HttpMethod.permitsRequestBody(method)) { MediaType mediaType = null; if (headers.get("Content-Type") != null) { mediaType = MediaType.parse(headers.get("Content-Type")); } requestBody = RequestBody.create(mediaType, StreamUtils.copyToByteArray(inputStream)); } Request.Builder builder = new Request.Builder() .headers(headers.build()) .url(uri) .method(method, requestBody); Response response = httpClient.newCall(builder.build()).execute(); LinkedMultiValueMap<String, String> responseHeaders = new LinkedMultiValueMap<>(); for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) { responseHeaders.put(entry.getKey(), entry.getValue()); } this.helper.setResponse(response.code(), response.body().byteStream(), responseHeaders); context.setRouteHost(null); // prevent SimpleHostRoutingFilter from running return null; } }
The above filter translates Servlet request information into OkHttp3 request information, executes an HTTP request, then translates OkHttp3 reponse information to the Servlet response. WARNING: this filter might have bugs and not function correctly.
Post filters typically manipulate the response. In the filter below, we add a random UUID
as the X-Foo
header. Other manipulations, such as transforming the response body, are much more complex and compute-intensive.
public class AddResponseHeaderFilter extends ZuulFilter { @Override public String filterType() { return POST_TYPE; } @Override public int filterOrder() { return SEND_RESPONSE_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); HttpServletResponse servletResponse = context.getResponse(); servletResponse.addHeader("X-Foo", UUID.randomUUID().toString()); return null; } }
If an exception is thrown during any portion of the Zuul filter lifecycle, the error filters are executed. The SendErrorFilter
is only run if RequestContext.getThrowable()
is not null
. It then sets specific javax.servlet.error.*
attributes in the request and forwards the request to the Spring Boot error page.
Zuul internally uses Ribbon for calling the remote url’s and Ribbon clients are by default lazily loaded up by Spring Cloud on first call. This behavior can be changed for Zuul using the following configuration and will result in the child Ribbon related Application contexts being eagerly loaded up at application startup time.
application.yml.
zuul: ribbon: eager-load: enabled: true