Wie erstellen Sie eine asynchrone HTTP-Anforderung in JAVA?

81

Ich bin ziemlich neu in Java, daher scheint dies einigen offensichtlich zu sein. Ich habe viel mit ActionScript gearbeitet, das sehr ereignisbasiert ist, und das finde ich toll. Ich habe kürzlich versucht, ein kleines Stück Java-Code zu schreiben, der eine POST-Anforderung ausführt, aber ich hatte das Problem, dass es sich um eine synchrone Anforderung handelt. Daher wartet die Codeausführung darauf, dass die Anforderung abgeschlossen wird, eine Zeitüberschreitung auftritt oder ein Fehler auftritt.

Wie kann ich eine asynchrone Anforderung erstellen, bei der der Code die Ausführung fortsetzt und ein Rückruf aufgerufen wird, wenn die HTTP-Anforderung abgeschlossen ist? Ich habe einen Blick auf Fäden geworfen, aber ich denke, es ist übertrieben.

evilpenguin
quelle
Siehe auch Bayou Async http Client
ZhongYu

Antworten:

11

Beachten Sie, dass java11 jetzt einen neuen HTTP-API- HttpClient bietet , der den vollständig asynchronen Betrieb unter Verwendung von CompletableFuture von Java unterstützt .

Es unterstützt auch eine synchrone Version mit Aufrufen wie send (synchron) und sendAsync (asynchron).

Beispiel einer asynchronen Anfrage (aus dem 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
quelle
1
Wenn ich Java8 verwende, welche API ist die beste?
Alen
@alen ich weiß es nicht. hoffentlich kann bald jeder java11 benutzen ...
Emmanuel Touzery
31

Wenn Sie sich in einer JEE7-Umgebung befinden, muss eine anständige Implementierung von JAXRS vorhanden sein, mit der Sie mithilfe der Client-API problemlos asynchrone HTTP-Anforderungen stellen können.

Das würde so aussehen:

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));
    }
}

Natürlich werden hier nur Futures verwendet. Wenn Sie mit der Verwendung weiterer Bibliotheken einverstanden sind, können Sie sich RxJava ansehen. Der Code sieht dann folgendermaßen aus:

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");
}

Und last but not least, wenn Sie Ihren asynchronen Anruf wiederverwenden möchten, sollten Sie sich Hystrix ansehen, mit dem Sie - zusätzlich zu einer Unmenge super cooler anderer Dinge - Folgendes schreiben können:

Zum Beispiel:

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);
    }

 }

Das Aufrufen dieses Befehls würde folgendermaßen aussehen:

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");
}

PS: Ich weiß, dass der Thread alt ist, aber es fühlte sich falsch an, dass niemand den Rx / Hystrix-Weg in den hochgestimmten Antworten erwähnt.

Psyx
quelle
Wie kann ich es mit Proxy verwenden?
Dejell
Wäre großartig, wenn Sie diese Antwort näher erläutern möchten, insbesondere das RxJava-Beispiel. Ich sehe einen Methodenaufruf an newThread (), der zu implizieren scheint, dass dieser Code auch einen neuen Thread auslöst. Ich bin vage mit Rx 'asynchronen Fähigkeiten vertraut, daher überrascht mich diese Art von ...
Anders Martini
Der Scheduler.newThread () -Aufruf weist Rx einfach an, die Ausführung in diesem Fall auf einem neuen Thread zu drehen - dies erzwingt asynchrones Computing. Wenn Sie bereits eine asynchrone Einrichtung haben, können Sie diese natürlich ganz einfach verwenden (Scheduler.from (Executor) fällt Ihnen ein).
Psyx
1
@Gank Ja, da Lambdas verwendet werden, kann es nicht über 1.8 kompiliert werden. Es sollte einfach genug sein, es mit Consumer etc ...
Psyx
@psyx Müssen wir das Observable abbestellen?
Nick Gallimore
14

Aufgrund eines Links zu Apache-HTTP-Komponenten in diesem SO-Thread bin ich auf die Fluent-Fassaden-API für HTTP-Komponenten gestoßen. Ein Beispiel dort zeigt, wie Sie eine Warteschlange für asynchrone HTTP-Anforderungen einrichten (und über deren Abschluss / Fehler / Abbruch benachrichtigt werden). In meinem Fall brauchte ich keine Warteschlange, sondern jeweils nur eine asynchrone Anforderung.

Hier bin ich gelandet (auch mit URIBuilder von HTTP Components, Beispiel hier ).

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
quelle
6

Vielleicht möchten Sie sich diese Frage ansehen: Asynchrone E / A in Java?

Es sieht aus wie Ihre beste Wahl, wenn Sie nicht die Threads selbst streiten wollen, ist ein Rahmen. Im vorherigen Beitrag werden Grizzly ( https://grizzly.dev.java.net/ ) und Netty ( http://www.jboss.org/netty/) erwähnt .

Aus den Netty Docs:

Das Netty-Projekt ist ein Versuch, ein asynchrones ereignisgesteuertes Netzwerkanwendungsframework und Tools für die schnelle Entwicklung wartungsfähiger Protokollserver und -clients mit hoher Leistung und hoher Skalierbarkeit bereitzustellen.

Paul Rubel
quelle
3

Apache HttpComponents hat jetzt auch einen asynchronen http-Client:

/**
    <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
quelle
Wie kann ich es mit Proxy verwenden?
Dejell
@Dejel Ich würde annehmen, dass Sie die Systemeigenschaften wie hier angegeben festlegen: docs.oracle.com/javase/6/docs/technotes/guides/net/proxies.html
Dan Brough
1

Es muss klargestellt werden, dass das HTTP-Protokoll synchron ist und dies nichts mit der Programmiersprache zu tun hat. Der Client sendet eine Anfrage und erhält eine synchrone Antwort.

Wenn Sie ein asynchrones Verhalten über HTTP wünschen, muss dieses über HTTP erstellt werden (ich weiß nichts über ActionScript, aber ich nehme an, dass dies auch das ActionScript tut). Es gibt viele Bibliotheken, die Ihnen solche Funktionen bieten könnten (z . B. Jersey SSE ). Beachten Sie, dass sie Abhängigkeiten zwischen dem Client und dem Server irgendwie definieren, da sie sich auf die genaue nicht standardmäßige Kommunikationsmethode über HTTP einigen müssen.

Wenn Sie nicht sowohl den Client als auch den Server steuern können oder keine Abhängigkeiten zwischen ihnen haben möchten, verwendet der häufigste Ansatz zur Implementierung einer asynchronen (z. B. ereignisbasierten) Kommunikation über HTTP den Webhooks-Ansatz (Sie können dies auf eine überprüfen Beispiel Implementierung in Java).

Hoffe ich habe geholfen!

Pantelis Natsiavas
quelle
Diese Antwort ist zwar technisch gesehen zutreffend, kann jedoch irreführend sein, da die Client-Implementierung unabhängig von der Unterstützung des Servers oder des HTTP-Protokolls erhebliche Auswirkungen auf die Leistung haben kann, je nachdem, ob die Anforderung blockierend auf demselben Thread oder einem anderen Thread ausgeführt wird ein Thread-Pool oder idealerweise nicht blockierende E / A (NIO), bei der der aufrufende Thread in den Ruhezustand versetzt wird, bis das Betriebssystem ihn aufweckt, wenn die Antwort eintrifft. Es hört sich so an, als ob das OP eher am Client-Threading-Modell als am Protokoll interessiert ist.
geg