Làm cách nào để bạn tạo một yêu cầu HTTP không đồng bộ trong JAVA?


81

Tôi khá mới đối với Java, vì vậy điều này có vẻ hiển nhiên đối với một số người. Tôi đã làm việc rất nhiều với ActionScript, dựa trên rất nhiều sự kiện và tôi thích điều đó. Gần đây tôi đã cố gắng viết một đoạn mã Java nhỏ để thực hiện một yêu cầu ĐĂNG, nhưng tôi đã gặp phải sự cố rằng đó là một yêu cầu đồng bộ, vì vậy việc thực thi mã chờ yêu cầu hoàn tất, hết thời gian hoặc xuất hiện lỗi.

Làm cách nào để tạo một yêu cầu không đồng bộ, trong đó mã tiếp tục thực thi và một lệnh gọi lại được gọi khi yêu cầu HTTP hoàn tất? Tôi đã xem qua các chủ đề, nhưng tôi nghĩ rằng nó quá mức cần thiết.


Câu trả lời:


11

Lưu ý rằng java11 hiện cung cấp một HTTP api HttpClient mới , hỗ trợ hoạt động hoàn toàn không đồng bộ, sử dụng CompletableFuture của java .

Nó cũng hỗ trợ một phiên bản đồng bộ, với các cuộc gọi như gửi là đồng bộ và sendAsync , là không đồng bộ.

Ví dụ về yêu cầu không đồng bộ (lấy từ 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
nếu tôi sử dụng java8, API nào là tốt nhất?
Alen

@alen tôi không biết. hy vọng sớm mọi người có thể sử dụng java11 ...
Emmanuel Touzery

31

Nếu bạn đang ở trong môi trường JEE7, bạn phải có một triển khai JAXRS ổn định, điều này sẽ cho phép bạn dễ dàng thực hiện yêu cầu HTTP không đồng bộ bằng cách sử dụng API ứng dụng khách của nó.

Điều này sẽ trông như thế này:

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

Tất nhiên, đây chỉ là sử dụng tương lai. Nếu bạn đồng ý với việc sử dụng thêm một số thư viện, bạn có thể xem qua RxJava, mã sau đó sẽ giống như sau:

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

Và cuối cùng nhưng không kém phần quan trọng, nếu bạn muốn sử dụng lại lệnh gọi không đồng bộ của mình, bạn có thể muốn xem qua Hystrix, điều này - ngoài một thứ cực kỳ thú vị khác - sẽ cho phép bạn viết một cái gì đó như sau:

Ví dụ:

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

 }

Gọi lệnh này sẽ giống như sau:

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

Tái bút: Tôi biết chủ đề này đã cũ, nhưng cảm thấy sai khi không có ai đề cập đến cách Rx / Hystrix trong các câu trả lời được bình chọn cao.


làm thế nào tôi có thể sử dụng nó với proxy?
Dejell

sẽ rất tuyệt nếu bạn quan tâm đến câu trả lời này, cụ thể là ví dụ RxJava, tôi thấy một methodcall tới newThread (), có vẻ như ngụ ý rằng mã này cũng quay một luồng mới? Tôi mơ hồ quen thuộc với capabilties async Rx, vì vậy loại này bất ngờ tôi ...
Anders Martini

Lệnh gọi Scheduler.newThread () chỉ đơn giản là yêu cầu Rx quay việc thực thi trên một luồng mới trong trường hợp này - tính toán không đồng bộ thực thi này. Tất nhiên, nếu bạn đã có bất kỳ loại thiết lập không đồng bộ nào, bạn có thể sử dụng nó thay vào đó khá dễ dàng (Hãy nhớ đến Scheduler.from (Executor)).
Psyx 20/02/17

1
@Gank Có, vì nó sử dụng lambdas, nó không thể biên dịch trên 1.8. Nó sẽ được dễ dàng, đủ để viết nó trở thành chặng đường dài sử dụng tiêu dùng vv ...
Psyx

@psyx Chúng ta có phải hủy đăng ký đối với những thứ có thể quan sát được không?
Nick Gallimore


14

Dựa trên liên kết tới Thành phần Apache HTTP trên luồng SO này , tôi đã xem qua API mặt tiền Fluent cho Thành phần HTTP. Một ví dụ ở đó cho thấy cách thiết lập hàng đợi các yêu cầu HTTP không đồng bộ (và nhận thông báo về việc hoàn thành / thất bại / hủy bỏ của chúng). Trong trường hợp của tôi, tôi không cần hàng đợi, chỉ cần một yêu cầu không đồng bộ tại một thời điểm.

Đây là nơi tôi đã kết thúc (cũng sử dụng URIBuilder từ Thành phần HTTP, ví dụ ở đây ).

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

Bạn có thể muốn xem câu hỏi này: IO không đồng bộ trong Java?

Có vẻ như đặt cược tốt nhất của bạn, nếu bạn không muốn tự xoay xở các chủ đề là một khuôn khổ. Bài trước đề cập đến Grizzly, https://grizzly.dev.java.net/ và Netty, http://www.jboss.org/netty/ .

Từ các tài liệu netty:

Dự án Netty là một nỗ lực nhằm cung cấp một khuôn khổ ứng dụng mạng hướng sự kiện không đồng bộ và các công cụ để phát triển nhanh chóng các máy chủ & máy khách giao thức hiệu suất cao có thể bảo trì và khả năng mở rộng cao.


2

Apache HttpComponents hiện cũng có ứng dụng khách http không đồng bộ:

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

làm thế nào tôi có thể sử dụng nó với proxy?
Dejell

@Dejel Tôi giả sử bạn đặt thuộc tính hệ thống như được chỉ định tại đây: docs.oracle.com/javase/6/docs/technotes/guides/net/proxies.html
Dan Brough

Việc gọi future.get () sẽ chặn luồng. Cần đặt cái này vào một chuỗi khác để thực sự không đồng bộ. Thư viện HttpAsyncClients được đặt tên kém ...
jjbskir

1

Cần phải nói rõ rằng giao thức HTTP là đồng bộ và điều này không liên quan gì đến ngôn ngữ lập trình. Khách hàng gửi yêu cầu và nhận được phản hồi đồng bộ.

Nếu bạn muốn một hành vi không đồng bộ qua HTTP, điều này phải được xây dựng qua HTTP (tôi không biết gì về ActionScript nhưng tôi cho rằng đây là những gì ActionScript cũng làm). Có nhiều thư viện có thể cung cấp cho bạn chức năng như vậy (ví dụ như Jersey SSE ). Lưu ý rằng bằng cách nào đó họ xác định sự phụ thuộc giữa máy khách và máy chủ vì họ phải đồng ý về phương thức giao tiếp không chuẩn chính xác ở trên HTTP.

Nếu bạn không thể kiểm soát cả máy khách và máy chủ hoặc nếu bạn không muốn có sự phụ thuộc giữa chúng, thì phương pháp phổ biến nhất để triển khai giao tiếp không đồng bộ (ví dụ: dựa trên sự kiện) qua HTTP là sử dụng phương pháp webhooks (bạn có thể kiểm tra điều này để biết thực hiện ví dụ trong java).

Hy vọng tôi đã giúp!


Mặc dù đúng về mặt kỹ thuật, câu trả lời này có thể gây hiểu lầm bởi vì, bất kể máy chủ hoặc giao thức HTTP hỗ trợ những gì, việc triển khai máy khách có thể có tác động hiệu suất rất đáng kể tùy thuộc vào việc nó thực hiện yêu cầu theo cách chặn trên cùng một chuỗi, một chuỗi khác trong một nhóm luồng, hoặc lý tưởng là sử dụng IO không chặn (NIO) nơi luồng đang gọi sẽ ngủ cho đến khi Hệ điều hành đánh thức nó khi có phản hồi. Có vẻ như OP quan tâm đến mô hình phân luồng máy khách hơn là giao thức.
geg

0

Đây là một giải pháp sử dụng apache HttpClient và thực hiện cuộc gọi trong một chuỗi riêng. Giải pháp này hữu ích nếu bạn chỉ thực hiện một cuộc gọi không đồng bộ. Nếu bạn đang thực hiện nhiều cuộc gọi, tôi khuyên bạn nên sử dụng apache HttpAsyncClient và đặt các cuộc gọi trong một nhóm luồng.

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();
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.