Search code examples
javakotlinquarkusresteasyreactive

Using reactive @ServerRequestFilter with RestClient in Quarkus


I want to add some custom auth headers to my request, when the dev-mode is activated. This should make the developement easier for me, since I don't have to add them on my self manually.

What I have found is the method annotation ServerRequestFilter which intercepts an ongoing request before hitting the controller level. The annotated function provides ContainerRequestContext argument where I can add my headers easily.

Now the problem: To know the custom headers value, I have to make an external rest call (I'm using the RestClient for that) to an API. Since I'm using the reactive library I get the exception org.jboss.resteasy.reactive.common.core.BlockingNotAllowedException because of this call.

Since I'm using Kotlin, i tried to mark my method as suspendable. But this lead to a build error Method 'preCall$suspendImpl of class 'com.kaz.request.service.RequestInterceptor' cannot be static as it is annotated with '@org.jboss.resteasy.reactive.server.ServerRequestFilter'

Here my code:

@ApplicationScoped
class RequestInterceptor @Inject constructor(val authService: AuthService) {

    @ServerRequestFilter
    suspend fun preCall(requestContext: ContainerRequestContext) {
        validateIdTokenHeader(requestContext)
    }

    private suspend fun validateIdTokenHeader(requestContext: ContainerRequestContext) {
        val isTokenHeaderAbsent = requestContext.getHeaderString(Headers.X_ID_TOKEN) == null
        val isDevModeEnabled = LaunchMode.current() == LaunchMode.DEVELOPMENT

        if (isTokenHeaderAbsent && !isDevModeEnabled) {
            throw AuthExceptions.ID_TOKEN_IS_ABSENT
        } else {
            injectDevUserIdToken(requestContext)
        }
    }

    private suspend fun injectDevUserIdToken(requestContext: ContainerRequestContext) {
        // This call is making the request and block
        val idToken = authService.getIdToken("someHash")
        requestContext.headers.putSingle(Headers.X_ID_TOKEN, idToken)
    }
}

What I also tried to do is using Mutiny in my RestClient. I subscribed to the Uni and added the header when the result was available. But then I had the problem, that my controller/endpoint was already called before the header could be added to the request.

An endpoint could look like this:

    @Path("hello/{id}")
    @GET
    suspend fun get(
        //This header is what I want to add automatically, when dev mode is active.
        @RestHeader(Headers.X_ID_TOKEN) idToken: UserIdToken,
        @RestPath id: UUID,
        @Context httpRequest: HttpServerRequest
    ): Response {
        val request = RequestDTO(id, excludeFields, idToken.userId)
        val envelope = service.findById(request)

        return ResponseBuilder.build(httpRequest, envelope)
    }

Solution

  • Sometimes it makes sense not just to read the online documentation page but also the inline comments of the according class.

    ServerRequestFilter.java says:

    The return type of the method must be either be of type void, Response, RestResponse, Optional<Response>, Optional<RestResponse>, Uni<Void>, Uni<Response> or Uni<RestResponse>. 
    
    ...
    
    Uni<Void> should be used when filtering needs to perform a
    non-blocking operation but the filter cannot abort processing. Note
    that Uni<Void> can easily be produced using: Uni.createFrom().nullItem()
    

    So the entrypoint function has to return an Uni as well, not just the RestClient. Changing it like the following will be enough

    @ServerRequestFilter
    fun preCall(requestContext: ContainerRequestContext) : Uni<Void> {
        //Here I return a Uni<Void> back using Uni.createFrom().nullItem()
        return validateIdTokenHeader(requestContext)
    }