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?
java
httpurlconnection
kavai77
źródło
źródło
POST
i musi rozumieć tęX-HTTP-Method-Override
dziedzinę. Zobacz stackoverflow.com/a/46323891/3647724, aby uzyskać lepszą rzeczywistą poprawkę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.
źródło
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>
źródło
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.
źródło
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
źródło
\r\n
, a nie jako cokolwiekprintln()
zapewnia.Odbicie, jak opisano w tym poście i pokrewnym poście , nie działa, jeśli używasz
HttpsURLConnection
w środowisku Oracle JRE, ponieważsun.net.www.protocol.https.HttpsURLConnectionImpl
używamethod
pola zjava.net.HttpURLConnection
jegoDelegateHttpsURLConnection
!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); } }
źródło
Korzystając z odpowiedzi:
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.
źródło
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 .
źródło
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); }
źródło
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źródło
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.Configure
zanim zadzwonisz doapp.UseMvc()
.źródło
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
PATCH
dział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_MR2
naKITKAT
po testach w API 16, 19, 21.źródło
Mam swoje z klientem z Jersey. Sposób obejścia:
Client client = ClientBuilder.newClient(); client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);
źródło
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.
źródło
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
źródło
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();
źródło