Подтвердить что ты не робот

Spring RestTemplate Поведение при обработке ответов со статусом NO_CONTENT

Хорошо, у меня есть класс NamedSystems, у которого в качестве единственного поля есть Set of NamedSystem.

У меня есть метод поиска NamedSystems по определенным критериям. Это не очень важно. Когда он получает результаты, все работает нормально. Однако, когда он ничего не может найти и, таким образом, возвращает нулевой (или пустой - я попытался в обоих направлениях), у меня возникают проблемы. Позвольте мне объяснить.

Я использую класс Spring RestTemplate, и я делаю такой вызов в unit test:

ResponseEntity<?> responseEntity = template.exchange(BASE_SERVICE_URL + "?
  alias={aliasValue}&aliasAuthority={aliasAssigningAuthority}", 
  HttpMethod.GET, makeHttpEntity("xml"), NamedSystems.class, 
  alias1.getAlias(), alias1.getAuthority());

Теперь, поскольку это обычно возвращает 200, но я хочу вернуть 204, у меня есть перехватчик в моей службе, который определяет, является ли ModelAndView NamedSystem, и если его набор равен нулю. Если это так, я затем установил код состояния NO_CONTENT (204).

Когда я запускаю свой тест junit, я получаю эту ошибку:

org.springframework.web.client.RestClientException: Cannot extract response: no Content-Type found

Настройка статуса на NO_CONTENT, похоже, уничтожает поле типа содержимого (что имеет смысл, когда я думаю об этом). Так почему же он даже смотрит на него?

Spring HttpMessageConverterExtractor extractData:

public T extractData(ClientHttpResponse response) throws IOException {
    MediaType contentType = response.getHeaders().getContentType();
    if (contentType == null) {
        throw new RestClientException("Cannot extract response: no Content-Type found");
    }
    for (HttpMessageConverter messageConverter : messageConverters) {
        if (messageConverter.canRead(responseType, contentType)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Reading [" + responseType.getName() + "] as \"" + contentType
                    +"\" using [" + messageConverter + "]");
            }
            return (T) messageConverter.read(this.responseType, response);
        }
    }
    throw new RestClientException(
        "Could not extract response: no suitable HttpMessageConverter found for response type [" +
        this.responseType.getName() + "] and content type [" + contentType + "]");
}

Поднимая цепочку немного, чтобы узнать, где установлен этот Extractor, я пришел к методу RestTemplate exchange(), который я использовал в тесте:

public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
  HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException {
    HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, responseType);
    ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(responseType);
    return execute(url, method, requestCallback, responseExtractor, uriVariables);
}

Итак, он пытается конвертировать то, что ничего не значит из-за предоставленного типа ответа из обменного вызова. Если я изменил responseType с NamedSystems.class на null, он работает так, как ожидалось. Он ничего не пытается конвертировать. Если бы я попытался установить код состояния на 404, он также выполняет штраф.

Я ошибаюсь, или это кажется недостатком в RestTemplate? Конечно, я использую junit прямо сейчас, поэтому я знаю, что произойдет, но если кто-то использует RestTemplate для вызова этого и не знает результата вызова службы, у них, естественно, будет NamedSystems как тип ответа. Однако, если они попробовали поиск критериев, в которых не было элементов, у них была бы эта неприятная ошибка.

Есть ли способ обойти это без переопределения каких-либо материалов RestTemplate? Я рассматриваю эту ситуацию неправильно? Пожалуйста, помогите, поскольку я немного озадачен.

4b9b3361

Ответ 2

Я считаю, что вам, вероятно, следует взглянуть на интерфейс ResponseExtractor и вызвать выполнение на RestTemplate, обеспечивающее реализацию экстрактора. Для меня это похоже на общее требование для этого, поэтому зарегистрировалось это:

https://jira.springsource.org/browse/SPR-8016

Здесь я подготовил ранее:

private class MyResponseExtractor extends HttpMessageConverterExtractor<MyEntity> {

    public MyResponseExtractor (Class<MyEntity> responseType,
      List<HttpMessageConverter<?>> messageConverters) {
        super(responseType, messageConverters);
    }

    @Override
    public MyEntity extractData(ClientHttpResponse response) throws IOException {

        MyEntity result;

        if (response.getStatusCode() == HttpStatus.OK) {
            result = super.extractData(response);
        } else {
            result = null;
        }

        return result;
    }
}

Я тестировал это и, похоже, делаю то, что хочу.

Чтобы создать экземпляр ResponseExtractor, я вызываю конструктор и передаю преобразователи из экземпляра RestTemplate, который был введен;

например.

ResponseExtractor<MyEntity> responseExtractor =
    new MyResponseExtractor(MyEntity.class, restTemplate.getMessageConverters());

Тогда вызов:

MyEntity responseAsEntity =
    restTemplate.execute(urlToCall, HttpMethod.GET, null, responseExtractor);

Ваш пробег может отличаться.; -)

Ответ 3

Еще один способ решить это - сделать объект ответа как null, как показано ниже.

  ResponseEntity<?> response = restTemplate.exchange("http://localhost:8080/myapp/user/{userID}",
                                                             HttpMethod.DELETE, 
                                                             requestEntity,
                                                             null,
                                                             userID);

Если вам все еще нужны заголовки ответов, попробуйте выполнить ResponseErrorHandler.

Ответ 4

Вот простое решение, в котором вы можете установить стандартный Content-Type для использования, если он отсутствует в ответе. Content-Type добавляется в заголовок ответа до его возврата к предварительно сконфигурированному ResponseExtractor для извлечения.

public class CustomRestTemplate extends RestTemplate {

    private MediaType defaultResponseContentType;

    public CustomRestTemplate() {
        super();
    }

    public CustomRestTemplate(ClientHttpRequestFactory requestFactory) {
        super(requestFactory);
    }

    public void setDefaultResponseContentType(String defaultResponseContentType) {
        this.defaultResponseContentType = MediaType.parseMediaType(defaultResponseContentType);
    }

    @Override
    protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback, final ResponseExtractor<T> responseExtractor)
            throws RestClientException {

        return super.doExecute(url, method, requestCallback, new ResponseExtractor<T>() {
            public T extractData(ClientHttpResponse response) throws IOException {
                if (response.getHeaders().getContentType() == null && defaultResponseContentType != null) {
                    response.getHeaders().setContentType(defaultResponseContentType);
                }

                return responseExtractor.extractData(response);
            }
        });
    }
}

Ответ 5

Я думаю, что ты прав. У меня такая же проблема. Я думаю, что мы должны получить ResponseEntity с HttpStatus из NO_CONTENT и нулевого тела.

Ответ 6

Или вы можете расширить RestTemplate и переопределить doExecute (..) и проверить тело ответа.

Например, вот что я реализовал и работает для нас:

@Override
protected <T> T doExecute(final URI url, final HttpMethod method, final RequestCallback requestCallback, final ResponseExtractor<T> responseExtractor)
        throws RestClientException
{
    Assert.notNull(url, "'url' must not be null");
    Assert.notNull(method, "'method' must not be null");
    ClientHttpResponse response = null;
    try
    {
        final ClientHttpRequest request = createRequest(url, method);
        if (requestCallback != null)
        {
            requestCallback.doWithRequest(request);
        }
        response = request.execute();
        if (!getErrorHandler().hasError(response))
        {
            logResponseStatus(method, url, response);
        }
        else
        {
            handleResponseError(method, url, response);
        }
        if ((response.getBody() == null) || (responseExtractor == null))
        {
            return null;
        }
        return responseExtractor.extractData(response);
    }
    catch (final IOException ex)
    {
        throw new ResourceAccessException("I/O error: " + ex.getMessage(), ex);
    }
    finally
    {
        if (response != null)
        {
            response.close();
        }
    }
}