Quarkus: Add @Patch support for RestClient

Created on 15 May 2020  路  19Comments  路  Source: quarkusio/quarkus

When I try to use a PATCH method with the RestClient in none-native mode, We obtain this error

javax.ws.rs.ProcessingException: RESTEASY004655: Unable to invoke request: java.net.ProtocolException: Invalid HTTP method: PATCH at org.jboss.resteasy.client.jaxrs.engines.URLConnectionEngine.invoke(URLConnectionEngine.java:63) at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.invoke(ClientInvocation.java:488) at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:149) at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:112) at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientProxy.invoke(ClientProxy.java:76) at com.sun.proxy.$Proxy83.updatePartial(Unknown Source) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.jboss.resteasy.microprofile.client.ProxyInvocationHandler.invoke(ProxyInvocationHandler.java:149) at com.sun.proxy.$Proxy86.updatePartial(Unknown Source) at com.example.ExampleRestConnector_27306872b9b9d6a5cde2e5a56fad18628bb23077_Synthetic_ClientProxy.updatePartial(TwinRestConnector_27306872b9b9d6a5cde2e5a56fad18628bb23077_Synthetic_ClientProxy.zig:233) at com.example.ExampleController.deleteNode(ExampleController.java:360) at com.example.ExampleController.update(ExampleController.java:223) at com.example.ExampleController_Subclass.update$$superaccessor11(ExampleController_Subclass.zig:172) at com.example.ExampleController_Subclass$$function$$11.apply(ExampleController_Subclass$$function$$11.zig:51) at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54) at io.quarkus.hibernate.validator.runtime.interceptor.AbstractMethodValidationInterceptor.validateMethodInvocation(AbstractMethodValidationInterceptor.java:69) at io.quarkus.hibernate.validator.runtime.interceptor.MethodValidationInterceptor.validateMethodInvocation(MethodValidationInterceptor.java:17) at io.quarkus.hibernate.validator.runtime.interceptor.MethodValidationInterceptor_Bean.intercept(MethodValidationInterceptor_Bean.zig:177) at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41) at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41) at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32) at com.example.ExampleController_Subclass.update(ExampleController_Subclass.zig:336) at com.example.ExampleController_ClientProxy.update(ExampleController_ClientProxy.zig:774) at com.exemple.ExampleService.update(ExampleService.java:151) at com.exemple.ExampleService_Subclass.update$$superaccessor7(ExampleService_Subclass.zig:208) at com.exemple.ExampleService_Subclass$$function$$7.apply(ExampleService_Subclass$$function$$7.zig:51) at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54) at io.quarkus.hibernate.validator.runtime.interceptor.AbstractMethodValidationInterceptor.validateMethodInvocation(AbstractMethodValidationInterceptor.java:69) at io.quarkus.hibernate.validator.runtime.jaxrs.JaxrsEndPointValidationInterceptor.validateMethodInvocation(JaxrsEndPointValidationInterceptor.java:32) at io.quarkus.hibernate.validator.runtime.jaxrs.JaxrsEndPointValidationInterceptor_Bean.intercept(JaxrsEndPointValidationInterceptor_Bean.zig:57) at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41) at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41) at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32) at com.exemple.ExampleService_Subclass.update(ExampleService_Subclass.zig:78) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:167) at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130) at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:621) at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:487) at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:437) at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:362) at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:439) at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:400) at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:374) at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:67) at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:488) at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:259) at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:160) at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:362) at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:163) at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:245) at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73) at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:123) at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.access$000(VertxRequestHandler.java:36) at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:87) at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35) at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2046) at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1578) at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452) at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29) at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29) at java.base/java.lang.Thread.run(Thread.java:834) at org.jboss.threads.JBossThread.run(JBossThread.java:479) Caused by: java.net.ProtocolException: Invalid HTTP method: PATCH at java.base/java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:487) at java.base/sun.net.www.protocol.http.HttpURLConnection.setRequestMethod(HttpURLConnection.java:570) at org.jboss.resteasy.client.jaxrs.engines.URLConnectionEngine.createConnection(URLConnectionEngine.java:176) at org.jboss.resteasy.client.jaxrs.engines.URLConnectionEngine.invoke(URLConnectionEngine.java:56) ... 68 common frames omitted 09:52:29.174 [executor-thread-1] ERROR o.jboss.resteasy.resteasy_jaxrs.i18n - RESTEASY002010: Failed to execute javax.ws.rs.WebApplicationException: HTTP 400 Bad Request at com.exemple.ExampleService.update(ExampleService.java:154) at com.exemple.ExampleService_Subclass.update$$superaccessor7(ExampleService_Subclass.zig:208) at com.exemple.ExampleService_Subclass$$function$$7.apply(ExampleService_Subclass$$function$$7.zig:51) at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54) at io.quarkus.hibernate.validator.runtime.interceptor.AbstractMethodValidationInterceptor.validateMethodInvocation(AbstractMethodValidationInterceptor.java:69) at io.quarkus.hibernate.validator.runtime.jaxrs.JaxrsEndPointValidationInterceptor.validateMethodInvocation(JaxrsEndPointValidationInterceptor.java:32) at io.quarkus.hibernate.validator.runtime.jaxrs.JaxrsEndPointValidationInterceptor_Bean.intercept(JaxrsEndPointValidationInterceptor_Bean.zig:57) at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41) at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41) at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32) at com.exemple.ExampleService_Subclass.update(ExampleService_Subclass.zig:78) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:167) at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130) at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:621) at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:487) at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:437) at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:362) at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:439) at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:400) at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:374) at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:67) at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:488) at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:259) at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:160) at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:362) at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:163) at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:245) at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73) at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:123) at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.access$000(VertxRequestHandler.java:36) at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:87) at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35) at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2046) at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1578) at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452) at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29) at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29) at java.base/java.lang.Thread.run(Thread.java:834) at org.jboss.threads.JBossThread.run(JBossThread.java:479)

I created a demo to reproduce the issue

https://github.com/survivant/quarkus-k8s-hello
HelloExampleTest.testPatch

@PATCH
@Produces("application/merge-patch+json")
@Consumes("application/merge-patch+json")
@Path("/nodes/{id}")
Node updatePartial(@PathParam("id") UUID id, JsonMergePatch patch);

here the code that is used in the other service that is in Spring

@PatchMapping(path = "/nodes/{id}", consumes = "application/merge-patch+json")
Node updatePartial(@PathVariable UUID id, @RequestBody JsonMergePatch patch);

I'm using Quarkus 1.4.2.Final

There is a workaround, is to use : quarkus.ssl.native=true

kinbug

All 19 comments

We need to figure out in which cases we default to the JDK http client instead of the Apache one.

We could also try to switch to the vert.x http client while we're at it.

From the brief look I took yesterday, it seems like we default to URLConnection when SSL isn't enabled.
The questions is, is there a good reason to stay with URLConnection?

Got PATCH request to work by setting quarkus.ssl.native=true

If you decide to stay with URLConnection for some reason I'd love to see a config property for selecting engine.

I don't see why URLConnection should be the default, so let's wait for input for @kenfinnigan and @asoldano

From the brief look I took yesterday, it seems like we default to URLConnection when SSL isn't enabled.
The questions is, is there a good reason to stay with URLConnection?

I think @kenfinnigan did this, I honestly don't remembers the reason.

If you and @kenfinnigan agree, I can take it up to use Apache HTTP Client as the default.

The reason this uses URLConnection is because the Apache HTTP Client still uses classes that rely on SSL when executing in native.

It's not possible to use the Apache HTTP Client in native without SSL enabled.

@kenfinnigan glad to have you back :).

Considering the cost of having SSL enabled is now reduced and most of the websites are now SSL, maybe we should just make the extension enable SSL? And we would not support having SSL disabled with the REST client. Sounds like an acceptable tradeoff considering the push to SSL.

Thanks.

Possibly, but it's a behavior change so we'd need to properly notify users.

In addition, it's probably worth ensuring that SSL on JVM is as simple as we can make it, as we'd need all users to begin doing it.

Would the preferred solution be to remove the use of URLConnection from RESTEasy REST Client? So that there is no way to not use Apache HTTP Client as the engine. It's then just down to Quarkus having SSL enabled all the time.

Would the preferred solution be to remove the use of URLConnection from RESTEasy REST Client? So that there is no way to not use Apache HTTP Client as the engine. It's then just down to Quarkus having SSL enabled all the time.

@kenfinnigan , can you please clarify this? I don't understand how an option in RESTEasy (the URLConnection engine), which is not the default behaviour but is used by Quarkus, could be considered a problem here.

Btw, I haven't looked into the details of Apache HTTP Client, but is there a way to patch it so that you can use native without SSL?

@asoldano The MP REST Client uses the URLConnection engine if SSL is disabled, that code is in RESTEasy.

The reason that was done is that classes within the Apache HTTP Client require SSL classes even when SSL is disabled. There was no way to fix it without modifying the internal Apache HTTP Client code as far as I recall

@kenfinnigan thanks, for some reasons I was remembering this was in Quarkus, while actually it's in resteasy 4.x because of Quarkus (you did this here https://github.com/resteasy/Resteasy/commit/a371154b9444d3689ea9f8ff35f25b7896dd6d2d and I merged it ;-) )
Does this mean you're proposing to revert the part about client engine in that PR?

Regarding Apache HTTP Client, my point was really if you thought about a contribution to Apache HTTP Client for making the SSL components used only when really needed, but clearly that won't be anything we can have quickly released.

Reverting it is one option, so it goes back to using Apache HTTP Client by default in any situation. Not sure if that would be considered a "breaking change" for RESTEasy or not.

I didn't look into fixing it in the upstream, but it might be something we want to consider to make it work for any situation. Though it wouldn't be a quick fix

Reverting it is one option, so it goes back to using Apache HTTP Client by default in any situation. Not sure if that would be considered a "breaking change" for RESTEasy or not.

I think that would be doable in a minor

@geoand so I was having a look at that one and I wonder if we have it already covered now with Target_javax_net_ssl_SSLContext. But the Apache HTTP Client is not using the getDefault() method (see SSLContexts) if HttpAsyncClientBuilder#useSystemProperties() is false, which is the default. Maybe we should just turn that to true and everything will work fine.

Note that we produce a ExtensionSslNativeSupportBuildItem from the REST Client processor so you need to force-disable SSL to test this.

Yeah, I use the Apache HTTP Client in the extensions that use boostrap config and there I just turn on SSL, but I'll have a closer look.

Would it make sense to use the JDK 11 HttpClient as the underlying implementation? Then it would work if you run in JDK 11+ without any extra configuration.

Would it make sense to use the JDK 11 HttpClient as the underlying implementation? Then it would work if you run in JDK 11+ without any extra configuration.

As long as we support Java 8, we can't really do this :(

9773 should take care of the issue (but it requires https://github.com/resteasy/Resteasy/pull/2431 and a RESTEasy version bump)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

hantsy picture hantsy  路  3Comments

gsmet picture gsmet  路  3Comments

emmanuelbernard picture emmanuelbernard  路  3Comments

Rashmini picture Rashmini  路  3Comments

halhelal picture halhelal  路  3Comments