HttpURLConnection 잘못된 HTTP 메서드 : PATCH


81

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 요청을 발행하는 해결 방법이 있습니까?

답변:


48

예, 이에 대한 해결 방법이 있습니다. 사용하다

X-HTTP-Method-Override

. 이 헤더는 POST 요청에서 다른 HTTP 메서드를 "위조"하는 데 사용할 수 있습니다. X-HTTP-Method-Override 헤더의 값을 실제로 수행하려는 HTTP 메서드로 설정하기 만하면됩니다. 따라서 다음 코드를 사용하십시오.

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

34
이것은 수신 측이 지원하는 경우에만 작동합니다. 여전히 "POST"를 라인 아래로 보냅니다.
Joseph Jaquinta 2015 년

3
수신기가 지원한다면 (나에게) 진행하는 가장 깨끗한 방법입니다.
Maxime T

4
이 메서드는 HttpUrlConnection을 사용하여 Firebase REST API를 호출 할 때 작동합니다.
Andrew Kelly

1
프로토콜은 서버가 지원하는만큼 문제가되지해야 @DuanBressan 중 하나 또는 둘 모두 (만에 연결을 허용해야 HTTPS하지만.)
알렉시스 WILKE

3
이것은 Javas 측의 문제를 해결하지 않기 때문에 유효한 대답이 아닙니다. 서버는 사용자가 사용할 수 있도록해야 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

1
실제로이 솔루션은 ... 서버에 의존 (내 경우에는 작동하지 않았다) 재정의 속성을 사용 하나, 매력으로 작동
Amichai Ungar

Java 9에서도 여전히 작동합니까? 아니면 모듈 일이 그것을 제한합니까
CLOVIS

2
JDK12로 시도했지만 "java.lang.NoSuchFieldException : modifiers"가 발생했습니다.
킨 청을


33

이에 대한 OpenJDK에는 수정되지 않는 버그가 있습니다 : https://bugs.openjdk.java.net/browse/JDK-7016595

그러나 Apache Http-Components Client 4.2 이상에서는 이것이 가능합니다. 사용자 지정 네트워킹 구현이 있으므로 PATCH와 같은 비표준 HTTP 메서드를 사용할 수 있습니다. 패치 메서드를 지원하는 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>

17

프로젝트가 Spring / Gradle에있는 경우 ; 다음 솔루션이 운동합니다.

build.gradle의 경우 다음 종속성을 추가하십시오.

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

그리고 com.company.project 내의 @SpringBootApplication 클래스에 다음 빈을 정의하십시오.

 @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));
John Tribe

@hirosht 감사합니다. 스프링 기반 앱에서 문제를 해결하는 가장 깨끗하고 간단한 방법입니다.
다니엘 Camarasa

4

나는 같은 예외가 있었고 소켓 솔루션 (그루비)을 썼지 만 당신을 위해 자바에 대한 대답 양식을 번역했습니다.

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

나는 그것이 자바에서 작동한다고 생각합니다. 서버와 포트 번호를 변경해야합니다. 호스트 헤더도 변경해야하며 예외를 잡아야 할 수도 있습니다.

친애하는


1
유효하지. HTTP의 줄 종결자는 제공 \r\n하는 것이 아니라로 지정됩니다 println().
user207421

4

반영이 게시물에 설명과 같이 관련 게시물 당신이 사용하는 경우 작동하지 않습니다 HttpsURLConnection오라클의 JRE에를하기 때문에, sun.net.www.protocol.https.HttpsURLConnectionImpl사용 method으로부터 필드 java.net.HttpURLConnection의의 DelegateHttpsURLConnection!

따라서 완전한 작업 솔루션은 다음과 같습니다.

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

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'를 수락하고 해석하면 솔루션이 작동합니다. 따라서 모든 경우에 솔루션을 사용할 수는 없습니다.
Emmanuel Devaux

2

자세한 답변을 찾는 Spring restTemplate을 사용하는 모든 사람을 위해.

restTemplate의 ClientHttpRequestFactory로 SimpleClientHttpRequestFactory를 사용하는 경우 문제가 발생합니다.

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 연결을 포함하는 "delegate"필드를 사용합니다. 그래서 대신 거기에서 변경해야합니다.
Per Cederberg

0

직접 액세스 할 수없는 경우에도 작동 할 수있는 자세한 솔루션을 찾을 수 있습니다 HttpUrlConnection(예 : Jersey Client로 작업하는 경우 : Jersey Client를 사용한 PATCH 요청


답변 해 주셔서 감사합니다. 그러나 직접 httpUrlConnection 또는 Jersey Client를 사용하는 것이 더 낫다고 생각하십니까?
Duan Bressan

0

서버에서 ASP.NET Core를 사용하는 X-HTTP-Method-Override경우 허용 된 답변에 설명 된대로 다음 코드를 추가하여 header를 사용하여 HTTP 메서드를 지정할 수 있습니다 .

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_MR2했습니다 KITKAT.


0

나는 Jersey 고객에게 내 것을 얻었다. 해결 방법은 다음과 같습니다.

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"를 주입하도록 메소드 변수를 수정하는 것입니다. 그러나 어떻게 든 우리는 솔루션이 일종의 해킹이고 너무 많은 작업이며 모든 연결을 만들고 이러한 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를 만들어 예외 처리기를 실행할 수 있으며 기본적으로 변수가 다음과 같이 수행됩니다. 아래 코드로 인해 거짓으로 할당 됨

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

메이븐 플러그인


> <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();
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.