1. Overview
Zuul is an edge service (or API gateway) from Netflix that provides dynamic routing, monitoring, resiliency, and security.
In this tutorial, we'll look at how to configure Zuul routes with fallbacks.
2. Initial Setup
To begin with, we'll first set up two Spring Boot applications. In the first application, we'll create a simple REST service. Whereas, in the second application, we'll use the Zuul proxy to create a route for the REST service of the first application.
2.1. A Simple REST Service
Let's say our application needs to display today's weather information to the user. So, we'll create a Spring Boot-based weather service application using the spring-boot-starter-web starter:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
Now, we'll create a controller for our weather service:
@RestController @RequestMapping("/weather") public class WeatherController { @GetMapping("/today") public String getMessage() { return "It's a bright sunny day today!"; } }
Now, let's run the weather service and check the weather service API:
$ curl -s localhost:8080/weather/today It's a bright sunny day today!
2.2. The API Gateway Application
Let's now create our second Spring Boot application, the API Gateway. In this application, we'll create a Zuul route for our weather service.
And since both our weather service and Zuul will want to use 8080 by default, we'll configure it to run on a different port, 7070.
So, let's first add the spring-cloud-starter-netflix-zuul in pom.xml:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency>
Next, we'll add the @EnableZuulProxy annotation to our API Gateway application class:
@SpringBootApplication @EnableZuulProxy public class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } }
Finally, we'll configure the Zuul route, using Ribbon, for our weather service API in application.yml:
spring: application: name: api-gateway server: port: 7070 zuul: igoredServices: '*' routes: weather-service: path: /weather/** serviceId: weather-service strip-prefix: false ribbon: eureka: enabled: false weather-service: ribbon: listOfServers: localhost:8080
2.3. Testing the Zuul Route
At this point, both Spring Boot applications are set up to expose the weather service API using Zuul proxy.
So, let's run both the applications and check the weather service API via Zuul:
$ curl -s localhost:7070/weather/today It's a bright sunny day today!
2.4. Testing the Zuul Route Failure Without Fallback
Now, let's stop the weather service application and check the weather service via Zuul again. As a result, we'll see an error message in the response:
$ curl -s localhost:7070/weather/today {"timestamp":"2019-10-08T12:42:09.479+0000","status":500, "error":"Internal Server Error","message":"GENERAL"}
Obviously, this is not the response the user would like to see. So, one of the ways we can take care of this is to create a fallback for the weather service Zuul route.
3. Zuul Fallback for a Route
The Zuul proxy uses Ribbon for load balancing and the requests execute in the Hystrix command. As a result, failures in the Zuul route appear in a Hystrix matrix.
Therefore, to create a custom fallback for a Zuul route, we'll create a bean of type FallbackProvider.
3.1. The WeatherServiceFallback Class
In this example, we want to return a message from the fallback response instead of the default error message that we saw earlier. So, let's create a simple implementation of FallbackProvider for the weather service route:
@Component class WeatherServiceFallback implements FallbackProvider { private static final String DEFAULT_MESSAGE = "Weather information is not available."; @Override public String getRoute() { return "weather-service"; } @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { if (cause instanceof HystrixTimeoutException) { return new GatewayClientResponse(HttpStatus.GATEWAY_TIMEOUT, DEFAULT_MESSAGE); } else { return new GatewayClientResponse(HttpStatus.INTERNAL_SERVER_ERROR, DEFAULT_MESSAGE); } } }
As we can see, we've overridden the methods getRoute and fallbackResponse. The getRoute method returns the Id of the route for which we have to create the fallback. Whereas, the fallbackResponse method returns the custom fallback response, an object of type GatewayClientResponse in our case. The GatewayClientResponse is a simple implementation of ClientHttpResponse.
3.2. Testing the Zuul Fallback
Let's now test the fallback we've created for weather service. Therefore, we'll run the API Gateway application and make sure that the weather service application is stopped.
Now, let's access the weather service API via the Zuul route and see the fallback response in action:
$ curl -s localhost:7070/weather/today Weather information is not available.
4. Fallback for All Routes
So far, we've seen how to create a fallback for a Zuul route using its route Id. However, let's suppose, we also want to create a generic fallback for all other routes in our application. We can do so by creating one more implementation of FallbackProvider and returning * or null from the getRoute method, instead of the route Id:
@Override public String getRoute() { return "*"; // or return null; }
5. Conclusion
In this tutorial, we've seen an example of creating a fallback for a Zuul route. We've also seen how we can create a generic fallback for all Zuul routes.
As usual, the implementation of all these examples and code snippets can be found over on GitHub.