Search code examples
javajax-rsquarkusresteasy

Migration from RestEasy to RestEasyReactive with ResteasyContext and ContainerRequestFilter


I'm migrating old Quarkus project from RestEasy to ResteasyReactive and I have some difficulties migrating ResteasyContext.pushContext since there is no real 1:1 alternative in rest easy.

I'm using the ResteasyContext.pushContext in my ContainerRequestFilter to push some custom object to Context and later retrieve it using @Context.

Something like in this minimal example i provided.

Filter:

package org.acme.filter;

import org.acme.pojo.CustomHttpRequest;
import org.jboss.resteasy.core.ResteasyContext;

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.ext.Provider;
import java.time.LocalDateTime;
import java.util.Random;

@Provider
@ApplicationScoped
public class HttpRequestFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext requestContext) {
        CustomHttpRequest request = CustomHttpRequest.builder()
                .headers(requestContext.getHeaders())
                .dateTime(LocalDateTime.now())
                .text("Some random text for example " + new Random().nextInt(100))
                .build();
        ResteasyContext.pushContext(CustomHttpRequest.class, request);
    }
}

Custom object I want to push to context:

package org.acme.pojo;

import lombok.Builder;
import lombok.Getter;
import lombok.ToString;

import javax.ws.rs.core.MultivaluedMap;
import java.time.LocalDateTime;

@Getter
@Builder
@ToString
public class CustomHttpRequest {

    private String text;
    private LocalDateTime dateTime;
    private MultivaluedMap<String, String> headers;
    private boolean secured;
}

And the later read it from context in my rest endpoint:

package org.acme;

import org.acme.pojo.CustomHttpRequest;
import org.acme.pojo.ResponseData;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

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

    @GET
    @Path("{pathText}")
    @Produces(MediaType.APPLICATION_JSON)
    public ResponseData testContext(@Context CustomHttpRequest httpRequest,
                                    @PathParam("pathText") String queryText) {

       return ResponseData.builder()
                .queryText(queryText)
                .httpRequestText(httpRequest.getText())
                .secured(httpRequest.isSecured())
                .build();
    }
}

Here is the full example on GitHub: https://github.com/pkristja/resteasy_context/tree/main

I have found some alternatives that work with RestEasyReactive like using ContainerRequestContext and setting the data using setProperty.

Build file changes: Changed from implementation("io.quarkus:quarkus-resteasy-jackson") to implementation("io.quarkus:quarkus-resteasy-reactive-jackson")

Filter for setting oblect to context:

package org.acme.filter;

import org.acme.pojo.CustomHttpRequest;

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;
import java.time.LocalDateTime;
import java.util.Random;

@Provider
@ApplicationScoped
public class HttpRequestFilter implements ContainerRequestFilter {

    @Context
    ContainerRequestContext crContext;

    @Override
    public void filter(ContainerRequestContext requestContext) {
        CustomHttpRequest request = CustomHttpRequest.builder()
                .headers(requestContext.getHeaders())
                .dateTime(LocalDateTime.now())
                .text("Some random text for example " + new Random().nextInt(100))
                .build();

        crContext.setProperty("customHttpRequest", request);
    }
}

Retrieving object from context:

package org.acme;

import org.acme.pojo.CustomHttpRequest;
import org.acme.pojo.ResponseData;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

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

    @GET
    @Path("{pathText}")
    @Produces(MediaType.APPLICATION_JSON)
    public ResponseData testContext(@Context ContainerRequestContext crContext,
                                    @PathParam("pathText") String queryText) {

        CustomHttpRequest httpRequest = (CustomHttpRequest) crContext.getProperty("customHttpRequest");

       return ResponseData.builder()
                .queryText(queryText)
                .httpRequestText(httpRequest.getText())
                .secured(httpRequest.isSecured())
                .build();
    }
}

Is there any way to get same functionality in RestEasyReactive like you had in RestEasy using ResteasyContext.pushContext because it's really verbose and inefficient to retrieve each object from context and cast it because in my real example I have multiple custom objects pushed to context with ResteasyContext.pushContext.

Thank you!


Solution

  • When using RESTEasy Reactive, there is a far easier way of doing things like this: just use a CDI request scoped bean.

    Something like the following should be just fine:

    @Singleton
    public class CustomHttpRequestProducer {
    
        @RequestScoped
        @Unremovable
        public CustomHttpRequest produce(HttpHeaders headers) {
            return new CustomHttpRequest(headers.getRequestHeaders(), LocalDateTime.now(), "dummy");
        }
    }
    

    Then you would use it in your JAX-RS Resource as easily as:

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello(@Context CustomHttpRequest customHttpRequest) {
        return customHttpRequest.getText();
    }
    

    Note that @Unremovable is only needed if you use CustomHttpRequest as method parameter. If you however inject it as a field, @Unremovable is unnecessary.

    UPDATE

    After https://github.com/quarkusio/quarkus/pull/33793 becomes part of Quarkus (likely in 3.2) then @Unremovable will no longer be necessary even for the method parameter