HttpURLConnection invalide méthode HTTP: PATCH

quand j'essaie d'utiliser une méthode HTTP non standard comme PATCH avec URLConnection:

    HttpURLConnection conn = (HttpURLConnection) new URL("http://example.com").openConnection();
    conn.setRequestMethod("PATCH");

j'obtiens une exception:

java.net.ProtocolException: Invalid HTTP method: PATCH
at java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:440)

utilisant une API de niveau supérieur comme Jersey génère la même erreur. Y a-t-il une solution de rechange pour émettre une requête HTTP PATCH?

37
demandé sur kavai77 2014-08-06 18:32:59

13 réponses

Oui il y a une solution. Use

X-HTTP-Method-Override

. Cet en-tête peut être utilisé dans une requête POST pour "truquer" d'autres méthodes HTTP. Il suffit de définir la valeur de l'en-tête X-HTTP-Method-Override à la méthode HTTP que vous souhaitez réellement exécuter. Utilisez donc le code suivant.

conn.setRequestProperty("X-HTTP-Method-Override", "PATCH");
conn.setRequestMethod("POST");
40
répondu sagar 2015-12-12 22:58:38

il y a un bug Won't Fix dans OpenJDK pour ceci: https://bugs.openjdk.java.net/browse/JDK-7016595

cependant, avec Apache Http-Components Client 4.2+ cela est possible. Il a une implémentation réseau personnalisée, donc l'utilisation de méthodes HTTP non standard comme PATCH est possible. Il a même une classe HttpPatch supportant la méthode patch.

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

Coordonnées Maven:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.2+</version>
</dependency>
21
répondu kavai77 2014-08-06 14:38:13

Il y a beaucoup de bonnes réponses, donc voici la mienne:

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

il utilise aussi la réflexion, mais au lieu de hacker chaque objet de connexion, nous hacker le champ statique HttpURLConnection#methods qui est utilisé dans les vérifications internes.

15
répondu okutane 2017-09-20 13:48:37

j'ai eu la même exception et j'ai écrit sockets solution (in groovy) mais je traslate dans le formulaire de réponse à java pour vous:

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

je pense que ça marche en java. Vous devez changer le serveur et le numéro de port.rappelez-vous de changer l'en-tête de L'hôte aussi et peut-être que vous devez attraper quelque exception.

Meilleures Salutations

4
répondu moralejaSinCuentoNiProverbio 2016-01-25 22:51:04

réflexion comme décrit dans ce post et un post lié ne fonctionne pas si vous utilisez un HttpsURLConnection sur Oracle JRE, parce que sun.net.www.protocol.https.HttpsURLConnectionImpl utilise le champ method du java.net.HttpURLConnection de son DelegateHttpsURLConnection !

donc une solution complète travailler est:

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);
    }
}
3
répondu rmuller 2017-05-23 11:47:26

à l'Aide de la réponse:

HttpURLConnection invalides méthode HTTP: PATCH

je me suis créé un exemple de demande et travailler comme un charme:

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

j'espère que ça vous aidera.

2
répondu Duan Bressan 2017-05-23 12:18:16

nous avons affronté le même problème avec un comportement légèrement différent. Nous utilisions la bibliothèque Apache cxf pour faire les autres appels. Pour nous, PATCH fonctionnait bien jusqu'à ce que nous parlions à nos faux services qui fonctionnaient sur http. Au moment où nous avons intégré les systèmes actuels (qui étaient sur https), nous avons commencé à faire face au même problème avec la suite de stack trace.

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]

question se produisait dans cette ligne de code

connection.setRequestMethod(httpRequestMethod); in URLConnectionHTTPConduit class of cxf library

Maintenant, la vraie raison de l'échec est que

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

et nous pouvons voir qu'il n'y a pas de méthode PATCH définie d'où le sens de l'erreur. Nous avons essayé beaucoup de choses différentes et regardé over stack overflow. La seule réponse raisonnable était d'utiliser la réflexion pour modifier la variable de méthodes pour injecter une autre valeur "PATCH". Mais d'une façon ou d'une autre, nous n'étions pas convaincus d'utiliser que la solution était une sorte de hack et est trop de travail et pourrait avoir un impact que nous avions bibliothèque commune à connectez-vous et passez ces appels de repos.

mais ensuite nous avons réalisé que la bibliothèque cxf elle-même gère l'exception et il y a du code écrit dans le bloc catch pour ajouter la méthode manquante en utilisant la réflexion.

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

maintenant cela nous a donné quelques espoirs, donc nous avons passé du temps à lire le code et avons trouvé que si nous fournissons une propriété pour URLConnectionHTTPConduit.HTTPURL_CONNECTION_METHOD_REFLECTION alors nous pouvons faire cxf pour exécuter la exception handler et notre travail est fait car par défaut la variable sera assignée à false en raison du code ci-dessous

DEFAULT_USE_REFLECTION = 
        Boolean.valueOf(SystemPropertyAction.getProperty(HTTPURL_CONNECTION_METHOD_REFLECTION, "false"));

alors voici ce que nous avons dû faire pour faire ce travail

WebClient.getConfig(client).getRequestContext().put("use.httpurlconnection.method.reflection", true);

ou

WebClient.getConfig(client).getRequestContext().put(HTTPURL_CONNECTION_METHOD_REFLECTION, true);

où WebClient est de la bibliothèque cxf elle-même.

J'espère que cette réponse aidera quelqu'un.

1
répondu Sanjay Bharwani 2018-04-17 09:23:52

Autre sale hack solution est la réflexion:

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);
 }
0
répondu dreamtangerine 2016-09-21 23:06:11

vous pouvez trouver une solution détaillée qui peut fonctionner même si vous n'avez pas un accès direct au HttpUrlConnection (comme en travaillant avec le client Jersey ici: demande de correctif en utilisant le Client Jersey

0
répondu Daniel L. 2017-05-23 11:47:26

si votre serveur utilise ASP.NET Core, vous pouvez simplement ajouter le code suivant pour spécifier la méthode HTTP en utilisant l'en-tête X-HTTP-Method-Override , comme décrit dans le accepted answer .

app.Use((context, next) => {
    var headers = context.Request.Headers["X-HTTP-Method-Override"];
    if(headers.Count == 1) {
        context.Request.Method = headers.First();
    }
    return next();
});

ajoutez simplement ce code dans Startup.Configure avant votre appel à app.UseMvc() .

0
répondu Peppe L-G 2017-05-23 12:18:14

dans l'émulateur de API 16 j'ai reçu une exception: java.net.ProtocolException: Unknown method 'PATCH'; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE] .

bien qu'une réponse acceptée fonctionne, je veux ajouter un détail. Dans la nouvelle API PATCH fonctionne bien, donc en conjonction avec https://github.com/OneDrive/onedrive-sdk-android/issues/16 vous devez écrire:

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

j'ai changé JELLY_BEAN_MR2 en KITKAT après les tests dans API 16, 19, 21.

0
répondu CoolMind 2017-05-18 08:38:40

si le projet est sur le ressort/Grad ; la solution suivante fonctionnera.

pour la construction.Grad, ajouter la dépendance suivante:

compile('org.apache.httpcomponents:httpclient:4.5.2')

et définissez la fève suivante dans votre classe @SpringBootApplication à l'intérieur de la COM.entreprise.projet;

 @Bean
 public RestTemplate restTemplate() {
  HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
  requestFactory.setReadTimeout(600000);
  requestFactory.setConnectTimeout(600000);
  return new RestTemplate(requestFactory);
 }

ces solutions ont fonctionné pour moi.

0
répondu hirosht 2017-10-24 10:18:00

j'ai le mien avec un client De Jersey. La solution était:

Client client = ClientBuilder.newClient();
client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);
0
répondu Daniel Jipa 2018-04-15 09:46:38