Jak utworzyć asynchroniczne żądanie HTTP w JAVA?

81

Jestem całkiem nowy w Javie, więc niektórym może się to wydawać oczywiste. Dużo pracowałem z ActionScriptem, który jest oparty na zdarzeniach i bardzo mi się to podoba. Niedawno próbowałem napisać mały fragment kodu Java, który wykonuje żądanie POST, ale napotkałem problem, że jest to żądanie synchroniczne, więc wykonanie kodu czeka na zakończenie żądania, przekroczenie limitu czasu lub wyświetlenie błędu.

Jak mogę utworzyć żądanie asynchroniczne, w którym kod kontynuuje wykonywanie, a wywołanie zwrotne jest wywoływane po zakończeniu żądania HTTP? Zerknąłem na nici, ale myślę, że to przesada.

zły pingwin
źródło
patrz także klient http bayou async
ZhongYu

Odpowiedzi:

11

Zwróć uwagę, że java11 oferuje teraz nowy HTTP api HttpClient , który obsługuje w pełni asynchroniczne operacje przy użyciu CompletableFuture języka Java .

Obsługuje również wersję synchroniczną z wywołaniami, takimi jak send , który jest synchroniczny, i sendAsync , który jest asynchroniczny.

Przykład żądania asynchronicznego (pobrane z 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
źródło
1
jeśli używam java8, który interfejs API jest najlepszy?
alen
@alen, nie wiem. miejmy nadzieję, że wkrótce każdy będzie mógł korzystać z java11 ...
Emmanuel Touzery,
31

Jeśli jesteś w środowisku JEE7, musisz mieć przyzwoitą implementację JAXRS, która umożliwiłaby łatwe tworzenie asynchronicznych żądań HTTP za pomocą jego klienta API.

Wyglądałoby to tak:

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

Oczywiście jest to po prostu wykorzystanie futures. Jeśli nie masz nic przeciwko używaniu większej liczby bibliotek, możesz rzucić okiem na RxJava, kod wyglądałby wtedy następująco:

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

I wreszcie, jeśli chcesz ponownie użyć wywołania asynchronicznego, możesz rzucić okiem na Hystrix, który - oprócz miliardów super fajnych innych rzeczy - pozwoliłby ci napisać coś takiego:

Na przykład:

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

 }

Wywołanie tego polecenia wyglądałoby tak:

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: Wiem, że wątek jest stary, ale czułem się źle, że nikt nie wspomina o sposobie Rx / Hystrix w odpowiedziach głosowanych w górę.

Psyx
źródło
jak mogę go używać z proxy?
Dejell
byłoby wspaniale, gdybyś chciał rozwinąć tę odpowiedź, a konkretnie przykład RxJava, widzę wywołanie metody do newThread (), co wydaje się sugerować, że ten kod również obraca nowy wątek? Jestem niejasno zaznajomiony z możliwościami asynchronizacji Rx, więc ten rodzaj mnie zaskakuje ...
Anders Martini
Wywołanie Scheduler.newThread () po prostu mówi Rx, aby w tym przypadku przestawił wykonanie na nowy wątek - to wymusza obliczenia asynchroniczne. Oczywiście, jeśli masz już jakąkolwiek konfigurację asynchroniczną, możesz jej łatwo użyć (przychodzi na myśl Scheduler.from (Executor)).
Psyx,
1
@Gank Tak, ponieważ używa lambd, nie może kompilować powyżej 1.8. Powinno być dość łatwo napisać to od dawna za pomocą Konsumenta itp ...
Psyx
@psyx Czy musimy wypisać się z obserwowalnych?
Nick Gallimore,
14

Bazując na linku do Apache HTTP Components w tym wątku SO , natknąłem się na Fluent Fasada API dla komponentów HTTP. Przykład tam pokazuje, jak ustawić kolejkę asynchronicznych żądań HTTP (i otrzymywać powiadomienia o ich zakończeniu / niepowodzeniu / anulowaniu). W moim przypadku nie potrzebowałem kolejki, tylko jedno żądanie asynchroniczne na raz.

Oto, gdzie skończyłem (również używając URIBuilder z HTTP Components, przykład tutaj ).

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
źródło
6

Możesz spojrzeć na to pytanie: Asynchroniczne operacje we / wy w Javie?

Wygląda na to, że najlepiej jest założyć, że jeśli nie chcesz samodzielnie walczyć z wątkami, jest to framework. Poprzedni post wspomina o Grizzly, https://grizzly.dev.java.net/ i Netty, http://www.jboss.org/netty/ .

Z dokumentów Netty:

Projekt Netty jest próbą dostarczenia asynchronicznego szkieletu aplikacji sieciowych sterowanych zdarzeniami oraz narzędzi do szybkiego tworzenia łatwych w utrzymaniu serwerów i klientów protokołów o wysokiej wydajności i skalowalności.

Paul Rubel
źródło
2

Apache HttpComponents ma teraz również asynchronicznego klienta http:

/**
    <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
źródło
jak mogę go używać z proxy?
Dejell
@Dejel Zakładam, że ustawiasz właściwości systemu tak, jak określono tutaj: docs.oracle.com/javase/6/docs/technotes/guides/net/proxies.html
Dan Brough
1
Wywołanie future.get () zablokuje wątek. Musisz umieścić to w innym wątku, aby było faktycznie asynchroniczne. Biblioteka HttpAsyncClients ma złą nazwę ...
jjbskir
1

Należy wyjaśnić, że protokół HTTP jest synchroniczny i nie ma to nic wspólnego z językiem programowania. Klient wysyła żądanie i otrzymuje synchroniczną odpowiedź.

Jeśli chcesz zachować asynchroniczne zachowanie przez HTTP, to musi to być zbudowane przez HTTP (nie wiem nic o ActionScript, ale przypuszczam, że to samo robi ActionScript). Istnieje wiele bibliotek, które mogą zapewnić taką funkcjonalność (np. Jersey SSE ). Zauważ, że w jakiś sposób definiują zależności między klientem a serwerem, ponieważ muszą uzgodnić dokładną niestandardową metodę komunikacji powyżej HTTP.

Jeżeli nie można sterować zarówno klienta jak i serwera lub jeśli nie chcemy mieć współzależności między nimi, najczęściej podejście wdrożenia asynchroniczny (np oparciu zdarzeń) Komunikacja za pośrednictwem protokołu HTTP używa zbliżyć webhooks (można sprawdzić to za przykładowa implementacja w java).

Mam nadzieję, że pomogłem!

Pantelis Natsiavas
źródło
Chociaż technicznie prawda ta odpowiedź może być myląca, ponieważ niezależnie od tego, co obsługuje serwer lub protokół HTTP, implementacja klienta może mieć bardzo znaczący wpływ na wydajność w zależności od tego, czy wykonuje żądanie w sposób blokujący w tym samym wątku, a inny wątek w pula wątków lub najlepiej przy użyciu nieblokującego wejścia / wyjścia (NIO), w którym wątek wywołujący śpi, dopóki system operacyjny nie obudzi go po nadejściu odpowiedzi. Wygląda na to, że OP jest zainteresowany modelem wątkowania klienta, a nie protokołem.
geg
0

Oto rozwiązanie wykorzystujące apache HttpClient i nawiązywanie połączenia w osobnym wątku. To rozwiązanie jest przydatne, jeśli wykonujesz tylko jedno połączenie asynchroniczne. Jeśli wykonujesz wiele wywołań, sugeruję użycie apache HttpAsyncClient i umieszczenie wywołań w puli wątków.

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
źródło