Когда я пытаюсь использовать нестандартный HTTP-метод, такой как PATCH с URLConnection:
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)
Использование API более высокого уровня, такого как Jersey, генерирует ту же ошибку. Есть ли обходной путь для выдачи HTTP-запроса PATCH?
java
httpurlconnection
kavai77
источник
источник
POST
и пониматьX-HTTP-Method-Override
поле. См. Stackoverflow.com/a/46323891/3647724 для лучшего фактического исправленияЕсть много хороших ответов, поэтому вот мой (не работает в 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, которое используется во внутренних проверках.
источник
Для этого в OpenJDK есть ошибка «Не исправить»: https://bugs.openjdk.java.net/browse/JDK-7016595
Однако с Apache Http-Components Client 4.2+ это возможно. Он имеет настраиваемую сетевую реализацию, поэтому возможно использование нестандартных методов HTTP, таких как PATCH. У него даже есть класс HttpPatch, поддерживающий метод patch.
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>
источник
Если проект находится на Spring / Gradle ; Следующее решение поможет.
Для build.gradle добавьте следующую зависимость;
compile('org.apache.httpcomponents:httpclient:4.5.2')
И определите следующий bean-компонент в своем классе @SpringBootApplication внутри com.company.project;
@Bean public RestTemplate restTemplate() { HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); requestFactory.setReadTimeout(600000); requestFactory.setConnectTimeout(600000); return new RestTemplate(requestFactory); }
Эти решения сработали для меня.
источник
У меня было такое же исключение, и я написал решение для сокетов (в 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. Вам нужно изменить сервер и номер порта, не забудьте также изменить заголовок Host и, возможно, вам нужно поймать какое-то исключение.
С уважением
источник
\r\n
, а не как что-либоprintln()
.Отражение, как описано в этом сообщении и связанном сообщении , не работает, если вы используете
HttpsURLConnection
Oracle JRE, потому чтоsun.net.www.protocol.https.HttpsURLConnectionImpl
используетmethod
поле изjava.net.HttpURLConnection
егоDelegateHttpsURLConnection
!Итак, полное рабочее решение:
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); } }
источник
Используя ответ:
Я создал образец запроса и работаю как шарм:
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; }
Надеюсь, это вам поможет.
источник
Для тех, кто использует Spring restTemplate и ищет подробный ответ.
Вы столкнетесь с проблемой, если используете SimpleClientHttpRequestFactory в качестве ClientHttpRequestFactory вашего restTemplate.
Из 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 в своем ответе .
источник
Еще одно грязное хакерское решение - рефлексия:
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); }
источник
Вы можете найти подробное решение, которое может работать, даже если у вас нет прямого доступа к
HttpUrlConnection
(например, при работе с Jersey Client здесь: запрос PATCH с использованием Jersey Clientисточник
Если ваш сервер использует ASP.NET Core, вы можете просто добавить следующий код, чтобы указать метод HTTP с помощью заголовка
X-HTTP-Method-Override
, как описано в принятом ответе .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()
.источник
В эмуляторе API 16 я получил исключение:
java.net.ProtocolException: Unknown method 'PATCH'; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE]
.Хотя принятый ответ работает, я хочу добавить одну деталь. В новых API
PATCH
работает хорошо, поэтому в сочетании с 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); }
Я изменил
JELLY_BEAN_MR2
наKITKAT
после тестирования в API 16, 19, 21.источник
Я получил свой с клиентом из Джерси. Обходной путь был:
Client client = ClientBuilder.newClient(); client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);
источник
Мы столкнулись с той же проблемой, но с немного другим поведением. Для остальных вызовов мы использовали библиотеку apache cxf. Для нас 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.
Надеюсь, этот ответ кому-то поможет.
источник
плагин 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>
используйте это, это действительно поможет вам
источник
В 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();
источник