Tuesday, April 3, 2018

ClientHttpResponse.getBody() throws ResourceAccessException when response code is 401

Leave a Comment

I am trying to log the request-response pairs for every request. The problem is that when the response code is 401, ClientHttpResponse.getBody() throws a ResourceAccessException and i cannot read the response body.

This is the RestTemplate configuration

RestTemplate restTemplate = new RestTemplate();  // Added this requestFactory to make response object readable more than once. ClientHttpRequestFactory requestFactory =         new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()); restTemplate.setRequestFactory(requestFactory); restTemplate.getInterceptors().add(new RequestLoggingInterceptor(vCloudRequest.getAction(),httpHeaders));     restTemplate.setErrorHandler(new RequestErrorHandler());  return restTemplate; 

The last line of the interceptor below throws the following exception.

How can i resolve this problem?

org.springframework.web.client.ResourceAccessException: I/O error on POST request for "https://example.com/api/sessions": Server returned HTTP response code: 401 for URL: https://example.com/api/sessions; nested exception is java.io.IOException: Server returned HTTP response code: 401 for URL: https://example.com.11/api/sessions

This is the related part of the interceptor.

@Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {     ClientHttpResponse response = execution.execute(request, body);      String requestString = new String(body);      // Calling this method because when we make a POST or PUT request and get an error     // response.getBody() throws IOException. But if we call response.getStatusCode() it works fine.     // I don't know the reason.     // I asked a question on stackoverflow     // https://stackoverflow.com/questions/47429978/resttemplate-response-getbody-throws-exception-on-4-and-5-errors-for-put-and     response.getStatusCode();     String responseString = new String(ByteStreams.toByteArray(response.getBody()), Charset.forName("UTF-8")); ... } 

This is the custom error handler

public class RequestErrorHandler implements ResponseErrorHandler {

@Override public boolean hasError(ClientHttpResponse response) throws IOException {         if (!response.getStatusCode().is2xxSuccessful()) {         return true;     }     return false; }  @Override public void handleError(ClientHttpResponse response) throws IOException {     JAXBElement<ErrorType> root = null;      try {         JAXBContext jaxbContext = JAXBContext.newInstance(ErrorType.class);         Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();          root = jaxbUnmarshaller.unmarshal(new StreamSource(                 response.getBody()), ErrorType.class);     } catch (JAXBException e) {         throw new IOException("XML converting error. Cannot convert response to ErrorType");     }      ErrorType error = root.getValue();     throw new VcloudException(error); } } 

4 Answers

Answers 1

Spring RestTemplate as default generates an error when httpstatus is not 200.

In my past project what I did is to add a custom org.springframework.web.client.ResponseErrorHandler to the rest template. You may wrote a class like this:

public class SimpleRestErrorHandler implements ResponseErrorHandler {     private static final Logger logger = LoggerFactory.getLogger(SimpleRestErrorHandler.class.getName());     @Override     public boolean hasError(ClientHttpResponse response) throws IOException     {         int statusCode = response.getStatusCode().value();         if( logger.isDebugEnabled() )         {              logger.debug("STATUS CODE ["+statusCode+"] ");         }         //Generate Error only when HTTP Status code is 500         if( statusCode == 500 )         {              return true;         }         return false;     }      @Override     public void handleError(ClientHttpResponse response) throws IOException     {       //Here you can manage the error (in this sample only the http status code = 500     }  } 

Then I added this to my rest template:

XML configuration sample

<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">        <constructor-arg ref="bufferingClientHttpRequestFactory" />         <property name="errorHandler" ref="simpleRestErrorHandler" />       <property name="messageConverters">             <list>                 <bean                     class="org.springframework.http.converter.StringHttpMessageConverter">                     <constructor-arg value="#{T(java.nio.charset.Charset).forName('UTF-8')}" />                 </bean>                 <bean                     class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />                 <bean                     class="org.springframework.http.converter.ResourceHttpMessageConverter" />                 <bean                     class="org.springframework.http.converter.xml.SourceHttpMessageConverter" />                 <bean                     class="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter" />                 <bean                     class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter" />                 <bean                     class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />                 <bean                     class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />          </list>         </property>         <property name="interceptors">          <!-- Custom interceptors -->        </property>     </bean> <bean id="bufferingClientHttpRequestFactory" name="bufferingClientHttpRequestFactory"       class="org.springframework.http.client.BufferingClientHttpRequestFactory">      <constructor-arg ref="simpleReqFact" />     </bean> <bean id="simpleRestErrorHandler" class="handlers.SimpleRestErrorHandler" /> 

Java config

@Config public class restTemplConfig {  @Bean  public RestTemplate restTempl() {   RestTemplate result = new RestTemplate();   //Add converters   //Add custom error handler   result.setErrorHandler(new SimpleRestErrorHandler());  } } 

I hope it's useful

Answers 2

I do not think, ResourceAccessException is being thrown by response.getBody(), instead seems to be that ByteStreams.toByteArray(...) is sending probably a null stream in response.

Can you please try debugging if response.getBody() is null or it's ByteStreams.toByteArray(). Because RescourceAccessException is thrown when an I/O error occurs.

Answers 3

Use HttpComponentsClientHttpRequestFactory instead of SimpleClientHttpRequestFactory.

Also add dependency to 'org.apache.httpcomponents:httpclient.

HttpComponentsClientHttpRequestFactory ccan read body when status is 401.

Answers 4

public class RestTemplate extends InterceptingHttpAccessor implements RestOperations 

Spring's central class for synchronous client-side HTTP access. It simplifies communication with HTTP servers, and enforces RESTful principles. It handles HTTP connections, leaving application code to provide URLs (with possible template variables) and extract results. Note: by default the RestTemplate relies on standard JDK facilities to establish HTTP connections. You can switch to use a different HTTP library such as Apache HttpComponents, Netty, and OkHttp through the InterceptingHttpAccessor.setRequestFactory(org.springframework.http.client.ClientHttpRequestFactory) property.

The main entry points of this template are the methods named after the six main HTTP methods:

HTTP method RestTemplate methods

  • DELETE delete(java.lang.String, java.lang.Object...)
  • GET getForObject(java.lang.String, java.lang.Class, java.lang.Object...) getForEntity(java.lang.String, java.lang.Class, java.lang.Object...)
  • HEAD headForHeaders(java.lang.String, java.lang.Object...)
  • OPTIONS optionsForAllow(java.lang.String, java.lang.Object...)
  • POST postForLocation(java.lang.String, java.lang.Object, java.lang.Object...) postForObject(java.lang.String, java.lang.Object, java.lang.Class, java.lang.Object...)
  • PUT put(java.lang.String, java.lang.Object, java.lang.Object...)
  • any exchange(java.lang.String, org.springframework.http.HttpMethod, org.springframework.http.HttpEntity, java.lang.Class, java.lang.Object...) execute(java.lang.String, org.springframework.http.HttpMethod, org.springframework.web.client.RequestCallback, org.springframework.web.client.ResponseExtractor, java.lang.Object...)

RestTemplate


public class ResponseEntity<T> extends HttpEntity<T> 

Extension of HttpEntity that adds a HttpStatus status code. Used in RestTemplate as well @Controller methods.

ResponseEntity


Request use RestTemplate methods and response use ResponseEntity.

Sample code :

@Override public ResponseEntity<?> callLocalGeo(double lat, double lng) {     String url = "https://apis.sample.net/local/geo/coord2addr?apiKey=" + this.key + "&longitude=" + lng             + "&latitude=" + lat + "&output=json";      RestTemplate rest = new RestTemplate();      try {         // success         return rest.getForEntity(url, String.class);     } catch (HttpStatusCodeException e) {         // new Error Response         return ResponseEntity.status(e.getStatusCode()).headers(e.getResponseHeaders())                 .body(e.getResponseBodyAsString());     } } 

Call REST API

ResponseEntity<?> respEntity = sampleService.callLocalGeo(lat, lng);  if (HttpStatus.OK.equals(respEntity.getStatusCode())) {     String respBody = (String) respEntity.getBody();     // something...  } else {     // error handling... } 

RestTemplate example


ClientHttpRequestInterceptor

Your RestTemplate configuration edit.

RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));  List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();  // add your interceptor. interceptors.add(new RequestLoggingInterceptor());  restTemplate.setInterceptors(interceptors); 

Your interceptor edit.

@Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {     ClientHttpResponse response = execution.execute(request, body);      StringBuilder stringBuilder = new StringBuilder();     BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), "UTF-8"));      String line = null;     while ((line = bufferedReader.readLine()) != null) {         stringBuilder.append(line);         stringBuilder.append('\n');     }      // using response body.     log.debug("Response body: ", stringBuilder.toString());   } 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment