37

我们的应用程序中有两个部分:
服务器 - 提供 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 的原因可能是什么?

另外:前段时间这种机制运行良好。但是由于我们在 app.js 中更改了很多代码。

4

5 回答 5

31

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

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

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

您还可以使用 HttpClientPolicy 禁用块

于 2015-07-27T09:17:15.643 回答
20

此问题在 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>
于 2018-12-09T19:14:29.843 回答
12

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

于 2020-02-28T16:15:37.313 回答
10

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

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

于 2015-04-06T08:45:49.787 回答
1

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

于 2021-09-30T12:56:13.697 回答