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?
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");
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>
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.
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);
}
}
En utilisant la réponse:
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.
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.
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.
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);
}
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()
.
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
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.
**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
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);