Coding Filters and Interceptors for your RESTFul services

Jakarta Restful Web Services includes an Interceptor API that allows developers to intercept request and response processing. This allows addressing some advanced concepts like authentication, caching, and compressing without polluting application code. Jakarta Restful Web Services define two broad categories to intercept requests and responses: Filters and Entity Interceptors.

  • Filters are used to modify or process incoming and outgoing request/response headers. They can be applied both on the server side and on the client side of the communication.

  • Interceptors deal with marshalling and unmarshalling the content of HTTP message bodies. With this purpose, they wrap around the execution of javax.ws.rs.ext.MessageBodyReader and javax.ws.rs.ext.MessageBodyWriter instances.

In this tutorial we will learn how to include Interceptors in your RESTFul Web services.

REST Server Side Filters

On the server side, you can define two types of filters:

  • ContainerRequestFilters: these kinds of filters are executed before your REST resource method is invoked.

  • ContainerResponseFilters: they execute after your REST resource method is invoked.

The following example shows how to dump the HTTP Header before the actual method invocation using a ContainerRequestFilter:

@Provider
public class LoggingFilter implements ContainerRequestFilter  {
    @Override
    public void filter(ContainerRequestContext crc) throws IOException {
        System.out.println(crc.getMethod() + " " + crc.getUriInfo().getAbsolutePath());
        for (String key : crc.getHeaders().keySet()) {
            System.out.println("[REST Logging] " +key + ": " + crc.getHeaders().get(key));
        }
    }
}

The first thing to note here is the @Provider annotation, which is used to automatically discover and register the Filter. Within the filter callback method, it is possible to go through the HTTP Headers before the actual method invocation.

Request filters can do much more than logging anyway. Actually, the ContainerRequestContext provides access to the javax.ws.rs.core.SecurityContext interface, containing security related information. In the following code, we are checking that the authenticated user is included in the specified role named “secureuser”:

@Provider
public class SecurityRequestFilter implements ContainerRequestFilter {
    @Override
    public void filter(ContainerRequestContext requestContext)
                    throws IOException {
        final SecurityContext securityContext =
                    requestContext.getSecurityContext();
        if (securityContext == null ||
                    !securityContext.isUserInRole("secureuser")) {
                          requestContext.abortWith(Response
                              .status(Response.Status.UNAUTHORIZED)
                              .entity("User unauthorized!").build());
        }
    }
}

The above examples are meant to be Post Matching request filters, which means that filters are be applied only after a suitable resource method has been selected to process the actual request.

Since Post Matching filters are invoked when a particular resource method has already been selected, such filters cannot influence the resource method matching process. 

To overcome this caveat, there is a possibility to mark a server request filter with the @PreMatching annotation. PreMatching filters are request filters that are executed before the request matching is started. The advantage of PreMatching request filters is that they have the possibility to influence which method will be matched.

A PreMatching request filter example follows here:

@PreMatching
@Provider
public class PreMatchingFilter implements ContainerRequestFilter {
    @Override
    public void filter(ContainerRequestContext requestContext)
                        throws IOException {

        if (requestContext.getMethod().equals("PUT")) {
            requestContext.setMethod("POST");
        }
    }
}

In the above example, we are changing the request method from PUT to POST to circumvent a limitation that may arise from firewalls configuration.

After the resource class method is executed, all ContainerResponseFilters will be triggered. These filters allow you to modify the outgoing response before it is marshalled and sent to the client.

In the following example, we are showing how to use a ContainerResponseFilter to set a Cache-Control header, which disables caching:

@Provider
public class CachingFilter implements  ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext crc, ContainerResponseContext crc1) throws IOException {
            if (crc.getMethod().equals("GET")) {
                    crc.getHeaders().add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0");

                    crc.getHeaders().add("Expires", "-1");
            }
    }
}

REST Client Side Filters

Filters can be applied as well on the client side in order to perform some custom actions with the client request or the response received from the target resource. There can be two types of Client filters that can be registered:

  • Client Request Filters: are executed before your HTTP request is sent to the server.
  • Client Response Filters: are executed after a response is received from the server, but before the response body is unmarshalled.

Client side filters, just like server filters, can be implemented in distinct classes or in a single class.

In terms of workflow, after the ClientRequestFilter is executed, the request (if not aborted) is then physically sent over to the server side. Once a response is received back from the server, the client response filters (ClientResponseFilter) are executed; that’s the last option for varying the returned response.

We will show here how to set up a Client filter, which is implementing both type of interfaces (ClientRequestFilter and ClientResponseFilter) in a single class. This filter aims to demonstrate how to modify the initial HTTP request by adding to it a Basic Authorization header containing an username and a password. The method filter(ClientRequestContext req) contains the logic for modifying the HTTP Request

Conversely, we are dumping the HTTP Headers using the ClientResponseFilter into the method filter(ClientRequestContext arg0, ClientResponseContext crc1).

The class implementation follows here:

public class ClientFilter implements ClientRequestFilter,ClientResponseFilter {
        private final String username;
        private final String password;

        public ClientFilter(String username, String password) {
                this.username = username;
                this.password = password;
        }
        public void filter(ClientRequestContext req) {
                String token = username + ":" + password;

                String base64Token = new String
                            (Base64.encodeBase64(token.getBytes(StandardCharsets.UTF_8)));
               req.getHeaders().add("Authorization", "Basic " + base64Token);
                System.out.println("Added to HTTP Request Authorization ["+base64Token+"]");
        }

        @Override
        public void filter(ClientRequestContext arg0, ClientResponseContext crc1)
                        throws IOException {

                for (String key : crc1.getHeaders().keySet()) {
                        System.out.println("Response Header: " +crc1.getHeaders().get(key));
                }

        }
}

One more difference from Client Filters and Server Filters is that Client Filter needs to be registered programmatically on the Client object in order to be triggered. In the following example, we are showing how to register a ClientFilter by calling the register method and passing as parameter your Client Filter implementation:

Client client = ClientBuilder.newClient();
client.register(new ClientFilter("user","password"));

Reader and Writer Interceptors

While filters are primarily intended to manipulate request and response parameters (like HTTP header) interceptors can be used to manipulate entities, by changing their input/output streams. For example, you might need to compress or encode the entity body of a client request. There are two kinds of interceptors:

  • Reader interceptors are used to manipulate the request entity stream on the server side and response entity stream on the client side. Reader Interceptors wrap around the execution of a MessageBodyReader.
  • Writer interceptors are used triggered when the entity is written to the “wire”. On the server side that happens when writing out a response entity and on the client side when writing request entity to be sent out to the server.

The following example shows how to use a Writer interceptor to enable GZIP compression on the entity body:

@Provider
public class ZipWriterInterceptor implements WriterInterceptor {

    @Override
    public void aroundWriteTo(WriterInterceptorContext context)
                    throws IOException, WebApplicationException {
        final OutputStream outputStream = context.getOutputStream();
        context.setOutputStream(new GZIPOutputStream(outputStream));
        context.proceed();
    }
}

Within this example, the Writer interceptor gets an output stream from the WriterInterceptorContext and sets a new one, which is wrapped by a GZIP output stream. Because of it, the entity bytes will be written to the GZIPOutputStream that will compress the data and write it to the original output stream.

One more thing that you might have noticed is the proceed() invocation which is used to invoke the next interceptor in the chain and it’s necessary otherwise the entity would not be written.

Interceptors are different from Filters since Filters are not able to directly invoke the next Filter in the chain as Interceptors do. 

Let’s see now an example of ReaderInterceptor that could work together with the Writer interceptor that we have already coded:

@Provider
public class ZipReaderInterceptor implements ReaderInterceptor {
        @Override
        public Object aroundReadFrom(ReaderInterceptorContext context)
                        throws IOException, WebApplicationException {
                 final InputStream inputStream = context.getInputStream();
                 context.setInputStream(new GZIPInputStream(inputStream));
                 return context.proceed();
        }
}

In this example, the ZipReaderInterceptor collects the input stream and wraps it with the GZIPInputStream. Because of that, further reads from the entity stream will cause the data to be decompressed by this stream.

As for the writer counterpart, the proceed() method internally calls the wrapped interceptor which must also return an entity. The proceed method invoked from the last interceptor in the chain calls message body reader which deserializes the entity end returns it.

You can check the Content length of your REST Service with ZIP interceptors by means of your Browser tools. For example, Chrome contains a Developer’s tools menu, which allows displaying several elements concerning the HTTP Request, such as the Network data which is going across the net:

References

This tutorial is an excerpt from the Practical Enterprise Development guide which is an in-depth guide on Jakarta EE Development / Microprofile applications.

The source code of the book is hosted on GitHub: https://github.com/fmarchioni/practical-enterprise-development/tree/master/code/jaxrs/ee-rest-filters

The Interceptors offered by RESTEasy 2.x

If you are running an older JBoss environment (JBoss EAP 6 / JBoss AS 7) then you have to rely on RESTEasy’s own Interceptors. RESTEasy 2.3 offers 3 types of interceptors:

  • MessageBodyReader and MessageBodyWriter Interceptors: Invoked around the HTTP Request body marshalling/unmarshalling;
  • Method execution interceptors: Invoked before(PreProcessInterceptor) and after (PostProcessInterceptor) the JAX-RS method execution;
  • Client side interceptors: Invoked before and after the client performed the request(if you call the ClientExecutionContext proceed() method it will perform the request). For more information about RESTEasy Client API, See this tutorial: JAX-RS client API tutorial

By default these interceptor will be invoked to all requests that comes to your JAX-RS service, but you can implement the interface AcceptedByMethod and read the service information(HTTP Method, method to be invoked, etc) to chose if your interceptor should be executed, or not.
That’s enough, that’s what we need to know for going on this article. If you want to know more about this, please check the RESTEasy documentation..

Adding Logging

To gather information from the client and information of the method which will be executed, we created a simple Server Interceptor which implements PreProcessInterceptor interface. This means that it will be called before the JAX-RS method execution.

package org.mastertheboss.resteasy.resources.interceptors;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;

@Provider
@ServerInterceptor
public class LoggingInterceptor implements PreProcessInterceptor {
    Logger logger = Logger.getLogger(LoggingInterceptor.class);

    @Context
    HttpServletRequest servletRequest;

    public ServerResponse preProcess(HttpRequest request,
            ResourceMethod resourceMethod) throws Failure,
            WebApplicationException {

        String methodName = resourceMethod.getMethod().getName();
        logger.info("Receiving request from: " + servletRequest.getRemoteAddr());
        logger.info("Attempt to invoke method \"" + methodName + "\"");
        if (methodName.equals("calculateAllBasicTrigonometricFunctions")) {
            logger.info("\tCalculate will be performed with parameters:");
            logger.info("\tAdjacent: "
                    + request.getFormParameters().get("adjacent"));
            logger.info("\tOpposite: "
                    + request.getFormParameters().get("opposite"));
            logger.info("\tHypotenusa: "
                    + request.getFormParameters().get("hypotenusa"));
        }
        if (methodName.equals("history")) {
            logger.info("Retrieving history...");
        }
        if (methodName.equals("clearAll")) {
            logger.info("User " + servletRequest.getRemoteUser()
                    + " is trying to clear the history...");
        }
        return null;
    }
}

The implemented method receives parameters which contains information about the request done and Java information about the JAX-RS method. Our LoggingInterceptor simple catch request and method information and then log it. Notice we can also inject resources into this interceptor, in this case we are injecting the ServletRequest. In the method implementation,

Adding RESTEasy Validation

As the basic trigonometric functions need all the right triangle information (adjacent, opposite and hypotenusa), we will perform validation before the JAX-RS method execution.

package org.mastertheboss.resteasy.resources.interceptors;

import static java.lang.Double.parseDouble;
import static java.lang.Math.pow;

import java.lang.reflect.Method;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.Headers;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.AcceptedByMethod;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;

@Provider
@ServerInterceptor
public class ValidationInterceptor implements PreProcessInterceptor,
        AcceptedByMethod {

    private double adjacent;
    private double opposite;
    private double hypotenusa;

    @SuppressWarnings("rawtypes")
    public boolean accept(Class c, Method m) {
        return m.getName().equals("calculateAllBasicTrigonometricFunctions");
    }

    public ServerResponse preProcess(HttpRequest request,
            ResourceMethod resourceMethod) throws Failure,
            WebApplicationException {

        // start server response as null and perform the validations, if it gets
        // some value, it will be a valid response and the interceptor will stop
        // the request
        ServerResponse response = null;
        try {
            adjacent = parseDouble(request.getFormParameters()
                    .get("adjacent").get(0));
            opposite = Double.parseDouble(request.getFormParameters()
                    .get("opposite").get(0));
            hypotenusa = Double.parseDouble(request.getFormParameters()
                    .get("hypotenusa").get(0));

            // Pythagoras
            boolean isValid = pow(adjacent, 2) + pow(opposite, 2) == pow(
                    hypotenusa, 2);
            // If it is not valid we will create a response with an appropriated
            // HTTP Code
            if (!isValid) {
                response = new ServerResponse("Not a valid right triangle",
                        400, new Headers<Object>());
            }
        } catch (Exception e) {
            response = new ServerResponse(
                    "Please verify the sent parameters, can't convert for use",
                    400, new Headers<Object>());
        }
        return response;
    }
}

Notice this interceptor is implementing an the interface AcceptedByMethod. We also have to implement the method accept, where we receive the target method information and we can decide whether  if this interceptor will be executed or not. Also  notice that according the data sent by the client we will create a response which can stop the execution. If we return null the execution will flow normally, if we return a Server Response, the execution will stop in this method and the request will not get into your JAX-RS method neither in order interceptors.

RESTEasy Security Checking

For some methods we want to check security before executing it. For example, to clean the history we want to assure that a specific user can do this. Of course we could do this using Jaas or some application server built in mechanism. In this case we will use an interceptor that will check the presence of a parameter in the request and validate it against a fixed String.

package org.mastertheboss.resteasy.resources.interceptors;

import java.lang.reflect.Method;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.Headers;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.AcceptedByMethod;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;

@Provider
@ServerInterceptor
public class SecurityInterceptor implements PreProcessInterceptor,
        AcceptedByMethod {

    @SuppressWarnings("rawtypes")
    public boolean accept(Class c, Method m) {
        return m.getName().equals("clearAll");
    }

    public ServerResponse preProcess(HttpRequest request, ResourceMethod method)
            throws Failure, WebApplicationException {
        ServerResponse response = null;

        String username = request.getFormParameters().get("username").get(0);
        // very simple security validation
        if (username == null || username.isEmpty()) {
            response = new ServerResponse(
                    "To access this method you need to inform an username",
                    401, new Headers<Object>());
        } else if (!"john".equals(username)) {
            response = new ServerResponse("User \"" + username
                    + "\" is not authorized to access this method.", 403,
                    new Headers<Object>());
        }
        return response;
    }
}

The logic of the interceptor is simple, we just create a server response according the content of the parameter “username”.

Running the code

You can download the attached a Maven project which contains the interceptors described in this article. You can build the code using the command mvn clean install and deploy it in JBoss AS 7. When you access the root context of this application it will be displayed a very simple HTML page aimed to test the JAX-RS methods we demonstrated.