Reputation: 1515
We have two parts in our app:
Server - provide REST services
Client - consume them via Spring restTemplate
In addition to the HTTP status our server returns an HTTP body with JSON that describe error in detail. So, I've added custom Error handler to restTemplate to treat some error coded as non errors - it helps parse HTTP body very well.
But I get an exception via parsing of the HTTP body in the case of an HTTP/1.1 401 Unauthorized. All other error codes are handled fine(400, 402, etc. ) We are using plain server logic that sends HTTP response in the case of an error, no special rules for different types of an error:
writeErrorToResponse(int status, String errMsg, HttpServletResponse resp) throws IOException {
response.setStatus(status);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
String message = String.format("{\"error\":\"%s\"}", StringUtils.escapeJson(errMsg));
resp.getWriter().println(message);
}
But on client only HTTP/1.1 401 throws exception - "java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode"
I've made some debugging and see that the cause of the problem is code in SimpleClientHttpResponse:
HttpURLConnection.getInputStream()
Tracing with Fiddler have these next responses: Message is parsed correct on the client:
HTTP/1.1 402 Payment Required
X-Powered-By: Servlet/3.0
Content-Type: application/json
Content-Language: en-GB
Content-Length: 55
Connection: Close
Date: Sat, 25 May 2013 10:10:44 GMT
Server: WebSphere Application Server/8.0
{"error":"I cant find that user. Please try again."}
And message that is cause of exception:
HTTP/1.1 401 Unauthorized
X-Powered-By: Servlet/3.0
Content-Type: application/json
Content-Language: en-GB
Content-Length: 55
Date: Sat, 25 May 2013 11:00:21 GMT
Server: WebSphere Application Server/8.0
{"error":"I cant find that user. Please try again."}
What could be the cause of java.net.HttpRetryException in this situation?
In addition: Some times ago this mechanism worked fine. But since we have changed a lot of code in app.
Upvotes: 42
Views: 93968
Reputation: 4228
Since Spring Framework 6.1, the SimpleClientHttpRequestFactory
doesnt support toggling the streaming anymore, e.g. following doesn't work any longer:
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
simpleClientHttpRequestFactory.setOutputStreaming(false);
Instead you should use JdkClientHttpRequestFactory by checking out Spring's release notes.
Especially HttpClient based ClientHttpRequestFactory and Remove buffering in ClientHttpRequestFactory implementations.
Upvotes: 1
Reputation: 2348
This exception has a quite unexpected impact on Spring OAuth2 token retrieval. For this retrieval the standard implementation uses RestTemplate
which is configured with SimpleClientHttpRequestFactory
where output streaming set to true. If the 4XX authentication error happens , the standard ResponseErrorHandler
tries to read the output stream and receives, of course, the discussed exception, but decides to silently consume it, ignore.
Sometimes, however, OAuth2 authentication server writes important information into the output/error stream which must be read and analyzed.
To make this information available, upon the creation of OAuth2RestTemplate
, its AccessTokenProvider
should be set to a custom one, let's name it ErrorStreamAwareAccessTokenProvider
:
oauth2RestTemplate.setAccessTokenProvider(new ErrorStreamAwareAccessTokenProvider());
and this custom AccessTokenProvider
should overwrite, e.g. getRestTemplate
method and tune the RestTemplate
correspondingly, as the accepted answers advise. For example:
class ErrorStreamAwareAccessTokenProvider extends OAuth2AccessTokenSupport{
// your exact superclass may vary depending on grant type and other things
protected RestOperations getRestTemplate() {
RestTemplate template = super.getRestTemplate();
template.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
// ... also set ResponseErrorHandler if needed
return template;
}
}
When this is done, the standard ResponseErrorHandler
could read the output/error stream or, if you want, for example, to throw a custom exception which has the retrieved information stored in it, you could set your custom ResponseErrorHandler
in the above overwritten method.
Upvotes: 1
Reputation: 1044
I was having the exact same issue using Spring Boot 2.2.4 and a TestRestTemplate. It only failed on 401's. Simply adding the latest version of Http Client (4.5.11) in a test scope fixed the problem. No further configuration was necessary.
Upvotes: 21
Reputation: 768
This issue is reported in Spring Framework.
Reference: https://jira.spring.io/browse/SPR-9367
Copying from Reference: It is very difficult (impossible) to handle a 401 response in the RestTemplate with default settings. In fact it is possible, but you have to supply an error handler and a request factory. The error handler was obvious, but the problem is that the default request factory uses java.net which can throw HttpRetryException when you try to look at the status code of the response (despite it being obviously available). The solution is to use HttpComponentsClientHttpRequestFactory. E.g.
template.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
template.setErrorHandler(new DefaultResponseErrorHandler() {
public boolean hasError(ClientHttpResponse response) throws IOException {
HttpStatus statusCode = response.getStatusCode();
return statusCode.series() == HttpStatus.Series.SERVER_ERROR;
}
});
HttpComponentsClientHttpRequestFactory requires below dependency. Add below dependency in your POM file:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
Upvotes: 23
Reputation: 1828
I faced the same issue when using SimpleClientHttpRequestFactory. Solved it by setting
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setOutputStreaming(false);
return requestFactory;
The problem is due to chunking and subsequent retry mechanism incase of authentication.
You can also disable chunks using HttpClientPolicy
Upvotes: 37
Reputation: 2070
I think passing a configured HttpComponentsClientHttpRequestFactory to your RestTemplate would help. Here you might find the solution on how to do it.
The reason that I used such development solution was to handle the response messages in the body of Http error responses like 401, 404, and so on, and also to let the application be deployed on real-server side environment.
Upvotes: 10