Cuando intento usar un método HTTP no estándar como PATCH con URLConnection:
HttpURLConnection conn = (HttpURLConnection) new URL("http://example.com").openConnection();
conn.setRequestMethod("PATCH");
Obtengo una excepción:
java.net.ProtocolException: Invalid HTTP method: PATCH
at java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:440)
El uso de una API de nivel superior como Jersey genera el mismo error. ¿Existe una solución alternativa para emitir una solicitud PATCH HTTP?
java
httpurlconnection
kavai77
fuente
fuente
POST
y tiene que entender elX-HTTP-Method-Override
campo. Consulte stackoverflow.com/a/46323891/3647724 para una mejor solución realHay muchas buenas respuestas, así que aquí está la mía (no funciona en 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); } } }
También usa la reflexión, pero en lugar de piratear cada objeto de conexión, estamos pirateando el campo estático de métodos HttpURLConnection # que se usa en las comprobaciones internamente.
fuente
Hay un error que no se corrige en OpenJDK para esto: https://bugs.openjdk.java.net/browse/JDK-7016595
Sin embargo, con Apache Http-Components Client 4.2+ esto es posible. Tiene una implementación de red personalizada, por lo que es posible utilizar métodos HTTP no estándar como PATCH. Incluso tiene una clase HttpPatch que admite el método de parche.
CloseableHttpClient httpClient = HttpClients.createDefault(); HttpPatch httpPatch = new HttpPatch(new URI("http://example.com")); CloseableHttpResponse response = httpClient.execute(httpPatch);
Coordenadas de Maven:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.2+</version> </dependency>
fuente
Si el proyecto está en Spring / Gradle ; la siguiente solución funcionará.
Para build.gradle, agregue la siguiente dependencia;
compile('org.apache.httpcomponents:httpclient:4.5.2')
Y defina el siguiente bean en su clase @SpringBootApplication dentro de com.company.project;
@Bean public RestTemplate restTemplate() { HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); requestFactory.setReadTimeout(600000); requestFactory.setConnectTimeout(600000); return new RestTemplate(requestFactory); }
Esta solución funcionó para mí.
fuente
Tuve la misma excepción y escribí la solución de sockets (en groovy) pero traduzco el formulario de respuesta a Java para ti:
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; }
Creo que funciona en java. Debe cambiar el servidor y el número de puerto, recuerde cambiar también el encabezado del host y tal vez tenga que detectar alguna excepción.
Atentamente
fuente
\r\n
, no como lo queprintln()
proporciona.La reflexión como se describe en esta publicación y una publicación relacionada no funciona si está utilizando un
HttpsURLConnection
JRE de Oracle, ¡porquesun.net.www.protocol.https.HttpsURLConnectionImpl
está utilizando elmethod
campojava.net.HttpURLConnection
de suDelegateHttpsURLConnection
!Entonces, una solución de trabajo completa es:
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); } }
fuente
Usando la respuesta:
He creado una solicitud de muestra y trabajo como un encanto:
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; }
Espero que te ayude.
fuente
Para cualquiera que use Spring restTemplate y busque una respuesta detallada.
Enfrentará el problema si está utilizando SimpleClientHttpRequestFactory como ClientHttpRequestFactory de restTemplate.
Desde java.net.HttpURLConnection:
/* valid HTTP methods */ private static final String[] methods = { "GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE" };
Como PATCH no es una operación compatible, esta línea de código de la misma clase se ejecutará:
throw new ProtocolException("Invalid HTTP method: " + method);
Terminé usando lo mismo que sugirió @hirosht en su respuesta .
fuente
Otra solución de truco sucio es la reflexión:
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); }
fuente
Puede encontrar una solución detallada que puede funcionar incluso si no tiene acceso directo al
HttpUrlConnection
(como cuando trabaja con Jersey Client aquí: solicitud de PATCH usando Jersey Clientfuente
Si su servidor usa ASP.NET Core, simplemente puede agregar el siguiente código para especificar el método HTTP usando el encabezado
X-HTTP-Method-Override
, como se describe en la respuesta aceptada .app.Use((context, next) => { var headers = context.Request.Headers["X-HTTP-Method-Override"]; if(headers.Count == 1) { context.Request.Method = headers.First(); } return next(); });
Simplemente agregue este código
Startup.Configure
antes de su llamada aapp.UseMvc()
.fuente
En emulador de la API 16 recibí una excepción:
java.net.ProtocolException: Unknown method 'PATCH'; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE]
.Si bien una respuesta aceptada funciona, quiero agregar un detalle. En las nuevas API
PATCH
funciona bien, por lo que, junto con https://github.com/OneDrive/onedrive-sdk-android/issues/16 , debe escribir: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); }
Cambié
JELLY_BEAN_MR2
aKITKAT
después de probar en API 16, 19, 21.fuente
Conseguí el mío con un cliente de Jersey. La solución alternativa fue:
Client client = ClientBuilder.newClient(); client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);
fuente
Hemos enfrentado el mismo problema con un comportamiento ligeramente diferente. Estábamos usando la biblioteca apache cxf para hacer el resto de llamadas. Para nosotros, PATCH funcionaba bien hasta que hablamos con nuestros servicios falsos que funcionaban a través de http. En el momento en que nos integramos con los sistemas reales (que estaban sobre https), comenzamos a enfrentar el mismo problema con el seguimiento de la pila siguiente.
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]
El problema estaba sucediendo en esta línea de código
connection.setRequestMethod(httpRequestMethod); in URLConnectionHTTPConduit class of cxf library
Ahora la verdadera razón del fracaso es 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" };
Y podemos ver que no hay un método PATCH definido, por lo que el error tiene sentido. Probamos muchas cosas diferentes y revisamos el desbordamiento de la pila. La única respuesta razonable fue utilizar la reflexión para modificar la variable de métodos para inyectar otro valor "PATCH". Pero de alguna manera no estábamos convencidos de usar eso, ya que la solución era una especie de pirateo y es demasiado trabajo y podría tener un impacto, ya que teníamos una biblioteca común para hacer todas las conexiones y realizar estas llamadas REST.
Pero luego nos dimos cuenta de que la biblioteca cxf en sí misma está manejando la excepción y hay un código escrito en el bloque catch para agregar el método que falta usando la reflexión.
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; } }
Ahora, esto nos dio algunas esperanzas, así que dedicamos un tiempo a leer el código y descubrimos que si proporcionamos una propiedad para URLConnectionHTTPConduit.HTTPURL_CONNECTION_METHOD_REFLECTION, entonces podemos hacer que cxf ejecute el controlador de excepciones y nuestro trabajo se realiza ya que, de forma predeterminada, la variable será asignado a falso debido al código siguiente
DEFAULT_USE_REFLECTION = Boolean.valueOf(SystemPropertyAction.getProperty(HTTPURL_CONNECTION_METHOD_REFLECTION, "false"));
Así que esto es lo que tuvimos que hacer para que esto funcione.
WebClient.getConfig(client).getRequestContext().put("use.httpurlconnection.method.reflection", true);
o
WebClient.getConfig(client).getRequestContext().put(HTTPURL_CONNECTION_METHOD_REFLECTION, true);
Donde WebClient es de la propia biblioteca cxf.
Espero que esta respuesta ayude a alguien.
fuente
complemento de 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>
usa esto realmente te ayudaría
fuente
En java 11+ puede usar la clase HttpRequest para hacer lo que quiera:
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();
fuente