HttpURLConnection Nieprawidłowa metoda HTTP: PATCH

81

Kiedy próbuję użyć niestandardowej metody HTTP, takiej jak PATCH z URLConnection:

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

Dostaję wyjątek:

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

Korzystanie z interfejsu API wyższego poziomu, takiego jak Jersey, generuje ten sam błąd. Czy istnieje obejście problemu wysyłania żądania HTTP PATCH?

kavai77
źródło

Odpowiedzi:

48

Tak, istnieje obejście tego problemu. Posługiwać się

X-HTTP-Method-Override

. Ten nagłówek może zostać użyty w żądaniu POST do „sfałszowania” innych metod HTTP. Po prostu ustaw wartość nagłówka X-HTTP-Method-Override na metodę HTTP, którą chcesz faktycznie wykonać. Więc użyj następującego kodu.

conn.setRequestProperty("X-HTTP-Method-Override", "PATCH");
conn.setRequestMethod("POST");
sagar
źródło
34
Działa to tylko wtedy, gdy strona odbierająca to obsługuje. Nadal wysyła „POST” w dół.
Joseph Jaquinta
3
Jeśli odbiornik go obsługuje, to (dla mnie) jest to najczystszy sposób postępowania.
Maxime T
4
Ta metoda działa w przypadku używania HttpUrlConnection do wywołania interfejsu Firebase REST API.
Andrew Kelly,
1
@DuanBressan protokół nie powinien stanowić problemu, o ile serwer obsługuje jedno z nich lub oba (powinien akceptować tylko połączenia HTTPS.)
Alexis Wilke
3
To nie jest prawidłowa odpowiedź, ponieważ nie rozwiązuje problemu po stronie javas. Serwer musi umożliwiać korzystanie POSTi musi rozumieć tę X-HTTP-Method-Overridedziedzinę. Zobacz stackoverflow.com/a/46323891/3647724, aby uzyskać lepszą rzeczywistą poprawkę
Feirell
51

Jest wiele dobrych odpowiedzi, więc oto moje (nie działa w 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);
        }
    }
}

Używa również odbicia, ale zamiast włamać się do każdego obiektu połączenia, hakujemy pole statyczne HttpURLConnection # methods, które jest używane w wewnętrznych kontrolach.

okutan
źródło
7
Bardzo dobra odpowiedź, powinna być akceptowana, ponieważ rozwiązuje rzeczywisty problem i nie sugeruje obejścia, które zależy od serwera odbierającego
Feirell
1
rzeczywiście to rozwiązanie działa jak urok, ponieważ ten, który używa właściwości override, jest zależny od serwera (iw moim przypadku nie zadziałało) ...
Amichai Ungar
Czy to nadal działa z Javą 9? A może moduł ogranicza to
CLOVIS
2
Próbowałem tego z JDK12, ale otrzymałem „java.lang.NoSuchFieldException: modifiers”
Kin Cheung
33

W OpenJDK występuje błąd „Nie można naprawić”: https://bugs.openjdk.java.net/browse/JDK-7016595

Jednak z Apache Http-Components Client 4.2+ jest to możliwe. Ma niestandardową implementację sieciową, dzięki czemu możliwe jest użycie niestandardowych metod HTTP, takich jak PATCH. Ma nawet klasę HttpPatch obsługującą metodę poprawiania.

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

Współrzędne Maven:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.2+</version>
</dependency>
kavai77
źródło
17

Jeśli projekt jest na Spring / Gradle ; poniższe rozwiązanie będzie ćwiczyć.

W przypadku build.gradle dodaj następującą zależność;

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

I zdefiniuj następujący bean w swojej klasie @SpringBootApplication wewnątrz com.company.project;

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

To rozwiązanie działało na mnie.

hirosht
źródło
dla Spring dev jest to najczystsze rozwiązanie: zwróć nowy RestTemplate (new (HttpComponentsClientHttpRequestFactory));
John Tribe
Dziękuję @hirosht. Najczystszy i najprostszy sposób rozwiązania tego problemu w aplikacji wiosennej.
Daniel Camarasa,
4

Miałem ten sam wyjątek i napisałem rozwiązanie gniazd (w groovy), ale prześlę w formularzu odpowiedzi do javy dla Ciebie:

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

Myślę, że to działa w Javie. Musisz zmienić serwer i numer portu, pamiętaj też o zmianie nagłówka Hosta i może musisz złapać jakiś wyjątek.

Z poważaniem

moralejaSinCuentoNiProverbio
źródło
1
Nieważny. Terminator linii w HTTP jest określony jako \r\n, a nie jako cokolwiek println()zapewnia.
user207421
4

Odbicie, jak opisano w tym poście i pokrewnym poście , nie działa, jeśli używasz HttpsURLConnectionw środowisku Oracle JRE, ponieważ sun.net.www.protocol.https.HttpsURLConnectionImplużywa methodpola z java.net.HttpURLConnectionjego DelegateHttpsURLConnection!

Tak więc kompletne działające rozwiązanie to:

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);
    }
}
rmuller
źródło
1
Jeśli już używasz odbicia, dlaczego nie dodać metody „PATCH”, przepisując wartość metody java.net.HttpURLConnection # raz na okres istnienia aplikacji?
okutane
Słuszna uwaga. Jednak moja odpowiedź jest tylko do pokazania, w jaki sposób proponowane rozwiązanie powinno działać, aby nie pokazać innym rozwiązaniem
rmuller
@okutane, czy możesz podać małą wskazówkę, jak możemy ponownie napisać do metod? ponieważ widziałem kilka postów mówiących w nim o delegatach
Coder
@Dhamayanthi Opublikowałem na to osobną odpowiedź.
okutane
czy mógłbyś udostępnić link?
Coder
2

Korzystając z odpowiedzi:

HttpURLConnection Nieprawidłowa metoda HTTP: PATCH

Utworzyłem przykładową prośbę i działam jak marzenie:

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

Mam nadzieję, że ci to pomoże.

Duan Bressan
źródło
Twoje rozwiązanie działa, jeśli podłączony serwer akceptuje i interpretuje nagłówek żądania „X-HTTP-Method-Override”. Dlatego Twoje rozwiązanie nie może być używane we wszystkich przypadkach.
Emmanuel Devaux,
2

Dla każdego, kto używa Spring restTemplate i szuka szczegółowej odpowiedzi.

Napotkasz problem, jeśli używasz SimpleClientHttpRequestFactory jako ClientHttpRequestFactory w restTemplate.

Z java.net.HttpURLConnection:

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

Ponieważ PATCH nie jest obsługiwaną operacją, zostanie wykonany następujący wiersz kodu z tej samej klasy:

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

Skończyło się na tym, że użyłem tego samego, co zasugerował @hirosht w swojej odpowiedzi .

Johnu
źródło
1

Innym brudnym rozwiązaniem jest refleksja:

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);
 }
Dreamtangerine
źródło
Działa dla połączeń http, ale nie dla https. Klasa sun.net.www.protocol.https.HttpsURLConnectionImpl używa pola „delegate”, które zawiera aktualne połączenie z adresem URL. Więc zamiast tego musi się zmienić.
Per Cederberg
0

Możesz znaleźć szczegółowe rozwiązanie, które może działać, nawet jeśli nie masz bezpośredniego dostępu do HttpUrlConnection(np. Podczas pracy z klientem Jersey tutaj: żądanie PATCH za pomocą klienta Jersey

Daniel L.
źródło
Dzięki za odpowiedź. Ale co według ciebie jest lepsze, użyj bezpośredniego protokołu httpUrlConnection lub klienta Jersey?
Duan Bressan
0

Jeśli Twój serwer używa ASP.NET Core, możesz po prostu dodać następujący kod, aby określić metodę HTTP przy użyciu nagłówka X-HTTP-Method-Override, zgodnie z opisem w zaakceptowanej odpowiedzi .

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

Po prostu dodaj ten kod, Startup.Configurezanim zadzwonisz do app.UseMvc().

Peppe LG
źródło
0

W emulatorze API 16 dostałam wyjątek: java.net.ProtocolException: Unknown method 'PATCH'; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE].

Chociaż zaakceptowana odpowiedź działa, chcę dodać jeden szczegół. W nowych API PATCHdziała dobrze, więc w połączeniu z https://github.com/OneDrive/onedrive-sdk-android/issues/16 należy napisać:

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

Zmieniłem JELLY_BEAN_MR2na KITKATpo testach w API 16, 19, 21.

CoolMind
źródło
0

Mam swoje z klientem z Jersey. Sposób obejścia:

Client client = ClientBuilder.newClient();
client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);
Daniel Jipa
źródło
0

Mieliśmy ten sam problem z nieco innym zachowaniem. Do wykonywania pozostałych wywołań używaliśmy biblioteki Apache cxf. Dla nas PATCH działał dobrze, dopóki nie rozmawialiśmy z naszymi fałszywymi usługami, które działały przez http. W chwili, gdy integrujemy się z rzeczywistymi systemami (które były przez https), zaczęliśmy napotykać ten sam problem z śledzeniem stosu.

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]

Problem występował w tej linii kodu

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

Teraz prawdziwym powodem niepowodzenia jest to

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

I widzimy, że nie ma zdefiniowanej metody PATCH, stąd błąd miał sens. Próbowaliśmy wielu różnych rzeczy i przyjrzeliśmy się przepełnieniu stosu. Jedyną rozsądną odpowiedzią było użycie refleksji w celu zmodyfikowania zmiennej Method, aby wprowadzić inną wartość „PATCH”. Ale jakoś nie byliśmy przekonani, aby to wykorzystać, ponieważ rozwiązanie było trochę hackem i jest zbyt pracochłonne i może mieć wpływ, ponieważ mieliśmy wspólną bibliotekę do nawiązywania wszystkich połączeń i wykonywania tych wywołań REST.

Ale potem zdaliśmy sobie sprawę, że sama biblioteka cxf obsługuje wyjątek i jest kod napisany w bloku catch, aby dodać brakującą metodę za pomocą odbicia.

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

Teraz dało nam to pewne nadzieje, więc spędziliśmy trochę czasu na czytaniu kodu i stwierdziliśmy, że jeśli dostarczymy właściwość dla URLConnectionHTTPConduit.HTTPURL_CONNECTION_METHOD_REFLECTION, wtedy możemy sprawić, że cxf uruchomi procedurę obsługi wyjątków i nasza praca będzie wykonywana domyślnie zmienna będzie przypisano wartość false z powodu poniższego kodu

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

Oto, co musieliśmy zrobić, aby to zadziałało

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

lub

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

Gdzie WebClient pochodzi z samej biblioteki cxf.

Mam nadzieję, że ta odpowiedź komuś pomoże.

Sanjay Bharwani
źródło
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()));**

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

użyj tego naprawdę, to by ci pomogło

perumal ks
źródło
0

W java 11+ możesz użyć klasy HttpRequest, aby robić, co chcesz:

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();
simonalexander2005
źródło