Los siguientes son varias “recetas” que se pueden seguir para realizar tareas comunes utilizando el Cliente HTTP de Java 11.
Como hacer un GET síncrono
public void get(String uri) throws Exception {
HttpClient cliente= HttpClient.newHttpClient();
HttpRequest solicitud = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
HttpResponse<String> respuesta =
cliente.send(solicitud, BodyHandlers.ofString());
System.out.println(respuesta.body());
}
El ejemplo anterior usa ofString BodyHandler para convertir los bytes del cuerpo de respuesta en una cadena de texto. Se debe proporcionar un BodyHandler por cada HttpRequest enviado. BodyHandler determina cómo manejar el cuerpo de respuesta, si lo hay.
BodyHandler se invoca una vez que el código de estado de respuesta y los encabezados están disponibles, pero antes de que se reciban los bytes del cuerpo de respuesta. BodyHandler es responsable de crear BodySubscriber, que es un suscriptor de flujo reactivo que recibe flujos de datos con contrapresión sin bloqueo. BodySubscriber es responsable de, posiblemente, convertir los bytes del cuerpo de la respuesta en un tipo Java de nivel superior.
La clase HttpResponse.BodyHandlers proporciona una serie de convenientes métodos estáticos de fábrica para crear un BodyHandler. Varios de estos acumulan los bytes de respuesta en la memoria hasta que se recibe por completo, después de lo cual se convierte en el tipo Java de nivel superior, por ejemplo, ofString y ofByteArray. Otros transmiten los datos de respuesta a medida que llegan; ofFile, ofByteArrayConsumer y ofInputStream. Alternativamente, se puede proporcionar una implementación de suscriptor escrita personalizada.
Cuerpo de respuesta como un archivo
public void get(String uri) throws Exception {
HttpClient cliente = HttpClient.newHttpClient();
HttpRequest solicitud = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
HttpResponse<Path> respuesta =
cliente.send(solicitud, BodyHandlers.ofFile(Paths.get("datos.txt")));
System.out.println("Response in file:" + respuesta.body());
}
Como hacer un GET asíncrono
La API asincrónica regresa inmediatamente con un CompletableFuture que se completa con HttpResponse cuando esté disponible. CompletableFuture se agregó en Java 8 y admite programación asincrónica.
Cuerpo de respuesta como una cadena
public CompletableFuture<String> get(String uri) {
HttpClient cliente = HttpClient.newHttpClient();
HttpRequest solicitud= HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
return cliente.sendAsync(solicitud, BodyHandlers.ofString())
.thenApply(HttpResponse::body);
}
El método CompletableFuture.thenApply se puede utilizar para asignar HttpResponse a su tipo de cuerpo, código de estado, etc.
Cuerpo de respuesta como un archivo
public CompletableFuture<Path> get(String uri) {
HttpClient cliente = HttpClient.newHttpClient();
HttpRequest solicitud = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
return cliente.sendAsync(solicitud, BodyHandlers.ofFile(Paths.get("datos.txt")))
.thenApply(HttpResponse::body);
}
POST
Un cuerpo de solicitud puede ser suministrado por un HttpRequest.BodyPublisher.
public void post(String uri, String data) throws Exception {
HttpClient cliente = HttpClient.newBuilder().build();
HttpRequest solicitud = HttpRequest.newBuilder()
.uri(URI.create(uri))
.POST(BodyPublishers.ofString(data))
.build();
HttpResponse<?> respuesta = cliente .send(solicitud, BodyHandlers.discarding());
System.out.println(respuesta.statusCode());
}
El ejemplo anterior utiliza ofString BodyPublisher para convertir la cadena dada en bytes del cuerpo de la solicitud.
BodyPublisher es un editor de flujo reactivo que publica flujos de cuerpo de solicitud a pedido. HttpRequest.Builder tiene varios métodos que permiten configurar un BodyPublisher; Builder::POST, Builder::PUT y Builder::método. La clase HttpRequest.BodyPublishers tiene varios métodos convenientes de fábrica estáticos que crean un BodyPublisher para tipos comunes de datos; ofString, ofByteArray, ofFile.
El BodyHandler de descarte puede usarse para recibir y descartar el cuerpo de respuesta cuando no es de interés.
Solicitudes Concurrentes
Es fácil combinar Java Streams y la API CompletableFuture para emitir una serie de solicitudes y esperar sus respuestas. El siguiente ejemplo envía una solicitud GET para cada uno de los URI de la lista y almacena todas las respuestas como cadenas.
public void getURIs(List<URI> uris) {
HttpClient cliente = HttpClient.newHttpClient();
List<HttpRequest> solicitudes = uris.stream()
.map(HttpRequest::newBuilder)
.map(reqBuilder -> reqBuilder.build())
.collect(toList());
CompletableFuture.allOf(solicitudes.stream()
.map(solicitud -> cliente.sendAsync(solicitud, ofString()))
.toArray(CompletableFuture<?>[]::new))
.join();
}
Obtener JSON
En muchos casos, el cuerpo de respuesta estará en algún formato de nivel superior. Se pueden usar los manejadores de cuerpo de respuesta de conveniencia, junto con una biblioteca de terceros para convertir el cuerpo de respuesta a ese formato.
El siguiente ejemplo muestra cómo usar la biblioteca Jackson, en combinación con BodyHandlers::ofString para convertir una respuesta JSON en un mapa de pares clave / valor de cadena.
public CompletableFuture<Map<String,String>> JSONBodyAsMap(URI uri) {
UncheckedObjectMapper objectMapper = new UncheckedObjectMapper();
HttpRequest request = HttpRequest.newBuilder(uri)
.header("Accept", "application/json")
.build();
return HttpClient.newHttpClient()
.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenApply(objectMapper::readValue);
}
class UncheckedObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper {
/** Parses the given JSON string into a Map. */
Map<String,String> readValue(String content) {
try {
return this.readValue(content, new TypeReference<>(){});
} catch (IOException ioe) {
throw new CompletionException(ioe);
}
}
El ejemplo anterior usa ofString que acumula los bytes del cuerpo de respuesta en la memoria. Alternativamente, se podría usar un suscriptor de transmisión, como ofInputStream.
POST JSON
En muchos casos, el cuerpo de la solicitud estará en algún formato de nivel superior. Se pueden utilizar los manejadores de cuerpo de solicitud de conveniencia, junto con una biblioteca de terceros para convertir el cuerpo de solicitud a ese formato.
El siguiente ejemplo muestra cómo usar la biblioteca Jackson, en combinación con BodyPublishers::ofString para convertir un par de claves / valores de Map of String en JSON.
public CompletableFuture<Void> postJSON(URI uri,
Map<String,String> map)
throws IOException
{
ObjectMapper objectMapper = new ObjectMapper();
String requestBody = objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(map);
HttpRequest request = HttpRequest.newBuilder(uri)
.header("Content-Type", "application/json")
.POST(BodyPublishers.ofString(requestBody))
.build();
return HttpClient.newHttpClient()
.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::statusCode)
.thenAccept(System.out::println);
}
Configurando un proxy
Se puede configurar un ProxySelector en el HttpClient a través del método Builder :: proxy del cliente. La API ProxySelector devuelve un proxy específico para un URI dado. En muchos casos, un solo proxy estático es suficiente. El método ProxySelector::of static factory puede usarse para crear dicho selector.
Cuerpo de respuesta como una cadena con un proxy especificado
public CompletableFuture<String> get(String uri) {
HttpClient client = HttpClient.newBuilder()
.proxy(ProxySelector.of(new InetSocketAddress("www-proxy.com", 8080)))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
return client.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body);
}
Alternativamente, se puede usar el selector de proxy predeterminado en todo el sistema, que es el predeterminado en macOS.
HttpClient.newBuilder()
.proxy(ProxySelector.getDefault())
.build();