12

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

Below is my DataClient class used by customer by passing DataRequest object to it. I am using timeout value passed by customer in DataRequest to timeout the request if it is taking too much time in getSyncData method.

public class DataClient implements Client {

    private final RestTemplate restTemplate = new RestTemplate();
    private final ExecutorService service = Executors.newFixedThreadPool(10);

    // this constructor will be called only once through my factory
    // so initializing here
    public DataClient() {
        try {
          restTemplate.setRequestFactory(clientHttpRequestFactory());
        } catch (Exception ex) {
          // log exception
        }
    }           

    @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, restTemplate);
        Future<DataResponse> future = service.submit(task);

        return future;
    }

    // how to set socket timeout value by using `key.getSocketTimeout()` instead of using hard coded 400
    private ClientHttpRequestFactory clientHttpRequestFactory() {
        HttpComponentsClientHttpRequestFactory requestFactory =
            new HttpComponentsClientHttpRequestFactory();
        RequestConfig requestConfig =
            RequestConfig.custom().setConnectionRequestTimeout(400).setConnectTimeout(400)
                .setSocketTimeout(400).setStaleConnectionCheckEnabled(false).build();
        SocketConfig socketConfig =
            SocketConfig.custom().setSoKeepAlive(true).setTcpNoDelay(true).build();

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

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

        requestFactory.setHttpClient(httpClientBuilder);
        return requestFactory;
    }       
}

DataFetcherTask class:

public class DataFetcherTask implements Callable<DataResponse> {

    private final DataRequest key;
    private final RestTemplate restTemplate;

    public DataFetcherTask(DataRequest key, RestTemplate restTemplate) {
        this.key = key;
        this.restTemplate = restTemplate;
    }

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

Customer within our company will use my library like this as shown below by 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.

Problem Statement:-

I am going to add another timeout variable called socket timeout in my DataRequest class and I want to use that variable value (key.getSocketTimeout()) in my clientHttpRequestFactory() method instead of using hard coded 400 value. What is the best and efficient way to do that?

Right now I am using Inversion of Control and passing RestTemplate in a constructor to share the RestTemplate between all my Task objects. I am confuse now how to use key.getSocketTimeout() value in my clientHttpRequestFactory() method. I think this is mostly design question of how to use RestTemplate efficiently here so that I can use key.getSocketTimeout() value in my clientHttpRequestFactory() method.

I have simplified the code so that idea gets clear what I am trying to do and I am on Java 7. Using ThreadLocal is the only option I have here or there is any better and optimized way?

user1950349
  • 4,738
  • 19
  • 67
  • 119

2 Answers2

4

As Peter explains, using ThreadLocal is not a good idea here. But I also could not find a way to "pass the value up the chain of method calls".

If you use plain "Apache HttpClient", you can create an HttpGet/Put/etc. and simply call httpRequest.setConfig(myRequestConfig). In other words: set a request configuration per request (if nothing is set in the request, the request configuration from the HttpClient which executes the request is used).

In contrast, the RestTemplate calls createRequest(URI, HttpMethod) (defined in HttpAccessor) which uses the ClientHttpRequestFactory. In other words: there is no option to set a request configuration per request.
I'm not sure why Spring left this option out, it seems a reasonable functional requirement (or maybe I'm still missing something).

Some notes about the "they can bombard our service without any control":

  • This is one of the reasons to use the PoolingHttpClientConnectionManager: by setting the appropriate maximum values, there can never be more than the specified maximum connections in use (and thus requests running) at the same time. The assumption here is that you re-use the same RestTemplate instance (and thus connection manager) for each request.
  • To catch a flood earlier, specify a maximum amount of waiting tasks in the threadpool and set a proper error-handler (use the workQueue and handler in this constructor).
Community
  • 1
  • 1
vanOekel
  • 6,358
  • 1
  • 21
  • 56
  • Yeah I got the point regarding `ThreadLocal` but are there any other options which I can use here? It looks like there is no way which I can use here? – user1950349 May 11 '16 at 14:52
  • 1) open a feature request for spring web client. 2) use Apache HttpClient directly (no resttemplate) and "manually" convert the Json response to a DataObject (you'll be re-inventing the wheel somewhat but it is not that complicated). – vanOekel May 11 '16 at 16:12
  • sure I would open feature request. I tried using Apache HttpClient long time back but I was not able to make it work efficiently in multithreaded environment. If possible can you provide an example how can I use plain `Apache HttpClient` in my solution? That will be of great help and then I will do some load testing to see how it performs as compared to `RestTemplate`. – user1950349 May 11 '16 at 17:14
  • There are already many examples available (e.g. [this one](https://hc.apache.org/httpcomponents-client-ga/httpclient/examples/org/apache/http/examples/client/ClientMultiThreadedExecution.java)), just re-use one `CloseableHttpClient` instance like you would re-use one `RestTemplate` instance. – vanOekel May 11 '16 at 17:24
  • I am also using `CloseableHttpClient` in my current solution but along with `RestTemplate`. Is this not same? If I directly start using Apache HttpClient then how will that help me here? – user1950349 May 11 '16 at 18:59
  • By directly using the Apache HttpClient you can set a custom request-config per request (with the `key.getSocketTimeout()`), as I explained in my answer. – vanOekel May 11 '16 at 20:52
1

ThreadLocal is a way to pass dynamic value which normally you would pass via method properties, but you are using an API you can't/don't want to change.

You set the ThreadLocal (possible a data structure containing multiple values) at some level in the thread stack and you can use it further up the stack.

Is this the best approach? NO, you should really pass the value up the chain of method calls, but sometimes this is not practical.

Can you provide an example of how my code will look like with ThreadLocal

You might start with

static final ThreadLocal<Long> SOCKET_TIMEOUT = new ThreadLocal<>();

To set it you can do

SOCKET_TIMEOUT .set(key.getSocketTimeout());

and to get the value you can do

long socketTimeout = SOCKET_TIMEOUT.get();
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • Thanks for your suggestion so what are the options I have which I can use here? Any thoughts? – user1950349 May 11 '16 at 14:49
  • @user1950349 AFAICS your other options are going to be worse than using a ThreadLocal. – Peter Lawrey May 11 '16 at 15:13
  • I see. Can you provide an example of how my code will look like with `ThreadLocal`. I can do some load testing and see whether it impacts on performance or not. I haven't worked with ThreadLocal before and reading docs is causing me more confusion so not sure how can I use it in my solution. – user1950349 May 11 '16 at 20:50
  • @user1950349 What was your decision/test results on this matter? I have a similar requirement and am also considering using ThreadLocal – Edd Jul 14 '16 at 20:54