undefined## Router and Filter: Zuul {#router-and-filter-zuul}
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:
Authentication
Insights
Stress Testing
Canary Testing
Dynamic Routing
Service Migration
Load Shedding
Security
Static Response handling
Active/Active traffic management
Zuul’s rule engine allows rules and filters to be written in essentially any JVM language, with built in support for Java and Groovy.
Embedded Zuul Reverse Proxy
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 Eureka 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 Eureka, 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.
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 Eureka service) or a "url" (for a physical location), e.g.
application.yml
zuul:
routes:
users:
path: /myusers/**
url: http://example.com/users_service
These simple url-routes doesn’t get executed as HystrixCommand nor can you loadbalance multiple url with Ribbon. To achieve this specify a service-route and configure a Ribbon client for the serviceId (this currently 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
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
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 ProxyRouteLocator
. 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 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 the @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.
Uploading Files through Zuul
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]](/cdn-cgi/l/email-protection) **Illegal HTML tag removed :** /* */ " localhost:9999/zuul/simple/file
Plain Embedded Zuul
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.
Disable Zuul Filters
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
.
Polyglot support with Sidecar
Do you have non-jvm languages you want to take advantage of Eureka, Ribbon and Config Server? The Spring Cloud Netflix Sidecar was inspired by Netflix Prana. It includes a simple http api to get all of the instances (ie host and port) for a given service. You can also proxy service calls through an embedded Zuul proxy which gets its route entries from Eureka. The Spring Cloud Config Server can be accessed directly via host lookup or through the Zuul Proxy. The non-jvm app should implement a health check so the Sidecar can report to eureka if the app is up or down.
To enable the Sidecar, create a Spring Boot application with @EnableSidecar
. This annotation includes @EnableCircuitBreaker
, @EnableDiscoveryClient
, and @EnableZuulProxy
. Run the resulting application on the same host as the non-jvm application.
To configure the side car add sidecar.port
and sidecar.health-uri
to application.yml
. The sidecar.port
property is the port the non-jvm app is listening on. This is so the Sidecar can properly register the app with Eureka. The sidecar.health-uri
is a uri accessible on the non-jvm app that mimicks a Spring Boot health indicator. It should return a json document like the following:
health-uri-document
{
"status":"UP"
}
Here is an example application.yml for a Sidecar application:
application.yml
server:
port: 5678
spring:
application:
name: sidecar
sidecar:
port: 8000
health-uri: http://localhost:8000/health.json
The api for the DiscoveryClient.getInstances()
method is /hosts/{serviceId}
. Here is an example response for /hosts/customers
that returns two instances on different hosts. This api is accessible to the non-jvm app (if the sidecar is on port 5678) at [http://localhost:5678/hosts/{serviceId}](http://localhost:5678/hosts/{serviceId})
.
/hosts/customers
[
{
"host": "myhost",
"port": 9000,
"uri": "http://myhost:9000",
"serviceId": "CUSTOMERS",
"secure": false
},
{
"host": "myhost2",
"port": 9000,
"uri": "http://myhost2:9000",
"serviceId": "CUSTOMERS",
"secure": false
}
]
The Zuul proxy automatically adds routes for each service known in eureka to /<serviceId>
, so the customers service is available at /customers
. The Non-jvm app can access the customer service via [http://localhost:5678/customers](http://localhost:5678/customers)
(assuming the sidecar is listening on port 5678).
If the Config Server is registered with Eureka, non-jvm application can access it via the Zuul proxy. If the serviceId of the ConfigServer is configserver
and the Sidecar is on port 5678, then it can be accessed at http://localhost:5678/configserver
Non-jvm app can take advantage of the Config Server’s ability to return YAML documents. For example, a call to http://sidecar.local.spring.io:5678/configserver/default-master.yml might result in a YAML document like the following
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
password: password
info:
description: Spring Cloud Samples
url: https://github.com/spring-cloud-samples