6698


表題の通りですが、`java.net.HttpURLConnection`を利用して`PATCH`リクエストを投げる方法はあるでしょうか。  
Javaから`PATCH`リクエストを送るにはApache
HttpClientを使うのが正攻法なのかなとは思うのですが、依存するライブラリを極力少なくしたい思いがありまして・・・。

リフレクションを使って`HttpURLConnection.method`の書き換えは試してみましたが、どうやら実行時に`GET`に置き換えられているような様子です。  
解決法またはApache HttpClient以外にも良いライブラリをご存知の方いらっしゃいましたら、ご教示いただければ幸いです。


CC BY-SA 3.0
2015-02-18T06:50:33.090
6685
2015-02-18T10:50:19.197
2015-02-18T08:48:16.100
845
8365
question
java http
java.net.HttpURLConnectionを利用してPATCHリクエスト


サーバ依存になってしまいますが、 `X-Http-Method-Override` ヘッダが使えるかも知れません。 サーバ側にて、HTTP
メソッドが、ヘッダ指定のメソッドにより上書きされるというものです。 Restful サービスの提供サーバでは、 ファイアウォールで `PUT`,
`DELETE` が弾かれる環境を考慮しこのヘッダを使用できるようにしているところがあるようです。

  * Google Data API, [Updating Entry](https://developers.google.com/gdata/docs/2.0/basics?csw=1#UpdatingEntry)
  * Github, [expressjs/method-override](https://github.com/expressjs/method-override)
  * Django tastypie, [Using PUT/DELETE/PATCH In Unsupported Places](http://django-tastypie.readthedocs.org/en/latest/resources.html#using-put-delete-patch-in-unsupported-places)
  * Django REST framework, [HTTP header based method overriding](http://www.django-rest-framework.org/topics/browser-enhancements/)

_DoPatch.java_ :

[code]

    import java.io.*;
    import java.net.*;
    
    public class DoPatch {
        
        public static void main( String[] args) throws MalformedURLException, IOException {
    
            final String strData = "{}";
    
            final HttpURLConnection conn = ( HttpURLConnection) new URL( "http://example.com/").openConnection();
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "application/octet-stream");
            conn.setRequestProperty("Content-Length", "" + Integer.toString(strData.getBytes().length));
            conn.setRequestProperty("Content-Language", "ja-JP");
            conn.setRequestProperty("X-HTTP-Method", "PATCH"); // Microsoft
            conn.setRequestProperty("X-HTTP-Method-Override", "PATCH");  // Google/GData
            conn.setRequestProperty("X-Method-Override", "PATCH"); // IBM
            conn.setDoOutput(true);
            conn.setDoInput(true);
            
            PrintWriter out = new PrintWriter( new BufferedOutputStream( conn.getOutputStream()));
            out.println(strData);
            out.flush();
    
            System.out.println( "Code: " + conn.getHeaderFields());
            System.out.println( conn.getResponseCode() + ": " + conn.getResponseMessage());
    
            try (BufferedReader reader = new BufferedReader( new InputStreamReader( conn.getInputStream()))) {
                String line;
                while ( (line = reader.readLine()) != null) {
                    System.out.println( line);
                }
            }
        }
    }
    
[/code]

また、別の方法を探すと、Jersey に、[リフレクションを使った HTTP
メソッドの変更を行っている箇所](https://github.com/jersey/jersey/blob/045ba16b7ebaf0d36d2313c313e43d60ba019447/core-
client/src/main/java/org/glassfish/jersey/client/HttpUrlConnector.java#L352)があり、これが流用できそうでしたが、試してみると、メソッド変更はできるものの
`PATCH` メソッドのデータ送信時に別の例外が発生してしまいました。 ( メソッド変更はできているので、別のメソッドならうまく動くでしょう。 )

_DoPatchInJersey.java_ :

[code]

    import java.io.*;
    import java.lang.reflect.Field;
    import java.net.*;
    import java.security.*;
    
    public class DoPatchInJersey {
    
        public static void main( String[] args) throws MalformedURLException, IOException {
            final String strData = "{}";
            final HttpURLConnection conn = ( HttpURLConnection) new URL( "http://example.com/").openConnection();
            setRequestMethodViaJreBugWorkaround( conn, "PATCH");
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setRequestProperty("Content-Type", "application/octet-stream");
            conn.setRequestProperty("Content-Length", "" + Integer.toString(strData.getBytes().length));
            conn.setRequestProperty("Content-Language", "ja-JP");
    
            // ERROR: as PATCH method doesn't support data output
            PrintWriter out = new PrintWriter( new BufferedOutputStream( conn.getOutputStream()));
            out.println(strData);
            out.flush();
    
            System.out.println( conn.getResponseCode() + ": " + conn.getResponseMessage());
            try (BufferedReader reader = new BufferedReader( new InputStreamReader( conn.getInputStream()))) {
                String line;
                while ( (line = reader.readLine()) != null) {
                    System.out.println( line);
                }
            }
        }
        
        /**
         * Workaround for a bug in {@code HttpURLConnection.setRequestMethod(String)} The implementation of Sun/Oracle is throwing a
         * {@code ProtocolException} when the method is other than the HTTP/1.1 default methods. So to use {@code PROPFIND} and others, we must apply this
         * workaround.
         * See issue http://java.net/jira/browse/JERSEY-639
         */
        private static void setRequestMethodViaJreBugWorkaround( final HttpURLConnection httpURLConnection, final String method) {
            try {
                httpURLConnection.setRequestMethod( method); // Check whether we are running on a buggy JRE
            }
            catch ( final ProtocolException pe) {
                try {
                    final Class<?> httpURLConnectionClass = httpURLConnection.getClass();
                    AccessController.doPrivileged( new PrivilegedExceptionAction<Object>() {
                        @Override
                        public Object run() throws NoSuchFieldException, IllegalAccessException {
                            try {
                                httpURLConnection.setRequestMethod( method);
                                // Check whether we are running on a buggy
                                // JRE
                            }
                            catch ( final ProtocolException pe) {
                                Class<?> connectionClass = httpURLConnection.getClass();
                                Field delegateField = null;
                                try {
                                    delegateField = connectionClass.getDeclaredField( "delegate");
                                    delegateField.setAccessible( true);
                                    HttpURLConnection delegateConnection = ( HttpURLConnection) delegateField.get( httpURLConnection);
                                    setRequestMethodViaJreBugWorkaround( delegateConnection, method);
                                }
                                catch ( NoSuchFieldException e) {
                                    // Ignore for now, keep going
                                }
                                catch ( IllegalArgumentException e) {
                                    throw new RuntimeException( e);
                                }
                                catch ( IllegalAccessException e) {
                                    throw new RuntimeException( e);
                                }
                                try {
                                    Field methodField;
                                    while ( connectionClass != null) {
                                        try {
                                            methodField = connectionClass.getDeclaredField( "method");
                                        }
                                        catch ( NoSuchFieldException e) {
                                            connectionClass = connectionClass.getSuperclass();
                                            continue;
                                        }
                                        methodField.setAccessible( true);
                                        methodField.set( httpURLConnection, method);
                                        break;
                                    }
                                }
                                catch ( final Exception e) {
                                    throw new RuntimeException( e);
                                }
                            }
                            return null;
                        }
                    });
                }
                catch ( final PrivilegedActionException e) {
                    final Throwable cause = e.getCause();
                    if ( cause instanceof RuntimeException) {
                        throw ( RuntimeException) cause;
                    }
                    else {
                        throw new RuntimeException( cause);
                    }
                }
            }
        }
    }
    
[/code]

_Output_ :

[code]

    Exception in thread "main" java.net.ProtocolException: HTTP method PATCH doesn't support output
        at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1081)
        at DoPatchInJersey.main(DoPatchInJersey.java:18)
    
[/code]

`X-HTTP-Method-Override` でできなければ、直に HTTP プロトコルを喋らせるか、 [Apache
HttpClient](https://hc.apache.org/httpcomponents-client-ga/index.html)
を使用するのが手っ取り早いでしょうね。

**参考** :

  * IETF, [RFC 5789 - PATCH Method for HTTP](https://www.rfc-editor.org/rfc/rfc5789)
  * OpenJDK, [JDK-7016595: HttpURLConnection must not restrict method name](https://bugs.openjdk.java.net/browse/JDK-7016595)
  * Jersey, [Fixed setRequestMethodViaJreBugWorkaround() to work with HTTPS as well as HTTP](https://github.com/jersey/jersey/pull/45/files)
  * Jersey, [Client API implementation must be able to process non-standard HTTP methods like PROPFIND or PATCH](https://java.net/jira/browse/JERSEY-639)
  * openshift-java-client, [support PATCH http method](https://issues.jboss.org/browse/OSJC-4)
  * Technology highlights Blog, [X-HTTP header method override and REST APIs](http://fandry.blogspot.fr/2012/03/x-http-header-method-override-and-rest.html)
  * MSDN, [2.2.5.8 X-HTTP-Method](https://msdn.microsoft.com/en-us/library/dd541471.aspx)


CC BY-SA 3.0
2015-02-18T10:50:19.197
6698
2015-02-18T10:50:19.197
2021-10-07T07:34:52.683
-1
4978
6685
answer