12
\$\begingroup\$

I have a library which is being used by customers and they are passing DataRequest object which has userid, various timeouts and some other fields in it. Now I use this DataRequest object to create a URL, then make an HTTP call using Apache HttpClient for which my service returns a JSON response which I use to create and return a DataResponse object back to them.

Here is my DataClient class used by customers by passing a DataRequest object to it:

public class DataClient implements Client {

    private final ExecutorService service = Executors.newFixedThreadPool(10);
    private CloseableHttpClient httpClientBuilder;

    // this constructor will be called only once through my factory
    // so initializing defaults here
    public DataClient() {
        // do I need this request config object here if I am creating it in my call method as well?
        RequestConfig requestConfig =
            RequestConfig.custom().setConnectionRequestTimeout(500).setConnectTimeout(500)
              .setSocketTimeout(500).setStaleConnectionCheckEnabled(false).build();

        SocketConfig socketConfig =
            SocketConfig.custom().setSoKeepAlive(true).setTcpNoDelay(true).build();

        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager =
            new PoolingHttpClientConnectionManager();
        poolingHttpClientConnectionManager.setMaxTotal(300);
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(200);

        httpClientBuilder =
        HttpClientBuilder.create().setConnectionManager(poolingHttpClientConnectionManager)
          .setDefaultRequestConfig(requestConfig).setDefaultSocketConfig(socketConfig).build();
    }

    @Override
    public DataResponse getSyncData(DataRequest key) {
        DataResponse response = null;
        Future<DataResponse> responseFuture = null;

        try {
            responseFuture = getAsyncData(key);
            response = responseFuture.get(key.getTimeout(), key.getTimeoutUnit());
        } catch (TimeoutException ex) {
            response = new DataResponse(DataErrorEnum.CLIENT_TIMEOUT, DataStatusEnum.ERROR);
            responseFuture.cancel(true);
            // logging exception here
        }

        return response;
    }

    @Override
    public Future<DataResponse> getAsyncData(DataRequest key) {
        DataFetcherTask task = new DataFetcherTask(key, httpClientBuilder);
        Future<DataResponse> future = service.submit(task);
        return future;
    }
}

DataFetcherTask class:

public class DataFetcherTask implements Callable<DataResponse> {

    private final DataRequest key;
    private final CloseableHttpClient httpClientBuilder;

    public DataFetcherTask(DataRequest key, CloseableHttpClient httpClientBuilder) {
        this.key = key;
        this.httpClientBuilder = httpClientBuilder;
    }

    // In a nutshell below is what I am doing here.
    // 1. Make an url using DataRequest key.
    // 2. And then execute the url using Apache HttpClient.
    // 3. Make a DataResponse object and return it.     
    @Override
    public DataResponse call() throws Exception {

        // some code to find out the "hostnames"

        for (String hostname : hostnames) {
          try {
            HttpGet httpGet = new HttpGet(makeURL(hostname));
            httpGet.setConfig(makeConfig());
            httpGet.addHeader(key.getHeader());

            // closing CloseableHttpResponse in try resource block
            try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
                HttpEntity entity = response.getEntity();
                String responseBody = IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8);
                int statusCode = response.getStatusLine().getStatusCode();

                if (statusCode == HttpStatus.OK.value()) {
                    // create successful DataResponse and return it
                }
                // otherwise log error and return DataResponse with error in it
            }
          } catch (InterruptedIOException ex) {
            // usually comes here if there is socket timeout or connect timed out
            // log error and call another machine
          } catch (IOException ex) {
            // log error
          }
        }

        // not sure whether I can use copy method of RequestConfig here
        private RequestConfig makeConfig() {
            return RequestConfig.custom().setConnectionRequestTimeout(key.getConnectionRequestTimeout())
                .setConnectTimeout(key.getConnectTimeout()).setSocketTimeout(key.getSocketTimeout())
                .setStaleConnectionCheckEnabled(false).build();
        }           
    }
}

A customer within our company will use my library like this using my factory in their code base:

// if they are calling `getSyncData()` method
DataResponse response = DataClientFactory.getInstance().getSyncData(key);

// and if they want to call `getAsyncData()` method
Future<DataResponse> response = DataClientFactory.getInstance().getAsyncData(key);

I am implementing sync call as async + waiting since I want to throttle them with the number of threads otherwise they can bombard our service without any control. My library will be used by lots of customers within our company and their applications won't ever shutdown, they will keep running all the time. The only thing that will happen is their machines will get restarted, that's all. I have to use timeout values present in my DataRequest class in my Apache HttpClient calls so that's why I am creating a RequestConfig and using it with HttpClient for each request. I am on Java 7.

I'd like to know if there's any efficient way of using Apache HttpClient in a multithreaded environment in production.

\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

Based on my experience with a proof of concept, usage of response handlers can improve performance up to 10 times. It also avoids the necesity of explicit release of resources like the closable response.

This block of code:

try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
    HttpEntity entity = response.getEntity();
    String responseBody = IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8);
    int statusCode = response.getStatusLine().getStatusCode();
    if (statusCode == HttpStatus.OK.value()) {
        // create successful DataResponse and return it
    }
    // otherwise log error and return DataResponse with error in it
} catch(....) {

can be replaced with something similar to:

// Create a custom response handler
ResponseHandler<String> responseHandler = new ResponseHandler<String>() {

    @Override
    public String handleResponse(final HttpResponse response) throws ClientProtocolException, IOException {

        int status = response.getStatusLine().getStatusCode();
        if (status >= 200 && status < 300) {
            HttpEntity entity = response.getEntity();
            return entity != null ? EntityUtils.toString(entity) : null;
        } else {
            throw new ClientProtocolException("Unexpected response status: " + status);
        }
    }
};
String responseBody = httpclient.execute(httpget, responseHandler);
// TODO handle ClientProtocolException, raised when status code is not OK
\$\endgroup\$
3
\$\begingroup\$
    Future<DataResponse> future = service.submit(task);
    return future;

This could be collapsed to return service.submit(task); - the variable name and datatype are both duplicate information.

            if (statusCode == HttpStatus.OK.value()) {
                // create successful DataResponse and return it
            }
            // otherwise log error and return DataResponse with error in it

You might have snipped too much out here...

    for (String hostname : hostnames) {
      try {
        HttpGet httpGet = new HttpGet(makeURL(hostname));
        httpGet.setConfig(makeConfig());
        httpGet.addHeader(key.getHeader());

        // closing CloseableHttpResponse in try resource block
        try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
            HttpEntity entity = response.getEntity();

Indentation levels inconsistent - pick 4 spaces or 2, but don't swap between them.

public DataResponse getSyncData(DataRequest key) {
    DataResponse response = null;
    Future<DataResponse> responseFuture = null;

    try {
        responseFuture = getAsyncData(key);

getAsyncData(key) does not throw TimeoutException, so you can move this statement out of the try-catch. If it DID throw exceptions and you caught them,

    } catch (TimeoutException ex) {
        response = new DataResponse(DataErrorEnum.CLIENT_TIMEOUT, DataStatusEnum.ERROR);
        responseFuture.cancel(true);
        // logging exception here
    }

Then responseFuture.cancel(true) would give you a null pointer exception and thus fail to produce an error response.

\$\endgroup\$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.