studio programacion para móviles libro edición desarrollo desarrollar curso aprende aplicaciones java httpurlconnection

java - programacion - HttpURLConnection Método HTTP no válido: PARCHE



manual de programacion android pdf (13)

Cuando trato de usar un método HTTP no estándar como PATCH con URLConnection:

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

Me sale una excepción:

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

Usar una API de nivel más alto como Jersey genera el mismo error. ¿Existe una solución alternativa para emitir una solicitud PATCH HTTP?


En el emulador de API 16 recibí una excepción: java.net.ProtocolException: Unknown method ''PATCH''; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE] 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 lo siguiente:

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 a KITKAT después de probar en API 16, 19, 21.


Hay muchas buenas respuestas, así que aquí está el mío:

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 utiliza la reflexión, pero en lugar de piratear cada objeto de conexión, estamos pirateando el campo estático HttpURLConnection # methods que se usa en los controles internos.


Hay un error de Will not Fix 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 usar 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>


La reflexión descrita en esta publicación y en una publicación relacionada no funciona si está utilizando una HttpsURLConnection en JRE de Oracle, porque sun.net.www.protocol.https.HttpsURLConnectionImpl está utilizando el campo de method de la java.net.HttpURLConnection de DelegateHttpsURLConnection !

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


Nos hemos enfrentado al mismo problema con un comportamiento ligeramente diferente. Estábamos usando la biblioteca apache cxf para hacer las llamadas de descanso. Para nosotros, PATCH funcionaba bien hasta que estábamos hablando con nuestros servicios falsos que funcionaban en http. En el momento en que nos integramos con los sistemas reales (que estaban por encima de https) comenzamos a enfrentar el mismo problema con el siguiente seguimiento de pila.

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 tanto, el error tiene sentido. Probamos muchas cosas diferentes y revisamos el desbordamiento de la pila. La única respuesta razonable fue usar 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 truco y es demasiado trabajo y podría tener un impacto ya que teníamos una biblioteca común para hacer toda la conexión y realizar estas llamadas REST.

Pero luego nos dimos cuenta de que la biblioteca cxf está manejando la excepción y hay un código escrito en el bloque catch para agregar el método que falta usando reflection.

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 manejador de excepciones y nuestro trabajo se realiza porque de manera predeterminada la variable será asignado a falso debido al código de abajo

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

Así que aquí está lo que teníamos 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 biblioteca cxf en sí.

Espero que esta respuesta ayude a alguien.


Otra solución de hack 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); }


Puede encontrar una solución detallada que puede funcionar incluso si no tiene acceso directo a HttpUrlConnection (como cuando trabaja con Jersey Client aquí: Petición PATCH utilizando Jersey Client


Sí, hay una solución para esto. Utilizar

X-HTTP-Method-Override

. Este encabezado se puede usar en una solicitud POST para "falsificar" otros métodos HTTP. Simplemente establezca el valor del encabezado X-HTTP-Method-Override en el método HTTP que le gustaría realizar realmente. Entonces usa el siguiente código.

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


Si su servidor utiliza ASP.NET Core, puede simplemente agregar el siguiente código para especificar el método HTTP utilizando el encabezado X-HTTP-Method-Override , tal 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 en el app.UseMvc() . app.UseMvc() antes de llamar a la app.UseMvc() .


Tengo el mío con el cliente de Jersey. La solución fue:

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


Tuve la misma excepción y escribí la solución de sockets (en groovy) pero lo transcribo en el formulario de respuesta a java por usted:

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. Tienes que cambiar el servidor y el número de puerto. Recuerda también cambiar el encabezado del host. Tal vez tengas que atrapar alguna excepción.

Atentamente


Usando la respuesta:

HttpURLConnection Método HTTP no válido: PARCHE

Me crearon una solicitud de muestra y funcionan 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


Si el proyecto está en Spring / Gradle ; la siguiente solución se ejercitará.

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