【问题标题】:HttpURLConnection Invalid HTTP method: PATCHHttpURLConnection 无效的 HTTP 方法:PATCH
【发布时间】:2014-09-29 13:12:56
【问题描述】:

当我尝试使用带有 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 请求?

【问题讨论】:

    标签: java httpurlconnection


    【解决方案1】:

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

    【讨论】:

    • 非常好的答案,应该被接受,因为它解决了实际问题,并且没有提出取决于接收服务器的解决方法
    • 确实这个解决方案很有魅力,因为使用 override 属性的解决方案取决于服务器(在我的情况下没有工作)......
    • 这仍然适用于 Java 9 吗?还是模块的东西限制了它
    • 用 JDK12 试过了,但我得到了“java.lang.NoSuchFieldException: modifiers”
    【解决方案2】:

    是的,有解决方法。使用

    X-HTTP-Method-Override

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

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

    【讨论】:

    • 只有在接收端支持的情况下才有效。它仍然会发送一个“POST”。
    • 如果接收器支持它,那么(对我来说)这是最干净的方式。
    • 此方法在使用 HttpUrlConnection 调用 Firebase REST API 时有效。
    • @DuanBressan 只要服务器支持其中一个或两者,协议就不应该成为问题(它应该只接受到 HTTPS 的连接。)
    • 这不是一个有效的答案,因为它不能解决 java 方面的问题。服务器必须允许您使用POST 并且必须了解X-HTTP-Method-Override 字段。请参阅stackoverflow.com/a/46323891/3647724 以获得更好的实际修复
    【解决方案3】:

    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>
    

    【讨论】:

    • 为什么说Patch非标准?这在很大程度上是标准的一部分......
    • 这应该是答案,但删除了非标准语句
    【解决方案4】:

    如果项目在 Spring/Gradle 上;以下解决方案将锻炼。

    对于build.gradle,添加如下依赖;

    compile('org.apache.httpcomponents:httpclient:4.5.2')
    

    并在 @SpringBootApplication 类中定义以下 bean com.company.project;

     @Bean
     public RestTemplate restTemplate() {
      HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
      requestFactory.setReadTimeout(600000);
      requestFactory.setConnectTimeout(600000);
      return new RestTemplate(requestFactory);
     }
    

    这个解决方案对我有用。

    【讨论】:

    • 对于 Spring dev 这是最干净的解决方案:return new RestTemplate(new (HttpComponentsClientHttpRequestFactory));
    • 谢谢@hirosht。在基于 spring 的应用程序中解决它的最简洁和最简单的方法。
    • 我喜欢将 because 原因添加到我的 Gradle 依赖项中,以明确为什么需要它,因为甚至可能没有导入的语句在使用它:implementation('org.apache.httpcomponents:httpclient') { because('java.net.HttpURLConnection does not support PATCH') }
    【解决方案5】:

    如果您在 Oracle 的 JRE 上使用 HttpsURLConnection,则本文所述的反射和 related post 不起作用,因为sun.net.www.protocol.https.HttpsURLConnectionImpl 正在使用其 java.net.HttpURLConnectionDelegateHttpsURLConnection 中的 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.net.HttpURLConnection#methods 值来添加“PATCH”方法?
    • 好点。但是,我的回答只是为了展示建议的解决方案应该如何工作,而不是展示另一个解决方案
    • @okutane,您能否提供一点提示我们如何重新编写方法?因为我看到很少有帖子在里面谈论代表
    • @Dhamayanthi 我已经发布了单独的答案。
    • 可以分享一下链接吗?
    【解决方案6】:

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

    最好的问候

    【讨论】:

    • 无效。 HTTP 中的行终止符指定为\r\n,而不是println() 提供的任何内容。
    【解决方案7】:

    使用答案:

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

    希望对你有帮助。

    【讨论】:

    • 如果连接的服务器确实接受并解释请求标头“X-HTTP-Method-Override”,则您的解决方案有效。因此,您的解决方案不能在所有情况下都使用。
    【解决方案8】:

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

    【讨论】:

      【解决方案9】:

      对于任何使用 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 在他的answer 中建议的相同。

      【讨论】:

        【解决方案10】:

        另一个肮脏的黑客解决方案是反射:

        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 连接的“委托”字段。所以必须改在那里。
        【解决方案11】:

        即使您无法直接访问HttpUrlConnection,您也可以找到一个可行的详细解决方案(例如在此处与 Jersey 客户合作时:PATCH request using Jersey Client

        【讨论】:

        • 感谢您的回答。但是你觉得哪个更好,直接使用 httpUrlConnection 还是 Jersey Client?
        【解决方案12】:

        如果您的服务器使用 ASP.NET Core,您只需添加以下代码即可使用标头 X-HTTP-Method-Override 指定 HTTP 方法,如 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();
        });
        

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

        【讨论】:

          【解决方案13】:

          在 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_MR2 更改为 KITKAT

          【讨论】:

            【解决方案14】:

            我从泽西岛的客户那里得到了我的。 解决方法是:

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

            【讨论】:

              【解决方案15】:

              我们遇到了同样的问题,但行为略有不同。我们使用 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 库本身。

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

              【讨论】:

                【解决方案16】:
                 **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>
                

                用这个真的会对你有帮助

                【讨论】:

                  猜你喜欢
                  • 2017-06-20
                  • 2015-01-05
                  • 2016-05-24
                  • 1970-01-01
                  • 2017-11-09
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-01-23
                  相关资源
                  最近更新 更多