问题
When I try to use a non-standard HTTP Method like PATCH with URLConnection:
HttpURLConnection conn = (HttpURLConnection) new URL(\"http://example.com\").openConnection();
conn.setRequestMethod(\"PATCH\");
I get an exception:
java.net.ProtocolException: Invalid HTTP method: PATCH
at java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:440)
Using a higher level API like Jersey generates the same error. Is there a workaround to issue a PATCH HTTP request?
回答1:
Yes there is workaround for this. Use
X-HTTP-Method-Override
. This header can be used in a POST request to “fake” other HTTP methods. Simply set the value of the X-HTTP-Method-Override header to the HTTP method you would like to actually perform. So use following code.
conn.setRequestProperty("X-HTTP-Method-Override", "PATCH");
conn.setRequestMethod("POST");
回答2:
There are a lot of good answers, so here is mine (not work in jdk12):
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
public class SupportPatch {
public static void main(String... args) throws IOException {
allowMethods("PATCH");
HttpURLConnection conn = (HttpURLConnection) new URL("http://example.com").openConnection();
conn.setRequestMethod("PATCH");
}
private static void allowMethods(String... methods) {
try {
Field methodsField = HttpURLConnection.class.getDeclaredField("methods");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(methodsField, methodsField.getModifiers() & ~Modifier.FINAL);
methodsField.setAccessible(true);
String[] oldMethods = (String[]) methodsField.get(null);
Set<String> methodsSet = new LinkedHashSet<>(Arrays.asList(oldMethods));
methodsSet.addAll(Arrays.asList(methods));
String[] newMethods = methodsSet.toArray(new String[0]);
methodsField.set(null/*static field*/, newMethods);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}
It also uses reflection, but instead of hacking into every connection object we're hacking HttpURLConnection#methods static field which is used in the checks internally.
回答3:
There is a Won't Fix bug in OpenJDK for this: https://bugs.openjdk.java.net/browse/JDK-7016595
However, with Apache Http-Components Client 4.2+ this is possible. It has a custom networking implementation, thus using non-standard HTTP methods like PATCH is possible. It even has a HttpPatch class supporting the patch method.
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPatch httpPatch = new HttpPatch(new URI("http://example.com"));
CloseableHttpResponse response = httpClient.execute(httpPatch);
Maven Coordinates:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.2+</version>
</dependency>
回答4:
If the project is on Spring/Gradle; the following solution will workout.
For the build.gradle, add the following dependency;
compile('org.apache.httpcomponents:httpclient:4.5.2')
And define the following bean in your @SpringBootApplication class inside the com.company.project;
@Bean
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setReadTimeout(600000);
requestFactory.setConnectTimeout(600000);
return new RestTemplate(requestFactory);
}
This solutions worked for me.
回答5:
I had the same exception and wrote sockets solution (in groovy) but i traslate in the answer form to java for you:
String doInvalidHttpMethod(String method, String resource){
Socket s = new Socket(InetAddress.getByName("google.com"), 80);
PrintWriter pw = new PrintWriter(s.getOutputStream());
pw.println(method +" "+resource+" HTTP/1.1");
pw.println("User-Agent: my own");
pw.println("Host: google.com:80");
pw.println("Content-Type: */*");
pw.println("Accept: */*");
pw.println("");
pw.flush();
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String t = null;
String response = "";
while((t = br.readLine()) != null){
response += t;
}
br.close();
return response;
}
I think it work in java. You have to change the server and port number remember change the Host header too and Maybe you have to catch some exception.
Best Regards
回答6:
Reflection as described in this post and a related post does not work if you are using a HttpsURLConnection
on Oracle's JRE, becausesun.net.www.protocol.https.HttpsURLConnectionImpl
is using the method
field from the java.net.HttpURLConnection
of its DelegateHttpsURLConnection
!
So a complete working solution is:
private void setRequestMethod(final HttpURLConnection c, final String value) {
try {
final Object target;
if (c instanceof HttpsURLConnectionImpl) {
final Field delegate = HttpsURLConnectionImpl.class.getDeclaredField("delegate");
delegate.setAccessible(true);
target = delegate.get(c);
} else {
target = c;
}
final Field f = HttpURLConnection.class.getDeclaredField("method");
f.setAccessible(true);
f.set(target, value);
} catch (IllegalAccessException | NoSuchFieldException ex) {
throw new AssertionError(ex);
}
}
回答7:
Using the answer:
HttpURLConnection Invalid HTTP method: PATCH
I'm created a sample request and work like a charm:
public void request(String requestURL, String authorization, JsonObject json) {
try {
URL url = new URL(requestURL);
httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("POST");
httpConn.setRequestProperty("X-HTTP-Method-Override", "PATCH");
httpConn.setRequestProperty("Content-Type", "application/json");
httpConn.setRequestProperty("Authorization", authorization);
httpConn.setRequestProperty("charset", "utf-8");
DataOutputStream wr = new DataOutputStream(httpConn.getOutputStream());
wr.writeBytes(json.toString());
wr.flush();
wr.close();
httpConn.connect();
String response = finish();
if (response != null && !response.equals("")) {
created = true;
}
}
catch (Exception e) {
e.printStackTrace();
}
}
public String finish() throws IOException {
String response = "";
int status = httpConn.getResponseCode();
if (status == HttpURLConnection.HTTP_OK || status == HttpURLConnection.HTTP_CREATED) {
BufferedReader reader = new BufferedReader(new InputStreamReader(
httpConn.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
response += line;
}
reader.close();
httpConn.disconnect();
} else {
throw new IOException("Server returned non-OK status: " + status);
}
return response;
}
I hope it help you.
回答8:
We have faced the same problem with slightly different behavior. We were using apache cxf library for making the rest calls. For us, PATCH was working fine till we were talking to our fake services which were working over http. The moment we integrated with actual systems (which were over https) we started facing the same issue with following stack trace.
java.net.ProtocolException: Invalid HTTP method: PATCH at java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:428) ~[na:1.7.0_51] at sun.net.www.protocol.https.HttpsURLConnectionImpl.setRequestMethod(HttpsURLConnectionImpl.java:374) ~[na:1.7.0_51] at org.apache.cxf.transport.http.URLConnectionHTTPConduit.setupConnection(URLConnectionHTTPConduit.java:149) ~[cxf-rt-transports-http-3.1.14.jar:3.1.14]
Issue was happening in this line of code
connection.setRequestMethod(httpRequestMethod); in URLConnectionHTTPConduit class of cxf library
Now the real reason for the failure is that
java.net.HttpURLConnection contains a methods variable which looks like below
/* valid HTTP methods */
private static final String[] methods = {
"GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
};
And we can see that there is no PATCH method defined hence the error made sense. We tried lots of different thing and looked over stack overflow. The only reasonable answer was to use reflection to modify the methods variable to inject another value "PATCH". But somehow we were not convinced to use that as the solution was kind of hack and is too much work and might have impact as we had common library to make all connection and performing these REST calls.
But then we realized that cxf library itself is handling the exception and there is code written in the catch block to add the missing method using reflection.
try {
connection.setRequestMethod(httpRequestMethod);
} catch (java.net.ProtocolException ex) {
Object o = message.getContextualProperty(HTTPURL_CONNECTION_METHOD_REFLECTION);
boolean b = DEFAULT_USE_REFLECTION;
if (o != null) {
b = MessageUtils.isTrue(o);
}
if (b) {
try {
java.lang.reflect.Field f = ReflectionUtil.getDeclaredField(HttpURLConnection.class, "method");
if (connection instanceof HttpsURLConnection) {
try {
java.lang.reflect.Field f2 = ReflectionUtil.getDeclaredField(connection.getClass(),
"delegate");
Object c = ReflectionUtil.setAccessible(f2).get(connection);
if (c instanceof HttpURLConnection) {
ReflectionUtil.setAccessible(f).set(c, httpRequestMethod);
}
f2 = ReflectionUtil.getDeclaredField(c.getClass(), "httpsURLConnection");
HttpsURLConnection c2 = (HttpsURLConnection)ReflectionUtil.setAccessible(f2)
.get(c);
ReflectionUtil.setAccessible(f).set(c2, httpRequestMethod);
} catch (Throwable t) {
//ignore
logStackTrace(t);
}
}
ReflectionUtil.setAccessible(f).set(connection, httpRequestMethod);
message.put(HTTPURL_CONNECTION_METHOD_REFLECTION, true);
} catch (Throwable t) {
logStackTrace(t);
throw ex;
}
}
Now this gave us some hopes, so we spent some time in reading the code and found that if we provide a property for URLConnectionHTTPConduit.HTTPURL_CONNECTION_METHOD_REFLECTION then we can make cxf to execute the exception handler and our work is done as by default the variable will be assigned to false due to below code
DEFAULT_USE_REFLECTION =
Boolean.valueOf(SystemPropertyAction.getProperty(HTTPURL_CONNECTION_METHOD_REFLECTION, "false"));
So here is what we had to do to make this work
WebClient.getConfig(client).getRequestContext().put("use.httpurlconnection.method.reflection", true);
or
WebClient.getConfig(client).getRequestContext().put(HTTPURL_CONNECTION_METHOD_REFLECTION, true);
Where WebClient is from cxf library itself.
Hope this answer helps some one.
回答9:
Another dirty hack solution is reflexion:
private void setVerb(HttpURLConnection cn, String verb) throws IOException {
switch (verb) {
case "GET":
case "POST":
case "HEAD":
case "OPTIONS":
case "PUT":
case "DELETE":
case "TRACE":
cn.setRequestMethod(verb);
break;
default:
// set a dummy POST verb
cn.setRequestMethod("POST");
try {
// Change protected field called "method" of public class HttpURLConnection
setProtectedFieldValue(HttpURLConnection.class, "method", cn, verb);
} catch (Exception ex) {
throw new IOException(ex);
}
break;
}
}
public static <T> void setProtectedFieldValue(Class<T> clazz, String fieldName, T object, Object newValue) throws Exception {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, newValue);
}
回答10:
You can find a detailed solution that can work even if you don't have direct access to the HttpUrlConnection
(like when working with Jersey Client here: PATCH request using Jersey Client
回答11:
If your server is using ASP.NET Core, you can simply add the following code to specify the HTTP method using the header X-HTTP-Method-Override
, as described in the accepted answer.
app.Use((context, next) => {
var headers = context.Request.Headers["X-HTTP-Method-Override"];
if(headers.Count == 1) {
context.Request.Method = headers.First();
}
return next();
});
Simply add this code in Startup.Configure
before your call to app.UseMvc()
.
回答12:
In emulator of API 16 I received an exception: java.net.ProtocolException: Unknown method 'PATCH'; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE]
.
While an accepted answer works, I want to add one detail. In new APIs PATCH
works well, so in conjunction with https://github.com/OneDrive/onedrive-sdk-android/issues/16 you should write:
if (method.equals("PATCH") && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
httpConnection.setRequestProperty("X-HTTP-Method-Override", "PATCH");
httpConnection.setRequestMethod("POST");
} else {
httpConnection.setRequestMethod(method);
}
I changed JELLY_BEAN_MR2
to KITKAT
after testing in API 16, 19, 21.
回答13:
I got mine with Jersey client. The workaround was:
Client client = ClientBuilder.newClient();
client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);
回答14:
**CloseableHttpClient http = HttpClientBuilder.create().build(); HttpPatch updateRequest = new HttpPatch("URL"); updateRequest.setEntity(new StringEntity("inputjsonString", ContentType.APPLICATION_JSON)); updateRequest.setHeader("Bearer", "auth"); HttpResponse response = http.execute(updateRequest); JSONObject result = new JSONObject(IOUtils.toString(response.getEntity().getContent()));**
maven plugin
> <dependency>
> <groupId>org.apache.httpcomponents</groupId>
> <artifactId>httpclient</artifactId>
> <version>4.3.4</version>
> <!-- Exclude Commons Logging in favor of SLF4j -->
> <exclusions>
> <exclusion>
> <groupId>commons-logging</groupId>
> <artifactId>commons-logging</artifactId>
> </exclusion>
> </exclusions>
> </dependency>
use this really it would helps you
回答15:
For anyone using Spring restTemplate looking for a detailed answer.
You will face the problem if you are using SimpleClientHttpRequestFactory as your restTemplate's ClientHttpRequestFactory.
From java.net.HttpURLConnection:
/* valid HTTP methods */
private static final String[] methods = {
"GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
};
As PATCH is not a supported operation, this line of code from the same class will execute:
throw new ProtocolException("Invalid HTTP method: " + method);
I ended up using the same as what @hirosht suggested in his answer.
来源:https://stackoverflow.com/questions/25163131/httpurlconnection-invalid-http-method-patch