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 С помощью


используя ответ:

HttpURLConnection недопустимый метод HTTP: PATCH

Я создал образец запроса и работать как шарм:

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