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-メソッド-オーバーライド

。このヘッダーは、POSTリクエストで他のHTTPメソッドを「偽造」するために使用できます。X-HTTP-Method-Overrideヘッダーの値を、実際に実行するHTTPメソッドに設定するだけです。したがって、次のコードを使用します。

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

34
これは、受信側がサポートしている場合にのみ機能します。それでも「POST」が送信されます。
ジョセフジャキンタ2015年

3
受信者がそれをサポートしている場合、(私にとって)それが最もクリーンな方法です。
Maxime T

4
このメソッドは、HttpUrlConnectionを使用してFirebase RESTAPIを呼び出すときに機能します。
アンドリューケリー

1
@DuanBressanサーバーがいずれかまたは両方をサポートしている限り、プロトコルは問題になりません(ただし、HTTPSへの接続のみを受け入れる必要があります)
Alexis Wilke 2017

3
これはjavas側の問題を修正しないため、有効な答えではありません。サーバーは、ユーザーが使用できるようにしPOSTX-HTTP-Method-Overrideフィールドを理解する必要があります。より良い実際の修正については、stackoverflow.com / a
46323891/3647724を

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 2018

1
実際、このソリューションは魅力として機能します。オーバーライドプロパティを使用するソリューションはサーバーに依存しているためです(私の場合は機能しませんでした)...
Amichai Ungar 2018

これはJava9でも機能しますか?または、モジュールのことでそれが制限されますか
CLOVIS 2018

2
:「修飾子java.lang.NoSuchFieldExceptionの」JDK12で、私得たことを試してみました
キンチャンを


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を定義します。

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

このソリューションは私のために働いた。


Spring devの場合、これが最もクリーンなソリューションです。returnnew RestTemplate(new(HttpComponentsClientHttpRequestFactory));
ジョントライブ

@hiroshtありがとうございます。春ベースのアプリでそれを解決するための最もクリーンで簡単な方法。
ダニエルカマラ

4

私は同じ例外があり、(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で動作すると思います。サーバーとポート番号を変更する必要があります。ホストヘッダーも変更することを忘れないでください。例外をキャッチする必要があるかもしれません。

宜しくお願いします


1
有効ではありません。HTTPの行末記号は\r\nprintln()提供されるものではなく、として指定されます。
user207421 2017年

4

OracleのJREでを使用している場合、この投稿および関連する投稿で説明されているリフレクションは機能しませんHttpsURLConnection。これsun.net.www.protocol.https.HttpsURLConnectionImplは、!のmethodフィールドを使用しているためです。java.net.HttpURLConnectionDelegateHttpsURLConnection

したがって、完全に機能するソリューションは次のとおりです。

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値を1回書き換えて、「PATCH」メソッドを追加してみませんか?
okutane 2017

いい視点ね。ただし、私の答えは、提案されたソリューションがどのように機能するかを示すためだけであり、別のソリューションを示すためではありません
rmuller 2017

@okutane、メソッドを書き直す方法について少しヒントを教えてください。その中のデリゲートについて話している投稿をほとんど見たことがないので
Coder

@Dhamayanthi私はそれに対する別の答えを投稿しました。
okutane 2017

リンクを共有していただけませんか?
コーダー2017年

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 2018年

2

詳細な回答を探しているSpringrestTemplateを使用している人のために。

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

もう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接続を含む「デリゲート」フィールドを使用します。したがって、代わりにそこで変更する必要があります。
–Cederbergによる2017


0

サーバーがASP.NETCoreを使用している場合は、次のコードを追加するだけX-HTTP-Method-Overrideで、受け入れられた回答で説明されているように、ヘッダーを使用して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]

受け入れられた回答は機能しますが、詳細を1つ追加したいと思います。新しいAPIPATCHではうまく機能するため、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_MR2KITKAT後にに変更しました。


0

私はジャージーのクライアントと私のものを手に入れました。回避策は次のとおりです。

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

0

動作が少し異なる同じ問題に直面しました。残りの呼び出しを行うためにapachecxfライブラリを使用していました。私たちにとって、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で例外ハンドラーを実行できることがわかりました。デフォルトでは、変数は次のようになります。以下のコードのために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ライブラリ自体からのものです。

この答えが誰かを助けることを願っています。


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

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>

これを実際に使用すると、


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.