如何在JAVA中创建异步HTTP请求?


81

我对Java相当陌生,因此对于某些人来说这似乎很明显。我已经使用ActionScript进行了大量工作,ActionScript非常基于事件,我很喜欢。我最近尝试编写一小段执行POST请求的Java代码,但是我一直面临着它是一个同步请求的问题,因此代码执行会等待请求完成,超时或出现错误。

如何创建异步请求,使代码继续执行并在HTTP请求完成后调用回调?我瞥了一眼线程,但我认为这太过分了。


Answers:


11

请注意,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);

1
如果我使用java8,哪个API最好?
alen

@alen我不知道。希望很快每个人都可以使用java11 ...
Emmanuel Touzery

31

如果您在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方式,这是错误的。


如何与代理一起使用?
Dejell

如果您想详细说明这个答案,那就太好了,特别是RxJava示例,我看到了对newThread()的方法调用,这似乎意味着该代码也正在启动一个新线程?我有点熟悉Rx的异步功能,所以这让我感到惊讶...
Anders Martini

在这种情况下,Scheduler.newThread()调用只是告诉Rx在新线程上旋转执行-这将强制执行异步计算。当然,如果您已经有任何一种异步设置,则可以非常轻松地使用它(想到了Scheduler.from(Executor))。
Psyx

1
@Gank是的,因为它使用lambda,所以不能编译超过1.8。它应该很容易用Consumer等方式写出来很长的时间...
Psyx

@psyx我们必须退订可观察的吗?
Nick Gallimore


14

基于此SO线程上与Apache HTTP Components的链接,我遇到了针对HTTP Components的Fluent Facade API。 那里的示例显示了如何设置异步HTTP请求队列(并通知其完成/失败/取消通知)。就我而言,我不需要队列,一次只需要一个异步请求。

这是我最终的结果(也使用HTTP组件中的URIBuilder,此处为示例)。

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


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
Dan Brough

调用future.get()将阻塞线程。需要将此放置在另一个线程中实际上是异步的。HttpAsyncClients库的名称不正确...
jjbskir

1

必须明确HTTP协议是同步的,这与编程语言无关。客户端发送请求并获得同步响应。

如果要通过HTTP进行异步行为,则必须通过HTTP构建异步行为(我对ActionScript一无所知,但我想这也是ActionScript所做的事情)。有许多库可以为您提供这种功能(例如Jersey SSE)。请注意,它们确实以某种方式定义了客户端和服务器之间的依赖关系,因为它们必须就HTTP之上的确切非标准通信方法达成共识。

如果你无法控制客户端和服务器,或者,如果你不想让他们之间的依赖关系,通过HTTP实现异步(例如基于事件的)通信使用的最常用的方法网络挂接接近(你可以检查一个Java中的示例实现)。

希望我能帮上忙!


尽管从技术上讲是正确的,但此答案可能会产生误导,因为无论服务器或HTTP协议支持什么,客户端实现对性能的影响都非常大,这取决于它是否在同一线程,不同线程中以阻塞方式执行请求。线程池,或者理想情况下使用非阻塞IO(NIO),在此线程中,调用线程会休眠,直到响应到达时OS将其唤醒。听起来好像OP对客户端线程模型而不是协议感兴趣。
geg19年

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();
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.