¿Cómo se crea una solicitud HTTP asincrónica en JAVA?

81

Soy bastante nuevo en Java, por lo que esto puede parecer obvio para algunos. He trabajado mucho con ActionScript, que se basa en gran medida en eventos y eso me encanta. Recientemente intenté escribir un poco de código Java que realiza una solicitud POST, pero me he enfrentado al problema de que es una solicitud síncrona, por lo que la ejecución del código espera a que la solicitud se complete, se agote el tiempo de espera o presente un error.

¿Cómo puedo crear una solicitud asincrónica, donde el código continúa la ejecución y se invoca una devolución de llamada cuando se completa la solicitud HTTP? He echado un vistazo a los hilos, pero creo que es excesivo.

maligno
fuente
ver también cliente http async bayou
ZhongYu

Respuestas:

11

Tenga en cuenta que java11 ahora ofrece una nueva API HTTP HttpClient , que admite una operación totalmente asíncrona, utilizando CompletableFuture de Java .

También es compatible con una versión sincrónica, con llamadas como send , que es sincrónica, y sendAsync , que es asincrónica.

Ejemplo de una solicitud asincrónica (tomada de apidoc):

   HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://example.com/"))
        .timeout(Duration.ofMinutes(2))
        .header("Content-Type", "application/json")
        .POST(BodyPublishers.ofFile(Paths.get("file.json")))
        .build();
   client.sendAsync(request, BodyHandlers.ofString())
        .thenApply(HttpResponse::body)
        .thenAccept(System.out::println);
Emmanuel Touzery
fuente
1
si utilizo java8, ¿qué API es mejor?
alen
@alen no lo sé. espero que pronto todos puedan usar java11 ...
Emmanuel Touzery
31

Si se encuentra en un entorno JEE7, debe tener una implementación decente de JAXRS rondando, lo que le permitiría realizar fácilmente solicitudes HTTP asíncronas utilizando su API de cliente.

Esto se vería así:

public class Main {

    public static Future<Response> getAsyncHttp(final String url) {
        return ClientBuilder.newClient().target(url).request().async().get();
    }

    public static void main(String ...args) throws InterruptedException, ExecutionException {
        Future<Response> response = getAsyncHttp("http://www.nofrag.com");
        while (!response.isDone()) {
            System.out.println("Still waiting...");
            Thread.sleep(10);
        }
        System.out.println(response.get().readEntity(String.class));
    }
}

Por supuesto, esto es solo usar futuros. Si está de acuerdo con el uso de más bibliotecas, puede echar un vistazo a RxJava, el código se vería así:

public static void main(String... args) {
    final String url = "http://www.nofrag.com";
    rx.Observable.from(ClientBuilder.newClient().target(url).request().async().get(String.class), Schedulers
            .newThread())
            .subscribe(
                    next -> System.out.println(next),
                    error -> System.err.println(error),
                    () -> System.out.println("Stream ended.")
            );
    System.out.println("Async proof");
}

Y por último, pero no menos importante, si desea reutilizar su llamada asíncrona, es posible que desee echar un vistazo a Hystrix, que, además de un montón de otras cosas geniales, le permitiría escribir algo como esto:

Por ejemplo:

public class AsyncGetCommand extends HystrixCommand<String> {

    private final String url;

    public AsyncGetCommand(final String url) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HTTP"))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        .withExecutionIsolationThreadTimeoutInMilliseconds(5000)));
        this.url = url;
    }

    @Override
    protected String run() throws Exception {
        return ClientBuilder.newClient().target(url).request().get(String.class);
    }

 }

Llamar a este comando se vería así:

public static void main(String ...args) {
    new AsyncGetCommand("http://www.nofrag.com").observe().subscribe(
            next -> System.out.println(next),
            error -> System.err.println(error),
            () -> System.out.println("Stream ended.")
    );
    System.out.println("Async proof");
}

PD: Sé que el hilo es antiguo, pero me sentí mal que nadie mencionara el método Rx / Hystrix en las respuestas con votos positivos.

Psyx
fuente
¿cómo puedo usarlo con proxy?
Dejell
Sería genial si le interesa profundizar en esta respuesta, específicamente en el ejemplo de RxJava, veo un método llamado a newThread (), lo que parece implicar que este código también genera un nuevo hilo. Estoy vagamente familiarizado con las capacidades asíncronas de Rx, así que esto me sorprende ...
Anders Martini
La llamada Scheduler.newThread () simplemente le dice a Rx que haga girar la ejecución en un nuevo subproceso en este caso, esta aplicación de computación asíncrona. Por supuesto, si ya tiene algún tipo de configuración asíncrona, puede usarla con bastante facilidad (me viene a la mente Scheduler.from (Executor)).
Psyx
1
@Gank Sí, ya que usa lambdas, no puede compilar más de 1.8. Debería ser bastante fácil escribirlo por
completo
@psyx ¿Tenemos que darnos de baja del observable?
Nick Gallimore
14

Basado en un enlace a los componentes HTTP de Apache en este hilo SO , encontré la API de fachada Fluent para componentes HTTP. Un ejemplo muestra cómo configurar una cola de solicitudes HTTP asincrónicas (y recibir una notificación de su finalización / falla / cancelación). En mi caso, no necesitaba una cola, solo una solicitud asíncrona a la vez.

Aquí es donde terminé (también usando URIBuilder de HTTP Components, ejemplo aquí ).

import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.http.client.fluent.Async;
import org.apache.http.client.fluent.Content;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.concurrent.FutureCallback;

//...

URIBuilder builder = new URIBuilder();
builder.setScheme("http").setHost("myhost.com").setPath("/folder")
    .setParameter("query0", "val0")
    .setParameter("query1", "val1")
    ...;
URI requestURL = null;
try {
    requestURL = builder.build();
} catch (URISyntaxException use) {}

ExecutorService threadpool = Executors.newFixedThreadPool(2);
Async async = Async.newInstance().use(threadpool);
final Request request = Request.Get(requestURL);

Future<Content> future = async.execute(request, new FutureCallback<Content>() {
    public void failed (final Exception e) {
        System.out.println(e.getMessage() +": "+ request);
    }
    public void completed (final Content content) {
        System.out.println("Request completed: "+ request);
        System.out.println("Response:\n"+ content.asString());
    }

    public void cancelled () {}
});
ericsoco
fuente
6

Es posible que desee echar un vistazo a esta pregunta: ¿ E / S asíncrona en Java?

Parece su mejor opción, si no quiere discutir los hilos usted mismo, es un marco. La publicación anterior menciona a Grizzly, https://grizzly.dev.java.net/ y Netty, http://www.jboss.org/netty/ .

De los netty docs:

El proyecto Netty es un esfuerzo por proporcionar un marco de aplicación de red asincrónico impulsado por eventos y herramientas para el desarrollo rápido de servidores y clientes de protocolo de alto rendimiento y alta escalabilidad que se pueden mantener.

Paul Rubel
fuente
2

Apache HttpComponents también tiene un cliente http asíncrono ahora también:

/**
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpasyncclient</artifactId>
      <version>4.0-beta4</version>
    </dependency>
**/

import java.io.IOException;
import java.nio.CharBuffer;
import java.util.concurrent.Future;

import org.apache.http.HttpResponse;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.nio.IOControl;
import org.apache.http.nio.client.methods.AsyncCharConsumer;
import org.apache.http.nio.client.methods.HttpAsyncMethods;
import org.apache.http.protocol.HttpContext;

public class HttpTest {

  public static void main(final String[] args) throws Exception {

    final CloseableHttpAsyncClient httpclient = HttpAsyncClients
        .createDefault();
    httpclient.start();
    try {
      final Future<Boolean> future = httpclient.execute(
          HttpAsyncMethods.createGet("http://www.google.com/"),
          new MyResponseConsumer(), null);
      final Boolean result = future.get();
      if (result != null && result.booleanValue()) {
        System.out.println("Request successfully executed");
      } else {
        System.out.println("Request failed");
      }
      System.out.println("Shutting down");
    } finally {
      httpclient.close();
    }
    System.out.println("Done");
  }

  static class MyResponseConsumer extends AsyncCharConsumer<Boolean> {

    @Override
    protected void onResponseReceived(final HttpResponse response) {
    }

    @Override
    protected void onCharReceived(final CharBuffer buf, final IOControl ioctrl)
        throws IOException {
      while (buf.hasRemaining()) {
        System.out.print(buf.get());
      }
    }

    @Override
    protected void releaseResources() {
    }

    @Override
    protected Boolean buildResult(final HttpContext context) {
      return Boolean.TRUE;
    }
  }
}
Dan Brough
fuente
¿cómo puedo usarlo con proxy?
Dejell
@Dejel Supongo que configura las propiedades del sistema como se especifica aquí: docs.oracle.com/javase/6/docs/technotes/guides/net/proxies.html
Dan Brough
1
Llamar a future.get () bloqueará el hilo. Necesito colocar esto en otro hilo para que sea realmente asíncrono. La biblioteca HttpAsyncClients está mal nombrada ...
jjbskir
1

Debe quedar claro que el protocolo HTTP es síncrono y esto no tiene nada que ver con el lenguaje de programación. El cliente envía una solicitud y obtiene una respuesta sincrónica.

Si desea un comportamiento asincrónico a través de HTTP, esto debe construirse a través de HTTP (no sé nada sobre ActionScript, pero supongo que esto es lo que hace ActionScript también). Hay muchas bibliotecas que podrían brindarle dicha funcionalidad (por ejemplo, Jersey SSE ). Tenga en cuenta que de alguna manera definen las dependencias entre el cliente y el servidor, ya que tienen que estar de acuerdo con el método de comunicación no estándar exacto por encima de HTTP.

Si no puede controlar tanto al cliente como al servidor o si no desea tener dependencias entre ellos, el enfoque más común para implementar la comunicación asíncrona (por ejemplo, basada en eventos) a través de HTTP es utilizar el enfoque de webhooks (puede verificar esto para implementación de ejemplo en java).

¡Espero haber ayudado!

Pantelis Natsiavas
fuente
Si bien técnicamente es cierto, esta respuesta puede ser engañosa porque, independientemente de lo que admita el servidor o el protocolo HTTP, la implementación del cliente puede tener implicaciones de rendimiento muy significativas dependiendo de si está ejecutando la solicitud de forma de bloqueo en el mismo hilo, un hilo diferente en un grupo de subprocesos, o idealmente utilizando IO sin bloqueo (NIO) donde el subproceso que llama duerme hasta que el sistema operativo lo activa cuando llega la respuesta. Parece que el OP está interesado en el modelo de subprocesamiento del cliente en lugar del protocolo.
geg
0

Aquí hay una solución que usa apache HttpClient y realiza la llamada en un hilo separado. Esta solución es útil si solo realiza una llamada asíncrona. Si está haciendo varias llamadas, sugiero usar apache HttpAsyncClient y colocar las llamadas en un grupo de subprocesos.

import java.lang.Thread;

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;

public class ApacheHttpClientExample {
    public static void main(final String[] args) throws Exception {
        try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {
            final HttpGet httpget = new HttpGet("http://httpbin.org/get");
            new Thread(() -> {
                 final String responseBody = httpclient.execute(httpget);
            }).start();
        }
    }
}
jjbskir
fuente