1. Overview
In this tutorial, we're going to look at Netflix Zuul's post filter. Netflix Zuul is an edge service provider that sits between an API client and a plethora of microservices.
The post-filter runs before the final responses are sent to the API client. This gives us the opportunity to act on the raw response body and do things like logging and other data transformations we desire.
2. Dependencies
We're going to be working with Zuul in a Spring Cloud environment. So let's add the following to the dependency management section of our pom.xml:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> <version>2.2.1.RELEASE</version> </dependency> </dependencies>
The latest version of the Spring Cloud dependencies and spring-cloud-starter-netflix-zuul can be found on Maven Central.
3. Creating a Post Filter
A post filter is a regular class that extends the abstract class ZuulFilter and has a filter type of post:
public class ResponseLogFilter extends ZuulFilter { @Override public String filterType() { return POST_TYPE; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { return null; } }
Please note that we returned POST_TYPE in the filterType() method. This is what actually differentiates this filter from other types.
Another important method to take note of is the shouldFilter() method. We're returning true here since we want the filter to be run in the filter chain.
In a production-ready application, we may externalize this configuration for better flexibility.
Let's take a closer look at the run() which gets called whenever our filter is running.
4. Modifying the Response Body
As previously stated, Zuul sits between microservices and their clients. Consequently, it can access the response body and optionally modify it before passing it down.
For example, we can read the response body and log its content:
@Override public Object run() throws ZuulException { RequestContext context = RequestContext.getCurrentContext(); try (final InputStream responseDataStream = context.getResponseDataStream()) { if(responseDataStream == null) { logger.info("BODY: {}", ""); return null; } String responseData = CharStreams.toString(new InputStreamReader(responseDataStream, "UTF-8")); logger.info("BODY: {}", responseData); context.setResponseBody(responseData); } catch (Exception e) { throw new ZuulException(e, INTERNAL_SERVER_ERROR.value(), e.getMessage()); } return null; }
The snippet above shows the full implementation of the run() method in the ResponseLogFilter we created earlier. First, we obtained an instance of the RequestContext. And from that context, we were able to get the response data InputStream in a try with resources construct.
Note that the response input stream can be null, which is why we check for it. This can be due to service timeout or other unexpected exceptions on the microservice. In our case, we just log an empty response body when this occurs.
Going forward, we read the input stream into a String that we can then log.
Very importantly, we add the response body back to the context for processing using the context.setResponseBody(responseData). If we omit this step, we'll get an IOException along the following lines: java.io.IOException: Attempted read on a closed stream.
5. Conclusion
In conclusion, post filters in Zuul offer an opportunity for developers to do something with the service response before sending it to the client.
However, we have to be cautious not to expose sensitive information accidentally which can lead to a breach. Moreover, we should be wary of doing long-running tasks within our post filter as it can add considerably to the response time.
As usual, the source code is available over on GitHub.