Saltar al contenido

¿Cómo usar java.net.URLConnection para disparar y manejar solicitudes HTTP?

Hemos estado recabado en el mundo on line y de esta manera darte la respuesta a tu duda, si tienes dudas deja la pregunta y contestaremos sin falta, porque estamos para servirte.

Solución:

Primero, un descargo de responsabilidad de antemano: los fragmentos de código publicados son todos ejemplos básicos. Necesitarás manejar trivial IOExceptionarena RuntimeExceptiones como NullPointerException, ArrayIndexOutOfBoundsException y te consolas.


Preparando

Primero necesitamos saber al menos la URL y el juego de caracteres. Los parámetros son opcionales y dependen de los requisitos funcionales.

String url = "http://example.com";
String charset = "UTF-8";  // Or in Java 7 and later, use the constant: java.nio.charset.StandardCharsets.UTF_8.name()
String param1 = "value1";
String param2 = "value2";
// ...

String query = String.format("param1=%s¶m2=%s", 
     URLEncoder.encode(param1, charset), 
     URLEncoder.encode(param2, charset));

Los parámetros de consulta deben estar en name=value formato y ser concatenados por &. Normalmente, también codificaría en URL los parámetros de consulta con el juego de caracteres especificado usando URLEncoder#encode().

los String#format() es solo por conveniencia. Lo prefiero cuando necesitaría el operador de concatenación de cadenas + mas de dos veces.


Activando una solicitud HTTP GET con (opcionalmente) parámetros de consulta

Es una tarea trivial. Es el solicitud predeterminada método.

URLConnection connection = new URL(url + "?" + query).openConnection();
connection.setRequestProperty("Accept-Charset", charset);
InputStream response = connection.getInputStream();
// ...

Cualquier cadena de consulta debe concatenarse a la URL usando ?. los Accept-Charset El encabezado puede indicarle al servidor en qué codificación están los parámetros. Si no envía ninguna cadena de consulta, puede dejar el Accept-Charset encabezado de distancia. Si no necesita establecer ningún encabezado, incluso puede usar el URL#openStream() método de atajo.

InputStream response = new URL(url).openStream();
// ...

De cualquier manera, si el otro lado es un HttpServlet, Entonces es doGet() se llamará al método y los parámetros estarán disponibles por HttpServletRequest#getParameter().

Con fines de prueba, puede imprimir el cuerpo de la respuesta en stdout como se muestra a continuación:

try (Scanner scanner = new Scanner(response)) 
    String responseBody = scanner.useDelimiter("\A").next();
    System.out.println(responseBody);


Activando una solicitud HTTP POST con parámetros de consulta

Establecer el URLConnection#setDoOutput() para true establece implícitamente el método de solicitud en POST. El HTTP POST estándar como lo hacen los formularios web es de tipo application/x-www-form-urlencoded donde la cadena de consulta se escribe en el cuerpo de la solicitud.

URLConnection connection = new URL(url).openConnection();
connection.setDoOutput(true); // Triggers POST.
connection.setRequestProperty("Accept-Charset", charset);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + charset);

try (OutputStream output = connection.getOutputStream()) 
    output.write(query.getBytes(charset));


InputStream response = connection.getInputStream();
// ...

Nota: siempre que desee enviar un formulario HTML mediante programación, no olvide tomar el name=value pares de cualquier elementos en la cadena de consulta y, por supuesto, también el name=value par de la elemento que le gustaría “presionar” programáticamente (porque generalmente se usa en el lado del servidor para distinguir si se presionó un botón y, de ser así, cuál).

También puedes lanzar el obtenido URLConnection para HttpURLConnection y usa su HttpURLConnection#setRequestMethod() en lugar de. Pero si está tratando de usar la conexión para la salida, aún necesita configurar URLConnection#setDoOutput() para true.

HttpURLConnection httpConnection = (HttpURLConnection) new URL(url).openConnection();
httpConnection.setRequestMethod("POST");
// ...

De cualquier manera, si el otro lado es un HttpServlet, Entonces es doPost() se llamará al método y los parámetros estarán disponibles por HttpServletRequest#getParameter().


Realmente disparando la solicitud HTTP

Puede disparar la solicitud HTTP explícitamente con URLConnection#connect(), pero la solicitud se activará automáticamente a pedido cuando desee obtener información sobre la respuesta HTTP, como el cuerpo de la respuesta mediante URLConnection#getInputStream() etcétera. Los ejemplos anteriores hacen exactamente eso, por lo que el connect() de hecho, la llamada es superflua.


Recopilación de información de respuesta HTTP

  1. Estado de respuesta HTTP:

Necesitas un HttpURLConnection aquí. Lanzarlo primero si es necesario.

    int status = httpConnection.getResponseCode();
  1. Encabezados de respuesta HTTP:

     for (Entry> header : connection.getHeaderFields().entrySet()) 
         System.out.println(header.getKey() + "=" + header.getValue());
     
    
  2. Codificación de respuesta HTTP:

Cuando el Content-Type contiene una charset , entonces el cuerpo de la respuesta probablemente esté basado en texto y nos gustaría procesar el cuerpo de la respuesta con la codificación de caracteres especificada del lado del servidor.

    String contentType = connection.getHeaderField("Content-Type");
    String charset = null;

    for (String param : contentType.replace(" ", "").split(";")) 
        if (param.startsWith("charset=")) 
            charset = param.split("=", 2)[1];
            break;
        
    

    if (charset != null) 
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(response, charset))) 
            for (String line; (line = reader.readLine()) != null;) 
                // ... System.out.println(line) ?
            
        
     else 
        // It's likely binary content, use InputStream/OutputStream.
    

Manteniendo la sesión

La sesión del lado del servidor suele estar respaldada por una cookie. Algunos formularios web requieren que inicie sesión y / o que una sesión realice un seguimiento. Puedes usar el CookieHandler API para mantener cookies. Necesitas preparar un CookieManager con un CookiePolicy de ACCEPT_ALL antes de enviar todas las solicitudes HTTP.

// First set the default cookie manager.
CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));

// All the following subsequent URLConnections will use the same cookie manager.
URLConnection connection = new URL(url).openConnection();
// ...

connection = new URL(url).openConnection();
// ...

connection = new URL(url).openConnection();
// ...

Tenga en cuenta que se sabe que esto no siempre funciona correctamente en todas las circunstancias. Si le falla, lo mejor es recopilar y configurar manualmente los encabezados de las cookies. Básicamente necesitas agarrar todo Set-Cookie encabezados de la respuesta del inicio de sesión o el primero GET request y luego pasar esto a través de las solicitudes posteriores.

// Gather all cookies on the first request.
URLConnection connection = new URL(url).openConnection();
List cookies = connection.getHeaderFields().get("Set-Cookie");
// ...

// Then use the same cookies on all subsequent requests.
connection = new URL(url).openConnection();
for (String cookie : cookies) 
    connection.addRequestProperty("Cookie", cookie.split(";", 2)[0]);

// ...

los split(";", 2)[0] ¿Existe para deshacerse de los atributos de las cookies que son irrelevantes para el lado del servidor como expires, path, etc. Alternativamente, también puede usar cookie.substring(0, cookie.indexOf(';')) en lugar de split().


Modo de transmisión

los HttpURLConnection almacenará por defecto el completo el cuerpo de la solicitud antes de enviarlo, independientemente de si usted mismo ha establecido una longitud de contenido fija utilizando connection.setRequestProperty("Content-Length", contentLength);. Esto puede causar OutOfMemoryExceptions cada vez que envía simultáneamente grandes solicitudes POST (por ejemplo, carga de archivos). Para evitar esto, le gustaría configurar el HttpURLConnection#setFixedLengthStreamingMode().

httpConnection.setFixedLengthStreamingMode(contentLength);

Pero si la longitud del contenido realmente no se conoce de antemano, entonces puede hacer uso del modo de transmisión fragmentada configurando el HttpURLConnection#setChunkedStreamingMode() respectivamente. Esto establecerá el HTTP Transfer-Encoding encabezado a chunked lo que obligará a que el cuerpo de la solicitud se envíe en trozos. El siguiente ejemplo enviará el cuerpo en trozos de 1 KB.

httpConnection.setChunkedStreamingMode(1024);

Agente de usuario

Puede suceder que una solicitud devuelva una respuesta inesperada, mientras que funciona bien con un navegador web real. El lado del servidor probablemente está bloqueando las solicitudes basadas en el User-Agent encabezado de solicitud. los URLConnection lo configurará de forma predeterminada en Java/1.6.0_19 donde la última parte es obviamente la versión JRE. Puede anular esto de la siguiente manera:

connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"); // Do as if you're using Chrome 41 on Windows 7.

Utilice la cadena de usuario-agente de un navegador reciente.


Manejo de errores

Si el código de respuesta HTTP es 4nn (Error del cliente) o 5nn (Error del servidor), es posible que desee leer el HttpURLConnection#getErrorStream() para ver si el servidor ha enviado alguna información de error útil.

InputStream error = ((HttpURLConnection) connection).getErrorStream();

Si el código de respuesta HTTP es -1, entonces algo salió mal con la conexión y el manejo de la respuesta. los HttpURLConnection La implementación en los JRE más antiguos tiene algunos errores para mantener vivas las conexiones. Es posible que desee apagarlo configurando el http.keepAlive propiedad del sistema para false. Puede hacer esto mediante programación al comienzo de su aplicación de la siguiente manera:

System.setProperty("http.keepAlive", "false");

Subiendo archivos

Normalmente usarías multipart/form-data codificación para contenido POST mixto (datos binarios y de caracteres). La codificación se describe con más detalle en RFC2388.

String param = "value";
File textFile = new File("/path/to/file.txt");
File binaryFile = new File("/path/to/file.bin");
String boundary = Long.toHexString(System.currentTimeMillis()); // Just generate some unique random value.
String CRLF = "rn"; // Line separator required by multipart/form-data.
URLConnection connection = new URL(url).openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

try (
    OutputStream output = connection.getOutputStream();
    PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, charset), true);
) 
    // Send normal param.
    writer.append("--" + boundary).append(CRLF);
    writer.append("Content-Disposition: form-data; name="param"").append(CRLF);
    writer.append("Content-Type: text/plain; charset=" + charset).append(CRLF);
    writer.append(CRLF).append(param).append(CRLF).flush();

    // Send text file.
    writer.append("--" + boundary).append(CRLF);
    writer.append("Content-Disposition: form-data; name="textFile"; filename="" + textFile.getName() + """).append(CRLF);
    writer.append("Content-Type: text/plain; charset=" + charset).append(CRLF); // Text file itself must be saved in this charset!
    writer.append(CRLF).flush();
    Files.copy(textFile.toPath(), output);
    output.flush(); // Important before continuing with writer!
    writer.append(CRLF).flush(); // CRLF is important! It indicates end of boundary.

    // Send binary file.
    writer.append("--" + boundary).append(CRLF);
    writer.append("Content-Disposition: form-data; name="binaryFile"; filename="" + binaryFile.getName() + """).append(CRLF);
    writer.append("Content-Type: " + URLConnection.guessContentTypeFromName(binaryFile.getName())).append(CRLF);
    writer.append("Content-Transfer-Encoding: binary").append(CRLF);
    writer.append(CRLF).flush();
    Files.copy(binaryFile.toPath(), output);
    output.flush(); // Important before continuing with writer!
    writer.append(CRLF).flush(); // CRLF is important! It indicates end of boundary.

    // End of multipart/form-data.
    writer.append("--" + boundary + "--").append(CRLF).flush();

Si el otro lado es un HttpServlet, Entonces es doPost() se llamará al método y las partes estarán disponibles por HttpServletRequest#getPart() (nota, así nogetParameter() ¡etcétera!). los getPart() Sin embargo, el método es relativamente nuevo, se introdujo en Servlet 3.0 (Glassfish 3, Tomcat 7, etc.). Antes de Servlet 3.0, su mejor opción es utilizar Apache Commons FileUpload para analizar un multipart/form-data solicitud. También vea esta respuesta para ver ejemplos de los enfoques FileUpload y Servelt 3.0.


Tratar con sitios HTTPS que no son de confianza o están mal configurados

A veces necesitas conectar una URL HTTPS, quizás porque estás escribiendo un raspador web. En ese caso, es probable que se enfrente a una javax.net.ssl.SSLException: Not trusted server certificate en algunos sitios HTTPS que no mantienen actualizados sus certificados SSL, o un java.security.cert.CertificateException: No subject alternative DNS name matching [hostname] found o javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name en algunos sitios HTTPS mal configurados.

La siguiente ejecución única static inicializador en su clase de raspador web debería hacer HttpsURLConnection más indulgente en cuanto a esos sitios HTTPS y, por lo tanto, ya no lanzar esas excepciones.

static 
    TrustManager[] trustAllCertificates = new TrustManager[] 
        new X509TrustManager() 
            @Override
            public X509Certificate[] getAcceptedIssuers() 
                return null; // Not relevant.
            
            @Override
            public void checkClientTrusted(X509Certificate[] certs, String authType) 
                // Do nothing. Just allow them all.
            
            @Override
            public void checkServerTrusted(X509Certificate[] certs, String authType) 
                // Do nothing. Just allow them all.
            
        
    ;

    HostnameVerifier trustAllHostnames = new HostnameVerifier() 
        @Override
        public boolean verify(String hostname, SSLSession session) 
            return true; // Just allow them all.
        
    ;

    try 
        System.setProperty("jsse.enableSNIExtension", "false");
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCertificates, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(trustAllHostnames);
    
    catch (GeneralSecurityException e) 
        throw new ExceptionInInitializerError(e);
    


Ultimas palabras

Apache HttpComponents HttpClient es mucho más conveniente en todo esto 🙂

  • Tutorial de HttpClient
  • Ejemplos de HttpClient

Analizar y extraer HTML

Si todo lo que desea es analizar y extraer datos de HTML, entonces mejor use un analizador HTML como Jsoup

  • ¿Cuáles son las ventajas y desventajas de los principales analizadores de HTML en Java?
  • Cómo escanear y extraer una página web en Java

Cuando se trabaja con HTTP, casi siempre es más útil hacer referencia a HttpURLConnection en lugar de la clase base URLConnection (ya que URLConnection es una clase abstracta cuando pides URLConnection.openConnection() en una URL HTTP, eso es lo que obtendrá de todos modos).

Entonces puedes, en lugar de confiar en URLConnection#setDoOutput(true) para establecer implícitamente el método de solicitud en CORREO en lugar de hacerlo httpURLConnection.setRequestMethod("POST") que algunos pueden encontrar más natural (y que también le permite especificar otros métodos de solicitud como PONER, ELIMINAR, …).

También proporciona constantes HTTP útiles para que pueda hacer lo siguiente:

int responseCode = httpURLConnection.getResponseCode();

if (responseCode == HttpURLConnection.HTTP_OK) {

Inspirado por esta y otras preguntas sobre SO, he creado un cliente http básico de código abierto mínimo que incorpora la mayoría de las técnicas que se encuentran aquí.

google-http-java-client también es un gran recurso de código abierto.

Comentarios y calificaciones del artículo

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *