如何将异步HTTP请求抽象为同步

时间:2018-07-05 16:11:20

标签: java rest concurrency rx-java

我有一个第三方REST API服务,其行为“异步”;例如在请求中以中间响应进行响应,并在回调中添加与中间响应相关的回调。

回调几乎是通过“回调URL”返回的。

我正在尝试设计一个解决方案,一种适配器来调用该资源,就好像它是“同步的”,因为处理回调很麻烦,尤其是当我需要通过以下方式将连续的请求串行批处理到其他类似的API时同一第三方。基本上,我想对绿色部分进行抽象,以便调用者仅获得完整的回调,错误或超时异常。 enter image description here

我的研究指向使用RxJava,但我无法弄清楚如何通过反应式编程原理解决此问题(我的理解是有限的)。

设计注意事项:

  1. 将相关器持久保存到数据存储库以供以后在回调中进行查找是不理想的,因为它很昂贵
  2. 等待回调的策略是可以的,因为回调的响应时间少于1秒

如何使用CompletableFutureObservable-Observer模式来等待回调并返回到调用者?

2 个答案:

答案 0 :(得分:1)

请考虑使用CountDownLatch作为主线程,以等待工作线程处理了第三方API。工作者获取回调,以便它知道请求何时完成,完成,超时,发生错误等。

这是一个粗略的模拟:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;

public class TreatAsSync {

    public static void main(String[] args) {
        TreatAsSync app = new TreatAsSync();
        app.call();
    }

    private void call() {
        RestClient restClient = new RestClient();
        Request request = new Request();
        Response response = restClient.call(request);
        System.out.println("Response was: " + response);
    }

    private class Request {

    }

    private class Response {
        private final boolean error;
        private final boolean timedOut;
        private final String result;

        public Response(boolean error, boolean timedOut, String result) {
            this.error = error;
            this.timedOut = timedOut;
            this.result = result;
        }

        public String toString() {
            return "error:" + error + ", timedOut: " + timedOut + ", result: " + result;
        }
    }

    private class ResponseWrapper {
        private Response response;

        public Response getResponse() {
            return response;
        }

        public void setRespose(Response response) {
            this.response = response;
        }
    }

    private class RestClient {

        public Response call(Request request) {
            ResponseWrapper wrapper = new ResponseWrapper();
            CountDownLatch latch = new CountDownLatch(1);

            ThirdPartyRunner runner = new ThirdPartyRunner(request, wrapper, latch);
            new Thread(runner).start();

            try {
                latch.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            return wrapper.getResponse();
        }
    }

    private interface ThirdPartyCallBack {
        public void onProgress(Response response);
        public void onComplete(Response response);
        public void onTimeOut(Response response);
        public void onError(Response response);
    }

    private class ThirdPartyRunner implements ThirdPartyCallBack, Runnable {
        private final Request request;
        private final ResponseWrapper wrapper;
        private final CountDownLatch latch;

        public ThirdPartyRunner(Request request, ResponseWrapper wrapper, CountDownLatch latch) {
            this.request = request;
            this.wrapper = wrapper;
            this.latch = latch;
        }

        @Override
        public void onProgress(Response response) {
            System.out.println("some progress was made...");
        }

        @Override
        public void onComplete(Response response) {
            System.out.println("request completed");
            finished(response);
        }

        @Override
        public void onTimeOut(Response response) {
            System.out.println("request timed out");
            finished(response);
        }

        @Override
        public void onError(Response response) {
            System.out.println("request had an error");
            finished(response);
        }

        private void finished(Response response) {
            wrapper.setRespose(response);
            latch.countDown();
        }

        @Override
        public void run() {
            try {
                callThirdParty();
            } catch (Exception e) {
                finished(new Response(true, false, e.getMessage()));
            }
        }

        private void callThirdParty() {
            // simulate api.call(request, this);
            for (int i = 0; i < ThreadLocalRandom.current().nextInt(10) + 1; i++) {
                onProgress(new Response(false, false, "in progress"));
            }

            switch (ThreadLocalRandom.current().nextInt(3)) {
            case 0:
                onComplete(new Response(false, false, "done"));
                break;

            case 1:
                onTimeOut(new Response(false, true, "hello?"));
                break;

            case 2:
                onError(new Response(true, false, "uh oh!"));
                break;
            }
        }
    }

}

答案 1 :(得分:0)

如果您有这个:

public ImmediateResponse asynchCall(Callback callback) throws ImmediateException {...}

Callback如下所示:

interface Callback {
    void onSuccess(EventualResponse eventualResponse);
    void onFailure(Exception e);
}

然后您想要的是这样的东西:

public static EventualResponse synchCall(long timeout, TimeUnit timeUnit)
        throws InterruptedException, TimeoutException, EventualException
{
    CompletableFuture<EventualResponse> responseFuture = new CompletableFuture<>();
    Callback callback = new Callback() {
        public void onSuccess(EventualResponse response) { 
            responseFuture.complete(response); 
        }
        public void onFailure(Exception e) { 
            responseFuture.completeExceptionally(e); 
        }
    };
    try {
        /*ImmediateResponse immediateResponse = */asynchCall(callback); 
        // use immediateResponse if you need it
        return responseFuture.get(timeout, timeUnit);
    } catch (ImmediateException e) {
        throw new EventualException(e);
    } catch (ExecutionException ee) {
        throw new EventualException(ee.getCause());
    }
}