Java HttpURLConnection无效的HTTP方法:修补程序

Java HttpURLConnection无效的HTTP方法:修补程序,java,httpurlconnection,Java,Httpurlconnection,当我尝试将非标准HTTP方法(如修补程序)用于URLConnection时: HttpURLConnection conn = (HttpURLConnection) new URL("http://example.com").openConnection(); conn.setRequestMethod("PATCH"); /* valid HTTP methods */ private static final String[] methods = { "GET",

当我尝试将非标准HTTP方法(如修补程序)用于URLConnection时:

    HttpURLConnection conn = (HttpURLConnection) new URL("http://example.com").openConnection();
    conn.setRequestMethod("PATCH");
/* valid HTTP methods */
private static final String[] methods = {
    "GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
};
我得到一个例外:

java.net.ProtocolException: Invalid HTTP method: PATCH
at java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:440)

使用更高级别的API(如Jersey)会产生相同的错误。是否有解决办法来发出修补程序HTTP请求?

OpenJDK中有一个无法修复的错误:

然而,使用ApacheHTTP组件客户端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>

org.apache.httpcomponents
httpclient
4.2+

是的,有解决方法。使用

X-HTTP-Method-Override

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

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

我也有同样的例外,并编写了sockets解决方案(用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中工作。您必须更改服务器和端口号,记住也要更改主机头,也许您必须捕获一些异常


致以最诚挚的问候

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

private void setVerb(HttpURLConnection cn,String verb)引发IOException{
开关(动词){
案例“GET”:
案例“职位”:
案例“头”:
案例“选项”:
案例“付诸表决”:
案例“删除”:
案例“跟踪”:
cn.setRequestMethod(动词);
打破
违约:
//设置一个虚拟后置动词
cn.setRequestMethod(“POST”);
试一试{
//更改公共类HttpURLConnection的名为“method”的受保护字段
setProtectedFieldValue(HttpURLConnection.class,“方法”,cn,动词);
}捕获(例外情况除外){
抛出新的IOException(ex);
}
打破
}
}
公共静态void setProtectedFieldValue(类clazz,字符串fieldName,T对象,对象newValue)引发异常{
字段字段=clazz.getDeclaredField(字段名);
字段。setAccessible(true);
field.set(object,newValue);
}

即使您无法直接访问
HttpUrlConnection
(就像在这里使用Jersey客户端时:

如本文所述的反射,如果您在Oracle的JRE上使用
HttpsURLConnection
,则a不起作用,因为
sun.net.www.protocol.https.HttpsURLConnectionImpl
正在使用其
D的
java.net.HttpURLConnection
字段中的
方法elegateHttpsURLConnection

因此,一个完整的工作解决方案是:

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

如果您的服务器使用ASP.NET Core,只需添加以下代码,即可使用标题
X-HTTP-method-Override
指定HTTP方法,如中所述

只需在
启动中添加此代码。在调用
app.UseMvc()
之前,使用以下答案配置

我创建了一个示例请求,并像一个符咒一样工作:

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

我希望它能帮助您。

在API 16的emulator中,我收到一个异常:
java.net.ProtocolException:未知方法“PATCH”;必须是[OPTIONS,GET,HEAD,POST,PUT,DELETE,TRACE]之一。

虽然一个公认的答案是有效的,但我想添加一个细节。在新的API中,
补丁
效果很好,因此结合您应该编写:

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

if(method.equals(“PATCH”)和&Build.VERSION.SDK_INT有很多很好的答案,下面是我的答案(不适用于jdk12):

import java.io.IOException;
导入java.lang.reflect.Field;
导入java.lang.reflect.Modifier;
导入java.net.HttpURLConnection;
导入java.net.URL;
导入java.util.array;
导入java.util.LinkedHashSet;
导入java.util.Set;
公共类SupportPatch{
公共静态void main(字符串…参数)引发IOException{
allowMethods(“补丁”);
HttpURLConnection conn=(HttpURLConnection)新URL(“http://example.com”).openConnection();
conn.setRequestMethod(“补丁”);
}
私有静态void allowMethods(字符串…方法){
试一试{
FieldMethodsField=HttpURLConnection.class.getDeclaredField(“方法”);
字段修饰符字段=Field.class.getDeclaredField(“修饰符”);
modifiersField.setAccessible(true);
modifiersField.setInt(methodsField,methodsField.getModifiers()&~Modifier.FINAL);
methodsField.setAccessible(true);
String[]oldMethods=(String[])methodsField.get(null);
Set methodset=newlinkedhashset(Arrays.asList(oldMethods));
methodsSet.addAll(Arrays.asList(methods));
String[]newMethods=MethodSet.toArray(新字符串[0]);
methodsField.set(null/*静态字段*/,newMethods);
}捕获(NoSuchFieldException | IllegalacessException e){
抛出新的非法状态异常(e);
}
}
}
它也使用反射,但不是侵入每个连接对象,而是侵入内部检查中使用的HttpURLConnection#methods静态字段。

如果项目在Spring/Gradle上,以下解决方案将得到解决

对于build.gradle,添加以下依赖项

compile('org.apache.httpcomponents:httpclient:4.5.2')
并在 com.company.project

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

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

我的客户是泽西。 解决办法是:

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

我们遇到了相同的问题,但行为略有不同。我们使用ApacheCXF库进行rest调用。 对我们来说,在我们与通过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
现在是失败的真正原因
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"
    };
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;
            }
        }
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);
 **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()));**
> <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>
/* valid HTTP methods */
private static final String[] methods = {
    "GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
};
throw new ProtocolException("Invalid HTTP method: " + method);
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();