簡體   English   中英

JAVA中如何創建異步HTTP請求?

[英]How do you create an asynchronous HTTP request in JAVA?

我對 Java 還很陌生,所以這對某些人來說似乎很明顯。 我在 ActionScript 方面做了很多工作,它非常基於事件,我很喜歡它。 我最近嘗試編寫一小段執行 POST 請求的 Java 代碼,但我遇到了它是同步請求的問題,因此代碼執行等待請求完成、超時或出現錯誤。

如何創建異步請求,其中代碼繼續執行並在 HTTP 請求完成時調用回調? 我瞥了一眼線程,但我認為這太過分了。

Java確實比ActionScript更低級別。 這就像比較蘋果和橘子。 雖然ActionScript在“引擎蓋下”透明地處理它,但在Java中,您需要自己管理異步處理(線程)。

幸運的是,在Java中有java.util.concurrent API,可以很好地完成。

你的問題基本上可以解決如下:

// Have one (or more) threads ready to do the async tasks. Do this during startup of your app.
ExecutorService executor = Executors.newFixedThreadPool(1); 

// Fire a request.
Future<Response> response = executor.submit(new Request(new URL("http://google.com")));

// Do your other tasks here (will be processed immediately, current thread won't block).
// ...

// Get the response (here the current thread will block until response is returned).
InputStream body = response.get().getBody();
// ...

// Shutdown the threads during shutdown of your app.
executor.shutdown();

其中RequestResponse如下所示:

public class Request implements Callable<Response> {
    private URL url;

    public Request(URL url) {
        this.url = url;
    }

    @Override
    public Response call() throws Exception {
        return new Response(url.openStream());
    }
}

public class Response {
    private InputStream body;

    public Response(InputStream body) {
        this.body = body;
    }

    public InputStream getBody() {
        return body;
    }
}

也可以看看:

如果您在 JEE7 環境中,您必須有一個體面的 JAXRS 實現,這將允許您使用其客戶端 API 輕松發出異步 HTTP 請求。

這看起來像這樣:

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 方式,這感覺是錯誤的。

您可能還想查看Async Http Client

請注意,java11 現在提供了一個新的 HTTP api HttpClient ,它支持完全異步操作,使用 java 的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);

基於此 SO 線程Apache HTTP 組件的鏈接,我遇到了 HTTP 組件的 Fluent 外觀 API。 那里的示例顯示了如何設置異步 HTTP 請求隊列(並獲得完成/失敗/取消的通知)。 就我而言,我不需要隊列,一次只需要一個異步請求。

這是我結束的地方(也使用來自 HTTP 組件的 URIBuilder, 示例here )。

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

您可能想看看這個問題: Java 中的異步 IO?

看起來你最好的選擇,如果你不想自己糾纏線程是一個框架。 上一篇文章提到了 Grizzly, https ://grizzly.dev.java.net/ 和 Netty, http: //www.jboss.org/netty/。

來自 netty 文檔:

Netty 項目致力於提供異步事件驅動的網絡應用程序框架和工具,以快速開發可維護的高性能和高可擴展性協議服務器和客戶端。

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

必須明確 HTTP 協議是同步的,這與編程語言無關。 客戶端發送請求並獲得同步響應。

如果您想通過 HTTP 進行異步行為,則必須通過HTTP 構建(我對 ActionScript 一無所知,但我想這也是 ActionScript 所做的)。 有許多庫可以為您提供此類功能(例如Jersey SSE )。 請注意,他們確實以某種方式定義了客戶端和服務器之間的依賴關系,因為他們必須就 HTTP 之上的確切非標准通信方法達成一致。

如果您無法同時控制客戶端和服務器,或者您不想在它們之間建立依賴關系,那么通過 HTTP 實現異步(例如基於事件)通信的最常見方法是使用webhooks 方法(您可以檢查這個java中的示例實現)。

希望我有所幫助!

這是使用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();
        }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM