Cliente HTTP en Java: haciendo solicitudes correctamente (nivel avanzado)
Trabajar con solicitudes HTTP es una de las tareas básicas en el desarrollo moderno de Java. Sin embargo, escribir código que no solo envíe solicitudes, sino que lo haga de manera confiable, con un manejo adecuado de errores, tiempos de espera y gestión de recursos, requiere un conocimiento profundo. En este artículo, iremos más allá del simple HttpURLConnection y analizaremos técnicas avanzadas para trabajar con clientes HTTP en Java, utilizando el java.net.http.HttpClient integrado (Java 11+) y la popular biblioteca OkHttp.
1. Gestión de tiempos de espera: no dejes que tu aplicación se congele
Uno de los problemas más comunes al trabajar con HTTP es que la aplicación se congele debido a un servidor lento o no disponible. Configurar correctamente los tiempos de espera es el primer paso para crear un cliente tolerante a fallos.
Tipos de tiempos de espera:
- Tiempo de espera de conexión: Tiempo de espera para establecer la conexión TCP con el servidor.
- Tiempo de espera de lectura: Tiempo de espera para recibir datos del servidor después de establecer la conexión.
- Tiempo de espera de escritura: Tiempo de espera para enviar datos al servidor (relevante para POST/PUT).
- Tiempo de espera de solicitud: Tiempo total para completar toda la solicitud HTTP (incluyendo conexión, envío y recepción de la respuesta).
Ejemplo de configuración para java.net.http.HttpClient:
import java.net.http.HttpClient;import java.time.Duration;
public class TimeoutExample { public static HttpClient createClientWithTimeouts() { return HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(5)) // Tiempo de espera de conexión .build(); }
public static void main(String[] args) { HttpClient client = createClientWithTimeouts(); // Para tiempos de espera de lectura/escritura usamos HttpRequest var request = java.net.http.HttpRequest.newBuilder() .uri(java.net.URI.create("https://api.example.com/data")) .timeout(Duration.ofSeconds(10)) // Tiempo de espera total de la solicitud .GET() .build(); System.out.println("Cliente con tiempos de espera creado: " + client); }}Ejemplo de configuración para OkHttp:
import okhttp3.OkHttpClient;import java.util.concurrent.TimeUnit;
public class OkHttpTimeoutExample { public static OkHttpClient createOkHttpClient() { return new OkHttpClient.Builder() .connectTimeout(5, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .callTimeout(30, TimeUnit.SECONDS) // Tiempo de espera total para toda la llamada .build(); }
public static void main(String[] args) { OkHttpClient client = createOkHttpClient(); System.out.println("Cliente OkHttp con tiempos de espera listo para trabajar."); }}2. Grupo de conexiones y reutilización: ahorrando recursos
Cada nueva conexión HTTP requiere recursos para establecer el handshake TCP y, posiblemente, el handshake TLS. Para aplicaciones que realizan muchas solicitudes al mismo servidor, es fundamental utilizar un grupo de conexiones (connection pool). Esto permite reutilizar las conexiones existentes, lo que acelera significativamente las solicitudes y reduce la carga en la red.
java.net.http.HttpClient tiene un grupo de conexiones integrado que se gestiona automáticamente. Sin embargo, puede configurar su tamaño:
import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.net.URI;import java.time.Duration;
public class PoolExample { public static void main(String[] args) throws Exception { // HttpClient gestiona el grupo automáticamente, pero podemos limit