HttpURLConnection Ungültige HTTP-Methode: PATCH


80

Wenn ich versuche, eine nicht standardmäßige HTTP-Methode wie PATCH mit URLConnection zu verwenden:

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

Ich bekomme eine Ausnahme:

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

Die Verwendung einer übergeordneten API wie Jersey generiert denselben Fehler. Gibt es eine Problemumgehung, um eine PATCH-HTTP-Anforderung auszugeben?

Antworten:


48

Ja, dafür gibt es eine Problemumgehung. Verwenden

X-HTTP-Methodenüberschreibung

. Dieser Header kann in einer POST-Anforderung verwendet werden, um andere HTTP-Methoden zu "fälschen". Setzen Sie einfach den Wert des Headers X-HTTP-Method-Override auf die HTTP-Methode, die Sie tatsächlich ausführen möchten. Verwenden Sie also den folgenden Code.

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

33
Dies funktioniert nur, wenn das empfangende Ende dies unterstützt. Es wird immer noch ein "POST" gesendet.
Joseph Jaquinta

3
Wenn der Empfänger dies unterstützt, ist dies (für mich) der sauberste Weg, um fortzufahren.
Maxime T

4
Diese Methode funktioniert, wenn HttpUrlConnection zum Aufrufen der Firebase-REST-API verwendet wird.
Andrew Kelly

1
@DuanBressan das Protokoll sollte kein Problem sein, solange der Server eines oder beide unterstützt (es sollte jedoch nur Verbindungen zu HTTPS akzeptieren.)
Alexis Wilke

3
Dies ist keine gültige Antwort, da das Problem auf javas Seite nicht behoben wird. Der Server muss Ihnen die Verwendung ermöglichen POSTund das X-HTTP-Method-OverrideFeld verstehen . Siehe stackoverflow.com/a/46323891/3647724 für eine bessere und aktuelle Lösung
Feirell

50

Es gibt viele gute Antworten, also hier ist meine (funktioniert nicht in 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);
        }
    }
}

Es wird auch Reflektion verwendet, aber anstatt in jedes Verbindungsobjekt zu hacken, hacken wir das statische Feld der HttpURLConnection # -Methoden, das bei den internen Überprüfungen verwendet wird.


7
Sehr gute Antwort, sollte die akzeptierte sein, da sie das eigentliche Problem löst und keine Problemumgehung vorschlägt, die vom empfangenden Server abhängt
Feirell

1
In der Tat funktioniert diese Lösung als Zauber, da diejenige, die die Override-Eigenschaft verwendet, vom Server abhängig ist (und in meinem Fall nicht funktioniert hat) ...
Amichai Ungar

Funktioniert das noch mit Java 9? Oder schränkt das Modul es ein
CLOVIS

2
Versuchte das mit JDK12, aber ich bekam "java.lang.NoSuchFieldException: modifiers"
Kin Cheung


33

In OpenJDK gibt es einen Fehler, der nicht behoben werden kann: https://bugs.openjdk.java.net/browse/JDK-7016595

Mit Apache Http-Components Client 4.2+ ist dies jedoch möglich. Es verfügt über eine benutzerdefinierte Netzwerkimplementierung, sodass die Verwendung von nicht standardmäßigen HTTP-Methoden wie PATCH möglich ist. Es gibt sogar eine HttpPatch-Klasse, die die Patch-Methode unterstützt.

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

Maven-Koordinaten:

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

16

Wenn das Projekt auf Spring / Gradle läuft ; Die folgende Lösung wird trainieren.

Fügen Sie für das build.gradle die folgende Abhängigkeit hinzu:

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

Definieren Sie die folgende Bean in Ihrer @ SpringBootApplication-Klasse im com.company.project.

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

Diese Lösung hat bei mir funktioniert.


Für Spring Dev ist dies die sauberste Lösung: return new RestTemplate (new (HttpComponentsClientHttpRequestFactory));
John Tribe

Vielen Dank, dass Sie @hirosht. Der sauberste und einfachste Weg, dies in einer frühlingsbasierten App zu lösen.
Daniel Camarasa

4

Ich hatte die gleiche Ausnahme und schrieb Sockets-Lösung (in groovig), aber ich übersetze im Antwortformular Java für Sie:

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

Ich denke, es funktioniert in Java. Sie müssen den Server und die Portnummer ändern. Denken Sie daran, auch den Host-Header zu ändern. Möglicherweise müssen Sie eine Ausnahme abfangen.

Freundliche Grüße


1
Ungültig. Der Zeilenabschluss in HTTP wird als angegeben \r\n, nicht als was auch immer vorgesehen println()ist.
Marquis von Lorne

4

Reflexion , wie in diesem Beitrag beschrieben und ein damit verbundener Beitrag funktioniert nicht, wenn Sie ein verwenden HttpsURLConnectionauf Oracle JRE, da sun.net.www.protocol.https.HttpsURLConnectionImplist die Verwendung von methodFeld von der java.net.HttpURLConnectionseine DelegateHttpsURLConnection!

Eine vollständig funktionierende Lösung ist also:

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
Wenn Sie bereits Reflection verwenden, fügen Sie einfach die Methode "PATCH" hinzu, indem Sie den Methodenwert java.net.HttpURLConnection # einmal pro Anwendungslebensdauer neu schreiben.
Okutane

Guter Punkt. Meine Antwort ist jedoch nur zu zeigen, wie die vorgeschlagene Lösung funktionieren sollte, nicht um eine andere Lösung zu zeigen
Müller

@okutane, könnten Sie bitte einen kleinen Hinweis geben, wie wir Methoden neu schreiben können? weil ich nur wenige Posts gesehen habe, in denen über Delegierte gesprochen wurde
Coder

@ Dhamayanthi Ich habe eine separate Antwort dafür gepostet.
Okutane

Könnten Sie bitte den Link teilen?
Coder

2

Mit der Antwort:

HttpURLConnection Ungültige HTTP-Methode: PATCH

Ich habe eine Beispielanfrage erstellt und arbeite wie ein Zauber:

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

Ich hoffe es hilft dir.


Ihre Lösung funktioniert, wenn der verbundene Server den Anforderungsheader 'X-HTTP-Method-Override' akzeptiert und interpretiert. Daher kann Ihre Lösung nicht in allen Fällen verwendet werden.
Emmanuel Devaux

2

Für alle, die Spring restTemplate verwenden und nach einer detaillierten Antwort suchen.

Sie werden mit dem Problem konfrontiert, wenn Sie SimpleClientHttpRequestFactory als ClientHttpRequestFactory Ihres restTemplate verwenden.

Von java.net.HttpURLConnection:

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

Da PATCH keine unterstützte Operation ist, wird diese Codezeile derselben Klasse ausgeführt:

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

Am Ende habe ich dasselbe verwendet, was @hirosht in seiner Antwort vorgeschlagen hat .


1

Eine andere schmutzige Hack-Lösung ist Reflexion:

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

Funktioniert für http-Verbindungen, jedoch nicht für https. Die Klasse sun.net.www.protocol.https.HttpsURLConnectionImpl verwendet ein "Delegate" -Feld, das die tatsächliche URL-Verbindung enthält. Muss sich also stattdessen dort ändern.
Per Cederberg

0

Sie können eine detaillierte Lösung finden, die auch dann funktioniert, wenn Sie keinen direkten Zugriff auf die haben HttpUrlConnection(wie bei der Arbeit mit Jersey Client hier: PATCH-Anfrage mit Jersey Client


Danke für deine Antwort. Aber was ist Ihrer Meinung nach besser, wenn Sie direkt httpUrlConnection oder Jersey Client verwenden?
Duan Bressan

0

Wenn Ihr Server ASP.NET Core verwendet, können Sie einfach den folgenden Code hinzufügen, um die HTTP-Methode mithilfe des Headers anzugeben X-HTTP-Method-Override, wie in der akzeptierten Antwort beschrieben .

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

Fügen Sie diesen Code einfach Startup.Configurevor Ihrem Anruf bei hinzu app.UseMvc().


0

Im Emulator von API 16 habe ich eine Ausnahme erhalten: java.net.ProtocolException: Unknown method 'PATCH'; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE] .

Während eine akzeptierte Antwort funktioniert, möchte ich ein Detail hinzufügen. In neuen APIs PATCHfunktioniert dies gut. In Verbindung mit https://github.com/OneDrive/onedrive-sdk-android/issues/16 sollten Sie Folgendes schreiben:

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

Ich JELLY_BEAN_MR2habe KITKATnach dem Testen in API 16, 19, 21 zu gewechselt .


0

Ich habe meine mit Jersey-Kunden. Die Problemumgehung war:

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

0

Wir haben das gleiche Problem mit leicht unterschiedlichem Verhalten konfrontiert. Wir haben die Apache-CXF-Bibliothek verwendet, um die restlichen Anrufe zu tätigen. Für uns funktionierte PATCH einwandfrei, bis wir mit unseren gefälschten Diensten sprachen, die über http funktionierten. In dem Moment, in dem wir uns in tatsächliche Systeme integriert haben (die über https lagen), hatten wir das gleiche Problem mit der folgenden Stapelverfolgung.

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]

In dieser Codezeile ist ein Problem aufgetreten

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

Der wahre Grund für das Scheitern ist das

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

Und wir können sehen, dass keine PATCH-Methode definiert ist, daher machte der Fehler Sinn. Wir haben viele verschiedene Dinge ausprobiert und uns den Stapelüberlauf angesehen. Die einzig vernünftige Antwort bestand darin, die Methodenvariable mithilfe der Reflexion zu ändern, um einen weiteren Wert "PATCH" einzufügen. Aber irgendwie waren wir nicht davon überzeugt, dies zu verwenden, da die Lösung eine Art Hack war und zu viel Arbeit ist und Auswirkungen haben könnte, da wir eine gemeinsame Bibliothek hatten, um alle Verbindungen herzustellen und diese REST-Aufrufe auszuführen.

Aber dann haben wir festgestellt, dass die cxf-Bibliothek selbst die Ausnahme behandelt und im catch-Block Code geschrieben ist, um die fehlende Methode mithilfe von Reflection hinzuzufügen.

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

Dies gab uns einige Hoffnungen, so dass wir einige Zeit mit dem Lesen des Codes verbracht haben und festgestellt haben, dass wir, wenn wir eine Eigenschaft für URLConnectionHTTPConduit.HTTPURL_CONNECTION_METHOD_REFLECTION bereitstellen, cxf zum Ausführen des Ausnahmebehandlungsprogramms verwenden können und unsere Arbeit standardmäßig ausgeführt wird aufgrund des folgenden Codes false zugewiesen

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

Hier ist also, was wir tun mussten, damit dies funktioniert

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

oder

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

Wo WebClient aus der cxf-Bibliothek selbst stammt.

Hoffe diese Antwort hilft jemandem.


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 Plugin


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

Verwenden Sie dies wirklich, es würde Ihnen helfen


0

In Java 11+ können Sie die HttpRequest-Klasse verwenden, um das zu tun, was Sie wollen:

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();
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.