HttpURLConnection Неверный метод HTTP: PATCH
Когда я пытаюсь использовать нестандартный метод 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 более высокого уровня, такого как Джерси, приводит к той же ошибке. Есть ли обходной путь для выдачи HTTP-запроса PATCH?
13 ответов
Да, есть обходной путь для этого. использование
X-HTTP-метод-Переопределение
, Этот заголовок может использоваться в запросе POST для "подделки" других HTTP-методов. Просто установите значение заголовка X-HTTP-Method-Override равным HTTP-методу, который вы хотели бы фактически выполнить. Так что используйте следующий код.
conn.setRequestProperty("X-HTTP-Method-Override", "PATCH");
conn.setRequestMethod("POST");
Есть много хороших ответов, так что вот мой:
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# методов, которое используется для внутренних проверок.
Для этого в 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')
И определите следующий компонент в вашем классе @SpringBootApplication внутри com.company.project;
@Bean
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setReadTimeout(600000);
requestFactory.setConnectTimeout(600000);
return new RestTemplate(requestFactory);
}
Это решение работало для меня.
В 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();
Отражение, как описано в этом посте, и связанный пост не работает, если вы используете HttpsURLConnection
на JRE Oracle, потому что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);
}
}
У меня было то же исключение и я написал решение для сокетов (в 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. Вы должны изменить сервер и номер порта, не забудьте изменить заголовок хоста и, возможно, вам нужно поймать какое-то исключение.
С уважением
Используя ответ:
Я создал образец запроса и работаю как шарм:
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 в своем ответе.
Мы столкнулись с той же проблемой с немного другим поведением. Мы использовали библиотеку 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.
Надеюсь, что этот ответ поможет кому-то.
Другое грязное решение взлома - рефлексия:
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
**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
> <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>
используйте это, это действительно поможет вам
Я получил мой с клиентом Джерси. Обходной путь был:
Client client = ClientBuilder.newClient();
client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);
Если ваш сервер использует 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.