我对Java相当陌生,因此对于某些人来说这似乎很明显。我已经使用ActionScript进行了大量工作,ActionScript非常基于事件,我很喜欢。我最近尝试编写一小段执行POST请求的Java代码,但是我一直面临着它是一个同步请求的问题,因此代码执行会等待请求完成,超时或出现错误。
如何创建异步请求,使代码继续执行并在HTTP请求完成后调用回调?我瞥了一眼线程,但我认为这太过分了。
Answers:
请注意,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);
如果您在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。
基于此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 () {}
});
您可能想看一下这个问题: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实现异步(例如基于事件的)通信使用的最常用的方法网络挂接接近(你可以检查这一个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();
}
}
}