web-dev-qa-db-ja.com

Spring MVC-HTTP 404が発生した場合のRestTemplate起動例外

リソースが見つからないときに404エラーを送信するRESTサービスがあります。ここで、Http 404を送信するコントローラーと例外のソース。

@Controller
@RequestMapping("/site")
public class SiteController
{

    @Autowired
    private IStoreManager storeManager;

    @RequestMapping(value = "/stores/{pkStore}", method = RequestMethod.GET, produces = "application/json")
    @ResponseBody
    public StoreDto getStoreByPk(@PathVariable long pkStore) {       
        Store s = storeManager.getStore(pkStore);
        if (null == s) {
            throw new ResourceNotFoundException("no store with pkStore : " + pkStore);
        }
        return StoreDto.entityToDto(s);       

    }
}

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException
{       
    private static final long serialVersionUID = -6252766749487342137L;    
    public ResourceNotFoundException(String message) {
        super(message);
    }    
}

このコードでRestTemplateでそれを呼び出そうとすると:

ResponseEntity<StoreDto> r = restTemplate.getForEntity(url, StoreDto.class, m);
 System.out.println(r.getStatusCode());
 System.out.println(r.getBody());

私はこの例外を受け取ります:

org.springframework.web.client.RestTemplate handleResponseError
ATTENTION: GET request for "http://........./stores/99" resulted in 404 (Introuvable); invoking error handler
org.springframework.web.client.HttpClientErrorException: 404 Introuvable

私はresponseEntityオブジェクトを探索し、statusCodeでいくつかのことができると考えていました。しかし、例外は起動であり、アプリはダウンします。

RestTemplateに例外を送信せず、ResponseEntityに入力する特定の構成がありますか?.

助けてくれてありがとう。

-

ロイック

25
loic

私の知る限り、実際のResponseEntityを取得することはできませんが、例外からステータスコードと本文(ある場合)を取得できます。

try {
    ResponseEntity<StoreDto> r = restTemplate.getForEntity(url, StoreDto.class, m);
}
catch (final HttpClientErrorException e) {
    System.out.println(e.getStatusCode());
    System.out.println(e.getResponseBodyAsString());
}
33
Squatting Bear

RESTTemplateは、この分野のIMOで非常に不十分です。エラーが発生したときに応答本文を抽出する方法については、こちらのブログ記事をご覧ください。

http://springinpractice.com/2013/10/07/handling-json-error-object-responses-with-springs-resttemplate

今日の時点で、テンプレートが応答本文を抽出する可能性を提供する未解決のJIRAリクエストがあります。

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

Squatting Bearの答えの問題は、キャッチブロック内のステータスコードを調べる必要があることです。たとえば、404だけを扱いたい場合です。

最後のプロジェクトでこれをどのように回避したかを以下に示します。より良い方法があるかもしれませんが、私のソリューションはResponseBodyをまったく抽出しません。

public class ClientErrorHandler implements ResponseErrorHandler
{
   @Override
   public void handleError(ClientHttpResponse response) throws IOException 
   {
       if (response.getStatusCode() == HttpStatus.NOT_FOUND)
       {
           throw new ResourceNotFoundException();
       }

       // handle other possibilities, then use the catch all... 

       throw new UnexpectedHttpException(response.getStatusCode());
   }

   @Override
   public boolean hasError(ClientHttpResponse response) throws IOException 
   {
       return response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR
         || response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR;
   }

ResourceNotFoundExceptionとUnexpectedHttpExceptionは、私自身の未チェックの例外です。

残りのテンプレートを作成するとき:

    RestTemplate template = new RestTemplate();
    template.setErrorHandler(new ClientErrorHandler());

これで、リクエストを作成するときに、少しきれいな構造が得られます。

    try
    {
        HttpEntity response = template.exchange("http://localhost:8080/mywebapp/customer/100029",
                                        HttpMethod.GET, requestEntity, String.class);
        System.out.println(response.getBody());
    }
    catch (ResourceNotFoundException e)
    {
        System.out.println("Customer not found");
    }
13

例外をスローしない独自のRestTemplateラッパーを作成できますが、受信したステータスコードで応答を返します。 (本文を返すこともできますが、タイプセーフではなくなるため、以下のコードでは本文は単にnullのままです。)

/**
 * A Rest Template that doesn't throw exceptions if a method returns something other than 2xx
 */
public class GracefulRestTemplate extends RestTemplate {
    private final RestTemplate restTemplate;

    public GracefulRestTemplate(RestTemplate restTemplate) {
        super(restTemplate.getMessageConverters());
        this.restTemplate = restTemplate;
    }

    @Override
    public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException {
        return withExceptionHandling(() -> restTemplate.getForEntity(url, responseType));
    }

    @Override
    public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException {
        return withExceptionHandling(() -> restTemplate.postForEntity(url, request, responseType));
    }

    private <T> ResponseEntity<T> withExceptionHandling(Supplier<ResponseEntity<T>> action) {
        try {
            return action.get();
        } catch (HttpClientErrorException ex) {
            return new ResponseEntity<>(ex.getStatusCode());
        }
    }
}
3
siledh

最近、これのユースケースがありました。私の解決策:

public class MyErrorHandler implements ResponseErrorHandler {

@Override
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
    return hasError(clientHttpResponse.getStatusCode());
}

@Override
public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
    HttpStatus statusCode = clientHttpResponse.getStatusCode();
    MediaType contentType = clientHttpResponse
        .getHeaders()
        .getContentType();
    Charset charset = contentType != null ? contentType.getCharset() : null;
    byte[] body = FileCopyUtils.copyToByteArray(clientHttpResponse.getBody());

    switch (statusCode.series()) {
        case CLIENT_ERROR:
            throw new HttpClientErrorException(statusCode, clientHttpResponse.getStatusText(), body, charset);
        case SERVER_ERROR:
            throw new HttpServerErrorException(statusCode, clientHttpResponse.getStatusText(), body, charset);
        default:
            throw new RestClientException("Unknown status code [" + statusCode + "]");
    }

}

private boolean hasError(HttpStatus statusCode) {
    return (statusCode.series() == HttpStatus.Series.CLIENT_ERROR ||
        statusCode.series() == HttpStatus.Series.SERVER_ERROR);
}
0
eosimosu