0

I'm writing a custom RestService(wrapper of Apache Http Client 4.5.10) and RestServiceAsync(wrapper of Apache httpasyncclient 4.1.4) in Spring MVC monolithic application.

Below is my IRestService

public interface IRestService {

    public <T> T request(HttpUriRequest request, final ResponseParser<T> responseParser);

    public <T> Optional<T> request(final HttpMethod method);

    public <T> T call(HttpUriRequest request, ResponseHandler<T> responseHandler);

    default <T> Optional<T> get() {
        return request(HttpMethod.GET);
    }

    public default <T> Optional<T> get(String URL) {
        return get(URL, Collections.EMPTY_MAP, ResponseParsers.STRING);
    }

    public default <T> Optional<T> get(String URL, boolean isAsync) {
        return get(URL, Collections.EMPTY_MAP, ResponseParsers.STRING);
    }

    public default <T> Optional<T> get(String URL, Map params) {
        return get(URL, params, ResponseParsers.STRING);
    }

    public default <T> Optional<T> get(String url, Map<String, String> params, ResponseParser<T> responseParser) {
        return request(url, params, HttpMethod.GET, responseParser);
    }

    public default <T> Optional<T> get(String URL, ResponseParser<T> responseParser) {
        return request(URL, Collections.EMPTY_MAP, HttpMethod.GET, responseParser);
    }

    public default <T> Optional<T> request(String url, Map params, HttpMethod httpMethod, ResponseParser<T> responseParser) {
        return request(url, null, params, httpMethod, responseParser);
    }

    public  <T> Optional<T> request(String url, HttpEntity entity, Map params, HttpMethod httpMethod, ResponseParser<T> responseParser);

    HttpResponse execute(HttpUriRequest request, Closeable httpClient) ;
}

Below is my RestServiceBase

public abstract class RestServiceBase implements IRestService{

    @Override
    public <T> Optional<T> request(HttpMethod method) {
        return Optional.empty();
    }

    @Override
    public <T> Optional<T> request(String url, HttpEntity entity, Map params, HttpMethod httpMethod, ResponseParser<T> responseParser) {
        T respose = null;
        try {
            final HttpUriRequest httpUriRequest = buildHttpUriRequest(httpMethod, url, entity, params);

            respose = (T) request(httpUriRequest, responseParser);
        } catch (HttpException | URISyntaxException e) {
            e.printStackTrace();
        }
        return Optional.ofNullable(respose);
    }

    public <T> T request(HttpUriRequest request, final ResponseParser<T> responseParser) {
        return call(request, new BaseResponseHandler<>(entity -> {
            // Gets the content as a string
            String content = entity != null ? EntityUtils.toString(entity, "UTF-8") : null;
            // Parses the response
            return responseParser.parse(content);
        }));
    }


    private HttpUriRequest buildHttpUriRequest(HttpMethod httpMethod, String url, HttpEntity entity,
                                               Map<String, String> params) throws HttpException, URISyntaxException {
        URI uri = buildURI(url, params);
        final RequestBuilder requestBuilder = resolveRequestBuilder(httpMethod,uri);
        Optional.ofNullable(entity).ifPresent(requestBuilder::setEntity);
        return requestBuilder.build();
    }

    private URI buildURI(String url, Map<String, String> params) throws URISyntaxException {
        URIBuilder uriBuilder = new URIBuilder(url);
        Optional.ofNullable(params.entrySet())
                .ifPresent(map -> map.forEach(e -> uriBuilder.setParameter(e.getKey(), e.getValue())));

        return uriBuilder.build();
    }


    private RequestBuilder resolveRequestBuilder(HttpMethod method, URI uri) throws HttpException {
        switch (method) {
            case GET:
                return RequestBuilder.get().setUri(uri);

            case PUT:
                return RequestBuilder.put().setUri(uri);

            case POST:
                return RequestBuilder.post().setUri(uri);

            case DELETE:
                return RequestBuilder.delete().setUri(uri);
            default:
                throw new HttpException("Unsupported HttpMethod " + method);
        }

    }
}

Below is my RestService( which uses Synchronous version of Apache http client)

@Service
public class RestService extends RestServiceBase{

     @Autowired
    HttpClientFactory           httpClientFactory;

    public HttpResponse execute(HttpUriRequest request, Closeable httpClient) {
        if(! (httpClient instanceof CloseableHttpClient))
            throw new RuntimeException("UnSupported HttpClient Exception");
        try {
            CloseableHttpClient closeableHttpClient = (CloseableHttpClient) httpClient;
            return closeableHttpClient.execute(request);
        } catch (IOException e) {
            throw new RuntimeException();
        }
    }

    private CloseableHttpClient getHttpClient() {
        ApacheHttpClient apacheHttpClient = (ApacheHttpClient) httpClientFactory.getApacheHttpClient();
        return (CloseableHttpClient) apacheHttpClient.getHttpClient();
    }

    public <T> T call(HttpUriRequest request, ResponseHandler<T> responseHandler) {
        try {
            try (CloseableHttpClient httpClient = getHttpClient()) {
                HttpResponse response = execute(request, httpClient);
                // Entity response
                HttpEntity entity = response.getEntity();
                try {
                    return responseHandler.handleResponse(request, response, entity);
                } finally {
                    EntityUtils.consume(entity);
                }
            }
        } catch (IOException e) {
            throw new ClientGeneralException(request, e);
        } finally {
            ((HttpRequestBase) request).releaseConnection();
        }
    }

}

Below is my RestService( which uses Asynchronous version of Apache http client)

@Service
public class RestServiceAsync extends RestServiceBase{

     @Autowired
    HttpClientFactory           httpClientFactory;

    public HttpResponse execute(HttpUriRequest request, Closeable httpClient) {
        if(! (httpClient instanceof CloseableHttpAsyncClient))
            throw new RuntimeException("UnSupported HttpClient Exception");
        try {
            CloseableHttpAsyncClient closeableHttpClient = (CloseableHttpAsyncClient) httpClient;
            Future<HttpResponse> future = closeableHttpClient.execute(request, null);
            HttpResponse response = future.get();
            return response;
            //return (HttpResponse) closeableHttpClient.execute(request,null);
        } catch (InterruptedException | ExecutionException e) {
            request.abort();
            throw new RuntimeException();
        }
    }

    private CloseableHttpAsyncClient getHttpClient() {
        ApacheAsyncClient apacheAsyncClient = (ApacheAsyncClient) httpClientFactory.getApacheAsyncHttpClient();
        return (CloseableHttpClient) apacheAsyncClient.getHttpClient();
    }

    public <T> T call(HttpUriRequest request, ResponseHandler<T> responseHandler) {
        try {
            try (CloseableHttpAsyncClient httpClient = getHttpClient()) {
                HttpResponse response = execute(request, httpClient);
                // Entity response
                HttpEntity entity = response.getEntity();
                try {
                    return responseHandler.handleResponse(request, response, entity);
                } finally {
                    EntityUtils.consume(entity);
                }
            }
        } catch (IOException e) {
            throw new ClientGeneralException(request, e);
        } finally {
            ((HttpRequestBase) request).releaseConnection();
        }
    }
}

Below is my sample use case of RestServiceAsync

@Service
ReportsService(){

    @Autowired
    @Qualifier("restServiceAsync")
    RestService restService;

    public getReport(){
            restService.get("http://localhost/reports");
    }
}

I want to introduce Response read timeout so that main thread thats executing RestServiceAsync.execute method wont wait more than the desired amount of time for different use cases. Below were my approaches for doing this

1. Introduce a private timeout variable and use setter method in RestServiceAsync and do HttpResponse response = future.get(timeout, TimeUnit.Seconds);. This approach is not viable since RestServiceAsync is a singleton bean setting timeout variable would reflect for all the users of the application

2. Send timeout as a parameter from the method call as restService.get("http://localhost/reports",30); this would work but I have to update all the relevant methods in IRestService.

I am wondering if there is an alternative efficient approach to introduce timeout without having to touch all the methods in IRestService but making changes only in RestServiceAsync

PS: ApacheAsyncClient uses PoolingNHttpClientConnectionManager and ApacheHttpClient uses PoolingHttpClientConnectionManager

OTUser
  • 3,788
  • 19
  • 69
  • 127
  • Are you just reinventing `RestTemplate`? – chrylis -cautiouslyoptimistic- May 27 '20 at 00:28
  • @chrylis-cautiouslyoptimistic-our application is a decade old monolithic application and back then we implemented RestService using `Apache Http Client` so we are still using it instead of switching it to RestClient, we are going to convert it to Microservice in few years so we are spending the time and effort on upgrading the `RestService` to RestTemplate – OTUser May 27 '20 at 00:39
  • HttpClientFactory can do a job. When creating HttpClient set timeout. Could you please give more info how you have autowired HttpClientFactory and its configuration – user2880879 May 27 '20 at 01:09
  • I have already set the timeout for `HttpClient` as `60` seconds which is for all of the outbound requests, but my `RestServiceAsync` is used in various modules and there are various usecases where main thread should wait for only `10` or `20` or `30` seconds and other places it can be `60` seconds based on the module `RestServiceAsync` is being used, thats why I need something like `restService.get(url,timeout)` so that different modules can specify different timeout – OTUser May 27 '20 at 02:17

0 Answers0