Ahmet K
Ahmet K

Reputation: 813

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

Upvotes: 2

Views: 3385

Answers (3)

Ahmet K
Ahmet K

Reputation: 813

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

Upvotes: 1

ghostix
ghostix

Reputation: 59

Something in your implementation is blocking IO. So you can try to find it and wrap it with Uni or you can mark the method with @Blocking annotation, then the filters will be run on the worker thread.

Upvotes: 0

yazinnnn
yazinnnn

Reputation: 11

how about create a defer Uni and emit it on worker pool?

   val idToken = Uni.createFrom().item { authService.getIdToken("someHash") }
            .runSubscriptionOn(Infrastructure.getDefaultWorkerPool())

or call the blocking code with the default io coroutines dispatcher

withContext(Dispatchers.IO){
        authService.getIdToken("someHash")
}

and finally, maybe use Vertx.executeBlocking will be the simplest way

vertx.executeBlocking(Uni.createFrom().item { 
     authService.getIdToken("someHash")
})

Upvotes: 0

Related Questions