Microservices

Zuul – Edge Server

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.

SOURCE_CODE

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s