Spring RestTemplate Behavior при обработке ответов со статусом NO_CONTENT - PullRequest
18 голосов
/ 24 июля 2010

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

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

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

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 и имеет ли его набор значение NULL.Если это так, то я устанавливаю код состояния в NO_CONTENT (204).

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

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

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

Метод SpringD 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) метод, который я использовал в тесте:

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?Я неправильно рассматриваю эту ситуацию?Пожалуйста, помогите, поскольку я немного сбит с толку.

Ответы [ 6 ]

12 голосов
/ 28 октября 2011

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

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

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

10 голосов
/ 02 марта 2011

Полагаю, вам, вероятно, следует взглянуть на интерфейс ResponseExtractor и вызвать execute на 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);

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

6 голосов
/ 12 ноября 2011

Вот простое решение, в котором вы можете установить тип контента по умолчанию для использования, если он отсутствует в ответе. 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);
            }
        });
    }
}
3 голосов
/ 12 июня 2012

Это должно быть исправлено в Spring 3.1 RC1.

https://jira.spring.io/browse/SPR-7911

1 голос
/ 11 ноября 2011

Или вы можете расширить 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();
        }
    }
}
1 голос
/ 06 сентября 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...