Application scenarios
When the server program provides a set of RESTful interfaces for third-party calls, it often needs to provide the other party with an SDK. At this time, if you simply use the okhttp3 client to encapsulate the calls, when the request volume is large, request blocking will often occur and the JVM memory will increase. In high cases, you can use CompletableFuture in jdk8 to achieve asynchronous calls to increase concurrency.
Build interface definition
Assume that we are requesting an interface to obtain user details. At this time, the asynchronous object encapsulated by CompletableFuture returned by the interface defines the following interface:
public interface AsyncRestV2Client {<!-- --> CompletableFuture<UserDetails> getUser(); }
Build client
When building okhttp3, you can pass in interface authentication information such as api key and api secret, and you can also specify the JsonMapper object deserialized by the interface to process the return value. The code is as follows:
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 ); }
Interface implementation
okhttp3 provides a callback mechanism, which is also the key to asynchronous. When the http request is completed, the callback method will be triggered, and the processing of the request result will be encapsulated into CompletableFuture to realize the asynchronousization of the request. The code is as follows:
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; }
Request a build
The build request uses the API support provided by okhttp3 and can be used as a reference:
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(); }
Interface call
At this time, the get method will wait for the callback to be executed before it can obtain the result, which can prevent all requests from being queued and blocked, thereby improving concurrency.
@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(); } }