HttpURLConnection Phương thức HTTP không hợp lệ: PATCH


81

Khi tôi cố gắng sử dụng Phương thức HTTP không chuẩn như PATCH với URLConnection:

    HttpURLConnection conn = (HttpURLConnection) new URL("http://example.com").openConnection();
    conn.setRequestMethod("PATCH");

Tôi nhận được một ngoại lệ:

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

Sử dụng API cấp cao hơn như Jersey sẽ tạo ra lỗi tương tự. Có cách giải quyết nào để đưa ra yêu cầu PATCH HTTP không?

Câu trả lời:


48

Có, có giải pháp cho điều này. Sử dụng

X-HTTP-Method-Override

. Tiêu đề này có thể được sử dụng trong một yêu cầu POST để "giả mạo" các phương thức HTTP khác. Chỉ cần đặt giá trị của tiêu đề X-HTTP-Method-Override thành phương thức HTTP mà bạn muốn thực hiện. Vì vậy, hãy sử dụng mã sau.

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

34
Điều này chỉ hoạt động nếu đầu nhận hỗ trợ nó. Nó vẫn gửi một "POST" xuống dòng.
Joseph Jaquinta

3
Nếu người nhận hỗ trợ nó, thì (đối với tôi) đó là cách tốt nhất để tiếp tục.
Maxime T

4
Phương pháp này hoạt động khi sử dụng HttpUrlConnection để gọi API REST của Firebase.
Andrew Kelly

1
@DuanBressan giao thức không phải là một vấn đề miễn là hỗ trợ máy chủ một trong hai hoặc cả hai (nó chỉ nên chấp nhận các kết nối HTTPS mặc dù.)
Alexis Wilke

3
Đây không phải là câu trả lời hợp lệ vì nó không khắc phục được sự cố về phía javas. Máy chủ phải cho phép bạn sử dụng POSTvà phải hiểu X-HTTP-Method-Overridelĩnh vực này. Xem stackoverflow.com/a/46323891/3647724 để có bản sửa lỗi thực tế tốt hơn
Feirell

51

Có rất nhiều câu trả lời hay, vì vậy đây là câu trả lời của tôi (không hoạt động trong 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);
        }
    }
}

Nó cũng sử dụng sự phản chiếu, nhưng thay vì hack vào mọi đối tượng kết nối, chúng tôi đang hack trường tĩnh HttpURLConnection # phương thức được sử dụng để kiểm tra nội bộ.


7
Câu trả lời rất hay, nên là câu trả lời được chấp nhận vì nó giải quyết được vấn đề thực tế và không đề xuất cách giải quyết phụ thuộc vào máy chủ nhận
Feirell

1
thực sự giải pháp này hoạt động như một nét duyên dáng, như một trong đó sử dụng tài sản ghi đè phụ thuộc vào máy chủ (và trong trường hợp của tôi không làm việc) ...
Amichai Ungar

Điều này vẫn hoạt động với Java 9? Hoặc không điều mô-đun hạn chế nó
CLOVIS

2
Đã thử điều đó với JDK12, nhưng tôi nhận được "java.lang.NoSuchFieldException: modifiers"
Kin Cheung


33

Có một lỗi sẽ không sửa được trong OpenJDK cho điều này: https://bugs.openjdk.java.net/browse/JDK-7016595

Tuy nhiên, với Apache Http-Components Client 4.2+, điều này là có thể. Nó có triển khai mạng tùy chỉnh, do đó có thể sử dụng các phương thức HTTP không chuẩn như PATCH. Nó thậm chí còn có một lớp HttpPatch hỗ trợ phương pháp vá lỗi.

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPatch httpPatch = new HttpPatch(new URI("http://example.com"));
CloseableHttpResponse response = httpClient.execute(httpPatch);

Tọa độ Maven:

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

17

Nếu dự án trên Spring / Gradle ; giải pháp sau đây sẽ hiệu quả.

Đối với build.gradle, hãy thêm phần phụ thuộc sau;

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

Và xác định bean sau trong lớp @SpringBootApplication của bạn bên trong com.company.project;

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

Giải pháp này đã làm việc cho tôi.


đối với Spring dev, đây là giải pháp sạch sẽ nhất: trả về RestTemplate mới (mới (HttpComponentsClientHttpRequestFactory));
John Tribe

Cảm ơn bạn @hirosht. Cách sạch sẽ và đơn giản nhất để giải quyết nó trong một ứng dụng dựa trên mùa xuân.
Daniel Camarasa

4

Tôi đã có cùng một ngoại lệ và đã viết giải pháp sockets (trong groovy) nhưng tôi traslate trong biểu mẫu câu trả lời cho java cho bạn:

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

Tôi nghĩ rằng nó hoạt động trong java. Bạn phải thay đổi máy chủ và số cổng, hãy nhớ thay đổi cả tiêu đề Máy chủ lưu trữ và Có thể bạn phải bắt một số ngoại lệ.

Trân trọng


1
Không hợp lệ. Dấu kết thúc dòng trong HTTP được chỉ định \r\n, không phải như bất cứ điều gì println()cung cấp.
user207421

4

Phản ánh như được mô tả trong bài đăng này và một bài đăng liên quan sẽ không hoạt động nếu bạn đang sử dụng một HttpsURLConnectiontrên Oracle's JRE, vì sun.net.www.protocol.https.HttpsURLConnectionImplđang sử dụng methodtrường từ java.net.HttpURLConnectioncủa nó DelegateHttpsURLConnection!

Vì vậy, một giải pháp làm việc hoàn chỉnh là:

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
Nếu bạn đã sử dụng tính năng phản chiếu, tại sao không chỉ thêm phương thức "PATCH" bằng cách viết lại giá trị phương thức java.net.HttpURLConnection # một lần cho mỗi vòng đời ứng dụng?
okutane

Điểm tốt. Tuy nhiên, câu trả lời của tôi là chỉ cho thấy làm thế nào các giải pháp đề nghị nên làm việc, chứ không phải để hiển thị một giải pháp
rmuller

@okutane, bạn có thể vui lòng cung cấp gợi ý nhỏ về cách chúng tôi có thể ghi lại các phương thức không? bởi vì tôi đã thấy vài bài nói về các đại biểu trong nó
Coder

@Dhamayanthi Tôi đã đăng câu trả lời riêng cho nó.
okutane

bạn có thể vui lòng chia sẻ liên kết?
Coder

2

Sử dụng câu trả lời:

HttpURLConnection Phương thức HTTP không hợp lệ: PATCH

Tôi đã tạo một yêu cầu mẫu và làm việc như một cái duyên:

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

Tôi hy vọng nó sẽ giúp bạn.


Giải pháp của bạn hoạt động nếu máy chủ được kết nối chấp nhận và diễn giải tiêu đề yêu cầu 'X-HTTP-Method-Override'. Do đó, giải pháp của bạn không thể được sử dụng trong mọi trường hợp.
Emmanuel Devaux

2

Đối với bất kỳ ai sử dụng Spring restTemplate đang tìm kiếm câu trả lời chi tiết.

Bạn sẽ phải đối mặt với vấn đề nếu bạn đang sử dụng SimpleClientHttpRequestFactory làm ClientHttpRequestFactory của restTemplate.

Từ java.net.HttpURLConnection:

/* valid HTTP methods */
private static final String[] methods = {
    "GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
};

Vì PATCH không phải là một hoạt động được hỗ trợ, dòng mã này từ cùng một lớp sẽ thực thi:

throw new ProtocolException("Invalid HTTP method: " + method);

Tôi đã kết thúc bằng cách sử dụng giống như những gì @hirosht đề xuất trong câu trả lời của anh ấy .


1

Một giải pháp hack bẩn thỉu khác là phản xạ:

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

Hoạt động cho các kết nối http, nhưng không hoạt động cho https. Lớp sun.net.www.protocol.https.HttpsURLConnectionImpl sử dụng trường "ủy nhiệm" chứa kết nối URL thực. Vì vậy, phải thay đổi có thay.
Per Cederberg

0

Bạn có thể tìm thấy một giải pháp chi tiết có thể hoạt động ngay cả khi bạn không có quyền truy cập trực tiếp vào HttpUrlConnection(như khi làm việc với Jersey Client tại đây: Yêu cầu PATCH bằng cách sử dụng Jersey Client


Cảm ơn câu trả lời của bạn. Nhưng bạn nghĩ điều gì tốt hơn, sử dụng httpUrlConnection trực tiếp hoặc Jersey Client?
Duẩn Bressan

0

Nếu máy chủ của bạn đang sử dụng ASP.NET Core, bạn có thể chỉ cần thêm mã sau để chỉ định phương thức HTTP bằng cách sử dụng tiêu đề X-HTTP-Method-Override, như được mô tả trong câu trả lời được chấp nhận .

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

Chỉ cần thêm mã này vào Startup.Configuretrước khi bạn gọi đến app.UseMvc().


0

Trong giả lập của API 16 tôi nhận được một ngoại lệ: java.net.ProtocolException: Unknown method 'PATCH'; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE].

Mặc dù một câu trả lời được chấp nhận hoạt động, tôi muốn thêm một chi tiết. Trong các API mới PATCHhoạt động tốt, vì vậy khi kết hợp với https://github.com/OneDrive/onedrive-sdk-android/issues/16, bạn nên viết:

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

Tôi đã thay đổi JELLY_BEAN_MR2thành KITKATsau khi thử nghiệm trong API 16, 19, 21.


0

Tôi nhận được của tôi với khách hàng Jersey. Cách giải quyết là:

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

0

Chúng tôi đã đối mặt với cùng một vấn đề với hành vi hơi khác nhau. Chúng tôi đang sử dụng thư viện cxf apache để thực hiện các cuộc gọi còn lại. Đối với chúng tôi, PATCH đã hoạt động tốt cho đến khi chúng tôi nói chuyện với các dịch vụ giả mạo đang hoạt động trên http. Thời điểm chúng tôi tích hợp với các hệ thống thực tế (hơn https), chúng tôi bắt đầu gặp phải vấn đề tương tự với theo dõi ngăn xếp sau.

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]

Sự cố đã xảy ra trong dòng mã này

connection.setRequestMethod(httpRequestMethod); in URLConnectionHTTPConduit class of cxf library

Bây giờ lý do thực sự cho sự thất bại là

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

Và chúng ta có thể thấy rằng không có phương thức PATCH nào được định nghĩa do đó lỗi có ý nghĩa. Chúng tôi đã thử rất nhiều thứ khác nhau và xem xét tình trạng tràn ngăn xếp. Câu trả lời hợp lý duy nhất là sử dụng phản chiếu để sửa đổi biến phương thức để đưa vào một giá trị khác "PATCH". Nhưng bằng cách nào đó, chúng tôi không được thuyết phục để sử dụng nó vì giải pháp này là một loại hack và quá nhiều công việc và có thể có tác động vì chúng tôi có thư viện chung để tạo tất cả kết nối và thực hiện các cuộc gọi REST này.

Nhưng sau đó chúng tôi nhận ra rằng bản thân thư viện cxf đang xử lý ngoại lệ và có mã được viết trong khối catch để thêm phương thức còn thiếu bằng cách sử dụng phản chiếu.

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

Bây giờ điều này đã cho chúng tôi một số hy vọng, vì vậy chúng tôi đã dành một chút thời gian để đọc mã và nhận thấy rằng nếu chúng tôi cung cấp một thuộc tính cho URLConnectionHTTPConduit.HTTPURL_CONNECTION_METHOD_REFLECTION thì chúng tôi có thể tạo cxf để thực thi trình xử lý ngoại lệ và công việc của chúng tôi được thực hiện theo mặc định, biến sẽ là được gán cho sai do mã bên dưới

DEFAULT_USE_REFLECTION = 
        Boolean.valueOf(SystemPropertyAction.getProperty(HTTPURL_CONNECTION_METHOD_REFLECTION, "false"));

Vì vậy, đây là những gì chúng tôi phải làm để làm cho công việc này

WebClient.getConfig(client).getRequestContext().put("use.httpurlconnection.method.reflection", true);

hoặc là

WebClient.getConfig(client).getRequestContext().put(HTTPURL_CONNECTION_METHOD_REFLECTION, true);

Nơi WebClient là từ chính thư viện cxf.

Hy vọng câu trả lời này sẽ giúp một số người.


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

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

sử dụng điều này thực sự nó sẽ giúp bạn


0

Trong java 11+, bạn có thể sử dụng lớp HttpRequest để làm những gì bạn muốn:

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();
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.