java.net.HttpRetryException:由于服务器身份验证而无法重试,在流模式下

Posted

技术标签:

【中文标题】java.net.HttpRetryException:由于服务器身份验证而无法重试,在流模式下【英文标题】:java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode 【发布时间】:2013-05-20 21:09:10 【问题描述】:

我们的应用中有两个部分: 服务器 - 提供 REST 服务 客户端 - 通过 Spring restTemplate 使用它们

除了 HTTP 状态之外,我们的服务器还返回一个带有 JSON 的 HTTP 正文,详细描述了错误。 因此,我在 restTemplate 中添加了自定义错误处理程序,以将一些错误编码为非错误 - 它有助于很好地解析 HTTP 正文。

但是在 HTTP/1.1 401 Unauthorized 的情况下,我通过解析 HTTP 正文得到一个异常。 所有其他错误代码都处理得很好(400、402 等) 我们使用普通的服务器逻辑,在发生错误时发送 HTTP 响应,没有针对不同类型错误的特殊规则:

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);
    

但是在客户端只有 HTTP/1.1 401 抛出异常 - "java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode"

我做了一些调试,发现问题的原因是 SimpleClientHttpResponse 中的代码:

HttpURLConnection.getInputStream()

使用 Fiddler 进行跟踪有以下响应: 消息在客户端解析正确:

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."

以及导致异常的消息:

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."

在这种情况下,java.net.HttpRetryException 的原因可能是什么?

另外:前段时间这个机制运行良好。但是由于我们在应用程序中更改了很多代码。

【问题讨论】:

我也有同样的问题...如何解决这个问题??? 我已经做了解决方法:1)在自定义错误处理程序中添加以将 403 处理为错误。 2)捕获抛出的异常并处理403错误代码。但在这种情况下无法解析 HTTP 正文。 @user1182217 看我上面的回答 有人已经在 Spring Framework 中提出了问题。但是它已经关闭了,我正在重新打开它。请为它投票:jira.spring.io/browse/SPR-16781 【参考方案1】:

我在使用SimpleClientHttpRequestFactory 时遇到了同样的问题。通过设置解决了

SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setOutputStreaming(false);
return requestFactory;

问题是由于在身份验证时分块和后续重试机制造成的。

您还可以使用 HttpClientPolicy 禁用块

【讨论】:

【参考方案2】:

Spring Framework 中报告了此问题。

参考: https://jira.spring.io/browse/SPR-9367

从参考中复制: 使用默认设置在 RestTemplate 中处理 401 响应非常困难(不可能)。事实上这是可能的,但你必须提供一个错误处理程序和一个请求工厂。错误处理程序很明显,但问题是默认请求工厂使用 java.net,当您尝试查看响应的状态代码时会抛出 HttpRetryException(尽管它显然可用)。解决方案是使用 HttpComponentsClientHttpRequestFactory。例如

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 需要以下依赖项。在您的 POM 文件中添加以下依赖项:

<dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
</dependency>

【讨论】:

非常感谢您!这是我可以设法测试受“基本身份验证”保护的休息点的唯一方法。 救命稻草,不知道自己应该如何解决这个问题?【参考方案3】:

我在使用 Spring Boot 2.2.4 和 TestRestTemplate 时遇到了完全相同的问题。它仅在 401 上失败。只需在测试范围内添加最新版本的Http Client (4.5.11) 即可解决问题。无需进一步配置。

【讨论】:

【参考方案4】:

我认为将配置的 HttpComponentsClientHttpRequestFactory 传递给您的 RestTemplate 会有所帮助。 Here你可能会找到解决方法。

我之所以使用这样的开发方案,是为了处理401、404等Http错误响应体中的响应消息,也为了让应用部署在真实服务器端环境。

【讨论】:

【参考方案5】:

此异常对 Spring OAuth2 令牌检索有相当意想不到的影响。对于此检索,标准实现使用RestTemplate,它配置有SimpleClientHttpRequestFactory,其中输出流设置为true。如果发生 4XX 身份验证错误,标准 ResponseErrorHandler 会尝试读取输出流并接收当然讨论的异常,但决定静默使用它,忽略。 然而,有时 OAuth2 身份验证服务器会将重要信息写入必须读取和分析的输出/错误流中。 为了使这些信息可用,在创建OAuth2RestTemplate 时,其AccessTokenProvider 应设置为自定义,我们将其命名为ErrorStreamAwareAccessTokenProvider

oauth2RestTemplate.setAccessTokenProvider(new ErrorStreamAwareAccessTokenProvider());

这个自定义AccessTokenProvider 应该覆盖,例如getRestTemplate 方法并相应地调整 RestTemplate,作为接受的答案建议。例如:

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; 
    

完成此操作后,标准 ResponseErrorHandler 可以读取输出/错误流,或者,例如,如果您想抛出一个自定义异常,其中存储了检索到的信息,您可以设置您的自定义 @987654333上面覆盖方法中的@。

【讨论】:

以上是关于java.net.HttpRetryException:由于服务器身份验证而无法重试,在流模式下的主要内容,如果未能解决你的问题,请参考以下文章