Как установить RequestConfiguration для каждого запроса с помощью RestTemplate? - программирование
Подтвердить что ты не робот

Как установить 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 - единственный вариант, который у меня есть или есть лучший и оптимизированный способ?

4b9b3361

Ответ 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();