Cómo enviar solicitudes HTTP en Java

El Protocolo de transferencia de hipertexto (HTTP) es un protocolo de capa de aplicación que, sin exagerar, es prácticamente la columna vertebral de la navegación por Internet, como sabemos...

Introducción

Protocolo de transferencia de hipertexto (HTTP) es un protocolo de capa de aplicación que, sin exagerar, es prácticamente la columna vertebral de la navegación por Internet tal como la conocemos.

Se usa para transferir documentos hipermedia entre el cliente y el servidor y es una parte esencial de cada aplicación web, incluidas las API que usan la arquitectura REST.

Permite que el navegador se comunique con el servidor mediante el envío de solicitudes de ciertos documentos, ya sean documentos HTML (devueltos como las páginas que vemos) o los hipermedios (imágenes, videos, etc.) que se sirven en las páginas.

¿Cómo funciona HTTP?

Cuando decidimos visitar un sitio web, lo que sucede detrás de las cortinas es que nuestra computadora genera y envía una solicitud al servidor en el que está alojado el sitio web.

Una solicitud HTTP podría verse así:

1
2
3
4
GET /tag/java/ HTTP/1.1
Host: wikihtp.com
Accept: */*
User-Agent: Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion

Aquí, solicitamos que el servidor envíe la página bajo la URL wikihtp.com/tag/java/ usando HTTP versión 1.1.

Lo que el servidor debería responder es algo como esto:

1
2
3
4
5
6
7
HTTP/1.1 200 OK
Date: Fri, 01 Feb 2019 22:07:06 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: __cfduid=dd40ff971a6cefddc4855e028541f79581549051234; expires=Sat, 01-Feb-20 22:07:06 GMT; path=/; domain=.wikihtp.com; HttpOnly; Secure
...

Seguido por el cuerpo de respuesta:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>java - Page 1 - Stack Abuse</title>
    <meta name="description" content="" />
    <meta name="keywords" content="">

<!-- rest of the page -->

El cuerpo de la respuesta se mostrará en nuestro navegador y nos recibirá una página.

wikihtp_java

Además del código de estado HTTP/1.1 200 OK que hemos recibido como respuesta, hay varios otros que probablemente haya encontrado en su vida diaria:

  • 1xx: cualquier estado que comience con '1' es un estado informativo. La solicitud es recibida y está siendo procesada.
  • 2xx: cualquier estado que comience con '2' significa que la solicitud fue exitosa. La mayoría de las veces es 200 OK, lo que simplemente significa que todo salió bien.
  • 3xx: cualquier estado que comience con '3' significa que el usuario debe ser redireccionado para finalizar la acción.
  • 4xx: cualquier estado que comience con '4' indica un error de cliente. El más notorio es 404 Not Found, generalmente debido a una mala solicitud o sintaxis. Junto a él, hay 400 Bad Request, 401 Unauthorized y 403 Forbidden. Estos códigos de estado son los más comunes y existe una amplia variedad de ellos.
  • 5xx: cualquier estado que comience con '5' indica un error del servidor.

La lista completa de códigos de estado HTTP es bastante larga, aunque no es una mala idea revisarla.

Envío de solicitudes con HttpURLConnection

HttpURLConexión es la clase central de Java para manejar solicitudes y respuestas HTTP.

El uso de HttpURLConnection está perfectamente bien para solicitudes HTTP simples, aunque si desea agregar más fácilmente cosas como encabezados o autenticación, le resultará más fácil confiar en otras bibliotecas como Apache Commons.

La forma más sencilla de instanciar el objeto HttpURLConnection es usando el objeto URL:

1
2
URL url = new URL("https://wikihtp.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

Tipos de solicitudes

Ahora que existe nuestra instancia HttpURLConnection, podemos definir un tipo de solicitud HTTP para ella:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// For a GET request
connection.setRequestMethod("GET");

// For a POST request
connection.setRequestMethod("POST");

// For a HEAD request
connection.setRequestMethod("HEAD");

// For a OPTIONS request
connection.setRequestMethod("OPTIONS");

// For a PUT request
connection.setRequestMethod("PUT");

// For a DELETE request
connection.setRequestMethod("DELETE");

// For a TRACE request
connection.setRequestMethod("TRACE");

Sin embargo, en la mayoría de los casos solo usará GET, POST y DELETE.

Parámetros de solicitud

En algunos casos, nos gustaría enviar una solicitud HTTP con ciertos parámetros de consulta, como www.youtube.com/watch?v=dQw4w9WgXcQ.

Para lograr esto, por lo general encontraríamos una forma de emparejar estos valores. A veces, las personas definen sus propias clases para contener estos valores, aunque un simple ‘HashMap’ funcionará bien:

1
2
Map<String, String> params = new HashMap<>();
params.put("v", "dQw4w9WgXcQ");

Ahora que tenemos nuestros parámetros asignados, debemos hacer un par de cosas para prepararlos para nuestra solicitud:

  • Genere el parámetro en un formato similar a una cadena. Usaremos StringBuilder ya que es ideal para la concatenación que realizaremos
  • Usar la clase Codificador de URL para codificar nuestros parámetros
  • Convierta los datos en bytes, ya que nuestro DataOutputStream que activa la solicitud espera una matriz de tipo byte

Nota: si no está seguro de por qué necesitamos codificar nuestra URL, es porque ciertos caracteres pueden tener un significado especial dentro de las URL. Caracteres como "/", ".", "#" y "?" pueden alterar la solicitud, por lo que si están presentes, debemos codificarlos de una manera que no afecte la forma en que se interpreta la URL.

Implementemos los elementos de la lista:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Instantiate a requestData object to store our data
StringBuilder requestData = new StringBuilder();

for (Map.Entry<String, String> param : params.entrySet()) {
    if (requestData.length() != 0) {
        requestData.append('&');
    }
    // Encode the parameter based on the parameter map we've defined
    // and append the values from the map to form a single parameter
    requestData.append(URLEncoder.encode(param.getKey(), "UTF-8"));
    requestData.append('=');
    requestData.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8"));
}

// Convert the requestData into bytes 
byte[] requestDataByes = requestData.toString().getBytes("UTF-8");

Y así, nuestro parámetro está listo para ser utilizado en la solicitud.

Encabezados de solicitud

Si desea agregar un encabezado a una solicitud, es tan fácil como:

1
2
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Content-Length", String.valueOf(requestDataBytes.length));

Y si desea leer un encabezado de una solicitud:

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

Tiempos de espera

Otra característica que ofrece HttpURLConnection es establecer tiempos de espera. Podemos definir intervalos de tiempo de espera para leer o conectar:

1
2
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);

Los intervalos se definen, como es habitual, en milisegundos.

Entonces, por ejemplo, la conexión se agotará si no se puede establecer en 10 segundos. Del mismo modo, también se agotará el tiempo de espera si los datos no se pueden leer desde la conexión en 10 segundos.

Solicitud POST

Con nuestra solicitud configurada, podemos continuar y disparar la solicitud POST:

1
2
3
4
5
6
7
8
// Set the doOutput flag to true
connection.setDoOutput(true);

// Get the output stream of the connection instance
// and add the parameter to the request
try (DataOutputStream writer = new DataOutputStream(connection.getOutputStream())) {
    writer.write(postDataBytes);
}

Nota: Ya que estamos usando un bloque de prueba con recursos, no hay necesidad de vaciar y cerrar la conexión , ya que se hace automáticamente al final del bloque.

Si está utilizando un bloque try-catch tradicional, recuerde siempre vaciar y cerrar la conexión:

1
2
3
// Always flush and close
writer.flush();
writer.close();

Aquí estamos enviando postDataBytes en nuestra solicitud POST, que es una matriz de bytes. Consulte la demostración a continuación para obtener más detalles sobre cómo generar esto.

Solicitud OBTENER

Una vez que enviamos una solicitud POST, normalmente queremos hacer algo (o al menos ver) la respuesta.

Las solicitudes GET solo están destinadas a recuperar datos, así que sigamos adelante y obtengamos una respuesta:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// To store our response
StringBuilder content;

// Get the input stream of the connection
try (BufferedReader input = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
    String line;
    content = new StringBuilder();
    while ((line = input.readLine()) != null) {
        // Append each line of the response and separate them
        content.append(line);
        content.append(System.lineSeparator());
    }
} finally {
    connection.disconnect();
}

// Output the content to the console
System.out.println(content.toString());

Podemos extraer muchos tipos diferentes de información de la conexión en este punto:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Returns the value of the content-type header field
connection.getContentType();

// Returns an unmodifiable Map of the header fields
connection.getHeaderFields();

// Gets the status code from an HTTP response message
connection.getResponseCode();

// Gets the HTTP response message returned along with the response code from a server
connection.getResponseMessage();

// Returns the error stream if the connection failed but the server sent useful data nonetheless
connection.getErrorStream();

// ...etc

Demostración

Así es como una aplicación muy simple generaría una solicitud POST, la enviaría y luego leería la respuesta:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public static void main(String[] args) throws MalformedURLException, ProtocolException, IOException {
    URL url = new URL("https://www.youtube.com");
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();

    connection.setRequestMethod("POST");
    Map<String, String> params = new HashMap<>();
    params.put("v", "dQw4w9WgXcQ");

    StringBuilder postData = new StringBuilder();
    for (Map.Entry<String, String> param : params.entrySet()) {
        if (postData.length() != 0) {
            postData.append('&');
        }
        postData.append(URLEncoder.encode(param.getKey(), "UTF-8"));
        postData.append('=');
        postData.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8"));
    }

    byte[] postDataBytes = postData.toString().getBytes("UTF-8");
    connection.setDoOutput(true);
    try (DataOutputStream writer = new DataOutputStream(connection.getOutputStream())) {
        writer.write(postDataBytes);

        StringBuilder content;

        try (BufferedReader in = new BufferedReader(
                new InputStreamReader(connection.getInputStream()))) {
        String line;
        content = new StringBuilder();
           while ((line = in.readLine()) != null) {
                content.append(line);
                content.append(System.lineSeparator());
            }
        }
        System.out.println(content.toString());
    } finally {
        connection.disconnect();
    }
}

Ejecutar este fragmento de código seguramente produce el código fuente de la página que especificamos para obtener:

1
2
3
4
<!DOCTYPE html>
<html lang="sr" data-cast-api-enabled="true">
<head>
<!-- rest of the page -->  

Conclusión

Protocolo de transferencia de hipertexto (HTTP) es un protocolo de capa de aplicación que, sin exagerar, es un componente muy grande y crítico para las aplicaciones de Internet.

HttpURLConnection es la clase central de Java para manejar solicitudes y respuestas HTTP.

Usar HttpURLConnection está perfectamente bien para solicitudes HTTP simples, aunque si desea crear solicitudes HTTP más complejas con encabezados o autenticación, tendrá una experiencia mucho más simple con bibliotecas como Apache Commons.