PATCH request using Jersey Client
Asked Answered
B

4

21

I want to execute a PATCH request supported by our server for testing using Jersey client. My code is as below, but I get com.sun.jersey.api.client.ClientHandlerException: java.net.ProtocolException: HTTP method PATCH doesn't support output exception. Can someone let me know whats wrong with the code below?

String complete_url = "http://localhost:8080/api/request";
String request = "[{\"op\":\"add\", \"path\":\"/name\", \"value\":\"Hello\"}]";
DefaultClientConfig config = new DefaultClientConfig();
    config.getProperties().put(URLConnectionClientHandler.PROPERTY_HTTP_URL_CONNECTION_SET_METHOD_WORKAROUND, true);
Client client = Client.create(config);
WebResource resource = client.resource(complete_url);
ClientResponse response = resource.header("Authorization", "Basic xyzabCDef")
 .type(new MediaType("application", "json-patch+json"))
 .method("PATCH", ClientResponse.class, request);

Here is the full exception,

com.sun.jersey.api.client.ClientHandlerException: java.net.ProtocolException: HTTP method PATCH doesn't support output
    at com.sun.jersey.client.urlconnection.URLConnectionClientHandler.handle(URLConnectionClientHandler.java:155)
    at com.sun.jersey.api.client.Client.handle(Client.java:652)
    at com.sun.jersey.api.client.WebResource.handle(WebResource.java:682)
    at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74)
    at com.sun.jersey.api.client.WebResource$Builder.method(WebResource.java:634)
    at com.acceptance.common.PatchTest.patch(PatchTest.java:42)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.net.ProtocolException: HTTP method PATCH doesn't support output
    at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1021)
    at com.sun.jersey.client.urlconnection.URLConnectionClientHandler$1$1.getOutputStream(URLConnectionClientHandler.java:238)
    at com.sun.jersey.api.client.CommittingOutputStream.commitStream(CommittingOutputStream.java:117)
    at com.sun.jersey.api.client.CommittingOutputStream.write(CommittingOutputStream.java:89)
    at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:202)
    at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:272)
    at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:276)
    at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:122)
    at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:212)
    at java.io.BufferedWriter.flush(BufferedWriter.java:236)
    at com.sun.jersey.core.util.ReaderWriter.writeToAsString(ReaderWriter.java:191)
    at com.sun.jersey.core.provider.AbstractMessageReaderWriterProvider.writeToAsString(AbstractMessageReaderWriterProvider.java:128)
    at com.sun.jersey.core.impl.provider.entity.StringProvider.writeTo(StringProvider.java:88)
    at com.sun.jersey.core.impl.provider.entity.StringProvider.writeTo(StringProvider.java:58)
    at com.sun.jersey.api.client.RequestWriter.writeRequestEntity(RequestWriter.java:300)
    at com.sun.jersey.client.urlconnection.URLConnectionClientHandler._invoke(URLConnectionClientHandler.java:217)
    at com.sun.jersey.client.urlconnection.URLConnectionClientHandler.handle(URLConnectionClientHandler.java:153)
Brumaire answered 12/3, 2014 at 14:51 Comment(1)
Any way to suppress the warnings or avoid them altogether? - https://mcmap.net/q/103149/-how-to-use-patch-method-with-jersey-invocation-builderTelekinesis
S
12

This is a bug in the current JDK implementation which has been fixed in the JDK8 implementation.Checkout this link for details https://bugs.openjdk.java.net/browse/JDK-7157360. There is a way to hack around this but Jersey team decided not to fix it https://github.com/eclipse-ee4j/jersey/issues/1639

2 solutions which I can think of

  1. use Apache Http Client which supports HttpPatch method
  2. use Jersey Client PostReplaceFilter but the Container code has to be modified and include X-HTTP-Method-Override header with value as PATCH while making a post request. Refer to http://zcox.wordpress.com/2009/06/17/override-the-http-request-method-in-jersey/ ]
Segno answered 14/3, 2014 at 17:35 Comment(1)
I migrated to Jersey 2.1 (2.35), adding this property on client creation works fine jerseyClient.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);, the call is: Response response = invocationBuilder.method("PATCH", Entity.entity(jsonRequestString, MediaType.APPLICATION_JSON_TYPE));Shiau
P
57

FYI - just in case anyone runs into this in Jersey 2, see the HttpUrlConnectorProvider documentation

and use the SET_METHOD_WORKAROUND property as follows:

Client jerseyClient = ClientBuilder.newClient()
        .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true)
        ... etc ...

Took me forever to find this - figured I could help short circuit the learning curve for others.

Peduncle answered 13/10, 2014 at 13:26 Comment(2)
it worked ;) anyway, PATCH is part of the HTTP standard since 2010 (rfc 5789)... quite weird that in 2017 still Jersey does not support it nativelyLookout
It solved the error but I am getting unauthorized 401 using a client that works properly for get calls that needs authorization as well, and thoughs?Ankeny
P
15

If you're using HttpsUrlConnection (note the 's') - then setting the HttpUrlConnectorProvider.SET_METHOD_WORKAROUND won't work. Keep reading for a detailed solution.

In my case, setting HttpUrlConnectorProvider.SET_METHOD_WORKAROUNDproperty caused a NoSuchFieldException since my HttpUrlConnection instance was actually of type: sun.net.www.protocol.https.HttpsURLConnectionImpl and it's super: javax.net.ssl.HttpsURLConnection (which inherits from HttpUrlConnection).

So when Jackson code try to get the method field from my connection instance super (instance of javax.net.ssl.HttpsURLConnection) here:

/**
 * Workaround for a bug in {@code HttpURLConnection.setRequestMethod(String)}
 * The implementation of Sun/Oracle is throwing a {@code ProtocolException}
 * when the method is other than the HTTP/1.1 default methods. So to use {@code PROPFIND}
 * and others, we must apply this workaround.
 *
 * See issue http://java.net/jira/browse/JERSEY-639
 */
private static void setRequestMethodViaJreBugWorkaround(final HttpURLConnection httpURLConnection, final String method) {
    try {
        httpURLConnection.setRequestMethod(method); // Check whether we are running on a buggy JRE
    } catch (final ProtocolException pe) {
        try {
            final Class<?> httpURLConnectionClass = httpURLConnection.getClass();
            AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                @Override
                public Object run() throws NoSuchFieldException, IllegalAccessException {
                    final Field methodField = httpURLConnectionClass.getSuperclass().getDeclaredField("method");
                    methodField.setAccessible(true);
                    methodField.set(httpURLConnection, method);
                    return null;
                }
            });
        } catch (final PrivilegedActionException e) {
            final Throwable cause = e.getCause();
            if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            } else {
                throw new RuntimeException(cause);
            }
        }
    }
}

We get a NoSuchFieldException stating that a field named method doesn't exist (since getDeclaredFields() brings all the fields, regardless of their accessibility but only for the current class, not any base classes that the current class might be inheriting from).

So I looked into Java's HttpUrlConnection code and saw that the allowed methods are specified by an private static String[]:

 /* Adding PATCH to the valid HTTP methods */
 private static final String[] methods = {
     "GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
 };

The solution was to change this methods array using reflection:

 try {
         Field methodsField = HttpURLConnection.class.getDeclaredField("methods");
         methodsField.setAccessible(true);
         // get the methods field modifiers
         Field modifiersField = Field.class.getDeclaredField("modifiers");
         // bypass the "private" modifier 
         modifiersField.setAccessible(true);

         // remove the "final" modifier
         modifiersField.setInt(methodsField, methodsField.getModifiers() & ~Modifier.FINAL);

         /* valid HTTP methods */
         String[] methods = {
                    "GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE", "PATCH"
         };
         // set the new methods - including patch
         methodsField.set(null, methods);

     } catch (SecurityException | IllegalArgumentException | IllegalAccessException | NoSuchFieldException e) {
      e.printStackTrace();
     }

Since methods field is static, changing its value works for any concrete instance that is extending HttpUrlConnection including HttpsUrlConnection.

Side note: I would prefer Java to add the PATCH method to the JDK or from Jackson to perform the field look up through the entire hierarchy in their workaround.

Anyway, I hope this solution will save you some time.

Pneumatometer answered 22/9, 2016 at 14:17 Comment(3)
Thank you so much for posting this! I wouldn't have figured it out in years, and I told a coworker I could implement the feature having seen others do it, but not realizing a different class in between would screw it all up for me!Voiture
Thanks for the above solution using the java reflection, It works for me.Liturgical
Very advanced answer!Helper
S
12

This is a bug in the current JDK implementation which has been fixed in the JDK8 implementation.Checkout this link for details https://bugs.openjdk.java.net/browse/JDK-7157360. There is a way to hack around this but Jersey team decided not to fix it https://github.com/eclipse-ee4j/jersey/issues/1639

2 solutions which I can think of

  1. use Apache Http Client which supports HttpPatch method
  2. use Jersey Client PostReplaceFilter but the Container code has to be modified and include X-HTTP-Method-Override header with value as PATCH while making a post request. Refer to http://zcox.wordpress.com/2009/06/17/override-the-http-request-method-in-jersey/ ]
Segno answered 14/3, 2014 at 17:35 Comment(1)
I migrated to Jersey 2.1 (2.35), adding this property on client creation works fine jerseyClient.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);, the call is: Response response = invocationBuilder.method("PATCH", Entity.entity(jsonRequestString, MediaType.APPLICATION_JSON_TYPE));Shiau
F
3

Easy answer would be :

Dependencies

<!-- https://mvnrepository.com/artifact/org.glassfish.jersey.core/jersey-client -->
 <dependency>
     <groupId>org.glassfish.jersey.core</groupId>
     <artifactId>jersey-client</artifactId>
     <version>2.27</version>
 </dependency>

Need to add HttpUrlConnectorProvider.SET_METHOD_WORKAROUND =true

Client client = ClientBuilder.newClient(clientConfig).property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);

Request

client.target("base_url").path("end_point").request().headers("your_headers")
       .build("PATCH", Entity.entity("body_you_want_to_pass", "content-type"))
       .invoke();
Flogging answered 23/12, 2019 at 6:43 Comment(3)
hmm, that gives me an error "Not supported Method". But the same "PATCH" method works from javascript clientIdeatum
Are you using same dependencies as i mentioned ?Flogging
Yes, I used the above dependencies with same version.Ideatum

© 2022 - 2024 — McMap. All rights reserved.