[英]HttpURLConnection Invalid HTTP method: PATCH
當我嘗試使用帶有 URLConnection 的 PATCH 等非標准 HTTP 方法時:
HttpURLConnection conn = (HttpURLConnection) new URL("http://example.com").openConnection();
conn.setRequestMethod("PATCH");
我得到一個例外:
java.net.ProtocolException: Invalid HTTP method: PATCH
at java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:440)
使用像 Jersey 這樣的更高級別的 API 會產生相同的錯誤。 是否有解決方法來發出 PATCH HTTP 請求?
有很多好的答案,所以這是我的(不適用於 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);
}
}
}
它也使用反射,但我們不是侵入每個連接對象,而是侵入內部檢查中使用的 HttpURLConnection#methods 靜態字段。
是的,有解決方法。 利用
X-HTTP-Method-Override
. 此標頭可用於 POST 請求以“偽造”其他 HTTP 方法。 只需將 X-HTTP-Method-Override 標頭的值設置為您想要實際執行的 HTTP 方法。 所以使用下面的代碼。
conn.setRequestProperty("X-HTTP-Method-Override", "PATCH");
conn.setRequestMethod("POST");
OpenJDK 中有一個不會修復的錯誤: https ://bugs.openjdk.java.net/browse/JDK-7016595
但是,使用 Apache Http-Components Client 4.2+ 這是可能的。 它有一個自定義的網絡實現,因此可以使用所有標准的 HTTP 方法,如 PATCH。 它甚至有一個支持 patch 方法的 HttpPatch 類。
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPatch httpPatch = new HttpPatch(new URI("http://example.com"));
CloseableHttpResponse response = httpClient.execute(httpPatch);
馬文坐標:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.2+</version>
</dependency>
如果項目在 Spring/Gradle 上; 以下解決方案將鍛煉。
對於 build.gradle,添加以下依賴項;
compile('org.apache.httpcomponents:httpclient:4.5.2')
並在 com.company.project 內的 @SpringBootApplication 類中定義以下 bean;
@Bean
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setReadTimeout(600000);
requestFactory.setConnectTimeout(600000);
return new RestTemplate(requestFactory);
}
這個解決方案對我有用。
如果您在 Oracle 的 JRE 上使用HttpsURLConnection
,則本文和相關文章中描述的反射不起作用,因為sun.net.www.protocol.https.HttpsURLConnectionImpl
正在使用其DelegateHttpsURLConnection
的java.net.HttpURLConnection
中的method
字段!
所以一個完整的工作解決方案是:
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);
}
}
在 java 11+ 中,您可以使用 HttpRequest 類來做您想做的事情:
import java.net.http.HttpRequest;
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.method("PATCH", HttpRequest.BodyPublishers.ofString(message))
.header("Content-Type", "text/xml")
.build();
我有同樣的例外並編寫了套接字解決方案(在 Groovy 中),但我將答案形式翻譯為 Java:
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;
}
我認為它適用於Java。 您必須更改服務器和端口號,記住也要更改 Host 標頭,也許您必須捕獲一些異常。
使用答案:
我創建了一個示例請求並像魅力一樣工作:
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;
}
我希望它對你有幫助。
對於使用 Spring restTemplate 尋找詳細答案的任何人。
如果您使用 SimpleClientHttpRequestFactory 作為 restTemplate 的 ClientHttpRequestFactory,您將面臨問題。
來自 java.net.HttpURLConnection:
/* valid HTTP methods */
private static final String[] methods = {
"GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
};
由於 PATCH 不是受支持的操作,因此來自同一類的這行代碼將執行:
throw new ProtocolException("Invalid HTTP method: " + method);
我最終使用了與@hirosht 在他的回答中建議的相同。
另一個骯臟的黑客解決方案是反射:
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);
}
即使您沒有直接訪問HttpUrlConnection
的權限,您也可以找到一個可以工作的詳細解決方案(例如在此處使用 Jersey 客戶端時: PATCH request using Jersey Client
如果您的服務器使用 ASP.NET Core,您可以簡單地添加以下代碼以使用標頭X-HTTP-Method-Override
指定 HTTP 方法,如接受的答案中所述。
app.Use((context, next) => {
var headers = context.Request.Headers["X-HTTP-Method-Override"];
if(headers.Count == 1) {
context.Request.Method = headers.First();
}
return next();
});
只需在調用app.UseMvc()
之前在Startup.Configure
中添加此代碼。
在 API 16 的模擬器中,我收到了一個異常: java.net.ProtocolException: Unknown method 'PATCH'; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE]
java.net.ProtocolException: Unknown method 'PATCH'; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE]
。
雖然接受的答案有效,但我想添加一個細節。 在新的 API 中PATCH
運行良好,因此結合https://github.com/OneDrive/onedrive-sdk-android/issues/16您應該編寫:
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);
}
在 API 16、19、21 中測試后,我將JELLY_BEAN_MR2
更改為KITKAT
。
我和澤西客戶一起得到了我的。 解決方法是:
Client client = ClientBuilder.newClient();
client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);
我們遇到了同樣的問題,但行為略有不同。 我們使用 apache cxf 庫來進行其余的調用。 對我們來說,PATCH 工作正常,直到我們與通過 http 工作的虛假服務交談。 當我們與實際系統(通過 https)集成時,我們開始面臨與跟蹤堆棧跟蹤相同的問題。
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]
這行代碼中發生了問題
connection.setRequestMethod(httpRequestMethod); in URLConnectionHTTPConduit class of cxf library
現在失敗的真正原因是
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"
};
我們可以看到沒有定義 PATCH 方法,因此錯誤是有道理的。 我們嘗試了很多不同的東西並查看了堆棧溢出。 唯一合理的答案是使用反射來修改方法變量以注入另一個值“PATCH”。 但不知何故,我們不相信使用它,因為該解決方案是一種 hack,工作量太大,並且可能會產生影響,因為我們有通用庫來建立所有連接並執行這些 REST 調用。
但是后來我們意識到 cxf 庫本身正在處理異常,並且在 catch 塊中編寫了代碼來使用反射添加缺少的方法。
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;
}
}
現在這給了我們一些希望,所以我們花了一些時間閱讀代碼,發現如果我們為 URLConnectionHTTPConduit.HTTPURL_CONNECTION_METHOD_REFLECTION 提供一個屬性,那么我們可以讓 cxf 執行異常處理程序,我們的工作已經完成,默認情況下變量將為由於以下代碼,分配為 false
DEFAULT_USE_REFLECTION =
Boolean.valueOf(SystemPropertyAction.getProperty(HTTPURL_CONNECTION_METHOD_REFLECTION, "false"));
所以這是我們必須做的才能完成這項工作
WebClient.getConfig(client).getRequestContext().put("use.httpurlconnection.method.reflection", true);
或者
WebClient.getConfig(client).getRequestContext().put(HTTPURL_CONNECTION_METHOD_REFLECTION, true);
WebClient 來自 cxf 庫本身。
希望這個答案對某人有所幫助。
**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插件
> <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>
使用它真的會幫助你
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.