How to handle HttpClientException properly - java

I have a webservice which gets data from other webservice and return back to the browser.
I want to hide internal client errors
Want to throw 404, 400 etc which
are returned from the webservice in the below method.
How to resolve this problem in a neat way?
Option 1 or Option 2 is clean way?
Option 1
public <T> Optional<T> get(String url, Class<T> responseType) {
String fullUrl = url;
LOG.info("Retrieving data from url: "+fullUrl);
try {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(ImmutableList.of(MediaType.APPLICATION_JSON));
headers.add("Authorization", "Basic " + httpAuthCredentials);
HttpEntity<String> request = new HttpEntity<>(headers);
ResponseEntity<T> exchange = restTemplate.exchange(fullUrl, HttpMethod.GET, request, responseType);
if(exchange !=null)
return Optional.of(exchange.getBody());
} catch (HttpClientErrorException e) {
LOG.error("Client Exception ", e);
throw new HttpClientError("Client Exception: "+e.getStatusCode());
}
return Optional.empty();
}
(or)
Option 2
public <T> Optional<T> get(String url, Class<T> responseType) {
String fullUrl = url;
LOG.info("Retrieving data from url: "+fullUrl);
try {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(ImmutableList.of(MediaType.APPLICATION_JSON));
headers.add("Authorization", "Basic " + httpAuthCredentials);
HttpEntity<String> request = new HttpEntity<>(headers);
ResponseEntity<T> exchange = restTemplate.exchange(fullUrl, HttpMethod.GET, request, responseType);
if(exchange !=null)
return Optional.of(exchange.getBody());
throw new RestClientResponseException("", 400, "", null, null, null);
} catch (HttpStatusCodeException e) {
LOG.error("HttpStatusCodeException ", e);
throw new RestClientResponseException(e.getMessage(), e.getStatusCode().value(), e.getStatusText(), e.getResponseHeaders(), e.getResponseBodyAsByteArray(), Charset.defaultCharset());
}
return Optional.empty();
}

I have written a sample ResponseErrorHandler for you,
public class RestTemplateClientErrorHandler implements ResponseErrorHandler {
private static final Logger logger = LoggerFactory.getLogger(RestTemplateClientErrorHandler.class);
#Override
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
return RestUtil.isError(clientHttpResponse.getStatusCode());
}
#Override
public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
String responseBody = "";
if(clientHttpResponse != null && clientHttpResponse.getBody() != null){
responseBody = IOUtils.toString(clientHttpResponse.getBody());
}
switch(clientHttpResponse.getRawStatusCode()){
case 404:
logger.error("Entity not found. Message: {}. Status: {} ",responseBody,clientHttpResponse.getStatusCode());
throw new RestClientResponseException(responseBody);
case 400:
logger.error("Bad request for entity. Message: {}. Status: {}",responseBody, clientHttpResponse.getStatusCode());
throw new RestClientResponseException(StringUtils.EMPTY, 400,StringUtils.EMPTY, StringUtils.EMPTY, StringUtils.EMPTY, StringUtils.EMPTY);
default:
logger.error("Unexpected HTTP status: {} received when trying to delete entity in device repository.", clientHttpResponse.getStatusCode());
throw new RestClientResponseException(responseBody);
}
}
public static class RestUtil {
private RestUtil() {
throw new IllegalAccessError("Utility class");
}
public static boolean isError(HttpStatus status) {
HttpStatus.Series series = status.series();
return HttpStatus.Series.CLIENT_ERROR.equals(series)
|| HttpStatus.Series.SERVER_ERROR.equals(series);
}
}
}
Note : This is common ResponseErrorHandler for your restTemplate and it will catch all the exceptions thrown by restTemplate you don't require try,catch block in each method and you don't need to catch "HttpStatusCodeException" or any other exception.
Please use the below code to register this ErrorHandler.
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new RestTemplateClientErrorHandler());
You can also find examples here.
You can refactor your client class like this,
public <T> Optional<T> get(String url, Class<T> responseType) {
String fullUrl = url;
LOG.info("Retrieving data from url: "+fullUrl);
HttpHeaders headers = new HttpHeaders();
headers.setAccept(ImmutableList.of(MediaType.APPLICATION_JSON));
headers.add("Authorization", "Basic " + httpAuthCredentials);
HttpEntity<String> request = new HttpEntity<>(headers);
ResponseEntity<T> exchange = restTemplate.exchange(fullUrl, HttpMethod.GET, request, responseType);
if(exchange !=null)
return Optional.of(exchange.getBody());
return Optional.empty();
}
So your method not looking beautiful now ? Suggestions welcome.

Related

Is It possible to change the content of the Body of a responseEntity

I am trying to return the content of a Json file. But I want to modify before sending it to the front end. I want to add "[" and "]" at the beginning and end of the file. I am doing that because the json file has multiple json root elements.
Like for example extract the result as illustrated in
result = restTemplate.executeRequest(HttpMethod.GET, String.class);
//change Body and put it back in result
Question
Is it possible to change the body of the response and put it back in ResponseEntity?
Source Code
public ResponseEntity<String> getScalityObject(String chainCode, String dataCenter, String path, String byteRange) {
Map<String, Object> queryParams = new HashMap<>();
if (dataCenter != null && !dataCenter.isEmpty()) {
queryParams.put("dataCenter", dataCenter);
}
if (byteRange != null && !byteRange.isEmpty()) {
queryParams.put("byteRange", byteRange);
}
String decodedStr = URLDecoder.decode(path);
queryParams.put("path", decodedStr);
reservationService.setContext(
RESA_INTERNAL_SERVICE_NAME,
queryParams,
"/chains/{chainCode}/objects/file",
chainCode);
restTemplate.setServiceDefinition(reservationService);
ResponseEntity<String> result;
try {
result = restTemplate.executeRequest(HttpMethod.GET, String.class);
//Change responseBody here
return result;
} catch (IOException e) {
e.printStackTrace();
result = new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
return result;
}
public <T> ResponseEntity<T> executeRequest(HttpMethod method, Class<T> responseType) throws IOException {
if (this.serviceDefinition == null) {
throw new IllegalArgumentException("You haven't provided any service definition for this call. " +
"Are you sure you called the right method before using this Amadeus Rest Template?");
}
// Resolve the URI
URI url = this.serviceDefinition.getUriComponents().toUri();
// Add the extra headers if necessary
HttpHeaders headers = new HttpHeaders();
if (this.serviceDefinition.getHeaders() != null) {
for(Map.Entry<String,String> headerSet : this.serviceDefinition.getHeaders().entrySet()) {
headers.put(headerSet.getKey(), Arrays.asList(headerSet.getValue()));
}
}
HttpEntity entity = new HttpEntity(headers);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
RequestCallback requestCallback = httpEntityCallback(entity, responseType);
ClientHttpResponse response = null;
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
catch (IOException ex) {
throw ex;
}
finally {
if (response != null) {
response.close();
}
}
}
One of the way which I can think of is :
ResponseEntity<String> result = restTemplate.executeRequest(HttpMethod.GET, String.class);
StringBuilder builder = new StringBuilder(result.getBody());
... //do your transformation to stringbuilder reference.
result = ResponseEntity.status(result.getStatusCode()).body(builder.toString());
Another way if you want to avoid this is to return String response from your executeRequest & modify that response before creating ResponseEntity.
Try this:
Create your own HttpMessageConverter, implementing:
public interface HttpMessageConverter<T> {
// Indicates whether the given class can be read by this converter.
boolean canRead(Class<?> clazz, MediaType mediaType);
// Indicates whether the given class can be written by this converter.
boolean canWrite(Class<?> clazz, MediaType mediaType);
// Return the list of {#link MediaType} objects supported by this converter.
List<MediaType> getSupportedMediaTypes();
// Read an object of the given type form the given input message, and returns it.
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
// Write an given object to the given output message.
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
Register the custom converter into your restTemplate object:
String url = "url";
// Create a new RestTemplate instance
RestTemplate restTemplate = new RestTemplate();
// Add the String message converter
restTemplate.getMessageConverters().add(new YourConverter());
// Make the HTTP GET request, marshaling the response to a String
String result = restTemplate.getForObject(url, String.class);

How can I associate JSON object of the api response to java classes when having the intermediate json "data" attribute?

Thank you for clicking here.
I have an JSON REST API (providing by Directus CMS). All API responses contains a json object with a "data" attribute containing what I want.
{
"data": {
"id": 1,
"status": "published",
"sort": null,
"user_created": "5a91c184-908d-465e-a7d5-4b648029bbe0",
"date_created": "2022-04-26T09:43:37.000Z",
"user_updated": "5a91c184-908d-465e-a7d5-4b648029bbe0",
"date_updated": "2022-05-30T14:23:50.000Z",
"Titre": "Réseaux Sociaux",
"Description": "Retrouvez les dernières news en direct sur nos réseaux sociaux!",
"Lien": "https://www.instagram.com/univlorraine/",
"ImageArrierePlan": "f23ffd53-7244-4439-a8cf-41bd0fd3aa72",
"Erreur_Bloc": null
}
}
This data attribute can be a object or a list of objects depending the request.
I have a Java Spring application with a service consuming the API. I'm using RestTemplate with exchange method.
public Object callAPI(String url, HttpMethod httpMethod, Object body, MultiValueMap<String, String> headers, Class<?> classe) {
final RestTemplate rt = new RestTemplate();
try {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
rt.setRequestFactory(requestFactory);
final HttpEntity<?> request = new HttpEntity<>(body, headers);
final ResponseEntity<?> response = rt.exchange(url, httpMethod, request, classe);
if (response.getStatusCode().equals(HttpStatus.OK)) {
return response.getBody();
}
else return response.getStatusCode();
} catch (final Exception e) {
System.out.println(e);
return null;
}
}
In the exchange method I pass an existing class to directly link response data with the provided class.
The probleme is that I have this data attribute which prevents me from linking the data.
Does anyone have a solution to this probleme please?
----UPDATE----
Thanks to the response of AlbiKai, I created a generic Wrapper class :
public class Wrapper<T> {
private T data;
public void set(T data) {
this.data = data;
}
public T get() {
return data;
}
}
I then tried to put this Wrapper in the exchange :
public <classe> Object callAPI(String url, HttpMethod httpMethod, Object body, MultiValueMap<String, String> headers, Class<?> classe) {
final RestTemplate rt = new RestTemplate();
try {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
rt.setRequestFactory(requestFactory);
final HttpEntity<?> request = new HttpEntity<>(body, headers);
final ResponseEntity<?> response = rt.exchange(url, httpMethod, request, Wrapper<classe>.class);
But I get the error "Cannot select from parameterized type" on the Wrapper :/
You can create a wrapper class that match the json response : an object with only one attribute named "data" type of desire final class (or a list) and use it in the exchange method.
public class wrapper {
YourClass data;
}
I gave up with the Wrapper etc...
I just pass a String class and work with it in my controllers to delete this "data" property and map the string with a class.
Service :
public String callAPI(String url, HttpMethod httpMethod, Object body, MultiValueMap<String, String> headers) {
final RestTemplate rt = new RestTemplate();
try {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
rt.setRequestFactory(requestFactory);
final HttpEntity<?> request = new HttpEntity<>(body, headers);
final ResponseEntity<String> response = rt.exchange(url, httpMethod, request, String.class);
if (response.getStatusCode().equals(HttpStatus.OK)) {
return response.getBody();
}
else return response.getStatusCode().toString();
} catch (final Exception e) {
System.out.println(e);
return null;
}
}
One controller :
public List<BlocInformation> getBlocInformation() {
String url = "http://localhost:8055/items/bloc_information/?fields=*,Erreur_Bloc.*";
final RestAPIService blocService = new RestAPIService();
String response = blocService.callAPI(url, HttpMethod.GET, null, null);
if (response != null) {
String result = response.substring(8, response.length() - 1);
ObjectMapper mapper = new ObjectMapper();
List<BlocInformation> blocInformationList = null;
try {
blocInformationList = Arrays.asList(mapper.readValue(result, BlocInformation[].class));
} catch (IOException e) {
e.printStackTrace();
}
return blocInformationList;
}
return null;
}

How to get json response in Java in case of 4XX and 5XX error

I was trying out RestTemplate and Retrofit2. Both the libraries throw exception in case api returns 4XX/5XX. The api when hit from postman gives a JSON response body, along with 4XX/5XX.
How can I retrieve this JSON response using RestTemplate or Retrofit2.
Thanks.
Use the HttpClientErrorException, HttpStatusCodeException after try block as below.
try{
restTemplate.exchange("url", HttpMethod.GET, null, String.class);
}
catch (HttpClientErrorException errorException){
logger.info("Status code :: {}, Exception message :: {} , response body ::{}" , e.getStatusCode()
e.getMessage(), e.getResponseBodyAsString());
}
catch (HttpStatusCodeException e){
logger.info("Status code :: {}, Exception message :: {} , response body ::{}" , e.getStatusCode()
e.getMessage(), e.getResponseBodyAsString());
}
For that you have to create RestTemplateError handler and register that class while creating bean for RestTemplate.
#Bean
public RestTemplate getBasicRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new RestTemplateResponseErrorHandler());
return restTemplate;
}
where your handler class has to implements ResponseErrorHandler. You can read the json response that is stored in the body.
#Component
public class RestTemplateResponseErrorHandler implements ResponseErrorHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RestTemplateResponseErrorHandler.class);
#Override
public boolean hasError(ClientHttpResponse httpResponse) throws IOException {
return httpResponse.getStatusCode().series() == CLIENT_ERROR
|| httpResponse.getStatusCode().series() == SERVER_ERROR;
}
#Override
public void handleError(ClientHttpResponse httpResponse) throws IOException {
if (httpResponse.getStatusCode().series() == SERVER_ERROR) {
LOGGER.error("Handling server error response statusCode:{} ", httpResponse.getStatusCode());
} else if (httpResponse.getStatusCode().series() == CLIENT_ERROR) {
LOGGER.error("Handling Client error response statusCode:{} ", httpResponse.getStatusCode());
String body;
InputStreamReader inputStreamReader = new InputStreamReader(httpResponse.getBody(),
StandardCharsets.UTF_8);
body = new BufferedReader(inputStreamReader).lines().collect(Collectors.joining("\n"));
throw new CustomException(httpResponse.getStatusCode().toString(), httpResponse, body);
}
}
}

restTemplate Error 403

I'm trying to implemente a request method, but I can't figure out how to sent the X-XSRF-TOKEN to my webservice.
In the webservice, the token is configured to be X-XSRF-TOKEN
<beans:bean id="csrfTokenRepository"
class="org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository">
<beans:property name="headerName" value="X-XSRF-TOKEN" />
</beans:bean>
I have it in my android App
public class WSConfig {
private static String urlBase = "http://192.168.25.226:8080/webapi/";
private static HttpHeaders httpHeaders;
private static RestTemplate restTemplate = new RestTemplate();
private static HttpEntity<String> httpEntity = new HttpEntity(getXSRF());
private static ResponseEntity<String> response;
public static HttpHeaders getXSRF() {
try {
HttpEntity<String> responseEntity = restTemplate.exchange(urlBase, HttpMethod.GET, null, String.class);
CookieManager cookieManager = new CookieManager();
List<String> cookieHeader = responseEntity.getHeaders().get("Set-Cookie");
httpHeaders = new HttpHeaders();
httpHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
if (cookieHeader != null) {
for (String cookie : cookieHeader) {
String[] tokens = TextUtils.split(cookie, "=");
if (tokens[0].equals("XSRF-TOKEN")) {
String[] tokenValue = TextUtils.split(tokens[1],";");
httpHeaders.add("X-XSRF-TOKEN", tokenValue[0]);
}
if (tokens[0].equals("JSESSIONID")) {
String[] tokenValue = TextUtils.split(tokens[1],";");
httpHeaders.add("Cookie", "JSSESSIONID="+tokenValue[0]);
}
}
}
} finally {
return httpHeaders;
}
}
public static HttpEntity<String> makeRequest(String uri, HttpMethod method) {
try {
restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
protected boolean hasError(HttpStatus statusCode) {
return false;
}});
System.out.println(httpEntity.getHeaders());
response = restTemplate.exchange(urlBase + "registrar", HttpMethod.POST, null, String.class);
System.out.println(response.getHeaders());
System.out.println(response.getBody());
} catch (HttpStatusCodeException e) {
e.printStackTrace();
}
return null;
}
}
In my LogCat, I got these results from the System.outs
System.out.println(httpEntity.getHeaders());
{Accept=[application/json], Cookie=[JSSESSIONID=D0D537D4C38D2D69B01BF4F98B540763], X-XSRF-TOKEN=[8c21c671-bba4-4624-ada1-ff1e9e8f2e22]}
System.out.println(response.getHeaders());
{Server=[Apache-Coyote/1.1], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-XSS-Protection=[1; mode=block], X-Frame-Options=[DENY], X-Content-Type-Options=[nosniff], Set-Cookie=[JSESSIONID=7DBA84F6218BC9A8328A97587FC6293A; Path=/webapi/; HttpOnly], Content-Type=[text/html;charset=utf-8], Content-Language=[en], Content-Length=[1073], Date=[Thu, 20 Aug 2015 00:53:46 GMT], X-Android-Sent-Millis=[1440032027763], X-Android-Received-Millis=[1440032027805], X-Android-Response-Source=[NETWORK 403]}
And, the error
System.out.println(response.getBody());
HTTP Status 403 - Expected CSRF token not found. Has your session expired?
I can't figure what I have to do, I'm sending the header correctly, but can't make the post.
UPDATED
I'm think that this error have relation with JSESSIONID, instead of XSRF-TOKEN, someway after my first GET ( to get the XSRF ) the session is getting expired.
SOLUTION
As I said, this error is relationed with JSESSIONID.
When I split the JSESSIONID cookie it is losing something that need to make the cookie alive (the path, maybe?)
So, instead of add the cookie like this
httpHeaders.add("Cookie", "JSSESSIONID="+tokenValue[0]);
I've attached it this way
httpHeaders.add("Cookie", cookie);
Making it, I make sure that all content be attached to the new header.
The final method.
public static HttpHeaders getXSRF() {
try {
HttpEntity<String> responseEntity = restTemplate.exchange(urlBase, HttpMethod.GET, null, String.class);
CookieManager cookieManager = new CookieManager();
List<String> cookieHeader = responseEntity.getHeaders().get("Set-Cookie");
httpHeaders = new HttpHeaders();
httpHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
if (cookieHeader != null) {
for (String cookie : cookieHeader) {
String[] tokens = TextUtils.split(cookie, "=");
if (tokens[0].equals("XSRF-TOKEN")) {
String[] tokenValue = TextUtils.split(tokens[1],";");
httpHeaders.add("X-XSRF-TOKEN", tokenValue[0]);
}
if (tokens[0].equals("JSESSIONID"))
httpHeaders.add("Cookie", cookie);
}
}
} finally {
return httpHeaders;
}
}

Spring RestTemplate Behavior when handling responses with a status of NO_CONTENT

Okay, I have a class NamedSystems, that has as its only field a Set of NamedSystem.
I have a method to find NamedSystems by certain criteria. That's not really important. When it gets results, everything works fine. However, when it can't find anything, and thus returns a null (or empty -- I've tried both ways) set, I get problems. Let me explain.
I'm using the Spring RestTemplate class and I'm making a call like this in a unit test:
ResponseEntity<?> responseEntity = template.exchange(BASE_SERVICE_URL + "?
alias={aliasValue}&aliasAuthority={aliasAssigningAuthority}",
HttpMethod.GET, makeHttpEntity("xml"), NamedSystems.class,
alias1.getAlias(), alias1.getAuthority());
Now, since this would normally return a 200, but I want to return a 204, I have an interceptor in my service that determines if a ModelAndView is a NamedSystem and if its set is null. If so, I then the set the status code to NO_CONTENT (204).
When I run my junit test, I get this error:
org.springframework.web.client.RestClientException: Cannot extract response: no Content-Type found
Setting the status to NO_CONTENT seems to wipe the content-type field (which does make sense when I think about it). So why is it even looking at it?
Spring's HttpMessageConverterExtractor extractData method:
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 + "]");
}
Going up the chain a bit to find out where that Extractor is set, I come to RestTemplate's exchange() method that I used in the test:
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);
}
So, it's trying to convert what amounts to nothing because of the supplied response type from the exchange call. If I change the responseType from NamedSystems.class to null, it works as expected. It doesn't try to convert anything. If I had tried to set the status code to 404, it also executes fine.
Am I misguided, or does this seem like a flaw in RestTemplate? Sure, I'm using a junit right now so I know what's going to happen, but if someone is using RestTemplate to call this and doesn't know the outcome of the service call, they would naturally have NamedSystems as a response type. However, if they tried a criteria search that came up with no elements, they'd have this nasty error.
Is there a way around this without overriding any RestTemplate stuff? Am I viewing this situation incorrectly? Please help as I'm a bit baffled.
One more way to solve this would be to make response entity as null as shown below.
ResponseEntity<?> response = restTemplate.exchange("http://localhost:8080/myapp/user/{userID}",
HttpMethod.DELETE,
requestEntity,
null,
userID);
If you still need response headers, try implementing the ResponseErrorHandler.
I believe you should probably look at the ResponseExtractor interface & call execute on the RestTemplate providing your implementation of the extractor. To me it looks like a common requirement to do this so have logged this:
https://jira.springsource.org/browse/SPR-8016
Here's one I prepared earlier:
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;
}
}
I've tested this & it seems to do what I want.
To create the instance of the ResponseExtractor I call the constructor & pass the converters from a RestTemplate instance that's been injected;
E.g.
ResponseExtractor<MyEntity> responseExtractor =
new MyResponseExtractor(MyEntity.class, restTemplate.getMessageConverters());
Then the call is:
MyEntity responseAsEntity =
restTemplate.execute(urlToCall, HttpMethod.GET, null, responseExtractor);
Your mileage may vary. ;-)
Here's a simple solution where you can set the default Content-Type for use if it is missing in the response. The Content-Type is added to the response header before it is handed back off to the preconfigured ResponseExtractor for extraction.
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);
}
});
}
}
This should now be fixed in Spring 3.1 RC1.
https://jira.spring.io/browse/SPR-7911
Or you could extend RestTemplate and override doExecute(..) and check the response body.
For example here is what I implemented and works for us:
#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();
}
}
}
I think you are right.
I'm having a similar problem.
I think we should be getting a ResponseEntity with a HttpStatus of NO_CONTENT and a null body.
I came along a workaround (not sure if it meets your case):
First define a custom interceptor class which implements ClientHttpRequestInterceptor. and check if response.getStatusCode() meets your case (my case is != HttpStatus.NOT_FOUND and response.getBody() length is 0), define a custom class (e.x. DefaultResponseForEmptyRestTemplateBody) which has a static method of type MockClientHttpResponse:
public class RequestResponseInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
ClientHttpResponse response = execution.execute(request, body);
if(response.getStatusCode()!=HttpStatus.NOT_FOUND && response.getBody().readAllBytes().length==0){
response = DefaultResponseForEmptyRestTemplateBody.getResponse(response.getStatusCode());
}
return response;
}
}
public static class DefaultResponseForEmptyRestTemplateBody {
MockClientHttpResponse response;
private static byte[] content = new byte[0];
public static MockClientHttpResponse getResponse(HttpStatus statusCode){
content = "response body is empty".getBytes();
return new MockClientHttpResponse(content, statusCode);
}
}
finally add this interceptor to your restTemplate object as below:
restTemplate.setInterceptors(Collections.singletonList(new RequestResponseLoggingInterceptor()));
and call your restTemplate.postForEntity:
ResponseEntity<String> response = this.restTemplate.postForEntity(baseUrl, requestParams,String.class);

Categories