HTTP API del paquete java.net.http con ejemplos

Las API de cliente HTTP y WebSocket proporcionan interfaces de cliente de alto nivel para HTTP (versiones 1.1 y 2) e interfaces de cliente de bajo nivel para WebSocket. Los principales tipos definidos son los siguientes:

  • Cliente Http
  • Solicitud Http
  • HttpRespuesta

Los requisitos específicos del protocolo se definen en el Protocolo de transferencia de hipertexto versión 2 (HTTP/2), el Protocolo de transferencia de hipertexto (HTTP/1.1) y el Protocolo WebSocket.

En general, las tareas asincrónicas se ejecutan en el subproceso que invoca la operación, por ejemplo, enviando una solicitud HTTP, o mediante los subprocesos proporcionados por el ejecutor del cliente. Las tareas dependientes, aquellas que son desenstringdas por CompletionStages o CompletableFutures devueltos, que no especifican explícitamente un ejecutor, se ejecutan en el mismo ejecutor predeterminado que el de CompletableFuture, o el subproceso que invoca si la operación se completa antes de que se registre la tarea dependiente.

Los CompletableFutures devueltos por esta API lanzarán UnsupportedOperationException para sus métodos obtrudeValue y obtrudeException. Es posible que la invocación del método cancel en un CompletableFuture devuelto por esta API no interrumpa la operación subyacente, pero puede ser útil para completar, excepcionalmente, etapas dependientes que aún no se han completado.

A menos que se indique lo contrario, los valores de parámetros nulos harán que los métodos de todas las clases en este paquete arrojen NullPointerException .

1. Resumen de la interfaz 

El resumen de la interfaz es el siguiente en el formato tabular a continuación:

Componentes HTTP  Acción realizada 
HttpClient.Builder Un constructor de Clientes HTTP.
HttpRequest.BodyPublisher Un BodyPublisher convierte objetos Java de alto nivel en un flujo de búfer de bytes adecuado para enviar como cuerpo de solicitud.
HttpRequest.Generador Un generador de requests HTTP.
HttpResponse<T> Una respuesta HTTP.
HttpResponse.BodyHandler<T> Un controlador para los cuerpos de respuesta.
HttpResponse.BodySubscriber<T> Un BodySubscriber consume bytes del cuerpo de la respuesta y los convierte en un tipo Java de nivel superior.
HttpResponse.PushPromiseHandler<T> Un controlador para promesas de inserción.
HttpResponse.ResponseInfo Información de respuesta inicial proporcionada a un BodyHandler cuando se recibe inicialmente una respuesta y antes de que se procese el cuerpo.

2. Un cliente WebSocket.

  • WebSocket.Builder: un generador de clientes WebSocket.
  • WebSocket.Listener: la interfaz de recepción de WebSocket.2 . Clase
  • Descripción de la clase
  • Cliente Http

3. Un cliente HTTP

  • HttpHeaders: una vista de solo lectura de un conjunto de encabezados HTTP.
  • HttpRequest: una solicitud HTTP.
  • HttpRequest.BodyPublishers: implementaciones de BodyPublisher que implementan varios editores útiles, como publicar el cuerpo de la solicitud desde una string o desde un archivo.
  • HttpResponse.BodyHandlers: implementaciones de BodyHandler que implementan varios controladores útiles, como manejar el cuerpo de la respuesta como una string o transmitir el cuerpo de la respuesta a un archivo.
  • HttpResponse.BodySubscribers: implementaciones de BodySubscriber que implementan varios suscriptores útiles, como convertir los bytes del cuerpo de respuesta en una string o transmitir los bytes a un archivo.

4. Resumen de enumeración 

  • HttpClient.Redirect: Define la política de redirección automática.
  • HttpClient.VersionLa versión del protocolo HTTP.

5. Resumen de excepciones

gHttpConnectTimeoutException Se genera cuando una conexión, a través de la cual se pretende enviar una solicitud HttpRequest, no se establece correctamente dentro de un período de tiempo específico.
HttpTimeoutException Se lanza cuando no se recibe una respuesta dentro de un período de tiempo específico.
WebSocketHandshakeExceptionWebSocketHandshakeException Lanzado cuando el apretón de manos de apertura ha fallado.

Métodos:

Hay 5 formas de hacer requests HTTP, es una característica central de la programación moderna y, a menudo, es una de las primeras cosas que desea hacer cuando aprende un nuevo lenguaje de programación. Para los programadores de Java, hay muchas formas de hacerlo: bibliotecas centrales en JDK y bibliotecas de terceros. Se enumeran a continuación:

  1. Usando HttpURLConnection en J2SE
  2. Usando HttpClient en J2SE
  3. Uso de bibliotecas de terceros ApacheHttpClient
  4. Uso de bibliotecas de terceros de OkHttp
  5. Uso de bibliotecas de terceros Retrofit

Discutámoslos con una ilustración que justifique lo mismo.

Vía 1: Núcleo Java 

API principales de Java para realizar requests HTTP de Java. Desde Java 1.1 ha habido un cliente HTTP en las bibliotecas principales provistas con el JDK. Con Java 11 se agregó un nuevo cliente. Uno de estos podría ser una buena opción si le preocupa agregar dependencias adicionales a su proyecto.

Nota: Para ver la cobertura de los códigos, consulte la imagen astronómica de la API del día de las API de la NASA para los ejemplos de código.

1.1 Java 1.1 HttpURLConexión

Primero, ¿ponemos en mayúsculas los acrónimos en los nombres de las clases o no? Manten tu pesamiento. De todos modos, cierra los ojos y céntrate en 1997. Titanic estaba sacudiendo la taquilla e inspirando mil memes, las Spice Girls tenían un álbum más vendido, pero la noticia más importante del año seguramente fue que HttpURLConnection se agregó a Java 1.1. Se justifica en la ilustración proporcionada a continuación:

Ilustración: 

Uso y cómo obtener la solicitud GET para obtener los datos APOD

// Step 1: Create a neat value object to hold the URL
URL url = new URL("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY");

// Step 2: Open a connection(?) on the URL(??) and cast the response(???)
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

// Now it's "open", we can set the request method, headers etc.
connection.setRequestProperty("accept", "application/json");

// This line makes the request
InputStream responseStream = connection.getInputStream();

// Step 3: Manually converting the response body InputStream to
// APOD using Jackson
ObjectMapper mapper = new ObjectMapper();

APOD apod = mapper.readValue(responseStream, APOD.class);

// Step 5: Finally, display the response we have
System.out.println(apod.title);

Esto parece bastante detallado, y creo que el orden en que tenemos que hacer las cosas es confuso (¿por qué establecemos encabezados después de abrir la URL?). Si necesita realizar requests más complejas con cuerpos POST, o tiempos de espera personalizados , etc., entonces todo es posible, pero nunca encontré esta API intuitiva en absoluto.

Entonces, ¿cuándo usaría HTTPUrlConnection? Si está brindando soporte a clientes que usan versiones anteriores de Java y no puede agregar una dependencia, entonces esto podría ser para usted. Sospecho que es solo una pequeña minoría de desarrolladores, pero es posible que lo vea en bases de código más antiguas para enfoques más modernos, siga leyendo.

1.2 Java 11 HTTPClient

Más de veinte años después de HttpURLConnection teníamos Black Panther en los cines y un nuevo cliente HTTP agregado a Java 11: java.net.http.HttpClient. Tiene una API mucho más lógica y puede manejar HTTP/2 y Websockets. También tiene la opción de realizar requests de forma síncrona o asíncrona mediante el uso de la API CompletableFuture.

99 de cada 100 veces cuando hago una solicitud HTTP, quiero leer el cuerpo de la respuesta en mi código. Las bibliotecas que hacen esto difícil no despertarán alegría en mí. HttpClient acepta un BodyHandler que puede convertir una respuesta HTTP en una clase de su elección. Hay algunos controladores integrados: String, byte[] para datos binarios, Stream<String> que divide bylines y algunos otros. También puede definir el suyo propio, lo que podría ser útil ya que no hay un BodyHandler integrado para analizar JSON. He escrito uno (aquí) basado en Jackson siguiendo un ejemplo de Java Docs. Devuelve un proveedor para la clase APOD, por lo que llamamos a .get() cuando necesitamos el resultado.

Ilustración: solicitud síncrona

// Step 1: Create a client
var client = HttpClient.newHttpClient();

// Step 2: Create a request
var request = HttpRequest.newBuilder(URI.create("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY")).header("accept", "application/json").build();

// Step 3: Now use the client to send the request

var response = client.send(request, new JsonBodyHandler<>(APOD.class));


// Response
System.out.println(response.body().get().title);

// For an asynchronous request the client and request are made
// in the same way

// Step 3:  After this call .sendAsync instead of .send:
// Step 4: Use the client to send the request
var responseFuture = client.sendAsync(request, new JsonBodyHandler<>(APOD.class));

// We can do other things here while the request is in-flight
// This blocks until the request is complete
var response = responseFuture.get();

// Response
System.out.println(response.body().get().title);

Vía 2 : bibliotecas de cliente HTTP de Java de terceros. Si los clientes incorporados no funcionan para usted, ¡no se preocupe! Hay muchas bibliotecas que puede incorporar a su proyecto que harán el trabajo.

2.1 Biblioteca 1: Apache HttpClient

Los clientes HTTP de Apache Software Foundation existen desde hace mucho tiempo. Son ampliamente utilizados y son la base de muchas bibliotecas de alto nivel. La historia es un poco confusa. El antiguo Commons HttpClient ya no se está desarrollando y la nueva versión (también llamada HttpClient) está bajo el proyecto HttpComponents. La versión 5.0 se lanzó a principios de 2020 y agregó compatibilidad con HTTP/2. La biblioteca también admite requests síncronas y asíncronas.

En general, la API es de nivel bastante bajo, te queda mucho para implementar por ti mismo. El siguiente código llama a la API de la NASA. No parece demasiado difícil de usar, pero me salté gran parte del manejo de errores que desearía en el código de producción, y nuevamente tuve que agregar el código Jackson para analizar la respuesta JSON. También es posible que desee configurar un marco de registro para evitar advertencias en la salida estándar (no es gran cosa, pero me molesta un poco).

Apache proporciona varios ejemplos más para requests de sincronización y asíncronas.

Ilustración:

ObjectMapper mapper = new ObjectMapper();

try (CloseableHttpClient client = HttpClients.createDefault()) 
{

  HttpGet request = 
  new HttpGet("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY");

  APOD response = client.execute(request, httpResponse -> 
  mapper.readValue(httpResponse.getEntity().getContent(), APOD.class));

  System.out.println(response.title);
}

2.2 Biblioteca 2:OkHttp

OkHttp es un cliente HTTP de Square con muchas características integradas útiles, como el manejo automático de GZIP, almacenamiento en caché de respuestas y reintentos o respaldo a otros hosts en caso de errores de red, así como compatibilidad con HTTP/2 y WebSocket. La API está limpia, aunque no hay un análisis integrado de las respuestas JSON.

Ilustración: análisis de JSON con Jackson

ObjectMapper mapper = new ObjectMapper();
OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder().url("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY").build(); // defaults to GET

Response response = client.newCall(request).execute();

APOD apod = mapper.readValue(response.body().byteStream(), APOD.class);

System.out.println(apod.title);

Nota: Esto está bien, pero el poder real de OkHttp es claro cuando agrega Retrofit en la parte superior.

2.3 Biblioteca 3: Modernización

Retrofit es otra biblioteca de Square, construida sobre OkHttp. Junto con todas las funciones de bajo nivel de OkHttp, agrega una forma de crear clases de Java que abstraen los detalles de HTTP y presentan una buena API compatible con Java.

2.3.1 Primero, debemos crear una interfaz que declare los métodos que queremos llamar contra la API de APOD, con anotaciones que definan cómo se corresponden con las requests HTTP, que es la siguiente:

public interface APODClient 
{
  @GET("/planetary/apod")
  @Headers("accept: application/json")
  CompletableFuture<APOD> getApod(@Query("api_key") String apiKey);

}

2.3.2 El tipo de devolución de CompletableFuture<APOD> lo convierte en un cliente asíncrono. Square proporciona otros adaptadores, o puede escribir uno propio. Tener una interfaz como esta ayuda a burlarse del cliente para las pruebas, lo cual se agradece.

2.3.3 Después de declarar la interfaz, le pedimos a Retrofit que cree una implementación que podamos usar para realizar requests contra una URL base determinada. También es útil para las pruebas de integración poder cambiar la URL base.

Ilustración: Generando el cliente 

Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.nasa.gov").addConverterFactory(JacksonConverterFactory.create()).build();

APODClient apodClient = retrofit.create(APODClient.class);

CompletableFuture<APOD> response = apodClient.getApod("DEMO_KEY");

// Do all other stuffs here
// while the request is in-flight

APOD apod = response.get();

System.out.println(apod.title);

Autenticación de API

Si hay varios métodos en nuestra interfaz que necesitan una clave API, es posible configurarlo agregando un HttpInterceptor a la base OkHttpClient. El cliente personalizado se puede agregar a Retrofit.Builder. El código de ejemplo es el siguiente:

Implementación: Creación del cliente personalizado 

private OkHttpClient clientWithApiKey(String apiKey) {
    return new OkHttpClient.Builder()

    .addInterceptor(chain -> {

        Request originalRequest = chain.request();

        HttpUrl newUrl = originalRequest.url().newBuilder()

        .addQueryParameter("api_key", apiKey).build();

        Request request = originalRequest.newBuilder().url(newUrl).build();

        return chain.proceed(request);

    }).build();

}

Aquí se prefiere este tipo de API de Java para todos los casos excepto para los más simples. La creación de clases para representar las API remotas es una buena abstracción que funciona bien con la inyección de dependencia, y hacer que Retrofit las cree para usted en función de un cliente OkHttp personalizable es excelente.

Otros clientes HTTP para Java 

Si nada de lo anterior es exactamente lo que desea, eche un vistazo a las sugerencias que se enumeran a continuación:

  • REST Assured, un cliente HTTP diseñado para probar sus servicios REST. Ofrece una interfaz fluida para realizar requests y métodos útiles para realizar afirmaciones sobre las respuestas.
  • cvurl es un contenedor para Java 11 HttpClient que redondea algunos bordes afilados que puede encontrar al realizar requests complejas.
  • Fingir : similar a Retrofit, Fingir puede crear clases a partir de interfaces anotadas. Fingir es muy flexible con múltiples opciones para realizar y leer requests, métricas, reintentos y más.
  • Clientes Spring RestTemplate (síncrono) y WebClient (asíncrono) : si ha usado Spring para todo lo demás en su proyecto, podría ser una buena idea seguir con ese ecosistema. Baeldung tiene un artículo que los compara.
  • MicroProfile Rest Client: otro cliente en el modo «crear una clase a partir de una interfaz anotada», este es interesante porque también puede reutilizar la misma interfaz para crear un servidor web y asegurarse de que el cliente y el servidor coincidan. Si está creando un servicio y un cliente para ese servicio, entonces podría ser el indicado para usted.

A. Autenticación de contraseña

Me gusta este tipo de API de Java para todos los casos excepto para los más simples. La creación de clases para representar las API remotas es una buena abstracción que funciona bien con la inyección de dependencia, y hacer que Retrofit las cree para usted en función de un cliente OkHttp personalizable es excelente. Aquí podemos usar la clase PasswordAuthentication que es solo un titular de estos valores.

Nota: Cada solicitud debe usar el mismo nombre de usuario y contraseña. La clase Authenticator proporciona una serie de métodos getXXX (por ejemplo, getRequestingSite()) que se pueden usar para averiguar qué valores se deben proporcionar.

Ejemplo 

import java.io.*;

class {

    public static void main (String[] args) {

        HttpClient.newBuilder().authenticator(new Authenticator() {

            // @Override
            protectedPasswordAuthenticationgetPasswordAuthentication() {

                return new PasswordAuthentication( "username", "password".toCharArray());
            }

        }).build();
    }
}

B. Configuración de la política de redirección

Al llamar a la página de un sitio web, a veces la página a la que desea acceder se ha movido a una dirección diferente. En este caso, recibirá el código de estado HTTP 3xx, generalmente con la información sobre el nuevo URI. Al configurar una política de redirección adecuada, HttpClient puede redirigir la solicitud al nuevo URI automáticamente. Todas las políticas de redirección se definen y describen en enumeración con el nombre HttpClient.Redirect.

Nota: Usando el método followRedirects(), puede establecer la política de redirección

HttpClient.newBuilder()
followRedirects(HttpClient.Redirect.ALWAYS)
build();

C. Envío de requests de sincronización o asíncronas

  • HttpClient ofrece dos posibilidades para enviar una solicitud a un servidor:
  • enviar (…) sincrónicamente (bloquea hasta que llega la respuesta)
  • sendAsync (…) de forma asíncrona (no espera una respuesta, sin bloqueo)
  • Hasta ahora, el método send(…) naturalmente espera una respuesta:

Ejemplo 1:

HttpResponse<String> response = HttpClient.newBuilder()
.build()
.send(request, BodyHandlers.ofString());

Esta llamada devuelve un objeto HttpResponse y esto significa que la siguiente instrucción del flujo de su aplicación se ejecutará solo cuando ya se haya devuelto la respuesta.

Este enfoque tiene muchos inconvenientes, especialmente cuando procesa grandes cantidades de datos. Para superar esta limitación, puede utilizar el método sendAsync(…), que devuelve CompletableFeature<String> para procesar una solicitud de forma asíncrona:

CompletableFuture<String> response = HttpClient.newBuilder()
.build()
.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body);

Nota: la API también puede manejar múltiples respuestas y transmitir los cuerpos de la solicitud y la respuesta.

Ejemplo 2:

class  {

    public static void main (String[] args) {
        
        List<URI>uris = Arrays.asList()

new URI("https://postman-echo.com/get?foo1=bar1"),
new URI("https://postman-echo.com/get?foo2=bar2");


HttpClient client = HttpClient.newHttpClient();


List<HttpRequest> requests = uris.stream()
.map(HttpRequest::newBuilder)
.map(reqBuilder ->reqBuilder.build())
.collect(Collectors.toList());



CompletableFuture.allOf(requests.stream()
.map(request ->client.sendAsync(request, ofString()))
.toArray(CompletableFuture<?>[]::new))
.join();


    }
}
CompletableFuture.allOf(requests.stream()
.map(request ->client.sendAsync(request, ofString()))
.toArray(CompletableFuture<?>[]::new))
.join();
   }
}

La explicación del código y la interconexión son las siguientes:

Por lo tanto, su código principal seguirá ejecutándose, configurando la devolución de llamada en el futuro y luego Aceptar. Pero esta devolución de llamada solo se activará una vez que el servidor devuelva una respuesta. El cliente HTTP utilizará un subproceso de fondo para realizar la llamada. Tenga en cuenta que la respuesta del servidor tardará un tiempo. Y mientras tanto, su aplicación habrá terminado. Entonces, ¿qué haces para que este ejemplo funcione? Llame al método de unión en el futuro. Esto une el hilo de la aplicación en el que se ejecuta su código con el futuro. En este punto del código, el método de unión esperará hasta que se complete el futuro. Y si se completa, eso también significa que se ejecutará su devolución de llamada thenAccept. Y, de hecho, cuando ejecuta este ejemplo, obtiene los resultados esperados.

Ejemplo 3:

class {

    // Main driver method
    public static void main (String[] args) {

        HttpClient.newBuilder().authenticator(new Authenticator() {

            // @Override
            protectedPasswordAuthenticationgetPasswordAuthentication() {

                return new PasswordAuthentication("username", "password".toCharArray());
            }

        }).build();

    }
}
CompletableFuture<HttpResponse<String>> response1 = HttpClient.newBuilder()
.executor(executor)
.build()
.sendAsync(request,asString());

Nota: De manera predeterminada, HttpClient usa el ejecutor java.util.concurrent.Executors.newCachedThreadPool().

La API de envío síncrono y de bloqueo es más fácil de usar, pero la API asíncrona lo ayudará a crear aplicaciones receptivas y más escalables, por lo que tendrá que elegir lo que mejor se adapte a su caso de uso.

Objetivos de la API de cliente HTTP

Las API de cliente HTTP tienen muchos objetivos diseñados para ayudarlo a comprender las características importantes de esta API y cómo puede usarla en su programación:

  • Fácil de usar para casos comunes, incluido un modo de bloqueo simple.
  • API simple y concisa que satisface el 80-90 por ciento de las necesidades de las aplicaciones
  • Admite mecanismos de autenticación estándar y comunes
  • Fácil de configurar el protocolo de enlace de la interfaz WebSocket
  • Compatible con los requisitos del sistema integrado; en particular, la evitación de subprocesos de temporizador en ejecución permanente

Nota: Admite HTTPS/TLS

  • Debe estar orientado al rendimiento y su huella de memoria es menor que las API más antiguas y de terceros.
  • Proporciona semántica de solicitud y respuesta sin bloqueo a través de CompletableFuture, que se puede enstringr para desenstringr acciones dependientes.

Publicación traducida automáticamente

Artículo escrito por keerthanareddy153 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

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