HttpURLConnection Недійсний метод HTTP: PATCH


81

Коли я намагаюся використовувати нестандартний метод 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 вищого рівня, такого як Джерсі, генерує ту саму помилку. Чи є обхідне рішення для видачі запиту PATCH HTTP?

Відповіді:


48

Так, для цього є обхідне рішення. Використовуйте

Перевизначення методу X-HTTP

. Цей заголовок можна використовувати в запиті POST для «підробки» інших методів HTTP. Просто встановіть значення заголовка X-HTTP-Method-Override на метод HTTP, який ви хотіли б насправді виконати. Тому використовуйте наступний код.

conn.setRequestProperty("X-HTTP-Method-Override", "PATCH");
conn.setRequestMethod("POST");

34
Це працює, лише якщо кінець, що отримує, підтримує це. Він все ще надсилає "POST" по рядку.
Джозеф Хакінта

3
Якщо приймач підтримує це, то (для мене) це найчистіший спосіб продовжити.
Максим Т,

4
Цей метод працює під час використання HttpUrlConnection для виклику Firebase REST API.
Ендрю Келлі,

1
@DuanBressan, протокол не повинен бути проблемою, доки сервер підтримує або те, і інше (хоча він повинен приймати лише підключення до HTTPS.)
Alexis Wilke,

3
Це не вірна відповідь, оскільки вона не вирішує проблему на стороні Java. Сервер повинен дозволити вам користуватися POSTі повинен розуміти X-HTTP-Method-Overrideполе. Див. Stackoverflow.com/a/46323891/3647724 для кращого фактичного виправлення
Фейрелл

51

Є багато хороших відповідей, тому ось моя (не працює в 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 #, яке використовується для внутрішніх перевірок.


7
Дуже хороша відповідь, вона повинна бути прийнятою, оскільки вона вирішує фактичну проблему і не пропонує обхідний шлях, який залежить від приймаючого сервера
Фейрелл,

1
насправді це рішення працює як заклинання, оскільки те, що використовує властивість override, залежить від сервера (і в моєму випадку не спрацювало) ...
Amichai Ungar

Це все ще працює з Java 9? Або модуль обмежує це
CLOVIS

2
Спробував це з JDK12, але я отримав "java.lang.NoSuchFieldException: модифікатори"
Кін Ченг,


33

У OpenJDK є помилка Won't Fix для цього: https://bugs.openjdk.java.net/browse/JDK-7016595

Однак із Apache Http-Components Client 4.2+ це можливо. Він має власну мережеву реалізацію, тому можливе використання нестандартних методів HTTP, таких як PATCH. Він навіть має клас HttpPatch, що підтримує метод виправлення.

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPatch httpPatch = new HttpPatch(new URI("http://example.com"));
CloseableHttpResponse response = httpClient.execute(httpPatch);

Координати Мейвена:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.2+</version>
</dependency>

17

Якщо проект ведеться на 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);
 }

Ці рішення працювали для мене.


для Spring dev це найчистіше рішення: повернути новий RestTemplate (new (HttpComponentsClientHttpRequestFactory));
John Tribe

Дякую @hirosht. Найпростіший і найпростіший спосіб вирішити це у весняному додатку.
Даніель Камараса

4

Я мав такий самий виняток і писав рішення для сокетів (у 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. Вам доведеться змінити номер сервера та порту, пам’ятайте також про зміну заголовка хосту, і, можливо, вам доведеться зловити якийсь виняток.

З найкращими побажаннями


1
Не діє. Термінатор лінії в HTTP вказується як \r\n, а не як println()передбачено.
user207421

4

Відображення, як описано в цій публікації та пов’язаній публікації , не працює, якщо ви використовуєте 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);
    }
}

1
Якщо ви вже використовуєте рефлексію, чому б просто не додати метод "PATCH", переписавши значення java.net.HttpURLConnection # методів один раз за час життя програми?
okutane

Влучне зауваження. Однак моя відповідь лише на те, щоб показати, як має працювати запропоноване рішення, а не на те, щоб показати інше рішення
rmuller,

@okutane, не могли б ви надати невелику підказку, як ми можемо переписати методи? тому що я бачив у ньому мало повідомлень, які говорять про делегатів у ньому
Кодер

@Dhamayanthi Я опублікував окрему відповідь на це.
okutane

не могли б ви поділитися посиланням?
Кодер

2

Використовуючи відповідь:

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

Сподіваюся, це допоможе вам.


Ваше рішення працює, якщо підключений сервер приймає та інтерпретує заголовок запиту "X-HTTP-Method-Override". Тому ваше рішення не можна використовувати у всіх випадках.
Еммануель Дево

2

Для тих, хто користується 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 у своїй відповіді .


1

Ще одне брудне рішення хак - це рефлексія:

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

Працює для з'єднань http, але не для https. Клас sun.net.www.protocol.https.HttpsURLConnectionImpl використовує поле "делегат", що містить фактичне з'єднання URL-адреси. Тому натомість повинні змінитися.
Per Cederberg

0

Ви можете знайти детальне рішення, яке може працювати, навіть якщо у вас немає прямого доступу до HttpUrlConnection(наприклад, при роботі з Jersey Client тут: запит PATCH за допомогою Jersey Client


Дякую за вашу відповідь. Але що, на вашу думку, краще, використовувати прямий httpUrlConnection або Jersey Client?
Дуан Брессан,

0

Якщо ваш сервер використовує 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().


0

В емуляторі 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.


0

Я отримав свій з клієнтом Джерсі. Обхідним шляхом було:

Client client = ClientBuilder.newClient();
client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);

0

Ми зіткнулися з тією ж проблемою з дещо іншою поведінкою. Для здійснення інших дзвінків ми використовували бібліотеку 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.

Сподіваюся, ця відповідь допоможе комусь.


0
 **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>

використовуйте це насправді це допоможе вам


0

У 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();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.