HttpURLConnection无效的HTTP方法:PATCH


80

当我尝试使用非标准的HTTP方法(例如PATCH和URLConnection)时:

    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)

使用较高级别的API(例如Jersey)会产生相同的错误。是否存在发布PATCH HTTP请求的解决方法?

Answers:


48

是的,有解决方法。使用

X-HTTP-方法重写

。可以在POST请求中使用此标头来“伪造”其他HTTP方法。只需将X-HTTP-Method-Override标头的值设置为您要实际执行的HTTP方法。因此,请使用以下代码。

conn.setRequestProperty("X-HTTP-Method-Override", "PATCH");
conn.setRequestMethod("POST");

33
这只有在接收端支持的情况下才有效。它仍然沿行发送“ POST”。
约瑟夫·贾昆塔

3
如果接收方支持,那么(对我而言)这是最干净的方法。
Maxime T

4
使用HttpUrlConnection调用Firebase REST API时,此方法有效。
安德鲁·凯利

1
@DuanBressan,只要服务器支持其中一个或两个,协议就不会成为问题(尽管它只应接受与HTTPS的连接。)
Alexis Wilke

3
这不是有效的答案,因为它不能解决Java方面的问题。服务器必须允许您使用POST并且必须了解该X-HTTP-Method-Override领域。参见stackoverflow.com/a/46323891/3647724,以获得更好的实际解决方案
Feirell,

51

有很多好的答案,所以这是我的(不适用于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静态字段。


7
很好的答案,应该被接受,因为它可以解决实际问题,并且不建议依赖接收服务器的解决方法
Feirell 18-02-04

1
确实,这种解决方案是一种魅力,因为使用override属性的解决方案取决于服务器(在我的情况下不起作用)...
Amichai Ungar

这对Java 9仍然有效吗?还是模块限制了它
CLOVIS

2
在JDK12上进行了尝试,但是我得到了“ java.lang.NoSuchFieldException:修饰符”
Kin Cheung


33

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);

Maven坐标:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.2+</version>
</dependency>

16

如果项目在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);
 }

该解决方案为我工作。


对于Spring开发人员来说,这是最干净的解决方案:return new RestTemplate(new(HttpComponentsClientHttpRequestFactory));
约翰·特里布

谢谢@hirosht。在基于Spring的应用程序中解决问题的最简洁最简单的方法。
丹尼尔·卡马拉萨

4

我有同样的异常,并编写了套接字解决方案(以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标头,也许您必须捕获一些异常。

最好的祝福


1
无效。HTTP中的行终止符指定为\r\n,而不是任何println()提供的名称。
罗恩侯爵(Marquis of Lorne)

4

如果您在Oracle的JRE上使用,则本文和相关文章中所述的反射不起作用HttpsURLConnection,因为sun.net.www.protocol.https.HttpsURLConnectionImpl正在使用其!中的method字段!java.net.HttpURLConnectionDelegateHttpsURLConnection

因此,一个完整的可行解决方案是:

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);
    }
}

1
如果您已经在使用反射,为什么不通过在每个应用程序生命周期中重写一次java.net.HttpURLConnection#methods值来添加“ PATCH”方法?
okutane

好点子。但是,我的答案仅是为了显示建议的解决方案应如何工作,而不是为了展示其他解决方案
rmuller

@okutane,能否请您提供一点提示,我们如何重新编写方法?因为我很少看到有关代理的文章
Coder

@Dhamayanthi我已经发布了单独的答案。
okutane

能否请您分享链接?
编码器

2

使用答案:

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;
}

希望对您有帮助。


如果连接的服务器确实接受并解释了请求标头'X-HTTP-Method-Override',则您的解决方案有效。因此,不能在所有情况下都使用您的解决方案。
伊曼纽尔·德沃

2

对于使用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在他的回答中建议的内容。


1

另一个肮脏的骇客解决方案是反思:

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);
 }

适用于http连接,但不适用于https。sun.net.www.protocol.https.HttpsURLConnectionImpl类使用包含实际URL连接的“代理”字段。所以必须改为在那儿。
Per Cederberg


0

如果您的服务器使用的是ASP.NET Core,则可以简单地添加以下代码,以使用标头指定HTTP方法X-HTTP-Method-Override,如接受的答案所述

app.Use((context, next) => {
    var headers = context.Request.Headers["X-HTTP-Method-Override"];
    if(headers.Count == 1) {
        context.Request.Method = headers.First();
    }
    return next();
});

只需在Startup.Configure调用之前添加此代码app.UseMvc()


0

在API 16的仿真器中,我收到了一个异常: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_MR2KITKAT


0

我和泽西岛的客户见面了。解决方法是:

Client client = ClientBuilder.newClient();
client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);

0

我们面对的问题是行为稍有不同。我们正在使用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库本身。

希望这个答案对您有所帮助。


0
 **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>

用这个真的对你有帮助


0

在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();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.