如何使用RestTemplate为每个请求设置RequestConfiguration ?

时间:2022-10-23 21:01:53

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.

我有一个客户正在使用的库,他们正在传递DataRequest对象,其中包含userid、timeout和其他一些字段。现在我使用这个DataRequest对象来创建一个URL,然后使用RestTemplate进行HTTP调用,我的服务返回一个JSON响应,我用它来创建一个DataResponse对象,并将这个DataResponse对象返回给它们。

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.

下面是我的DataClient类,客户通过将DataRequest对象传递给它使用。我正在使用DataRequest中客户传递的超时值来超时请求,如果在getSyncData方法中请求占用了太多时间。

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:

DataFetcherTask类:

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.

我将同步调用实现为async + wait,因为我想用线程的数量限制它们,否则它们可以在没有任何控制的情况下对我们的服务进行轰击。

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?

在我的DataRequest类中,我将添加另一个名为socket超时的超时变量,我希望在我的clientHttpRequestFactory()方法中使用这个变量值(key.getSocketTimeout()),而不是使用硬编码的400值。最好和有效的方法是什么?

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.

现在,我正在使用控制反转,并在构造函数中传递RestTemplate,以便在所有任务对象之间共享RestTemplate。我现在很困惑如何在我的clientHttpRequestFactory()方法中使用key.getSocketTimeout()值。我认为这主要是关于如何在这里有效地使用RestTemplate的设计问题,以便在我的clientHttpRequestFactory()方法中使用key.getSocketTimeout()值。

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?

我简化了代码,这样我就可以清楚地知道我在做什么,我正在使用Java 7。使用ThreadLocal是这里唯一的选项,还是有更好的优化方法?

2 个解决方案

#1


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".

正如Peter解释的,使用ThreadLocal在这里不是一个好主意。但我也找不到一种“将值传递到方法调用链上”的方法。

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).

如果您使用普通的“Apache HttpClient”,您可以创建一个HttpGet/Put/等等。并简单地调用httpRequest.setConfig(myRequestConfig)。换句话说:为每个请求设置一个请求配置(如果请求中没有设置任何内容,则使用执行请求的HttpClient的请求配置)。

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).

相反,RestTemplate调用createRequest(URI, HttpMethod)(在HttpAccessor中定义),它使用ClientHttpRequestFactory。换句话说:没有为每个请求设置请求配置的选项。我不知道Spring为什么放弃了这个选项,它似乎是一个合理的功能需求(或者我可能还漏掉了什么)。

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.
  • 这是使用PoolingHttpClientConnectionManager的原因之一:通过设置适当的最大值,在同时使用的最大连接(从而运行请求)不能超过指定的最大连接数。这里的假设是,为每个请求重用相同的RestTemplate实例(以及因此连接管理器)。
  • 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).
  • 要更早地捕获洪水,请在threadpool中指定等待任务的最大数量,并设置适当的错误处理程序(在此构造函数中使用工作队列和处理程序)。

#2


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.

ThreadLocal是一种传递动态值的方法,通常通过方法属性传递,但是您正在使用一个您不能/不想更改的API。

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.

在线程堆栈的某个级别上设置ThreadLocal(可能是包含多个值的数据结构),然后可以在堆栈的更上层使用它。

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

您能提供一个使用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();

#1


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".

正如Peter解释的,使用ThreadLocal在这里不是一个好主意。但我也找不到一种“将值传递到方法调用链上”的方法。

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).

如果您使用普通的“Apache HttpClient”,您可以创建一个HttpGet/Put/等等。并简单地调用httpRequest.setConfig(myRequestConfig)。换句话说:为每个请求设置一个请求配置(如果请求中没有设置任何内容,则使用执行请求的HttpClient的请求配置)。

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).

相反,RestTemplate调用createRequest(URI, HttpMethod)(在HttpAccessor中定义),它使用ClientHttpRequestFactory。换句话说:没有为每个请求设置请求配置的选项。我不知道Spring为什么放弃了这个选项,它似乎是一个合理的功能需求(或者我可能还漏掉了什么)。

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.
  • 这是使用PoolingHttpClientConnectionManager的原因之一:通过设置适当的最大值,在同时使用的最大连接(从而运行请求)不能超过指定的最大连接数。这里的假设是,为每个请求重用相同的RestTemplate实例(以及因此连接管理器)。
  • 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).
  • 要更早地捕获洪水,请在threadpool中指定等待任务的最大数量,并设置适当的错误处理程序(在此构造函数中使用工作队列和处理程序)。

#2


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.

ThreadLocal是一种传递动态值的方法,通常通过方法属性传递,但是您正在使用一个您不能/不想更改的API。

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.

在线程堆栈的某个级别上设置ThreadLocal(可能是包含多个值的数据结构),然后可以在堆栈的更上层使用它。

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

您能提供一个使用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();