基于okhttp3和CompletableFuture异步调用RESTful接口

应用场景

当服务端程序提供一组RESTful接口给第三方调用的时候,往往需要给对方提供SDK,此时如果简单使用okhttp3客户端封装调用,当请求量较大时,往往会产生请求阻塞,jvm内存升高的情况,这是可以使用jdk8中的CompletableFuture达到异步调用提高并发量的目的。

构建接口定义

假设我们请求的是获取用户详情的接口,此时接口返回的CompletableFuture封装的异步对象,定义如下接口:

public interface AsyncRestV2Client {
    CompletableFuture<UserDetails> getUser();
 }

构建客户端

构建okhttp3,此时可以把api key和api secret等接口鉴权信息传入,也能指定接口反序列化的JsonMapper对象,用来处理返回值,代码如下:

public final class AsyncRestV2ClientImpl implements AsyncRestV2Client {
    static final Logger LOGGER = LoggerFactory.getLogger(AsyncRestV2ClientImpl.class);
    final String BASE_URL = "https://api.test.com";
    final String key;
    final String sec;
    final OkHttpClient client;
    final ObjectMapper jsonMapper;
    public AsyncRestV2ClientImpl(
            String key,
            String sec,
            ObjectMapper jsonMapper,
            OkHttpClient httpClient
    ) {
        this.key = key;
        this.sec = sec;
        this.jsonMapper = jsonMapper;
        this.client = httpClient;
    }

    @Override
    public CompletableFuture<UserDetails> getUser() {
        return asyncRequest(
                "GET",
                BASE_URL + "/v2/user",
                emptyMap(),
                CustomJacksonTypes.USER_DETAILS
        );
    }

接口实现

okhttp3提供了callback的机制,这也是异步的关键。当http请求完成后,会触发callback方法,把请求结果的处理封装进CompletableFuture,即可实现请求的异步化,代码如下:

private <T> CompletableFuture<T> asyncRequest(String method, String endpoint, Map<String, String> params, TypeReference<T> responseType) {
        CompletableFuture<T> future = new CompletableFuture<>();

        Request request;
        try {
            request = checkNotNull(buildRequest(method, endpoint, params));
        } catch (Exception e) {
            if (e.getClass().equals(ApiException.class)) {
                future.completeExceptionally(e);
            } else {
                future.completeExceptionally(new ApiException("failed to build request " + method + " | " + endpoint, e));
            }
            return future;
        }

        Callback callback = new Callback() {
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String responseString = "";
                int code = response.code();
                ResponseBody body = response.body();
                if (body != null) {
                    responseString = body.string();
                }

                if (!response.isSuccessful()) {
                    try {
                        Error error = jsonMapper.readValue(responseString, Error.class);
                        future.completeExceptionally(new ApiException("request failed  (" + code + ") : " + responseString, error));
                    } catch (Exception e) {
                        future.completeExceptionally(new ApiException("request failed (" + code + "), failed to parse response from '" + responseString + "'", e));
                    }
                } else {
                    try {
                        T result = jsonMapper.readValue(responseString, responseType);
                        future.complete(result);
                    } catch (Exception e) {
                        future.completeExceptionally(new ApiException("request successful  (" + code + "), failed to parse response from '" + responseString + "'", e));
                    }
                }
            }

            @Override
            public void onFailure(Call call, IOException e) {
                future.completeExceptionally(new ApiException(e));
            }
        };

        LOGGER.debug("{}", request);
        client.newCall(request).enqueue(callback);
        return future;
    }

请求构建

构建请求使用的是okhttp3提供的API支持,可以作为参考使用:

private Request buildRequest(String method, String urlString, Map<String, String> params) throws JsonProcessingException {
        Request.Builder builder = new Request.Builder();

        if (method.equals("GET")) {
            HttpUrl url = checkNotNull(HttpUrl.parse(urlString));
            if (!params.isEmpty()) {
                HttpUrl.Builder ub = url.newBuilder();
                params.forEach(ub::addQueryParameter);
                url = ub.build();
            }

            String signature = sign(method, url.toString());

            builder
                    .get()
                    .url(url)
                    .header("X-LA-APIKEY", key)
                    .header("X-LA-SIGNATURE", signature)
                    .header("User-Agent", getUserAgent());

        } else if (method.equals("POST")) {
            HttpUrl url = checkNotNull(HttpUrl.parse(urlString));
            String signature;
            RequestBody body;

            if (params.isEmpty()) {
                signature = sign(method, url.toString());
                body = RequestBody.create(null, new byte[0]);
                builder.header("Content-Length", "0");
            } else {
                HttpUrl.Builder ub = url.newBuilder();
                params.forEach(ub::addQueryParameter);
                HttpUrl urlWithParams = ub.build();
                signature = sign(method, urlWithParams.toString());
                LOGGER.debug("signature : {}", signature);

                String json = jsonMapper.writeValueAsString(params);
                LOGGER.debug("body : {}", json);
                body = RequestBody.create(MediaType.parse("application/json"), json);
            }

            builder
                    .post(body)
                    .url(url)
                    .header("Content-Length", "0")
                    .header("Content-Type", "application/json")
                    .header("X-LA-APIKEY", key)
                    .header("X-LA-SIGNATURE", signature)
                    .header("User-Agent", getUserAgent());
        } else {
            throw new ApiException("not supported request method " + method);
        }

        return builder.build();
    }

接口调用

此时get方法会等callback执行之后才能获取结果,可以防止所有请求排队阻塞,达到提高并发的目的。

    @Test
    public void getUser() {
        try {
            System.out.println(
                    asyncClientV2(key, sec, defaultObjectMapper(), defaultHttpClient()).getUser().get()
            );
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值