Skip to content

Rest.li Filters

soojungha edited this page Dec 15, 2014 · 46 revisions

Contents

Introduction

On the server side, Rest.li provides a mechanism to intercept incoming requests and outgoing responses via filters. Rest.li filters are primarily of two kinds:

  1. Request filters
  2. Response filters

As the name suggests, request filters intercept incoming requests, and response filters intercept outgoing responses. Request filters can be used for a wide range of use cases, including request validation, admission control, and throttling.

Similarly, response filters can be used for a wide range of use cases, including augmentation of response body and encrypting sensitive information in the response payload.

Request Filters

Creating a concrete request filter is simple. All you need to do is implement the com.linkedin.restli.server.filter.RequestFilter interface. This interface has only one method -- onRequest, which is invoked before the actual resource is invoked. The implementation of the onRequest method is free to modify the incoming request and/or reject the incoming request by throwing an exception.

The request filter has access to the FilterRequestContext, an interface that abstracts information regarding the incoming request, including the request URI, projection mask, request query parameters, and request headers. Please see documentation of FilterRequestContext for more info.

Example Request Filter

import com.linkedin.restli.server.filter.RequestFilter;

public class RestliExampleRequestFilter implements RequestFilter
{
  @Override
  public void onRequest(FilterRequestContext requestContext)
  {
    log.debug(String.format("Received %s request for %s resource.", requestContext.getMethodType(),
                                     requestContext.getFilterResourceModel().getResourceName()));
  }
}

This simple filter print the request type and resource name for every incoming request.

Response Filters

Creating a concrete response filter is simple. All you need to do is implement the com.linkedin.restli.server.filter.ResponseFilter interface. This interface has only one method -- onResponse, which is invoked after the actual resource is invoked and before the response is handed to the underlying R2 stack. The implementation of the onResponse method can inspect and modify the outgoing response body, HTTP status, and headers. Moreover, the response filter can return an error to the client by throwing an exception in the implementation of the onResponse method.

The response filter has access to the FilterRequestContext and FilterResponseContext. The FilterResponseContext is an interface that abstracts information regarding the outgoing response, including the response HTTP status, response body, and response headers. Please see documentation of FilterResponseContext for more info. Rest.li guarantees that for a given request-response pair, the same instance of FilterRequestContext is made available to both the request filter and response filter.

Example Response Filter

import com.linkedin.restli.server.filter.ResponseFilter;

public class RestliExampleResponseFilter implements ResponseFilter
{
  @Override
  public void onResponse(FilterRequestContext requestContext, FilterResponseContext responseContext)
  {
    System.out.println(String.format("Responding to %s request for %s resource with status code %d.", requestContext.getMethodType(),
                                     requestContext.getFilterResourceModel().getResourceName(), responseContext.getHttpStatus().getCode()));
  }
}

This simple filter prints the HTTP response code along with request type and resource name for every outgoing response.

Verifying a Successful Response

It may be helpful for response filters to fail early, if needed, in cases of exceptions returned by the server. See further below on exception handling in response filters for more details.

In such cases, filter writers should verify that the response is not an error before examining the body of a response.

For example, if an exception is thrown by the server, the getEntityResponse() would return null. It would then behoove the filter writer to quickly fail fast if there is an error, otherwise a NullPointerException could arise later on in the response filter. There is one notable exception to this, and that is responses that don't contain data which would require a filter to perform any examination. Such methods include Actions and anything that returns UpdateResponse (Updates and Deletes). Since these responses only contain an HttpStatus and do not contain entities, lists, maps, etc in their responses, it's not possible to run into accidental access of null objects.

Here is an example on how to fail early if there is an error:

 RestLiResponseData responseData = filterResponseContext.getResponseData();
 //Fail fast if the response is an error response.
 if (responseData.isErrorResponse())
 {
   return;
 }

Note that this advice of failing fast due to errors in the response applies only if the filter wants to exclusively deal with successful, happy-path responses by the server. If the filter writer wants to intentionally deal with error responses, they can also use the isErrorResponse() behavior described above to specifically deal with errors.

Configuring Filters

Configuring filters is done via com.linkedin.restli.server.RestLiConfig. RestLiConfig provides a couple of methods to configure request and response filters. These are addRequestFilter, setRequestFilters, addResponseFilter, and setResponseFilters.

Example Java configuration:

    final RestLiConfig config = new RestLiConfig();
    ...
    ...
    // Add request and response filters to the config.
    config.addRequestFilter(new RestLiExampleRequestFilter());
    config.addResponseFilter(new RestLiExampleResponseFilter());
    ...
    ...

Example Spring Configuration:

<bean class="com.linkedin.restli.server.RestLiConfig">
	<property name=“requestFilters>
		<list>
		    <bean class="RestLiExampleRequestFilter”/>
		</list>
	</property>
	<property name=“responseFilters>
		<list>
		    <bean class=“RestLiExampleResponseFilter”/>
		</list>
	</property>
</bean>

When a Rest.li server is configured to use filters, the filters will be invoked for all incoming requests and outgoing responses of all resources hosted by that server. Therefore, when implementing filters, please keep in mind that filters are cross-cutting and should be applicable to all resources that are hosted by the given Rest.li server.

Filter Chaining

Rest.li supports chaining of request filters and response filters. When a Rest.li server is configured to use multiple request filters/response filters, the request/response filters are invoked as per the order specified in the RestLiConfig. All request filters are invoked before the resource implementation is invoked and all response filters are invoked after the resource implementation is invoked.

Approach 1 to chain three request and response filters.

    final RestLiConfig config = new RestLiConfig();
    config.addRequestFilter(new ReqFilterOne(), new ReqFilterTwo(), new ReqFilterThree());
    config.addResponseFilter(new RespFilterOne(), new RespFilterTwo(), new RespFilterThree());

Approach 2 to chain three request and response filters.

    final RestLiConfig config = new RestLiConfig();
    config.addRequestFilter(new ReqFilterOne());
    config.addRequestFilter(new ReqFilterTwo());
    config.addRequestFilter(new ReqFilterThree());
    config.addResponseFilter(new RespFilterOne());
    config.addResponseFilter(new RespFilterTwo());
    config.addResponseFilter(new RespFilterThree());

Approach 3 to chain three request and response filters.

    final RestLiConfig config = new RestLiConfig();
    config.addRequestFilter(Arrays.asList(new ReqFilterOne(), new ReqFilterTwo(), new ReqFilterThree()));
    config.addResponseFilter(Arrays.asList(new RespFilterOne(), new RespFilterTwo(), new RespFilterThree()));

Approach 4 to chain three request and response filters.

<bean class="com.linkedin.restli.server.RestLiConfig">
	<property name=“requestFilters>
		<list>
		    <bean class=“ReqFilterOne”/>
		    <bean class=“ReqFilterTwo”/>
		    <bean class="ReqFilterThree”/>
		</list>
	</property>
	<property name=“responseFilters>
		<list>
		    <bean class=“RespFilterOne”/>
		    <bean class=“RespFilterTwo”/>
		    <bean class=“RespFilterThree”/>
		</list>
	</property>
</bean>

Transferring State Between Filters

It is recommended that Rest.li filters be stateless. To facilitate transfer of state between filters, Rest.li provides a scratch pad in the form of a Java Map. This scratch pad can be accessed via the getFilterScratchpad method on the FilterRequestContext. See below for an example Rest.li filter that computes the request processing time and print it to standard out.

import com.linkedin.RestLi.server.filter.Filter;

public class RestLiExampleFilter implements Filter
{
  private static final String START_TIME = "StartTime";
  @Override
  public void onRequest(FilterRequestContext requestContext)
  {
    requestContext.getFilterScratchpad().put(START_TIME, System.nanoTime());
  }
  @Override
  public void onResponse(FilterRequestContext requestContext, FilterResponseContext responseContext)
  {
    final Long startTime = (Long) requestContext.getFilterScratchpad().get(START_TIME);
    System.out.println(String.format("Request processing time: %d us", (System.nanoTime() - startTime) / 1000));
  }
}

Exception Handling and Filter Chains

The manner in which exceptions are handled in request filters and response filters are different.

Request Filters

If an exception is thrown by a filter that's part of the request filter chain, further processing of the request is terminated and the error handling logic is invoked on the Rest.li callback. In other words, in order for the incoming request to reach the resource implementation, invocation of all request filters need to be successful.

Response Filters

Exception/error handling in the context of response filters is a little more involved than in the case of request filters. Response filters are applied to both successful responses as well as all types of errors.

Such errors can include:

  1. Exceptions thrown by the resource method, including runtime exceptions such as NullPointerException or RestLiServiceException.
  2. Exceptions generated by restli due to bugs in resource methods. These could include bugs such as nulls returned directly from the resource methods, or indirectly such as null values inside of returned objects (e.g a null element list inside of a CollectionResult).

Subsequently, response filters can transform a successful response from the resource to an error response and vice versa. In addition, a successful response from a filter earlier in the filter chain can be transformed into a error response and vice versa by filters that are subsequent in the filter chain.

The exception/error handling behavior of response filters is summarized as follows:

  1. If the last filter in the response filter chain throws an exception, an error response is returned to the client corresponding to this exception.

  2. If an exception is thrown by any filter except the last filter in the filter chain, the exception is included in the RestLiResponseData. The subsequent filters can 'handle/process' this exception. Note that if a filter wants to handle the exception, it needs to change the HTTP status code too.

  3. The response that is generated as a result of executing the response filter chain is the response that is forwarded to the client. Note that the response filter chain can transform a successful/error response from the resource to a error/successful response that's sent to the client.

When a response filter throws an exception, the HTTP status code will be automatically set according to this rule:

  • If the exception is a RestLiServiceException, the status will be taken from the exception.
  • If not, the status will be set to 500 (Internal Server Error).

So if a filter wants to pass a non-RestLiServiceException to the next filter, but also set a custom status code, it can wrap the exception in a RestLiServiceException by setting the exception as the cause.

Also, note that response headers will be reset every time a filter throws an exception.

Clone this wiki locally