Skip navigation
2018
This project is read only now. Read more.

JAX-RS 2.0 shipped with support for filtering requests and responses, which enabled a lot of great use-cases for delegating duplicated code away from resources and into filters that would do the same processing for every resource method.

 

Request filters work by overriding the ContainerRequestFilter.filter method and observe or modify the given context object, or abort the filter chain with a response if the filter already has a response and the other filters and resource method are not required. Simply returning from the filter method will cause the next filter to be called, or when we have run all the filters, it will invoke the resource method.

 

Response filters are very similar, but execute after the resource method has been executed and produced an entity, status code, headers, which the filter can then modify if required, or simply return to let the next filters run, or the response be sent to the client.

 

This is all great, but how does it work in an asynchronous ecosystem ? It doesn't, really, because even though JAX-RS supports suspending the request, it only supports it within the resource method: filters are too early (for request filters), or too late (for response filters).

 

In RESTEasy 3.5 and 4.0.0, we introduced the ability to suspend the request in filters. To do that, write your request or response filter as usual, but then cast your context object down to SuspendableContainerRequestContext or SuspendableContainerResponseContext (for response filters), and you can then:

 

- suspend the request with SuspendableContainerRequestContext.suspend()

- resume it normally with SuspendableContainerRequestContext.resume(), to proceed to the next filter or resource method

- resume it with a response with the standard ContainerRequestContext.abortWith(), to directly send that response to the client

- resume it with an exception with SuspendableContainerRequestContext.resume(Throwable)

 

Similarly, for response filters, you can:

 

- suspend the request with SuspendableContainerResponseContext.suspend()

- resume it normally with SuspendableContainerResponseContext.resume(), to proceed to the next filter or return the response to the client

- resume it with an exception with SuspendableContainerResponseContext.resume(Throwable)

 

Of course, the resume() methods only work after you've called suspend(), but otherwise you can call resume() right after suspend(), before returning from the filter, in which case the request will not even be made asynchronous, or you can call resume() later after you return from the method, or even from another thread entirely, in which case the request will become asynchronous.

 

The fact that filters may turn requests asynchronous has no impact at all on the rest of your code: non-asynchronous and asynchronous resource methods continue to work exactly as normal, regardless of the asynchronous status of the request, so you don't need to modify your code to accommodate for asynchronous filters.

 

Asynchronous rate-limiter example with Redis

Asynchronous filters are useful for plugging in anything that requires asynchrony, such as reactive security frameworks, async response processing or async caching. We will illustrate how to use asynchronous filters with a rate-limiter example.

 

For that, we will use RateLimitJ for Redis, which uses Redis to store rate-limiting information for your API. This is very useful for sharing rate-limit between your API server cluster, because you can store that info in a Redis cluster, and you don't have to worry about blocking clients while you're waiting for Redis to give you the info: you just become asynchronous until you have an answer from Redis.

 

We will first import the right Maven dependency for RateLimitJ:

 

<dependency>
  <groupId>es.moki.ratelimitj</groupId>
  <artifactId>ratelimitj-redis</artifactId>
  <version>0.4.2</version>
</dependency>

 

And let's not forget to install and run a local Redis cluster.

 

We will start by declaring a @RateLimit annotation that we can use on our resource methods or classes to indicate we want rate limiting:

 

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface RateLimit {
 /**
 * Number of {@link #unit()} that defines our sliding window.
 */
 int duration();
 /**
 * Unit used for the sliding window {@link #duration()}.
 */
 TimeUnit unit();
 /**
 * Maximum number of requests to allow during our sliding window.
 */
 int maxRequest();
}

 

And we have to declare a DynamicFeature that enables the filter on annotated methods and classes:

 

@Provider
public class RateLimitFeature implements DynamicFeature {

  private StatefulRedisConnection<string,string> connection;

  public RateLimitFeature(){
    // connect to the local Redis
    connection = RedisClient.create("redis://localhost").connect();
  } 

  public void configure(ResourceInfo resourceInfo, FeatureContext context) {
    // See if we're rate-limiting
    RateLimit limit = resourceInfo.getResourceMethod().getAnnotation(RateLimit.class);
    if(limit == null)
    limit = resourceInfo.getResourceClass().getAnnotation(RateLimit.class);
    if(limit != null) {
      // add the rate-limiting filter
      Set rules = new HashSet<>();
      rules.add(RequestLimitRule.of(limit.duration(), limit.unit(), limit.maxRequest()));
      
      context.register(new RateLimitFilter(new RedisSlidingWindowRequestRateLimiter(connection, rules)));
    }
  }
}

 

And this is how we implement our asynchronous filter:

 

public class RateLimitFilter implements ContainerRequestFilter {

  private RedisSlidingWindowRequestRateLimiter requestRateLimiter;

  public RateLimitFilter(RedisSlidingWindowRequestRateLimiter requestRateLimiter) {
    this.requestRateLimiter = requestRateLimiter;
  }

  public void filter(ContainerRequestContext requestContext) throws IOException {
    // Get access to the remote address
    HttpServletRequest servletRequestContext = ResteasyProviderFactory.getContextData(HttpServletRequest.class);

    // Suspend the request
    SuspendableContainerRequestContext suspendableRequestContext = (SuspendableContainerRequestContext) requestContext;
    suspendableRequestContext.suspend();

    // Query and increment by remote IP
    requestRateLimiter.overLimitAsync("ip:"+servletRequestContext.getRemoteAddr())
      .whenComplete((overlimit, error) -> {
        // Error case
        if(error != null)
          suspendableRequestContext.resume(error);
        // Over limit
        else if(overlimit)
          suspendableRequestContext.abortWith(Response.status(429).build());
        // Good to go!
        else
          suspendableRequestContext.resume();
      });
  }
}

Now all we have left to do is to implement a resource with rate-limiting:

 

@Path("/")
public class Resource {

  @Path("free")
  @GET
  public String free() {
    return "Hello Free World";
  }

  @RateLimit(duration = 10, unit = TimeUnit.SECONDS, maxRequest = 2)
  @Path("limited")
  @GET
  public String limited() {
    return "Hello Limited World";
  }
}

 

If you go to /free you will get an unlimited number of requests, while if you go to /limited you will get two requests allowed every 10 seconds. The rest of the time you will get an HTTP response of Too Many Requests (429).

 

If you have the need for asynchronous request or response filters, don't hesitate to give RESTEasy 3.5.1.Final or 4.0.0.Beta2 a try.

Tracing feature is a way for the users of the RESTEasy to understand what's going on internally in the container when a request is processed. It's different from the pure logging system or profiling feature, which provide more general information about the request/response info, etc.

 

 

On the other hand, the tracing feature provides more internal states of the JAX-RS container. For example, it could be able to show what filters a request is going through, or how long time a request is processed, etc.

 

Introduction to the design of tracing feature

Currently it doesn't have a standard or spec to define the tracing feature, so the tracing feature is tightly coupled with the concrete JAX-RS implementation itself.

 

 

The RESTEasy tracing feature supports three working modes:

 

- OFF
- ON_DEMAND
- ALL

 

ALL will enable the tracing feature. ON_DEMAND mode will give the control to client side: A client can send a tracing request via HTTP header and get the tracing info back from response headers. OFF mode will disable the tracing feature, and this is the default mode.

 

 

On the other aspect, the tracing feature has different tracing logging levels. Here is the list of the levels:

 

- SUMMARY
- TRACE
- VERBOSE

 

The SUMMARY level will emit some brief tracing information. The TRACE level will produce more detailed tracing information, and the VERBOSE level will generate extremely detailed tracing information. Because there are no specs on these tracing levels yet, so the level of the tracing info is currently defined by RESTEasy internally.

 

 

The tracing feature uses the JBoss Logging framework to output the trace log, so the jboss logger configuration controls the final output of the tracing info. If you enable the tracing feature but disable the jboss logger output, you still can't get the tracing info you want. In addition, the tracing logging levels are mapped to jboss logger log levels, which means the jboss logger controls is the actual place to control the tracing level threshold.

 

Examples of using tracing feature

 

By default, the tracing feature is turned off. If you want to enable the tracing feature, you can set the tracing mode and tracing level via the context-param parameters in your web project’s web.xml file. Here is an example of the setting:

 

  resteasy.server.tracing.type
  ALL


  resteasy.server.tracing.threshold
  SUMMARY

 

With above setting, we have enabled the server tracing, and put the tracing level to summary. If the underlying jboss logger’s output threadshold is higher than the tracing level setting, then the users can start to get the tracing info from server side and from response headers.

 

 

Here is some sample text of the server side tracing log:

 

16:06:40,794 INFO  [general] PRE_MATCH_SUMMARY PreMatchRequest
summary: 0 filters [ 0.03 ms]
16:06:40,797 DEBUG [general] REQUEST_FILTER Filter by
[io.weli.tracing.HttpMethodOverride @50d53072] [ 0.09 ms]
16:06:40,797 DEBUG [general] REQUEST_FILTER Filter by
[org.jboss.resteasy.core.AcceptHeaderByFileSuffixFilter @7e6bde58] [
0.03 ms]
16:06:40,798 INFO  [general] REQUEST_FILTER_SUMMARY Request summary: 2
filters [ 1.24 ms]
16:06:40,804 DEBUG [general] REQUEST_FILTER Filter by
[org.jboss.resteasy.plugins.providers.sse.SseEventSinkInterceptor
@27930ef8 #2147483647] [ 0.50 ms]
16:06:40,804 INFO  [general] REQUEST_FILTER_SUMMARY Request summary: 1
filters [ 0.93 ms]
16:06:40,813 INFO  [general] METHOD_INVOKE Resource [SINGLETON|class
io.weli.tracing.TracingConfigResource|io.weli.tracing.TracingConfigResource@7a1234bf]
method=[public java.lang.String
io.weli.tracing.TracingConfigResource.type(org.jboss.resteasy.spi.ResteasyDeployment)]
[10.67 ms]
16:06:40,813 DEBUG [general] DISPATCH_RESPONSE Response:
[org.jboss.resteasy.specimpl.BuiltResponse @28a0b6dc
<200/SUCCESSFUL|OK|java.lang.String @52a345f7>] [ ---- ms]
16:06:40,814 INFO  [general] FINISHED Response status: 200 [ ---- ms]
16:06:40,827 DEBUG [general] RESPONSE_FILTER Filter by
[org.jboss.resteasy.plugins.interceptors.MessageSanitizerContainerResponseFilter
@35bb27cd #4000] [ 0.02 ms]
16:06:40,832 INFO  [general] RESPONSE_FILTER_SUMMARY Response summary:
2782639920301360 filters [2782639925.90 ms]

 

 

 

For client side, here is some sample text in response header:

 

16:06:40,938 FINE  [headers] http-outgoing-0 << HTTP/1.1 200 OK
16:06:40,939 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-008: FINISHED    [ ---- /  3.16 ms |  ---- %]
Response status: 200
16:06:40,939 FINE  [headers] http-outgoing-0 << Date: Fri, 08 Jun 2018
08:06:40 GMT
16:06:40,939 FINE  [headers] http-outgoing-0 << Connection: keep-alive
16:06:40,939 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-000: PRE-MATCH   [ 0.00 /  0.00 ms |  0.12 %]
PreMatchRequest summary: 0 filters
16:06:40,939 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-001: REQ-FILTER  [ 0.01 /  0.33 ms |  0.23 %]
Filter by [io.weli.tracing.HttpMethodOverride @50d53072]
16:06:40,940 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-002: REQ-FILTER  [ 0.01 /  0.60 ms |  0.18 %]
Filter by [org.jboss.resteasy.core.AcceptHeaderByFileSuffixFilter
@7e6bde58]
16:06:40,940 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-003: REQ-FILTER  [ 0.54 /  0.85 ms | 17.03 %]
Request summary: 2 filters
16:06:40,940 FINE  [headers] http-outgoing-0 << Content-Type:
application/octet-stream
16:06:40,940 FINE  [headers] http-outgoing-0 << Content-Length: 48
16:06:40,940 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-004: REQ-FILTER  [ 0.01 /  1.47 ms |  0.28 %]
Filter by [org.jboss.resteasy.plugins.providers.sse.SseEventSinkInterceptor
@27930ef8 #2147483647]
16:06:40,940 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-005: REQ-FILTER  [ 0.26 /  1.71 ms |  8.34 %]
Request summary: 1 filters
16:06:40,940 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-006: INVOKE      [ 0.76 /  2.23 ms | 24.08 %]
Resource [SINGLETON|class
io.weli.tracing.TracingConfigResource|io.weli.tracing.TracingConfigResource@7a1234bf]
method=[public java.lang.String
io.weli.tracing.TracingConfigResource.logger(org.jboss.resteasy.spi.HttpRequest)
throws java.lang.NoSuchMethodException]
16:06:40,941 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-007: INVOKE      [ ---- /  2.96 ms |  ---- %]
Response: [org.jboss.resteasy.specimpl.BuiltResponse @7534fda1
<200/SUCCESSFUL|OK|java.lang.String @7bf31d77>]

 

 

Above is some sample tracing text. Currently the tracing feature is still under development, and more tracing info entries will be provided to the users. And the formal document will be provided as the development is going on.

 

If you’d like to have a look at the tracing feature in action, you can current see the simple test case in RESTEasy master branch:

 

https://github.com/resteasy/Resteasy/blob/master/testsuite/integration-tests/src/test/java/org/jboss/resteasy/test/tracing/BasicTracingTest.java

 

In above is the brief description of the RESTEasy document.

JAX-RS 1.0: The blocking origins

 

Originally, JAX-RS code use to be blocking and straightforward, like this resource for example:

 

@Path("/hello")
public class HelloResource {

    @Path("classic")
    @GET
    public String classic() {
        return "Hello World";
    }

}

 

Here, the resource method would return a value immediately. If it would take a long time producing that value, the server would make one of its thread block for the entire duration of the request until it was returned, but more often than not, producing values took negligible time.

 

I say negligible, but with the appearance of micro-services, this became much less true. Let's take for example a micro-service that runs parallel to our first service, and delegates to it:

 

@Path("/service")
public class ServiceResource {


    @Context 
    private UriInfo uriInfo;
    
    private URI getUri(Class klass, String method) {
        return uriInfo.getBaseUriBuilder().path(klass).path(klass, method).build();
    }
    
    @Path("classic")
    @GET
    public String classic() {
        Client client = ClientBuilder.newClient();
        try {
            URI uri = getUri(HelloResource.class, "classic");
            String entity = client.target(uri).request().get(String.class);
            return "Service got: "+entity;
        } finally {
            client.close();
        }
    }
}

 

Here we're using the JAX-RS 2.0 REST client to make a blocking HTTP call to our classic hello resource, and then we return it. This is already a heavier class of resource method: one where the thread delegated to it will have to wait for IO for the web-service call to complete, before the response is returned to the client. So it will be more thread-intensive, where those threads will mostly sit around waiting for IO, which is less than optimal.

 

JAX-RS 2.0: First taste of asynchrony

 

JAX-RS 2.0 saw the writing on the wall and added support for asynchronous processing: you could now tell the container to suspend the request until you were ready to resume it. You could then run your heavy computation in another thread until you were ready to resume it without blocking the resource method threads:

 

@Path("/hello")
public class HelloResource {


    @Path("suspended")
    @GET
    public void suspended(@Suspended AsyncResponse response) {
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            response.resume("Hello World (resumed)");
        }).start();
    }
}

Naturally this is a bit contrived, with sleep as an example of a heavy task, and creating a new unmanaged thread, but in reality this allowed you to launch your IO requests in an IO pool that would be more efficient and would not block the resource method threads, and once you were done with your IO you could then resume it:

 

@Path("/service")
public class ServiceResource {

//...

    @Path("suspended")
    @GET
    public void suspended(@Suspended AsyncResponse response) {
        Client client = ClientBuilder.newClient();
        URI uri = getUri(HelloResource.class, "suspended");
        client.target(uri).request().async().get(new InvocationCallback() {


            @Override
            public void completed(String entity) {
                response.resume("Service got: "+entity);
            }


            @Override
            public void failed(Throwable throwable) {
                response.resume(throwable);
            }
        });
        response.register((CompletionCallback)t -> client.close());
    }
}

 

In this last example, we used the asynchronous support of the JAX-RS client to offload the IO processing to an IO worker, and notify us when we have a result to send back to our client by resuming our suspended request. Notice how everything got more complex than the original blocking example? We had to suspend the request, register listeners for success and failure, and a custom finally block.

 

JAX-RS 2.1: Getting Reactive with CompletionStage

 

JAX-RS 2.1 shipped with awesome features, such as support for the Promise-like JDK CompletionStage for dealing with asynchronous code without the associated Callback Hell.

 

Callback Hell

 

It became simpler to write asynchronous code because you didn't have to manually suspend the request: your return type of CompletionStage indicated that the request should be suspended until that CompletionStage was completed (successfully or not):

 

@Path("/hello")
public class HelloResource {

    @Path("completion-stage")
    @GET
    public CompletionStage completionStage() {
        CompletableFuture future = new CompletableFuture<>();
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                future.completeExceptionally(e);
                return;
            }
            future.complete("Hello World (resumed from CompletionStage)");
        }).start();
        return future;
    }
}

 

In this (yet again) contrived example, you can see we no longer had to use the @Suspended annotation or deal with the request in any way: we simply returned a CompletableFuture (which implements CompletionStage), and the container is responsible for registering a listener on it. Meanwhile, when we're ready to send our return value, we complete the CompletableFuture and that will resume the request.

 

Naturally, again in this case the benefits shine more when it comes to composition, because JAX-RS 2.1 also provides a Reactive REST Client interface via the rx() method, which allows us to get a CompletionStage for the client request, which becomes much easier to compose and return now:

 

@Path("/service")
public class ServiceResource {

  // ...

    @Path("completion-stage")
    @GET
    public CompletionStage completionStage() {
        Client client = ClientBuilder.newClient();
        URI uri = getUri(HelloResource.class, "completionStage");
        CompletionStage future = client.target(uri).request().rx().get(String.class);
        return future
                .thenApply(entity -> "Service got: "+entity)
                .whenComplete((entity, throwable) -> client.close());
    }
}

 

Here we really see the benefits of using reactive: we can provide a pipeline of work, on which we can register operations (thenApply) and even our finally block (whenComplete) that we just pass along to the container without doing any manual plumbing.

 

New in Resteasy: pluggable support for reactive libraries

It may have sounded like we were at the end of the adventure and had solved every issue, but in reality CompletionStage is great, but not that rich: libraries such as Reactive Streams and RxJava 1 and 2 provide types that are much richer than CompletionStage, which allow you to compose many more operations in an easier manner.

 

JAX-RS 2.1 added support for pluggable reactive libraries in the Reactive REST Client, via the rx(RxInvoker) method, but did not ship with any default implementation of it short of the default CompletionStage. But annoyingly it is not symmetrical because there is no support for pluggable resource method return types! You can make JAX-RS 2.1 support RxJava in the Reactive REST Client, but you cannot return RxJava values from your resource methods.

 

The latest Resteasy snapshots fix both issues:

 

Those mean that you can now implement your hello resource using RxJava 2:

 

@Path("/hello")
public class HelloResource {
    
    @Path("rx")
    @GET
    public Single rx() {
        return Single.just("Hello World (resumed from rx)")
                .delay(1, TimeUnit.SECONDS);
    }
}

 

And our service similarly:

 

@Path("/service")
public class ServiceResource {

  // ...
    
    @Path("rx")
    @GET
    public Single rx() {
        Client client = ClientBuilder.newClient();
        URI uri = getUri(HelloResource.class, "rx");
        Single ret = client.target(uri).request().rx(SingleRxInvoker.class).get(String.class);
        return ret.map(entity -> "Service got: "+entity)
                .doFinally(() -> client.close());
    }   
}

 

As you can see, it's now much easier to build Reactive pipelines in Resteasy.

 

Note that although the pluggable support for reactive types landed in Resteasy 3.5.0.Final and 4.0.0.Beta2 releases, the RxInvoker implementations are only available in the latest 4.0.0-SNAPSHOT snapshots (they're likely being added to 3.6 branch soon, though).

Filter Blog

By date:
By tag: