HttpURLConnection недопустимый метод HTTP: исправление
когда я пытаюсь использовать нестандартный метод 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-запроса исправления?
13 ответов
Да, для этого есть обходной путь. Использовать
X-HTTP-Method-Override
. Этот заголовок можно использовать в запросе POST для "подделки" других методов HTTP. Просто установите значение заголовка X-HTTP-Method-Override в HTTP-метод, который вы хотели бы фактически выполнить. Поэтому используйте следующий код.
conn.setRequestProperty("X-HTTP-Method-Override", "PATCH");
conn.setRequestMethod("POST");
для этого в 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>
есть много хороших ответов, так что вот мой:
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, которое используется во внутренних проверках.
У меня было такое же исключение и написал решение сокеты (в 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. Вы должны изменить сервер и номер порта, помните, что измените заголовок Хоста, и, возможно, вам нужно поймать какое-то исключение.
С Наилучшими Пожеланиями
отражения, как описано в этом посте и похожие статьи не работает, если вы используете HttpsURLConnection
на JRE Oracle, потому чтоsun.net.www.protocol.https.HttpsURLConnectionImpl
С помощью
используя ответ:
Я создал образец запроса и работать как шарм:
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;
}
Я надеюсь, что это поможет вам.
мы столкнулись с той же проблемой с немного разным поведением. Мы использовали библиотеку apache cxf для выполнения остальных вызовов. Для нас патч работал нормально, пока мы не разговаривали с нашими поддельными службами, которые работали по 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
теперь реальная причина неудача-вот что!--8-->
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"
};
и мы видим, что нет никакого метода исправления, поэтому ошибка имела смысл. Мы пробовали много разных вещей и смотрели на переполнение стека. Единственным разумным ответом было использование reflection для изменения переменной methods для ввода другого значения "PATCH". Но почему-то мы не были убеждены использовать это, так как решение было своего рода взломом и слишком много работы и может оказать влияние, поскольку у нас была общая библиотека, чтобы сделать все соединения и выполнить эти остальные звонки.
но затем мы поняли, что сама библиотека 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.
надеюсь, этот ответ поможет кому-то одному.
еще одно грязное решение для взлома-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);
}
вы можете найти подробное решение, которое может работать даже если у вас нет прямого доступа к HttpUrlConnection
(как при работе с клиентом Джерси здесь: запрос патча с использованием клиента Джерси
если ваш сервер использует 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]
.
пока принятый ответ работает, я хочу добавить одну деталь. В новом APIs 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
to KITKAT
после тестирования в API 16, 19, 21.
Если проект находится на Spring / Gradle; следующее решение будет тренировки.
для построения.gradle, добавьте следующую зависимость;
compile('org.apache.httpcomponents:httpclient:4.5.2')
и определите следующий компонент в своем классе @SpringBootApplication внутри com.компания.проект;
@Bean
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setReadTimeout(600000);
requestFactory.setConnectTimeout(600000);
return new RestTemplate(requestFactory);
}
это решение сработало для меня.
Я получил свой с клиентом Джерси. Решение было:
Client client = ClientBuilder.newClient();
client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);