Как создать асинхронный HTTP-запрос в JAVA?
Я новичок в Java, поэтому для некоторых это может показаться очевидным. Я много работал с ActionScript, который очень основан на событиях, и мне это нравится. Недавно я попытался написать небольшой код Java, который выполняет запрос POST, но я столкнулся с проблемой, что это синхронный запрос, поэтому выполнение кода ожидает завершения запроса, тайм-аута или возникновения ошибки.
Как я могу создать асинхронный запрос, где код продолжает выполнение, и обратный вызов вызывается, когда HTTP-запрос завершен? Я взглянул на потоки, но я думаю, что это перебор.
Ответы
Ответ 1
Java действительно более низкий уровень, чем ActionScript. Это похоже на сравнение яблок с апельсинами. Хотя ActionScript обрабатывает все прозрачно "под капотом", на Java вам нужно управлять асинхронной обработкой (threading) самостоятельно.
К счастью, в 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();
где Request
и Response
выглядят следующим образом:
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;
}
}
См. также:
Ответ 2
Если вы находитесь в среде 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 в голосовых ответах.
Ответ 3
Вы также можете посмотреть Async Http Client.
Ответ 4
На основе ссылки на Apache HTTP Components на этот поток SO, Я столкнулся с API-интерфейсом Fluent для HTTP-компонентов. Пример там показывает, как настроить очередь асинхронных 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 () {}
});
Ответ 5
Вы можете взглянуть на этот вопрос: Асинхронный ввод-вывод в Java?
Похоже, ваш лучший выбор, если вы не хотите пререкаться нитями сами по себе, является основой. Предыдущее сообщение упоминается
Grizzly, https://grizzly.dev.java.net/, и Netty, http://www.jboss.org/netty/.
Из Netty Docs:
Проект Netty - это попытка создать асинхронную платформу сетевого приложения и инструменты для быстрой разработки поддерживаемых серверов и клиентов протокола с высокой производительностью и высокой масштабируемостью.
Ответ 6
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;
}
}
}
Ответ 7
Должно быть ясно, что протокол HTTP является синхронным, и это не имеет никакого отношения к языку программирования. Клиент отправляет запрос и получает синхронный ответ.
Если вы хотите асинхронное поведение по HTTP, это должно быть построено через HTTP (я ничего не знаю о ActionScript, но я полагаю, что это то, что делает ActionScript). Существует множество библиотек, которые могут предоставить вам такую функциональность (например, Jersey SSE). Обратите внимание, что они каким-то образом определяют зависимости между клиентом и сервером, так как им приходится соглашаться на точный нестандартный метод связи над HTTP.
Если вы не можете управлять как клиентом, так и сервером, или если вы не хотите иметь зависимости между ними, наиболее распространенный подход к реализации асинхронной (например, основанной на событиях) связи через HTTP использует веб-хосты (вы можете проверить this для примера реализации в java).
Надеюсь, я помог!