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

问题描述 投票:0回答:6

我们的应用程序有两个部分:
服务器-提供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()

Tracing with 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 的原因可能是什么?

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

java http servlets
6个回答
36
投票

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

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

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

您还可以使用 HttpClientPolicy 禁用块


23
投票

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>

19
投票

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


10
投票

我认为将配置好的 HttpComponentsClientHttpRequestFactory 传递给 RestTemplate 会有所帮助。 在这里您可能会找到有关如何操作的解决方案。

我使用这样的开发方案的原因是为了处理Http错误响应正文中的响应消息,如401、404等,也是为了让应用程序部署在真实服务器端环境中。


1
投票

此异常对 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
可以读取输出/错误流,或者,如果您想抛出一个自定义异常,其中存储了检索到的信息,您可以在中设置自定义
ResponseErrorHandler
上面重写的方法。


0
投票

从 Spring Framework 6.1 开始,

SimpleClientHttpRequestFactory
不再支持切换流式传输,例如以下不再起作用:

SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
simpleClientHttpRequestFactory.setOutputStreaming(false);

相反,您应该通过查看 Spring 的发行说明来使用 JdkClientHttpRequestFactory。

特别是 基于 HttpClient 的 ClientHttpRequestFactory删除 ClientHttpRequestFactory 实现中的缓冲

© www.soinside.com 2019 - 2024. All rights reserved.