HttpURLConnection无效的HTTP方法:PATCH。

114

当我尝试使用非标准的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)

使用像Jersey这样的更高级别API会生成相同的错误。是否有解决方案来发出PATCH HTTP请求?

16个回答

70

有很多好的答案,这是我的一个(不适用于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静态字段,该字段在内部检查中使用。

8
非常好的回答,应该被接受,因为它解决了实际问题,而不是建议依赖于接收服务器的解决方法。 - Feirell
1
实际上,这个解决方案非常有效,因为使用覆盖属性的那个解决方案依赖于服务器(在我的情况下不起作用)... - Amichai Ungar
6
尝试在JDK12中使用该方法,但是遇到了"java.lang.NoSuchFieldException: modifiers"的错误。 - Kin Cheung
2
原因:https://dev59.com/-FMI5IYBdhLWcg3wg7up - Kin Cheung
1
这真的帮了我很多。我想你可以称之为“猴子补丁”? - Joshua Taylor
显示剩余4条评论

61

是的,有解决方法。使用

X-HTTP-Method-Override

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

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

48
仅当接收端支持时才有效。它仍然会向下发送“POST”请求。 - Joseph Jaquinta
3
如果接收方支持,那么(对我来说)这是最清晰明了的处理方式。 - Maxime T
5
使用 HttpUrlConnection 调用 Firebase REST API 时,该方法适用。 - Andrew Kelly
1
@DuanBressan 协议不应该是问题,只要服务器支持其中一个或两个(但它只应该接受 HTTPS 连接)。 - Alexis Wilke
8
这不是一个有效的答案,因为它没有解决Java端的问题。服务器必须允许您使用POST并且必须理解X-HTTP-Method-Override字段。请参见https://dev59.com/-l8e5IYBdhLWcg3w-OYn#46323891获取更好的实际修复方法。 - Feirell
显示剩余2条评论

42

在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>

3
为什么你称 Patch 是非标准的?它完全是标准的一部分... - trilogy
这应该是答案,但是非标准语句已被删除。 - Fulluphigh

29

如果项目使用Spring/Gradle,以下解决方案可以解决问题。

在build.gradle中,添加以下依赖项;

compile('org.apache.httpcomponents:httpclient:4.5.2')
在您的 @SpringBootApplication 类中,在 com.company.project 中定义以下 bean;
 @Bean
 public RestTemplate restTemplate() {
  HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
  requestFactory.setReadTimeout(600000);
  requestFactory.setConnectTimeout(600000);
  return new RestTemplate(requestFactory);
 }

这个解决方案对我很有效。


1
对于Spring开发人员来说,这是最简洁的解决方案:返回新的RestTemplate(new HttpComponentsClientHttpRequestFactory); - John Tribe
谢谢@hirosht。在基于Spring的应用程序中解决它的最干净和最简单的方法。 - Daniel Camarasa
我喜欢在我的Gradle依赖中添加“because”原因,以使它明显为什么需要它,考虑到没有导入语句可能会使用它: implementation('org.apache.httpcomponents:httpclient') { because('java.net.HttpURLConnection不支持PATCH') } - M. Justin
添加httpclient依赖就足以支持Springboot。 - kosgeinsky
这对我有效 - Mugeesh Husain

13

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

1
没错,这是官方替代已弃用的 java.net.HttpURLConnection 的类。 - Carsten

9

本文和相关问题中所述的反射在使用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);
    }
}

1
如果您已经在使用反射,为什么不通过重写 java.net.HttpURLConnection#methods 值来添加“PATCH”方法,从而实现应用程序生命周期内的一次性操作呢? - okutane
好的观点。然而,我的回答仅是为了展示建议的解决方案应该如何工作,而不是展示另一个解决方案。 - rmuller
@okutane,您能否提供一些提示,我们如何重写这些方法?因为我看到有几篇帖子中谈到了委托。 - Coder
@Dhamayanthi 我已经为此发布了单独的答案。 - okutane
请问您能分享链接吗? - Coder

3

我遇到了相同的异常,在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()提供的任何内容。 - user207421

3

使用这个答案:

HttpURLConnection Invalid HTTP method: 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;
}

我希望您能受益。

1
如果连接的服务器接受并解释请求头“X-HTTP-Method-Override”,则您的解决方案有效。因此,您的解决方案并非在所有情况下都可用。 - Emmanuel Devaux

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接