Writing Custom Filters for Spring Cloud Zuul

Snippet of programming code in IDE
Published on

Everything you Need to Know About Writing Custom Filters for Spring Cloud Zuul

When it comes to building microservices, Spring Cloud is a powerful framework, providing a suite of tools for the development of robust and scalable applications. One of the key components of Spring Cloud is Zuul, which operates as an API Gateway that allows for dynamic routing, monitoring, resiliency, and security.

Zuul provides a great deal of flexibility and extensibility through the use of filters, which can be customized to manipulate requests and responses as they pass through the gateway. In this article, we'll explore how to write custom filters for Spring Cloud Zuul to address specific requirements you may encounter during development.

What are Filters in Zuul?

Filters are essentially functions that allow developers to perform pre-routing and post-routing processing of requests. These filters can be used to execute various tasks such as authentication, logging, traffic management, and more. There are four types of filters in Zuul:

  1. Pre filters: These are executed before routing the request. They are helpful in tasks such as authentication, logging, request manipulation, etc.
  2. Route filters: These filters handle the actual routing of the request to the target service.
  3. Post filters: Executed after the request has been routed and can be utilized for tasks like response manipulation, logging, etc.
  4. Error filters: These are invoked when an error occurs during the routing of the request.

Creating a Custom Filter in Zuul

Let’s look at an example of creating a custom filter in Zuul. We'll create a simple pre-filter that logs the incoming request information.

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;

public class LoggingFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return "pre";
    }
    
   @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        System.out.println("Request Method : " + ctx.getRequest().getMethod() + " Request URL : " + ctx.getRequest().getRequestURL().toString());
        return null;
    }
}

In this example, we create a class LoggingFilter that extends the ZuulFilter class. We override the methods filterType, filterOrder, shouldFilter, and run:

  • filterType: Indicates when the filter should be run. In this case, it's a pre-filter.
  • filterOrder: Defines the order of execution if multiple filters are present. Lower numbers are executed first.
  • shouldFilter: Contains the logic to determine if the filter should be executed.
  • run: Contains the behavior of the filter. In this case, we log the request information to the console.

Registering a Custom Filter in Zuul

Once we have created our custom filter, we need to register it with Zuul. We can do this by creating a bean for our filter class in a @Configuration class.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public LoggingFilter loggingFilter() {
        return new LoggingFilter();
    }
}

In this example, we create a @Configuration class FilterConfig and define a bean for our LoggingFilter class using the @Bean annotation.

Using the Custom Filter

Now that we have our custom filter and it's registered with Zuul, it will be automatically executed for every incoming request. You can start your Zuul server, and when a request passes through it, you'll see the logging output in the console.

Complex Example: Rate Limiting with a Custom Filter

Let's delve into a more complex example by implementing a rate-limiting filter using Zuul. Rate limiting is crucial for preventing abuse and securing the availability of services. We can achieve this by leveraging Zuul's RibbonRoutingFilter, which is responsible for forwarding requests to the target service.

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

@Component
public class RateLimitingFilter extends ZuulFilter {
    
    private final RouteLocator routeLocator;
    
    public RateLimitingFilter(RouteLocator routeLocator) {
        this.routeLocator = routeLocator;
    }
    
    @Override
    public String filterType() {
        return FilterConstants.ROUTE_TYPE;
    }
    
    @Override
    public int filterOrder() {
        return FilterConstants.SIMPLE_HOST_ROUTING_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        // Add logic to determine if rate limiting should be applied
        return true;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        Route route = routeLocator.getMatchingRoute(ctx.getRequest().getRequestURI());
        // Implement rate limiting logic based on the route information
        return null;
    }
}

In this example, we create a RateLimitingFilter that extends ZuulFilter. The filter type is set to ROUTE_TYPE, as it's responsible for the routing of requests. We utilize the RouteLocator to obtain the route information and apply rate-limiting logic based on it.

Testing Custom Filters

Testing custom filters is a critical aspect of development, ensuring they behave as expected and handle various scenarios. Spring provides the MockMvc class to facilitate testing of Zuul filters. Here's a simple example of testing the previously mentioned LoggingFilter:

import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

public class LoggingFilterTest {

    @Test
    public void testLoggingFilter() {
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController()).addFilters(new LoggingFilter()).build();
        // Perform request and assert the logged output
    }
}

In this test, we utilize MockMvc to perform requests and assert the logged output to validate the behavior of the LoggingFilter.

Bringing It All Together

Custom filters in Spring Cloud Zuul offer tremendous flexibility to extend the functionality of the API Gateway. Whether it's logging, authentication, rate limiting, or any other specific requirement, custom filters provide a means to tailor the behavior of the gateway to suit the needs of your application. By understanding and harnessing the power of custom filters, you can ensure the seamless operation and security of your microservices architecture. Start implementing your own custom filters to enhance the capabilities of Spring Cloud Zuul today!