Goal of this example
This example demonstrates the main features of the Zuul API gateway integrated into spring cloud :
- Service auto registration via eureka
- Service registration by address
- Service registration by service ID
- Filters (logging, authentication)
- Serving static content
Technology Used
- Spring boot 1.3.5.RELEASE
- Eureka
- Zuul
This article is part of a Spring Cloud / Netflix OSS series
- Hystrix tutorial
- Eureka, Ribbon, Feign tutorial
- Microservice ecosystem with Docker, Spring Cloud and Spring Boot
- Zuul tutorial
API Gateway Overview
The Zuul API Gateway is part of the Netflix OSS package. It is very lightweight and integrates well with Eureka. API Gateway is the single entry point into the microservice ecosystem from the outside world.
Main features of Zuul
- Provides a unified access to multiple different microservices
- Hides internal details of the microservice ecosystem
- Load balances across multiple service instances
- Allowes access to services
- Restricts access to internal only services
- Looks up services from Eureka
- Implements filters for authentication or logging purposes
Zuul and NGINX are very similar the key differences are
- Unlike for NGINX Zuul can’t be downloaded as a binary distribution, instead it is running inside a Spring Boot application using the Spring Cloud integration
- Zuul is written in Java, therefore integration with the Java, Spring Boot, Spring Cloud and Netflix OSS ecosystem is quite easy
To start up a Spring Boot application with Zuul in it all we need to do is add the below dependency
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency>
and activate it using the @EnableZuulProxy annotation
@EnableZuulProxy @SpringBootApplication public class SpringCloudZuulExampleApplication {
Defining routing rules
Routing rules are configured in the application.properites / application.yml The below simple example shows how the /api/customer-service/** URI is mapped to an internal only service
zuul: #Service will be mapped under the /api URI prefix: /api routes: customer-service: path: /customer-service/** url: http://localhost:9098
Zuul integration with Eureka
Eureka is the service registry and part of Netflix OSS and Spring Cloud stack just like Zuul. This tutorial covers more in detail how Eureka works, but in essence, services register themself with a name and URL into the registry, and other services can query the available instances by service name. If Zuul is connected to Eureka then it will query all services and make them available. E.g.: if there is a service named CUSTOMER-SERVICE it will be available under the /customer-service URI. Configuring the connection is as simple as this :
eureka: client: serviceUrl: defaultZone: http://127.0.0.1:8761/eureka/
This is very convenient, but can be dangerous because exposes every service even if if one was meant to be internal only. A more fine grained approach is to enable services one by one.
zuul: #Service will be mapped under the /api URI prefix: /api # Uncomment to disable auto-registering all services read from Eureka # ignoredServices: '*' routes: test: path: /redirect/** url: http://google.com customer-by-address: path: /customer-by-address/** url: http://localhost:9098 customer-by-service: path: /customer-by-service/** serviceId: CUSTOMER-SERVICE
Given a service available at http://localhost:9098 that registers itself into Eureka under the name “customer-service” the above config will results the following URLs be available in Zuul
URI | Function |
---|---|
http://localhost:9090/routes | Display all available Routes (services) |
http://localhost:9090/api/customer-service/customer/1 | Service auto registered from Eureka |
http://localhost:9090/api/customer-by-address/customer/1 | Service registered by address |
http://localhost:9090/api/customer-by-service/customer/1 | Service registered by Eureka service id |
Zuul filters
If a spring Bean extends ZuulFilter then it will be auto registered as a filter. Filters are most commonly used for
- Authentication
- Logging
- Serving static content
- Dynamic routing based on some conditions (A/B testing)
Logging filter
@Component public class StaticFilter extends ZuulFilter { @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { String path = RequestContext.getCurrentContext().getRequest().getRequestURI(); return "/api/static".equals(path); } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); // Set the default response code for static filters to be 200 ctx.setResponseStatusCode(HttpServletResponse.SC_OK); // first StaticResponseFilter instance to match wins, others do not set body and/or status if (ctx.getResponseBody() == null) { ctx.setResponseBody("static content"); ctx.setSendZuulResponse(false); } return null; } }
Logging filter
@Component public class AccessLogFilter extends ZuulFilter { Logger logger = LoggerFactory.getLogger(AccessLogFilter.class); @Override public String filterType() { return "post"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { HttpServletRequest request = RequestContext.getCurrentContext().getRequest(); HttpServletResponse response = RequestContext.getCurrentContext().getResponse(); logger.info("REQUEST :: < " + request.getScheme() + " " + request.getLocalAddr() + ":" + request.getLocalPort()); logger.info("REQUEST :: < " + request.getMethod() + " " + request.getRequestURI() + " " + request.getProtocol()); logger.info("RESPONSE:: > HTTP:" + response.getStatus()); return null; } }
Redirect filter
The below filter redirects requests to a different server. It also demonstrates that filters can be written in groovy. The advantage (and danger) of groovy scripts is that they can be loaded from classpath, so the operations team can modify them without the need to release a jar / war file itself
import com.netflix.zuul.ZuulFilter import com.netflix.zuul.context.RequestContext import com.netflix.zuul.util.HTTPRequestUtils public class ExampleSurgicalDebugFilterBean extends ZuulFilter { @Override String filterType() { return "pre" } @Override int filterOrder() { return 96 } @Override boolean shouldFilter() { RequestContext.currentContext.getRequest().getRequestURI().matches("/api/redirect.*") } @Override Object run() { RequestContext.currentContext.routeHost = new URL("http://example.com"); if (HTTPRequestUtils.getInstance().getQueryParams() == null) { RequestContext.getCurrentContext().setRequestQueryParams(new HashMap<String, List<String>>()); } HTTPRequestUtils.getInstance().getQueryParams().put("debugRequest", ["true"]) RequestContext.currentContext.setDebugRequest(true) RequestContext.getCurrentContext().zuulToZuul = true } } beans { exampleSurgicalDebugFilterBean(ExampleSurgicalDebugFilterBean) { new ExampleSurgicalDebugFilterBean(); } }
Externalizing dynamic routing rules
By default routing rules are statically defined in the application.yml file. By using filters these rules can be made dynamic. Spring Boot has support to read groovy scripts from external locations, what allows rules to be defined externally. It can be configured in Spring Boot’s main class
@EnableZuulProxy @SpringBootApplication public class SpringCloudZuulExampleApplication { public static void main(String[] args) throws MalformedURLException { // Object[] sources = {SpringCloudZuulExampleApplication.class, new UrlResource(new URL("file:C:/dev/abc_dynamic.groovy"))}; Object[] sources = {SpringCloudZuulExampleApplication.class, new ClassPathResource("groovy/ExampleSurgicalDebugFilterBean.groovy")}; SpringApplication.run(sources, args); }
In a local development environment starting up a complete microservice ecosystem with Eureka is a complex task. Docker can help a lot in this, details are discussed in this blog post : https://exampledriven.wordpress.com/2016/06/24/spring-boot-docker-example/
Could you please elaborate how the routing is happeing? Would like to know if somebody tries to access /service1 is it getting forwarded or routed to /service1??
Hi I clarified the article see the “Defining routing rules” section
Pingback: Spring Cloud hystrix example | Example Driven (Self) Development
Pingback: Spring Cloud, Eureka, Ribbon, Feign example | Example Driven (Self) Development
Pingback: Microservice ecosystem with Docker, Spring Cloud and Spring Boot | Example Driven (Self) Development
Pingback: How to enable ZuulProxy in spring mvc | ASK Dev Archives
Pingback: Zuul – Edge Server – Learn. Write. Repeat.