Details
-
Bug
-
Resolution: Unresolved
-
Major
-
None
-
5.0.3.Final, 6.0.1.Final
-
None
Description
I came across this issue when I was trying to use RestEasy with an application originally generated by swagger, though that does not seem to be relevant since I can reproduce with a simple test project (see step to reproduce below).
Consider the following code that tries to send a POST request with multipart content via the reactive RX API:
MultipartFormDataOutput multipart = new MultipartFormDataOutput(); multipart.addFormData("key1", "val1", MediaType.TEXT_PLAIN_TYPE); multipart.addFormData("key2", "val2", MediaType.TEXT_PLAIN_TYPE); multipart.addFormData("key3", "val3", MediaType.TEXT_PLAIN_TYPE); Entity<?> entity = Entity.entity(multipart, MediaType.MULTIPART_FORM_DATA_TYPE); WebTarget target = client.target("http://localhost:9095/customers"); target.request().rx().post(entity).handle((response, error) -> { // do something with the response... return null; }
That code works as long as we use a synchronous ClientHttpEngine such as ApacheHttpClient43Engine. But once we use an asynchronous AsyncClientHttpEngine such as ApacheHttpAsyncClient4Engine (we are using a custom engine implementation, but again, does not seem to be relevant, since I can reproduce with the default Apache engine), the code fails with the following error:
java.util.concurrent.CompletionException: org.jboss.resteasy.spi.LoggableFailure: RESTEASY003880: Unable to find contextual data of type: jakarta.ws.rs.ext.Providers at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:315) at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:320) at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1770) at org.jboss.resteasy.concurrent.ContextualExecutors.lambda$runnable$2(ContextualExecutors.java:316) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) at java.base/java.lang.Thread.run(Thread.java:833)
After a little big of debugging, the issue seems to be in the way the context is handled by org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.
When the engine does not implement HttpClientEngine, it uses the method executorSubmit which calls invoke which then pushes the provider via pushProvidersContext and then calls httpEngine.invoke(). Now when the engine reads the request body, the Providers are available.
But when the engine implements AsyncHttpClientEngine, it uses the method asyncSubmit
private <Q extends Future<T>, T> Q asyncSubmit( final Function<ResultExtractor<T>, Q> asyncHttpEngineSubmitFn, final ResultExtractor<T> extractor, final Function<T, Q> abortedFn, final Function<Exception, Q> exceptionFn) { final ClientRequestContextImpl requestContext = new ClientRequestContextImpl(this); try(CloseableContext ctx = pushProvidersContext()) { ClientResponse aborted = filterRequest(requestContext); if (aborted != null) { // spec requires that aborted response go through filter/interceptor chains. aborted = filterResponse(requestContext, aborted); T result = extractor.extractResult(aborted); return abortedFn.apply(result); } } catch (Exception ex) { return exceptionFn.apply(ex); } return asyncHttpEngineSubmitFn.apply(response -> { try(CloseableContext ctx = pushProvidersContext()) { return extractor.extractResult(filterResponse(requestContext, response)); } }); }
As you can see, that method pushes the providers when it applies the filters, and also before it evaluates the respones – but, and that seems to be the issue, not when it calls the engine via exceptionFn.apply. So when the engine reads the request body, it does not have the Providers, resulting in the above error.
The AsyncHttpClientEngine is passed an asyncInvocationExecutor, but that does not help, since that executor only copies the context to the new thead, but the current context does not have such a context to begin with.
You can also readily verify this during debugging via the following expression, which evaluates to null when the async engine is called.
org.jboss.resteasy.core.ResteasyContext.getContextData(jakarta.ws.rs.ext.Providers.class)
Is this a real bug or am I doing something wrong?