PATCH request using Jersey Client
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_WORKAROUND
property 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.
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
- use Apache Http Client which supports HttpPatch method
- 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/ ]
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.