Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Doc: Can't issue PATCH request using RestTemplate with SimpleClientHttpRequestFactory [SPR-15052] #19618

Closed
spring-issuemaster opened this issue Dec 25, 2016 · 7 comments

Comments

@spring-issuemaster
Copy link
Collaborator

commented Dec 25, 2016

Behrang Saeedzadeh opened SPR-15052 and commented

This test case fails with at least OpenJDK 1.8.0_101 under Ubuntu (4.8.0-30-generic):

    @Test
    @DirtiesContext
    public void testPatchMethod() {
        final HttpEntity<Object> request = new HttpEntity<>("<echo>Hello</echo>");

        restTemplate.exchange("/patchy/echo", PATCH, request, String.class);
    }

with the following exception:

org.springframework.web.client.ResourceAccessException: I/O error on PATCH request for "http://localhost:44661/persons/1": Invalid HTTP method: PATCH; nested exception is java.net.ProtocolException: Invalid HTTP method: PATCH

This is due to HttpURLConnection only allowing the following HTTP methods:

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

PayPal guys have implemented a workaround for their own API 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 PATCH}
     * 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>() {
							public Object run() throws NoSuchFieldException,
									IllegalAccessException {
								try {
									httpURLConnection.setRequestMethod(method);
									// Check whether we are running on a buggy
									// JRE
								} catch (final ProtocolException pe) {
									Class<?> connectionClass = httpURLConnection
											.getClass();
									Field delegateField = null;
									try {
										delegateField = connectionClass
												.getDeclaredField("delegate");
										delegateField.setAccessible(true);
										HttpURLConnection delegateConnection = (HttpURLConnection) delegateField
												.get(httpURLConnection);
										setRequestMethodViaJreBugWorkaround(
												delegateConnection, method);
									} catch (NoSuchFieldException e) {
										// Ignore for now, keep going
									} catch (IllegalArgumentException e) {
										throw new RuntimeException(e);
									} catch (IllegalAccessException e) {
										throw new RuntimeException(e);
									}
									try {
										Field methodField;
										while (connectionClass != null) {
											try {
												methodField = connectionClass
														.getDeclaredField("method");
											} catch (NoSuchFieldException e) {
												connectionClass = connectionClass
														.getSuperclass();
												continue;
											}
											methodField.setAccessible(true);
											methodField.set(httpURLConnection,
													method);
											break;
										}
									} catch (final Exception e) {
										throw new RuntimeException(e);
									}
								}
								return null;
							}
						});
            } catch (final PrivilegedActionException e) {
                final Throwable cause = e.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                } else {
                    throw new RuntimeException(cause);
                }
            }
        }
}

Another alternative is to use Apache Http-Components Client 4.2+ instead.

I have attached a test case reproducing the issue (see org.behrang.howto.bugreport.PatchyControllerTest#testPatchMethod).


Affects: 4.3.5

Attachments:

Issue Links:

  • #19910 Enhance RestTemplate to support PATCH out of the box

Referenced from: commits 0480981, 20aaa88

0 votes, 5 watchers

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 26, 2016

Juergen Hoeller commented

Is this specific to TestRestTemplate, as your title suggests? It seems that this is a general limitation of RestTemplate with SimpleClientHttpRequestFactory...

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 27, 2016

Behrang Saeedzadeh commented

Hi Juergen,

I haven't tested sending a PATCH request with RestTemplate. But looks like this is a known issue of the sun.com... implementation of the networking stack.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 27, 2016

Kazuki Shimizu commented

Is this specific to TestRestTemplate, as your title suggests?

No. I tried to send PATCH request using RestTemplate with SimpleClientHttpRequestFactory. As result, it is same result as follow:
I think this behavior is known limitation.

...
Caused by: java.net.ProtocolException: Invalid HTTP method: PATCH
	at java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:440)
	at sun.net.www.protocol.http.HttpURLConnection.setRequestMethod(HttpURLConnection.java:519)
	at org.springframework.http.client.SimpleClientHttpRequestFactory.prepareConnection(SimpleClientHttpRequestFactory.java:216)
...

If you want to use the PATCH method, you can use the RestTemplate with some OSS libraries as follow:

  • Apache HttpComponents HttpClient
  • OkHttp 3
  • OkHttp 2
  • Netty 4

I tried to send a PATCH request using HttpComponentsClientHttpRequestFactory, Netty4ClientHttpRequestFactory, OkHttp3ClientHttpRequestFactory and OkHttpClientHttpRequestFactory. These work fine.

Thanks.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 27, 2016

Kazuki Shimizu commented

As one alternative...
If you use the HiddenHttpMethodFilter on your application(or target web application), you can access using POST method with _method=patch parameter as follow:

RestTemplate restTemplate = new RestTemplate();
restTemplate.postForObject("http://localhost:8080/persons/1?_method=patch", requestBody, String.class);
@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 27, 2016

Juergen Hoeller commented

Alright, I'll turn this into a documentation task then.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 27, 2016

Kazuki Shimizu commented

+1

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 29, 2016

Behrang Saeedzadeh commented

Thanks all!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.