web-dev-qa-db-ja.com

ステータスがNO_CONTENTの応答を処理するときのSpring RestTemplateの動作

OK、クラスNamedSystemsがあり、唯一のフィールドとしてNamedSystemのセットがあります。

特定の基準でNamedSystemsを検索する方法があります。それはそれほど重要ではありません。結果が出ると、すべて正常に動作します。ただし、何も見つからず、null(または空-両方の方法を試した)セットを返す場合、問題が発生します。説明させてください。

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に設定すると、コンテンツタイプフィールドがワイプされるように見えます(私が考えると、これは理にかなっています)。では、なぜそれを見ているのでしょうか?

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);
}

したがって、exchange呼び出しから提供された応答タイプのために、何を意味しないものに変換しようとしています。 responseTypeをNamedSystems.classからnullに変更すると、期待どおりに機能します。何も変換しようとしません。ステータスコードを404に設定しようとした場合も、正常に実行されます。

私は見当違いですか、それともRestTemplateの欠陥のように見えますか?確かに、私は今junitを使用しているので、何が起こるかはわかっていますが、誰かがRestTemplateを使用してこれを呼び出しており、サービス呼び出しの結果がわからない場合は、当然、応答タイプとしてNamedSystemsがあります。ただし、要素が見つからない条件検索を試行した場合、この厄介なエラーが発生します。

RestTemplateのものをオーバーライドせずにこれを回避する方法はありますか?この状況を誤って表示していますか?私は少し困惑しているので助けてください。

18
AHungerArtist

これは、Spring 3.1 RC1で修正されています。

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

3
AHungerArtist

これを解決するもう1つの方法は、以下に示すように、応答エンティティをnullにすることです。

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

それでも応答ヘッダーが必要な場合は、ResponseErrorHandlerを実装してみてください。

12
Chandra

おそらく、Extractorの実装を提供するRestTemplateでResponseExtractorインターフェイスを呼び出してexecuteを呼び出す必要があると思います。私にはこれを行うための一般的な要件のように見えるので、これをログに記録しました:

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

あなたのマイレージは異なる場合があります。 ;-)

10
David Victor

これは、応答に欠落している場合に使用するデフォルトの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);
            }
        });
    }
}
6
Jer K.

私はあなたが正しいと思います。同様の問題が発生しています。 HttpStatusがNO_CONTENTで、本文がnullのResponseEntityを取得する必要があると思います。

1

または、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
Mig56