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

RESTEasy

4 Posts authored by: fromage

Now that we've seen RxJava support in RESTEasy, we're ready to build on more reactive applications to illustrate common reactive use-cases.

 

Let's create an application with several types of requests, that we collect statistics on, such as the number of times each request has been called. And let's store those statistics in a Redis instance. For that we will use the Vert.x Redis client because it supports RxJava out of the box.

 

A sample application

 

So we will be importing the following Maven modules:

 

<!-- For RESTEasy -->
<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-jaxrs</artifactId>
    <version>4.0.0-SNAPSHOT</version>
</dependency>
<!-- For RESTEasy's support of RxJava 2 -->
<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-rxjava2</artifactId>
    <version>4.0.0-SNAPSHOT</version>
</dependency>
<!-- For the Vert.x Redis client -->
<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-redis-client</artifactId>
    <version>3.5.3</version>
</dependency>
<!-- For the Vert.x RxJava 2 support -->
<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-rx-java2</artifactId>
    <version>3.5.3</version>
</dependency>

 

Now, in order to make sure I get a single Redis client for my application, I will have to make it injectable by RESTEasy with the @Context annotation, and there's no support for pluggable injection in JAX-RS so it's a little convoluted, but we can achieve that with the help of this custom Feature:

 

@Provider
public class RedisFeature implements Feature {

  private RedisClient redis;

  public RedisFeature(){
    // connect to the local Redis
    redis = RedisClient.create(Vertx.vertx());
  }

  public boolean configure(FeatureContext context) {
    // this is tied to the deployment, which is what we want for the redis client
    if(context.getConfiguration().getRuntimeType() == RuntimeType.CLIENT)
      return false;
    Dispatcher dispatcher = ResteasyProviderFactory.getContextData(Dispatcher.class);
    if(dispatcher == null) {
      // this can happen, but it means we're not able to find a deployment
      return false;
    }
    dispatcher.getDefaultContextObjects().put(RedisClient.class, redis);
    return true;
  }
}

 

We can now write our three requests that collect usage statistics (they inject the RedisClient):

 

@Path("/")
public class Resource {
  @Context
  private RedisClient redis;

  @Path("req1")
  @GET
  public Single<String> req1() {
    return redis.rxIncr("req1.count").map(count -> "Req1 count: "+count);
  }

  @Path("req2")
  @GET
  public Single<String> req2() {
    return redis.rxIncr("req2.count").map(count -> "Req2 count: "+count);
  }

  @Path("req3")
  @GET
  public Single<String> req3() {
    return redis.rxIncr("req3.count").map(count -> "Req3 count: "+count);
  }
}

 

As you can see we count usage in the Redis keys req1.count, req2.count and req3.count.

 

Now, if we want to display them, we have to get all three values, which either means a lot of nesting with RxJava, or (better) using the Single.zip operator:

 

@GET
public Single<String> info(){
  return Single.zip(redis.rxGet("req1.count"), redis.rxGet("req2.count"), redis.rxGet("req3.count"),
    (req1, req2, req3) -> "Request 1: "+req1+"\nRequest 2: "+req2+"\nRequest 2: "+req3);  
}

 

As you can see, with RxJava, getting several values is a little more verbose than if we were doing it in blocking style. In fact, in real applications it is very common to start most requests with actions that depend on resolving a few asynchronous values. They can be waiting for database results, querying caches, or even obtaining permission lists, but eventually, lots of your requests will start with a Single.zip call to get the values you need in your request. That's annoying, and when they are often the same values, that's just plain boilerplate.

 

The solution

 

What if RESTEasy could take all those async values that you need, and resolve them before it called your resource method? This is called asynchronous injection, and the latest RESTEasy does just that.

 

The async values we want to be resolved are originally of type Single<String>, so their resolved value is of type String. In order to get async injection, we annotate our injected resolved value with @Context, and RESTEasy will look up a ContextInjector that is declared to resolve values to String. In our case, we declare our ContextInjector to resolve values from type Single<String> to String, but any async type is supported, thanks to the existing support for pluggable async types.

 

Once we've declared our ContextInjector, RESTEasy will call it to get the Single<String> that provides the String we want asynchronously injected, and will wait for the async value to be resolved, and only then proceed to inject it in your resource method. This way, when you start your resource method, you already have all your async values resolved!

 

For our example, we're going to describe our redis queries with the @RedisQuery annotation:

 

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisQuery {
  String value();
}

 

Now we can write our new resource method that wants these redis queries injected:

 

@Path("inject")
@GET
public String infoInjection(@Context @RedisQuery("req1.count") String req1,
    @Context @RedisQuery("req2.count") String req2,
    @Context @RedisQuery("req3.count") String req3){
  return "Request 1: "+req1+"\nRequest 2: "+req2+"\nRequest 2: "+req3;
}

 

And all we have to do for that async injection to work is to declare our ContextInjector:

 

@Provider
public class RedisInjector implements ContextInjector<Single<String>, String> {

  @Override
  public Single<String> resolve(Class<?> rawType, Type genericType, Annotation[] annotations) {
    RedisClient redisClient = ResteasyProviderFactory.getContextData(RedisClient.class);
    for (Annotation annotation : annotations) {
      if(annotation.annotationType() == RedisQuery.class) {
        String query = ((RedisQuery) annotation).value();
         // let's inject !
        return redisClient.rxGet(query);
      }
    }
    // not for us: try other injectors
    return null;
  }
}

 

As you can see, we just have to declare that our ContextInjector can provide values of type String via an async type Single<String>, and check the annotations on the injection point to figure out what query to run.

 

As I mentioned previously, this is not limited to async values of type Single, because any async value type is supported via plugins, and in fact only CompletionStage is supported by default (Single being provided by the resteasy-rxjava2 module we're using).

 

Conclusion

We've removed yet another common cause of boilerplate: rejoice!

 

Async injection was added in the latest 4.0.0.Beta4 release (RESTEASY-1905). Go ahead and try it out while it's fresh!

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.

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).

You know DRY (Don't Repeat Yourself) and you wish every feature would be reviewed according to this criteria?

Good news everyone!

I've always found it annoying to repeat the name of the parameter variable in my JAX-RS resources:

 

@Path("param/{userId}")
public class ParamResource {
    
    @PathParam("userId")
    private String userId;
    
    @GET
    @Path("{orderId}")
    public String getOrder(@PathParam("orderId") String orderId) {
        return "User "+userId+" / Order "+orderId;
    }
}

 

I mean: surely with all the magic that JAX-RS does to help me, it could have figured out how to not make me duplicate, every single time, the parameter name I'm injecting? In JAX-RS there are six parameter types you can inject from the request: path, query, matrix, cookie, header and form (respectively @PathParam, @QueryParam, @MatrixParam, @CookieParam and @FormParam). These annotations can be placed on fields, JavaBean property setters, or resource method parameters. Using Java reflection, it's pretty trivial to extract the field or JavaBean property name, but until Java 8, it has always been problematic to extract the resource method parameter name. This is probably why duplicating the parameter name in the annotation has always been required.

 

With Java 8, the Java compiler can actually record method parameter names in the bytecode. OK, ok, you can argue that we've always had those parameter names in the bytecode since the first Java release, because they're present in debugging symbols, which most people enable. But those debugging symbols are slightly annoying to get at because they require more than just reflection. In any case, with Java 8 it's become really trivial, so we decided to add new parameter annotations that would allow you to avoid specifying the name if it's the same as the variable you're annotating. Don't worry: if you want to have a different parameter name, you can always specify it (it's just optional).

 

We've kept the annotation names the same, and of course, the JAX-RS annotations are still supported, but if you want, you can now import annotations from the org.jboss.resteasy.annotations.jaxrs package, and start removing all those duplicated parameter names from the annotations, leading to much DRY-er code like this:

 

import org.jboss.resteasy.annotations.jaxrs.*;


@Path("param/{userId}")
public class ParamResource {
    
    @PathParam
    private String userId;
    
    @GET
    @Path("{orderId}")
    public String getOrder(@PathParam String orderId) {
        return "User "+userId+" / Order "+orderId;
    }
}

 

Although this will work out of the box for fields and JavaBean property setters, if you want it to work for resource method parameters, don't forget to tell your Java compiler to add parameter names using one or more of the following methods:

 

  • Using javac: add the -parameters flag.
  • Using Maven: add the maven.compiler.parameters property:
<properties>
  <maven.compiler.parameters>true</maven.compiler.parameters>
</properties>
  • Using Eclipse: Go to Project > Properties > Java Compiler and select Store information about method parameters (usable via reflection).
  • Using IntelliJ IDEA: Go to Build, Execution, Deployment > Compiler > Java Compiler > Additional command line parameters and add the -parameters flag.

 

Note that you will need to use the 4.0.0-SNAPSHOT or 3.6.0-SNAPSHOT versions of RESTEasy to try this, because the feature hasn't been included in a release yet.

Filter Blog

By date:
By tag: