web-dev-qa-db-fra.com

HttpURLConnection Méthode HTTP non valide: PATCH

Lorsque j'essaie d'utiliser une méthode HTTP non standard telle que PATCH avec URLConnection:

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

Je reçois une exception:

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

L'utilisation d'une API de niveau supérieur, telle que Jersey, génère la même erreur. Existe-t-il une solution de contournement pour émettre une requête HTTP PATCH?

43
kavai77

Oui, il existe une solution de contournement pour cela. Utilisation 

X-HTTP-Method-Override

. Cet en-tête peut être utilisé dans une requête POST pour "simuler" d'autres méthodes HTTP. Définissez simplement la valeur de l'en-tête X-HTTP-Method-Override sur 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");
43
sagar

Il y a un bogue Wont't Fix dans OpenJDK pour cela: 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, ce qui permet d'utiliser des méthodes HTTP non standard telles que PATCH. 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>
24
kavai77

Il y a beaucoup de bonnes réponses, alors 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 également la réflexion, mais au lieu de pirater tous les objets de connexion, nous piratons le champ statique des méthodes HttpURLConnection # utilisé en interne dans les contrôles.

22
okutane

J'ai eu la même exception et écrit des sockets solution (en groovy) mais je traduis sous forme de réponse en 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 cela fonctionne en Java. Vous devez changer le serveur et le numéro de port, n'oubliez pas de changer l'en-tête de l'hôte et vous devez peut-être intercepter une exception.

Meilleures salutations

La réflexion décrite dans cet article et un article lié ne fonctionne pas si vous utilisez un HttpsURLConnection sur l'environnement JRE d'Oracle, carSun.net.www.protocol.https.HttpsURLConnectionImpl utilise le champ method du Java.net.HttpURLConnection de son DelegateHttpsURLConnection!

Donc, une solution complète working 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
rmuller

En utilisant la réponse:

HttpURLConnection Méthode HTTP non valide: PATCH

J'ai créé un exemple de demande et je travaille 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 cela vous aidera.

2
Duan Bressan

Si le projet est sur Spring/Gradle ; la solution suivante fonctionnera. 

Pour le build.gradle, ajoutez la dépendance suivante;

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

Et définissez le bean suivant dans votre classe @SpringBootApplication dans le fichier Com.company.project;

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

Cette solution a fonctionné pour moi.

2
hirosht

Nous avons rencontré le même problème avec un comportement légèrement différent. Nous utilisions la bibliothèque Apache cxf pour passer les appels restants . Pour nous, PATCH fonctionnait correctement jusqu'à ce que nous parlions à nos faux services qui fonctionnaient sur http . Le moment où nous avons intégré les systèmes réels (qui étaient terminés) https) nous avons commencé à faire face au même problème avec le suivi de la pile.

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]

Un problème 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ù l'erreur prenait sens .. Nous avons essayé beaucoup de choses différentes et avons examiné le débordement de pile. La seule réponse raisonnable était d'utiliser la réflexion pour modifier la variable méthodes afin d'injecter une autre valeur "PATCH". Mais d’une manière ou d’une autre, nous n’étions pas convaincus de l’utiliser car la solution était un peu fastidieuse et demandait trop de travail et pourrait avoir un impact, car nous avions une bibliothèque commune pour établir toutes les connexions et effectuer ces appels REST.

Mais nous avons alors réalisé que la bibliothèque cxf elle-même gérait l'exception et qu'il y avait du code écrit dans le bloc catch pour ajouter la méthode manquante à l'aide de 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. Nous avons donc passé un peu de temps à lire le code et avons découvert que si nous fournissons une propriété à URLConnectionHTTPConduit.HTTPURL_CONNECTION_METHOD_REFLECTION, nous pouvons créer cxf pour exécuter le gestionnaire d'exceptions et que notre travail sera effectué comme par défaut la variable sera assigné à faux en raison du code ci-dessous

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

Voici donc ce que nous avons dû faire pour que cela fonctionne

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

ou 

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

Où WebClient provient de la bibliothèque cxf elle-même.

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

1
Sanjay Bharwani

Une autre solution de hack sale 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
dreamtangerine

Si votre serveur utilise ASP.NET Core, vous pouvez simplement ajouter le code suivant pour spécifier la méthode HTTP à l'aide de l'en-tête X-HTTP-Method-Override, comme décrit dans le réponse acceptée

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
Peppe L-G

Vous pouvez trouver une solution détaillée qui peut fonctionner même si vous n'avez pas d'accès direct à la HttpUrlConnection (comme lorsque vous travaillez avec Jersey Client ici: Demande PATCH utilisant Jersey Client

0
Daniel L.

Dans l'émulateur de l'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 souhaite ajouter un détail. Dans les nouvelles API, PATCH fonctionne bien. Ainsi, en conjonction avec https://github.com/OneDrive/onedrive-sdk-Android/issues/16 vous devriez é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 avoir testé dans les API 16, 19 et 21.

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


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

utilisez vraiment cela vous aiderait

0
perumal ks

J'ai eu le mien avec un client de Jersey… .. La solution de contournement était:

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