Understanding filters and interceptors in JAX-RS

 

Understanding filters and interceptors in JAX-RS

The default request-response model offered in the JAX-RS implementation fits well for many common use cases. However, at times, you may look at extending the default request-response model. For instance, you may need such extension capabilities while adding support for the custom authentication, customized caching of responses, encoding request content, and so on, without polluting the application code. JAX-RS allows you to do this by adding your own interceptors and filters for both the REST requests and responses, as appropriate.

Typically, filters are used for processing the request-response headers, whereas interceptors are concerned with the marshaling and unmarshaling of the HTTP message bodies. Filters and interceptors can be set on both the client and the server.

Modifying request and response parameters with JAX-RS filters

The JAX-RS APIs offer distinct filters for both the client and the server. 

Implementing server-side request message filters

You can build a request filter for the server by implementing the javax.ws.rs.container.ContainerRequestFilter interface. This filter intercepts the REST API calls and runs before the request invokes the REST resource. You will find this useful for performing authorization checks, auditing requests, or manipulating the request header parameters before invoking the REST API implementation.

The ContainerRequestFilters implementation falls into two categories on the basis of the stage in the request processing cycle that the filters are executed at:

  • Postmatching: These filters are executed after identifying the matching Java class resource method for processing the incoming HTTP request (the REST API call). The ContainerRequestFilters implementations, by default, are postmatching (unless you designate them as @PreMatching).
  • Prematching: These filters are executed before identifying the matching resource class for a REST API call. Prematching ContainerRequestFilters are designated with the @javax.ws.rs.container.PreMatching annotation.

Postmatching server-side request message filters

These postmatching filters are applied only after a matching Java class resource method has been identified to process the incoming request. As these filters are executed after the resource matching process, it is no longer possible to modify the request in order to influence the resource matching process.

Here is an example of a postmatching server-side request filter. AuthorizationRequestFilter, shown in the following example, ensures that only users with the ADMIN role can access the REST APIs used for configuring the system. The configuration APIs are identified in this example by checking whether the request URI path has the /config/part embedded in it:

//Other imports are omitted for brevity 
import java.io.IOException; 
import javax.ws.rs.container.ContainerRequestContext; 
import javax.ws.rs.container.ContainerRequestFilter; 
import javax.ws.rs.core.Response; 
import javax.ws.rs.core.SecurityContext; 
import javax.ws.rs.core.UriInfo; 
import javax.ws.rs.ext.Provider; 
  
@Provider  
public class AuthorizationRequestFilter implements  
    ContainerRequestFilter { 
  
    @Override 
    public void filter(ContainerRequestContext requestContext) 
        throws IOException { 
         
        //Get the URI for current request 
        UriInfo uriInfo = requestContext.getUriInfo(); 
        String uri = uriInfo.getRequestUri().toString(); 
        int index = uri.indexOf("/config/"); 
        boolean isSettingsService = (index != -1); 
        if (isSettingsService) { 
            SecurityContext securityContext 
                    = requestContext.getSecurityContext(); 
            if (securityContext == null 
                || !securityContext.isUserInRole("ADMIN")) { 
 
                requestContext.abortWith(Response 
                    .status(Response.Status.UNAUTHORIZED) 
                     .entity("Unauthorized access.") 
                     .build()); 
            } 
        } 
    } 
} 

The @javax.ws.rs.ext.Provider annotation on the implementation of a filter or an interceptor makes it discoverable by the JAX-RS runtime. You do not need to do any extra configurations for integrating the filters or interceptors.

Prematching server-side request message filters

You can designate ContainerRequestFilters as the prematching filter by annotating with the @javax.ws.rs.container.PreMatching annotation. The prematching filters are applied before the JAX-RS runtime identifies the matching Java class resource method. As this filter is executed before executing the resource matching process, you can use this type of filter to modify the HTTP request contents. Apparently, this can be used for influencing the request matching process, if required.

The following code snippet illustrates how you can use a prematching filter to modify the method type in the incoming HTTP request. This example modifies the method type from POST to PUT. As this is executed before identifying the Java resource method, the change in method type will influence the identification of the Java class resource method for executing the call:

import java.io.IOException; 
import javax.ws.rs.container.ContainerRequestContext; 
import javax.ws.rs.container.ContainerRequestFilter; 
import javax.ws.rs.ext.Provider; 
import javax.ws.rs.container.PreMatching; 
 
@Provider 
@PreMatching 
public class JAXRSContainerPrematchingRequestFilter implements 
ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) throws IOException { //Modify the method type from POST to PUT if (requestContext.getMethod().equals("POST")) { requestContext.setMethod("PUT"); } } }

Implementing server-side response message filters


You can build a server-side response filter by implementing the javax.ws.rs.container.ContainerResponseFilter interface. This filter gets executed after the response is generated by the REST API. The response filters, in general, can be used to manipulate the response header parameters present in the response messages. The following example shows how you can use them to modify the response header.

When a REST web client is no longer on the same domain as the server that hosts the REST APIs, the REST response message header should have the Cross Origin Resource Sharing (CORS) header values set to the appropriate domain names, which are allowed to access the APIs. This example uses ContainerResponseFilter to modify the REST response headers to include the CORS header, thus making the REST APIs accessible from a different domain:

//Other imports are omitted for brevity 
import javax.ws.rs.container.ContainerRequestContext; 
import javax.ws.rs.container.ContainerResponseContext; 
import javax.ws.rs.container.ContainerResponseFilter; 
import javax.ws.rs.ext.Provider; 
import java.io.IOException;  
 
@Provider 
public class CORSFilter implements ContainerResponseFilter { 
 
    @Override 
    public void filter(ContainerRequestContext requestContext, 
        ContainerResponseContext cres) throws IOException { 
       //Specify CORS headers: * represents allow all values 
        cres.getHeaders().add("Access-Control-Allow-Origin", "*"); 
        cres.getHeaders().add("Access-Control-Allow-Headers",  
            "*"); 
        cres.getHeaders().add("Access-Control-Allow-Credentials",  
            "true"); 
        cres.getHeaders().add("Access-Control-Allow-Methods",  
            "GET, POST, PUT, DELETE, OPTIONS, HEAD"); 
        cres.getHeaders().add("Access-Control-Max-Age",  
            "1209600"); 
    } 
} 

Implementing client-side request message filters


JAX-RS lets you build a client-side request filter for REST API calls by implementing the javax.ws.rs.client.ClientRequestFilter interface. This filter is used for intercepting the REST API calls on the client itself.

A very common use case scenario where you may find these filters useful is for checking the accuracy of specific request parameters on the client itself. Even you can abort the call and return the error response object from these filters if the request parameter values are not properly specified.

The following code snippet illustrates how you can use ClientRequestFilter to ensure that all the requests carry the Client-Application header field, which you may use on the server for tracking all clients:




//Other imports are omitted for brevity 
import javax.ws.rs.HttpMethod; 
import javax.ws.rs.client.ClientRequestContext; 
import javax.ws.rs.client.ClientRequestFilter;  
 
@Provider 
public class JAXRSClientRequestFilter implements 
    ClientRequestFilter { 
  
    @Override 
    public void filter(ClientRequestContext requestContext) 
        throws IOException { 
        if(requestContext.getHeaders() 
           .get("Client-Application") == null) { 
                requestContext.abortWith( 
                    Response.status(Response.Status.BAD_REQUEST) 
                        .entity( 
                           "Client-Application header is 
required.") .build()); } } }

You can register the ClientRequestFilter interface to the client by calling the register() method on the javax.ws.rs.client.Client object. Note that the client object implements the javax.ws.rs.core.Configurable interface, and all the configurations that you see on Client are offered by the Configurable interface. The following code snippet shows how you can add JAXRSClientRequestFilterto the JAX-RS client implementation:



import javax.ws.rs.client.Client; 
import javax.ws.rs.client.ClientBuilder; 
 
Client client = ClientBuilder.newClient(); 
client.register(JSXRSClientRequestFilter.class);  


Implementing client-side response message filters


You can build a client-side response filter by implementing the javax.ws.rs.client.ClientResponseFilter interface. This filter is used for manipulating the response message returned by the REST APIs.

The following code snippet shows the use of JAXRSClientResponseFilter to check the response received from the server and then log the error (if any) for audit purposes:

//Other imports are omitted for brevity 
import javax.ws.rs.client.ClientRequestContext; 
import javax.ws.rs.client.ClientResponseContext; 
import javax.ws.rs.client.ClientResponseFilter; 
 
public class JAXRSClientResponseFilter implements  
   ClientResponseFilter { 
 
    @Override 
    public void filter(ClientRequestContext reqContext,  
        ClientResponseContext respContext) throws IOException { 
        if (respContext.getStatus() == 200) { 
            return; 
        }else{ 
            logError(respContext); 
        } 
    } 
 
    private void logError(ClientResponseContext respContext) { 
        //Code for logging error goes here. 
    } 
} 

You can register the ClientResponseFilter interface to the JAX RS client by calling the register()method on the javax.ws.rs.client.Client object. An example is given here:

import javax.ws.rs.client.Client; 
import javax.ws.rs.client.ClientBuilder; 
 
Client client = ClientBuilder.newClient(); 
client.register(JAXRSClientResponseFilter.class);  



Modifying request and response message bodies with JAX-RS interceptors

JAX-RS provides the request and response interceptors to manipulate entities or message bodies by intercepting the input and output streams, respectively. In this section, we will learn the request and response interceptors offered by JAX-RS. You can use the interceptors on both the client and the server.

Unlike JAX-RS filters, interceptors do not have separate contracts for the client and the server.

Implementing request message body interceptors

You can use the javax.ws.rs.ext.ReaderInterceptor interface to intercept and manipulate the incoming message body.

The following example illustrates how you can implement ReaderInterceptor to intercept the incoming message in order to unzip the zipped body content:

//Other imports are omitted for brevity 
import java.util.zip.GZIPInputStream; 
import javax.ws.rs.WebApplicationException; 
import javax.ws.rs.ext.Provider; 
import javax.ws.rs.ext.ReaderInterceptor; 
import javax.ws.rs.ext.ReaderInterceptorContext; 
 
@Provider 
public class JAXRSReaderInterceptor implements ReaderInterceptor { 
 
    @Override 
    public Object aroundReadFrom(ReaderInterceptorContext context) 
        throws IOException, WebApplicationException { 
        List<String> header = context.getHeaders() 
            .get("Content-Encoding"); 
        // decompress gzip stream only 
        if (header != null && header.contains("gzip")) { 
 
            InputStream originalInputStream =  
            context.getInputStream(); 
            context.setInputStream(new  
            GZIPInputStream(originalInputStream)); 
        } 
        return context.proceed(); 
 
    } 
} 
You follow the same interface contract (ReaderInterceptor) for building the read interceptors for client-side use as well. You can register the ReaderInterceptor interface to the JAX-RS client by calling the register() method on the javax.ws.rs.client.Client object.

Implementing response message body interceptors

You may use the javax.ws.rs.ext.JAXRSWriterInterceptor interface to intercept and manipulate the outgoing message body. The following example illustrates the use of WriterInterceptor to compress the response body content:

//Other imports are omitted for brevity 
import java.util.zip.GZIPOutputStream; 
import javax.ws.rs.WebApplicationException; 
import javax.ws.rs.core.MultivaluedMap; 
import javax.ws.rs.ext.Provider; 
import javax.ws.rs.ext.WriterInterceptor; 
import javax.ws.rs.ext.WriterInterceptorContext; 
 
@Provider 
public class JAXRSWriterInterceptor implements WriterInterceptor { 
    @Override 
    public void aroundWriteTo(WriterInterceptorContext context) 
       throws IOException,  
       WebApplicationException { 
       MultivaluedMap<String, Object> headers =  
           context.getHeaders(); 
       headers.add("Content-Encoding", "gzip"); 
       OutputStream outputStream =  
           context.getOutputStream(); 
       context.setOutputStream(new  
            GZIPOutputStream(outputStream)); 
       context.proceed(); 
    } 
} 
You will use the same interface contract for building the write interceptors for the client side use as well. You can register the WriterInterceptor interface to the JAX-RS client by calling the register() method on the javax.ws.rs.client.Client object.



Managing the order of execution for filters and interceptors

It is perfectly legal to have multiple filters or interceptors added for a REST client or server. In such a case, you might want to control the order in which they are executed at runtime. JAX-RS allows you to manage the order of execution by adding the @javax.annotation.Priority annotation on the filter or the interceptor class. The Priorityvalues should be non-negative. You can use standard constants defined in the javax.ws.rs.Priorities class to set the priorities. An example is given here:

@Provider 
@Priority(Priorities.HEADER_DECORATOR) 
public class JAXRSReaderInterceptor implements ReaderInterceptor { 
    //Interceptor implementation goes here 
} 
Interceptors and filters are sorted on the basis of their priority in ascending order. While processing a request, the request filter with the lowest priority will be executed first and followed by the next one in the sequence. While processing a response, filters are executed in reverse order. In other words, the response filter with the highest priority will be executed first, followed by the next highest one in the sequence.


Selectively applying filters and interceptors on REST resources by using @NameBinding

The @javax.ws.rs.NameBinding annotation is used for creating the name-binding annotation for filters and interceptors. Later, developers can selectively apply the name-binding annotation on the desired REST resource classes or methods.

For example, consider the following name-binding annotation, RequestLogger:

@NameBinding 
@Retention(RetentionPolicy.RUNTIME) 
@Target({ElementType.METHOD,ElementType.TYPE}) 
public @interface RequestLogger { 
} 

You can use the preceding name-binding annotation, RequestLogger, to decorate the desired filters and interceptors, as shown here:

//imports are removed for brevity 
@RequestLogger  
public class RequestLoggerFilter implements ContainerRequestFilter { 
    @Override 
    public void filter(ContainerRequestContext requestContext)  
        throws IOException { 
    //Implementation body is not shown in this  
    //example for brevity 
    } 
} 

Note that the preceding filter is not annotated with the @Provider annotation, and therefore it will not be applied globally on all resources.

As the last step, you can apply the name-binding annotation to the resource class or method to which the name-bound JAX-RS provider(s) should be bound to. In the following code snippet, we apply the @RequestLogger name-binding annotation to the findDepartment() method. At runtime, the framework will identify all filters and interceptors decorated with @RequestLogger and will apply all of them to this method:



Selectively applying filters and interceptors on REST resources by using @NameBinding

The @javax.ws.rs.NameBinding annotation is used for creating the name-binding annotation for filters and interceptors. Later, developers can selectively apply the name-binding annotation on the desired REST resource classes or methods.

For example, consider the following name-binding annotation, RequestLogger:

@NameBinding 
@Retention(RetentionPolicy.RUNTIME) 
@Target({ElementType.METHOD,ElementType.TYPE}) 
public @interface RequestLogger { 
} 

You can use the preceding name-binding annotation, RequestLogger, to decorate the desired filters and interceptors, as shown here:

//imports are removed for brevity 
@RequestLogger  
public class RequestLoggerFilter implements ContainerRequestFilter { 
    @Override 
    public void filter(ContainerRequestContext requestContext)  
        throws IOException { 
    //Implementation body is not shown in this  
    //example for brevity 
    } 
} 

Note that the preceding filter is not annotated with the @Provider annotation, and therefore it will not be applied globally on all resources.

As the last step, you can apply the name-binding annotation to the resource class or method to which the name-bound JAX-RS provider(s) should be bound to. In the following code snippet, we apply the @RequestLogger name-binding annotation to the findDepartment() method. At runtime, the framework will identify all filters and interceptors decorated with @RequestLogger and will apply all of them to this method:

//imports are omitted for brevity 
@Stateless 
@Path("hr") 
public class HRService { 
    @GET 
    @Path("departments/{id}") 
    @Produces(MediaType.APPLICATION_JSON) 
    @RequestLogger 
    public Department findDepartment(@PathParam("id")  Short id) { 
 
        findDepartmentsEntity(id); 
        return department; 
    } 
} 


The @NameBinding annotation really helps you to selectively apply JAX-RS providers to REST resources. However, you need to choose the resource methods to which the name-binding annotation should be applied while developing the API. What if you want to apply the filters or interceptors to the REST resources dynamically on the basis of some business conditions? JAX-RS offers the javax.ws.rs.container.DynamicFeaturemetaprovider for such use cases. Let's discuss this feature in the next section.


Dynamically applying filters and interceptors on REST resources using DynamicFeature

The javax.ws.rs.container.DynamicFeature contract is used by the JAX-RS runtime to register providers, such as interceptors and filters, to a particular resource class or method during application deployment.

The following code snippet shows how you can build the DynamicFeature provider. This example applies RequestLoggerFilter to resource methods annotated with @RequestLogger.

import javax.ws.rs.container.DynamicFeature; 
import javax.ws.rs.container.ResourceInfo; 
import javax.ws.rs.core.FeatureContext; 
import javax.ws.rs.ext.Provider;  
 
@Provider 
public class DynamicFeatureRegister implements DynamicFeature { 
    @Override 
    public void configure(ResourceInfo resourceInfo, 
        FeatureContext context) { 
        //This simple example adds RequestLoggerFilter to methods 
        //annotated with @ RequestLogger 
        if (resourceInfo.getResourceMethod().isAnnotationPresent 
            (RequestLogger.class)) { 
            context.register(RequestLoggerFilter.class); 
        } 
    } 
} 

The DynamicFeature interface is executed at deployment time for each resource method. You can register filters or interceptors on the desired resource class or method in the DynamicFeature implementation.












Comments

Popular posts from this blog

Understanding the JAX-RS resource life cycle

Generating a chunked output using Jersey APIs