Як створити асинхронний HTTP-запит у JAVA?


81

Я досить новачок у Java, тому для когось це може здатися очевидним. Я багато працював з ActionScript, який дуже багато заснований на подіях, і мені це подобається. Нещодавно я намагався написати невеликий фрагмент коду Java, який робить запит POST, але я зіткнувся з проблемою, що це синхронний запит, тому виконання коду чекає завершення запиту, очікування або появи помилки.

Як я можу створити асинхронний запит, де код продовжує виконуватись, а зворотний виклик викликається, коли запит HTTP завершено? Я подивився на теми, але думаю, що це надмірно.


див. також bayou async http client
ZhongYu

Відповіді:


11

Зверніть увагу, що java11 тепер пропонує новий HTTP api HttpClient , який підтримує повністю асинхронну роботу, використовуючи java's CompletableFuture .

Він також підтримує синхронну версію з викликами, такими як send , який є синхронним, і sendAsync , який є асинхронним.

Приклад запиту на асинхронізацію (взято з 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);

1
якщо я використовую java8, який API найкращий?
alen

@alen я не знаю. сподіваємось, незабаром усі можуть скористатися java11 ...
Еммануель Тузері

31

Якщо ви перебуваєте в середовищі JEE7, ви повинні мати гідну реалізацію JAXRS, яка зависає, що дозволить вам легко робити асинхронний HTTP-запит, використовуючи його клієнтський API.

Це виглядає так:

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

Звичайно, це просто використання ф’ючерсів. Якщо ви в порядку з використанням деяких інших бібліотек, ви можете поглянути на RxJava, тоді код виглядатиме так:

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

І останнє, але не менш важливе: якщо ви хочете повторно використати свій асинхронний виклик, ви можете захотіти поглянути на Hystrix, який - крім безлічі супер крутих речей - дозволить вам написати щось подібне:

Наприклад:

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

 }

Виклик цієї команди буде виглядати так:

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: Я знаю, що нитка застаріла, але мені здалося неправильним, що ніхто не згадує Rx / Hystrix у відповідях, за якими проголосували.


як я можу використовувати його з проксі?
Dejell

було б чудово, якщо ви хочете детальніше розглянути цю відповідь, зокрема приклад RxJava, я бачу виклик методу newThread (), що, мабуть, означає, що цей код також закручує новий потік? Я неясно знайомий з асинхронними можливостями Rx, тому мене це дивує ...
Андерс Мартіні,

Виклик Scheduler.newThread () просто вказує Rx закрутити виконання у новому потоці - у цьому випадку це асинхронне обчислення. Звичайно, якщо у вас вже є будь-яка установка асинхронізації, ви можете використовувати її натомість досить легко (Scheduler.from (Executor) спадає на думку).
Psyx

1
@Gank Так, оскільки він використовує лямбди, він не може скомпілювати більше 1,8. Це повинно бути досить легко, щоб написати це довгий шлях, використовуючи споживчі тощо ...
Psyx

@psyx Чи маємо ми скасувати підписку на спостережуване?
Нік Галлімор,


14

На основі посилання на компоненти Apache HTTP на цьому потоці SO , я натрапив на Fluent facade API для HTTP Components. Приклад там показує, як налаштувати чергу асинхронних HTTP-запитів (і отримувати сповіщення про їх завершення / збій / скасування). У моєму випадку мені не потрібна була черга, лише один асинхронний запит за раз.

Ось де я опинився (також використовуючи URIBuilder з компонентів HTTP, приклад тут ).

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

6

Можливо, ви захочете поглянути на це питання: Асинхронний ввід-вивід у Java?

Схоже, вам найкраще зробити ставку, якщо ви не хочете самостійно перебирати нитки - це основа. У попередній публікації згадуються Гризлі, https://grizzly.dev.java.net/ та Нетті, http://www.jboss.org/netty/ .

З документації мережі:

Проект Netty - це спроба забезпечити асинхронну структуру мережевих додатків, керовану подіями, та інструменти для швидкого розвитку підтримуваних серверів та клієнтів протоколів високої продуктивності та високої масштабованості.


2

Apache HttpComponents також тепер має асинхронний 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;
    }
  }
}

як я можу використовувати його з проксі?
Dejell

@Dejel Я б припустив, що ви встановили системні властивості, як зазначено тут: docs.oracle.com/javase/6/docs/technotes/guides/net/proxies.html
Ден Бро,

1
Виклик future.get () заблокує потік. Потрібно помістити це в інший потік, щоб бути фактично асинхронним. Бібліотека HttpAsyncClients має погану назву ...
jjbskir,

1

Слід пояснити, що протокол HTTP є синхронним, і це не має нічого спільного з мовою програмування. Клієнт надсилає запит і отримує синхронну відповідь.

Якщо ви хочете мати асинхронну поведінку через HTTP, це має бути побудовано через HTTP (я нічого не знаю про ActionScript, але я вважаю, що це те, що робить і ActionScript). Є багато бібліотек, які можуть надати вам таку функціональність (наприклад, Jersey SSE ). Зауважте, що вони якимось чином визначають залежності між клієнтом та сервером, оскільки їм доводиться узгоджувати точний нестандартний метод зв'язку вище HTTP.

Якщо ви не можете керувати як клієнтом, так і сервером, або якщо ви не хочете мати залежностей між ними, найпоширенішим підходом реалізації асинхронного (наприклад, заснованого на подіях) зв'язку через HTTP є використання підходу webhooks (ви можете перевірити це на наявність приклад реалізації в java).

Сподіваюся, я допоміг!


Хоча технічно вірна, ця відповідь може ввести в оману, оскільки, незалежно від того, що підтримує сервер або протокол HTTP, реалізація клієнта може мати дуже значні наслідки для продуктивності, залежно від того, чи виконує він запит блокуючим способом у тому самому потоці, іншому потоці в пул потоків, або в ідеалі використання неблокуючого IO (NIO), де викличний потік перебуває в режимі сну, поки ОС не пробудить його, коли надійде відповідь. Здається, операційна система зацікавлена ​​в моделі потокових потоків клієнта, а не в протоколі.
geg

0

Ось рішення із використанням apache HttpClient та здійснення дзвінка в окремому потоці. Це рішення корисно, якщо ви здійснюєте лише один асинхронний виклик. Якщо ви здійснюєте кілька дзвінків, я пропоную використовувати apache HttpAsyncClient і розмістити виклики в пулі потоків.

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();
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.