Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RestTemplate's ResponseErrorHandler should take care the detail message at @ResponseBody from @ExceptionHandler [SPR-7001] #11666

Closed
spring-issuemaster opened this issue Mar 17, 2010 · 4 comments

Comments

@spring-issuemaster
Copy link
Collaborator

commented Mar 17, 2010

Simon Wong opened SPR-7001 and commented

Regarding to https://jira.springsource.org/browse/SPR-6902,
After implement the @ResponseBody at @ExceptionHandler, the ResponseErrorHandler's handleError() method should provide a way to access the @ResponseBody content.


Affects: 3.0.2

Issue Links:

  • #20103 RestTemplate is missing "Typed" error handler.

3 votes, 6 watchers

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Mar 17, 2010

Arjen Poutsma commented

I am not sure this is possible. The @ResponseBody content is read from the HttpRequestInputSteam. Once read, we cannot read it again...

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Mar 17, 2010

Simon Wong commented

Seems you are correct. The problem is the REST resources might return 4xx CLIENT_ERROR and according to REST best practice, detail problem information should be returned in the ResponseBody.

The detail exception information should be different from the expected domain object and seems the only way I could get the data is from the ResponseErrorHandler (but not the return value in the RestTemplate.getForObject() method).

However, current implementation of ResponseErrorHandler could only get HTTP status code and simple status text (fixed and could not be altered in runtime).

I am struggling in this situation for some time. Any better alternatives could be provided, regarding to this?

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Oct 6, 2011

Chris Astall commented

I had this exact same problem and felt it was missing from the framework and after a little reading developed the following solution. <aybe it can be included in a later release of the framework.

I introduced a new class RestResponseErrorHandler<T>, where T determines the type of the entity that will be returned in the event of an error. This class would be registered with the RestTemplate as an Error Handler. If a 400 series or 500 series error is returned from the REST service call then the handleError method of RestResponseErrorHandler<T> will handle the error. Handling the error involves created a ResponseEntity<T> from the unmarshalled response, headers and status code and then wrapping this in the other new class RestResponseException and throwing it. This can then be caught in the client code and used to get the extra data for the error.

Usage Example

RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new RestResponseErrorHandler<ErrorEntity>(ErrorEntity.class));

try {
    ResponseEntity<MainEntity> mainresponse = restTemplate.getForEntity(BASE_URL, MainEntity.class);
    // Do something with the successful response
    // ...
} catch (RestResponseException e) {
    ResponseEntity<ErrorEntity> errorResponse = (ResponseEntity<ErrorEntity>) e.getResponseEntity();
    // Do something with the error response
    // ...
}

RestResponseErrorHandler<T> class

package com.bac.util;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatus.Series;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.ClassUtils;
import org.springframework.web.client.HttpMessageConverterExtractor;
import org.springframework.web.client.ResponseErrorHandler;

import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;

public class RestResponseErrorHandler<T> implements ResponseErrorHandler {
	
	private static final boolean jaxb2Present =
			ClassUtils.isPresent("javax.xml.bind.Binder", RestResponseErrorHandler.class.getClassLoader());

	private static final boolean jacksonPresent =
			ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", RestResponseErrorHandler.class.getClassLoader()) &&
					ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", RestResponseErrorHandler.class.getClassLoader());

	private List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();

	private final HttpMessageConverterExtractor<T> delegate;
	
	public RestResponseErrorHandler(Class<T> responseType) {
		//Set up the message Converters
		this.messageConverters.add(new ByteArrayHttpMessageConverter());
		this.messageConverters.add(new StringHttpMessageConverter());
		this.messageConverters.add(new ResourceHttpMessageConverter());
		this.messageConverters.add(new FormHttpMessageConverter());
		this.messageConverters.add(new SourceHttpMessageConverter());
		if (jaxb2Present) {
			this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
		}
		if (jacksonPresent) {
			this.messageConverters.add(new MappingJacksonHttpMessageConverter());
		}
		
		this.delegate = new HttpMessageConverterExtractor<T>(responseType, this.messageConverters);;
	}

	// If a 400 or 500 series error is returned then we want to handle the error, otherwise not
	public boolean hasError(ClientHttpResponse response) throws IOException {
		HttpStatus statusCode = response.getStatusCode();
		if (statusCode.series() == Series.CLIENT_ERROR || statusCode.series() == Series.SERVER_ERROR)
			return true;
		return false;
	}

	public void handleError(ClientHttpResponse response) throws IOException {
		// Create a new generic Response Entity adding the unmarshalled response, headers and status code
		ResponseEntity<T> responseEntity = new ResponseEntity<T>(this.delegate.extractData(response), response.getHeaders(), response.getStatusCode());
		
		//Create a new RestResponseException and set the ResponseEntity
		RestResponseException responseException = new RestResponseException ();
		responseException.setResponseEntity(responseEntity);
		
		// Throw the Exception
		throw responseException;
	}

}

RestResponseException class

package com.bac.util;

import org.springframework.http.ResponseEntity;

import com.bac.util.wrappers.ExceptionWrapper;

public  class RestResponseException extends java.lang.RuntimeException {
	protected ResponseEntity<?> responseEntity;
	
	public RestResponseException () {
		super();
	}
	
	public ResponseEntity<?> getResponseEntity () {
		return this.responseEntity;
	}

	public void setResponseEntity(ResponseEntity<?> responseEntity) {
		this.responseEntity = (ResponseEntity<?>) responseEntity;
	}
	
}
@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jun 1, 2017

Rossen Stoyanchev commented

I believe this is now resolved after #20103 was addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.