RestTemplate - providing custom ResponseErrorHandler does not prevent an error with http 401 responses [SPR-16781]
See original GitHub issueJeff Synnestvedt opened SPR-16781 and commented
When a rest template is customized with a ResponseErrorHandler that does not return true on hasError or does not throw an exception in handleError for an http 401 response then something like the following is thrown:
org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:36639/someservice": cannot retry due to server authentication, in streaming mode; nested exception is java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:741) at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:684) at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:629) at com.example.demo.DemoApplicationTests.error401_withcustomhandler_noerrors(DemoApplicationTests.java:118) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73) at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)Caused by: java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1674) at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1474) at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480) at org.springframework.http.client.SimpleClientHttpResponse.getRawStatusCode(SimpleClientHttpResponse.java:55) at org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTags.getStatusMessage(RestTemplateExchangeTags.java:94) at org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTags.status(RestTemplateExchangeTags.java:86) at org.springframework.boot.actuate.metrics.web.client.DefaultRestTemplateExchangeTagsProvider.getTags(DefaultRestTemplateExchangeTagsProvider.java:43) at org.springframework.boot.actuate.metrics.web.client.MetricsClientHttpRequestInterceptor.getTimeBuilder(MetricsClientHttpRequestInterceptor.java:97) at org.springframework.boot.actuate.metrics.web.client.MetricsClientHttpRequestInterceptor.intercept(MetricsClientHttpRequestInterceptor.java:70) at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.execute(InterceptingClientHttpRequest.java:92) at org.springframework.http.client.InterceptingClientHttpRequest.executeInternal(InterceptingClientHttpRequest.java:76) at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48) at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53) at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:727) ... 35 more
I am expecting that if my error handler doesn’t want to consider 401’s errors then it shouldn’t throw an error further down the stack.
Affects: 5.0.5
Reference URL: https://github.com/spring-projects/spring-framework-issues/pull/179
Attachments:
- bug.zip (4.17 kB)
Issue Links:
- #14004 Update reference documentation on handling 401 response in the RestTemplate (“duplicates”)
1 votes, 3 watchers
Issue Analytics
- State:
- Created 5 years ago
- Comments:10
Top Results From Across the Web
Spring RestTemplate Error Handling - Baeldung
Learn how to handle errors with Spring's RestTemplate. ... ResponseErrorHandler will read the HTTP status from the response and either:.
Read more >http - java.net.HttpRetryException: cannot retry due to server ...
So, I've added custom Error handler to restTemplate to treat some error coded as non errors - it helps parse HTTP body very...
Read more >HttpClientException 401 Unauthorized - JiGyeong's study room
RestTemplate - providing custom ResponseErrorHandler does not prevent an error with http 401 responses [SPR-16781] · Issue #213.
Read more >RestTemplate Error Handling Example - Apps Developer Blog
Today, we'll implement a RestTemplate Error Handling Example. The objective is to implement a reusable error handler that managesthe errors ...
Read more >Spring Boot RestTemplate Error Handling
Implementing a custom error handler ... Sometimes, a try-catch block is not enough to handle errors as it is not scalable when the...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
I believe that in Spring 6 the correct dependancy is:
Brian Clozel commented
Hi Jeff Synnestvedt,
First, thanks a lot for your repro project!
I agree this is annoying, but I don’t think there’s anything we can fix here. But there are ways to work around this behavior.
The
ResponseErrorHandler
contract is about checking whether the given response should be considered as an error - but it doesn’t mean it will catch all errors that can happen when reading/extracting the response body at a later phase (for example, anIOException
while reading the response body). This is what’s happening in your sample code.This behavior is still surprising and there are ways to fix that.
You can choose to use a different HTTP client (i.e. not the JDK one); Spring Framework supports several. Adding the following dependency to your sample fixes things:
This points to an implementation detail in the JDK HTTP client. In Spring Framework, we’re using the
SimpleClientHttpResponse
to make sure that we use the error stream or the regular input stream, depending on the case. In this example, none of them works.I suspect this might be linked to a combination of the test server/response used in your setup and the JDK client, since sending the same request against
"http://httpbin.org/status/401"
works well. The difference should be somewhere in theHttpUrlConnection
implementation.I’m closing this issue for now, but don’t hesitate to reopen it if you’ve found a way to improve this behavior. Thanks!