Как установить RequestConfiguration для каждого запроса с помощью RestTemplate?
У меня есть библиотека, которая используется клиентом, и они передают объект DataRequest
, который имеет userid
, timeout
и некоторые другие поля в нем. Теперь я использую этот объект DataRequest
для создания URL-адреса, а затем я делаю HTTP-вызов с помощью RestTemplate
, и моя служба возвращает ответ JSON, который я использую для создания объекта DataResponse
и возвращает этот объект DataResponse
назад к ним.
Ниже мой класс 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
класс:
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.
}
}
Клиент в нашей компании будет использовать мою библиотеку, как показано ниже, используя мой factory в своей базе кода -
// 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);
Я реализую sync call as async + waiting
, так как я хочу дросселировать их количеством потоков, иначе они могут бомбить нашу службу без какого-либо контроля.
Проблема: -
Я собираюсь добавить еще одну переменную timeout с именем socket timeout
в мой класс DataRequest
, и я хочу использовать это значение переменной (key.getSocketTimeout())
в моем методе clientHttpRequestFactory()
вместо использования жесткого кодированного значения 400. Каков наилучший и эффективный способ сделать это?
Сейчас я использую Inversion of Control
и передаю RestTemplate
в конструкторе для совместного использования RestTemplate
между всеми объектами моей задачи. Теперь я запутался, как использовать значение key.getSocketTimeout()
в моем методе clientHttpRequestFactory()
. Я думаю, что в основном это вопрос дизайна о том, как эффективно использовать RestTemplate
здесь, чтобы я мог использовать значение key.getSocketTimeout()
в моем методе clientHttpRequestFactory()
.
Я упростил код, чтобы идея поняла, что я пытаюсь сделать, и я нахожусь на Java 7. Использование ThreadLocal
- единственный вариант, который у меня есть или есть лучший и оптимизированный способ?
Ответы
Ответ 1
Как объясняет Питер, использование ThreadLocal здесь не очень хорошая идея.
Но я также не мог найти способ "передать значение цепочке вызовов методов".
Если вы используете простой "Apache HttpClient", вы можете создать HttpGet/Put/etc. и просто позвоните
httpRequest.setConfig(myRequestConfig)
. Другими словами: задайте конфигурацию запроса для запроса
(если в запросе ничего не задано, используется конфигурация запроса из HttpClient
, которая выполняет запрос).
Напротив, RestTemplate
звонки createRequest(URI, HttpMethod)
(определенные в HttpAccessor
)
который использует ClientHttpRequestFactory
. Другими словами: нет возможности установить конфигурацию запроса для каждого запроса.
Я не уверен, почему Spring вышел из этой опции, это кажется разумным функциональным требованием (или, может быть, я все еще что-то не хватает).
Некоторые примечания о том, "они могут бомбардировать наш сервис без какого-либо контроля":
- Это одна из причин использования
PoolingHttpClientConnectionManager
:
установив соответствующие максимальные значения, никогда не может быть больше, чем указанные максимальные соединения, используемые (и, следовательно, запросы) одновременно. Предполагается, что вы повторно используете один и тот же экземпляр RestTemplate
(и, следовательно, диспетчер соединений) для каждого запроса.
- Чтобы поймать наводнение раньше, укажите максимальное количество ожидающих задач в поточном пуле и установите правильный обработчик ошибок
(используйте
workQueue
и handler
в этот конструктор).
Ответ 2
ThreadLocal - это способ передачи динамического значения, которое обычно передается через свойства метода, но вы используете API, который вы не можете/не хотите изменять.
Вы устанавливаете ThreadLocal (возможно, структуру данных, содержащую несколько значений) на некотором уровне в стеке потоков, и вы можете использовать его далее в стеке.
Это лучший подход? НЕТ, вы должны действительно передать значение цепочке вызовов методов, но иногда это нецелесообразно.
Можете ли вы привести пример того, как будет выглядеть мой код с помощью ThreadLocal
Вы можете начать с
static final ThreadLocal<Long> SOCKET_TIMEOUT = new ThreadLocal<>();
Чтобы установить его, вы можете сделать
SOCKET_TIMEOUT .set(key.getSocketTimeout());
и получить значение, которое вы можете сделать
long socketTimeout = SOCKET_TIMEOUT.get();