postforentity getforobject exchange example clienthttprequestinterceptor body baeldung java web-services unit-testing rest spring-mvc

java - getforobject - Spring RestTemplate Comportamiento al manejar respuestas con un estado de NO_CONTENT



resttemplate put example (6)

Aquí hay una solución simple donde puede establecer el tipo de contenido predeterminado para su uso si falta en la respuesta. El Content-Type se agrega al encabezado de respuesta antes de que se devuelva al ResponseExtractor preconfigurado para su extracción.

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

Bueno, tengo una clase de NamedSystems, que tiene como único campo un Conjunto de NamedSystem.

Tengo un método para encontrar NamedSystems por ciertos criterios. Eso no es realmente importante. Cuando se obtienen resultados, todo funciona bien. Sin embargo, cuando no puede encontrar nada y, por lo tanto, devuelve un conjunto nulo (o vacío: lo he intentado en ambos sentidos), tengo problemas. Dejame explicar.

Estoy usando la clase Spring RestTemplate y hago una llamada como esta en una prueba de unidad:

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

Ahora, dado que esto normalmente devolvería un 200, pero quiero devolver un 204, tengo un interceptor en mi servicio que determina si un ModelAndView es un NamedSystem y si su conjunto es nulo. Si es así, entonces puse el código de estado en NO_CONTENT (204).

Cuando ejecuto mi prueba junit, recibo este error:

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

Establecer el estado en NO_CONTENT parece borrar el campo de tipo de contenido (lo que tiene sentido cuando lo pienso). Entonces, ¿por qué lo está mirando?

Método HttpMessageConverterExtractor extractData de Spring:

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 + "]"); }

Subiendo un poco por la cadena para averiguar dónde está instalado Extractor, vengo al método de intercambio () de RestTemplate que usé en la prueba:

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

Por lo tanto, se trata de convertir lo que equivale a nada debido al tipo de respuesta provisto desde la llamada de intercambio. Si cambio el tipo de respuesta de NamedSystems.class a nulo, funciona como se esperaba. No intenta convertir nada. Si hubiera intentado establecer el código de estado en 404, también se ejecuta bien.

¿Estoy equivocado, o esto parece un defecto en RestTemplate? Claro, estoy usando un junit ahora mismo, así que sé lo que va a pasar, pero si alguien usa RestTemplate para llamar a esto y no sabe el resultado de la llamada de servicio, naturalmente tendrían NamedSystems como un tipo de respuesta. Sin embargo, si intentaran realizar una búsqueda de criterios sin elementos, tendrían este desagradable error.

¿Hay alguna forma de evitar esto sin anular ningún elemento RestTemplate? ¿Estoy viendo esta situación incorrectamente? Por favor ayuda ya que estoy un poco confundido.


Creo que probablemente debería mirar la interfaz de ResponseExtractor y ejecutar call en RestTemplate proporcionando su implementación del extractor. A mí me parece un requisito común para hacer esto, así que he registrado esto:

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

Aquí hay uno que preparé anteriormente:

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

He probado esto y parece hacer lo que quiero.

Para crear la instancia de ResponseExtractor, llamo al constructor y paso los convertidores de una instancia de RestTemplate que se ha inyectado;

P.ej

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

Entonces la llamada es:

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

Su experiencia puede ser diferente. ;-)


Creo que tienes razón. Estoy teniendo un problema similar. Creo que deberíamos obtener un ResponseEntity con un HttpStatus de NO_CONTENT y un cuerpo nulo.



O puede extender RestTemplate y anular doExecute (..) y verificar el cuerpo de la respuesta.

Por ejemplo, aquí está lo que implementé y trabajamos para nosotros:

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


Una forma más de resolver esto sería hacer que la entidad de respuesta sea nula como se muestra a continuación.

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

Si aún necesita encabezados de respuesta, intente implementar el ResponseErrorHandler.