簡體   English   中英

HttpURLConnection 無效的 HTTP 方法:PATCH

[英]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正在使用其DelegateHttpsURLConnectionjava.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 標頭,也許您必須捕獲一些異常。

使用答案:

HttpURLConnection 無效的 HTTP 方法:PATCH

我創建了一個示例請求並像魅力一樣工作:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM