In the last articles we have introduced an custom gateway in order to hide services and to load balance requests among others. Now the guys at netflix implemented this pattern and created Zuul. Personally, it reminds me of Apache Server.
Zuul core consists of filters, which you can use to intercept the request and response and add custom functionality. The anatomy of a zuul filter is the following:
-
Type: refers at the stage when to apply the filter
-
Execution Order: describes the chain of execution
-
Criteria: refers at the conditions in which a filter is triggered
-
Action: what to when the criteria is met
Filters do not communicate with each other directly, instead they share state through a RequestContext which is unique to each request.
The filters can be:
-
PRE Filters(“pre”) execute before routing to the origin. (eg request authentication, logging).
-
ROUTING Filters(“route”) handle routing the request to an origin. This is where the origin HTTP request is built and sent using Apache HttpClient or Netflix Ribbon.
-
POST Filters(“post”) execute after the request has been routed to the origin. (eg statistics and metrics)
-
ERROR Filters(“error”) execute when an error occurs during one of the other phases.
Zuul successfully works around CORS and the same-origin policy and allows us to customize and augment the HTTP request in transit.
For our example the routes would look like:
zuul: routes: item-command: path: /ic/** serviceId: item-command retryable: true item-view: path: /iv/** serviceId: item-view retryable: true bid-command: path: /bc/** serviceId: bid-command retryable: true bid-view: path: /bv/** serviceId: bid-view retryable: true
Our old gateway would transform into:
@SpringBootApplication @EnableZuulProxy @Import(GatewayConfiguration.class) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
We don’t need the feign clients anymore or hystrix fallback explicitly, as Zuul is build on top of these. The route serviceId is basically the name of the service registered in Eureka, and the fallbacks are transformed into ZuulFallbackProviders.
@Component public class GetLatestItemsFallback implements ZuulFallbackProvider { @Autowired private CacheService cacheService; private ObjectMapper objectMapper = new ObjectMapper(); @Override public String getRoute() { return "item-view"; } @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 { Collection latest = cacheService.getItemsFromCache(); return new ByteArrayInputStream(objectMapper.writeValueAsString(latest).getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }
As you can see getBody() method returns values from the cache. But, how do we save date into cache. Well the answer is filters. We build a filter that is called after the request is being sent(post filter) to the server and check the response. If we have 200 then we can save it into the cache.
@Component public class ItemCacheSaveFilter extends ZuulFilter { private CacheService cacheService; @Autowired private ItemCacheSaveFilter(CacheService cacheService) { this.cacheService = cacheService; } @Override public String filterType() { return "post"; } @Override public int filterOrder() { return 1; } @Override public boolean shouldFilter() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); return request.getMethod().equals("POST") && request.getRequestURI().contains("items"); } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); int responseStatus = ctx.getResponse().getStatus(); if (responseStatus == 200) { try { ServletInputStream request = ctx.getRequest().getInputStream(); String req = StreamUtils.copyToString(request, Charset.defaultCharset()); ItemRequest itemRequest = new ObjectMapper().readValue(req, ItemRequest.class); cacheService.saveItemToCache(itemRequest.getItemCode(), itemRequest); } catch (IOException e) { //log something - out of scope } } return null; } }
Cool. We have now replaced our old gateway with Zuul. One more thing is to set the hystrix fallback options. We can do this easily in out yaml file:
hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 5000
A nice addition to Zuul would be to be able to create routes based on HTTP method type, but it seems there is a issue raised for this. In this example the routes were added statically, but there is the possibility to add them dynamically. You can add them from an external groovy script or you can create your custom filter where you can use feign clients.
2 thoughts on “Zuul – Edge Server”