当我配置 RestTemplate 使用 HttpClient 时,我的拦截器仅第一次执行,第二次执行时它会挂起,在下面的这个 block 中。没有异常(exception),我不知道为什么! 如果我删除 httpClient 就没有问题了。 (我的拦截器意图是捕获 401 未授权状态以刷新访问 token )
if (url.contains("/auth") || url.contains("/custcare-common")) {
return execution.execute(request, body);
}
完整代码如下:
public class RestTemplateHeaderInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
String url = request.getURI().getPath();
if (url.contains("/auth") || url.contains("/custcare-common")) {
return execution.execute(request, body);
}
HttpSession session = SessionBean.getSession();
if (session != null) {
// check header does not has token then add to header
if (request.getHeaders().isEmpty() || !request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
// add jwt token to header
Object accessToken = session.getAttribute(AppConstants.Token.ACCESS_TOKEN);
if (accessToken != null) {
request.getHeaders().add(HttpHeaders.AUTHORIZATION, SecurityConstants.TOKEN_PREFIX + accessToken);
}
}
// refresh token when expire
ClientHttpResponse response = execution.execute(request, body);
if (response.getStatusCode() == HttpStatus.UNAUTHORIZED) {
Object refreshToken = session.getAttribute(AppConstants.Token.REFRESH_TOKEN);
if (refreshToken != null) {
TokenData newTokenData = getNewToken(refreshToken.toString());
if (newTokenData != null) {
session.setAttribute(AppConstants.Token.ACCESS_TOKEN, newTokenData.getAccessToken());
session.setAttribute(AppConstants.Token.REFRESH_TOKEN, newTokenData.getRefreshToken());
request.getHeaders().set(HttpHeaders.AUTHORIZATION, SecurityConstants.TOKEN_PREFIX + newTokenData.getAccessToken());
return execution.execute(request, body);
}
}
}
}
return execution.execute(request, body);
}
private TokenData getNewToken(String refreshToken) {
TokenData tokenData = null;
RefreshTokenRequest request = new RefreshTokenRequest();
request.setRefreshToken(refreshToken);
MessagesResponse<TokenData> response = RestUtil.post(RestUtil.getCcApiUrl("cc-api.authen.refresh-token"), request, new ParameterizedTypeReference<MessagesResponse<TokenData>>() {});
if (response != null) {
tokenData = response.getData();
}
return tokenData;
}
}
我的 RestTemplate 配置:
@Configuration
public class RestTemplateConfig {
@Bean
public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
PoolingHttpClientConnectionManager result = new PoolingHttpClientConnectionManager();
result.setMaxTotal(20);
return result;
}
@Bean
public RequestConfig requestConfig() {
RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(60000).setConnectTimeout(60000)
.setSocketTimeout(60000).build();
return requestConfig;
}
@Bean
public CloseableHttpClient httpClient() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
TrustStrategy trustStrategy = new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
return true;
}
};
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, trustStrategy).build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
CloseableHttpClient result = HttpClientBuilder.create()
.setConnectionManager(poolingHttpClientConnectionManager())
.setDefaultRequestConfig(requestConfig())
.setSSLSocketFactory(csf)
.build();
return result;
}
@Bean
@Primary
public RestTemplate restTemplate() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient());
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.setErrorHandler(new RestResponseErrorHandler());
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (interceptors.isEmpty()) {
interceptors = new ArrayList<>();
}
interceptors.add(new RestTemplateHeaderInterceptor());
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
最后,我通过设置 DefaultMaxPerRoute 解决了这个问题,但我仍然对此感到困惑。 :D
@Bean
public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
PoolingHttpClientConnectionManager result = new PoolingHttpClientConnectionManager();
result.setMaxTotal(100);
result.setDefaultMaxPerRoute(100);
return result;
}
最佳答案
这是一个老问题,但由于人们仍然通过他们选择的搜索引擎来到这里,并且对于为什么更改“有效”感到困惑,这可能会节省一些时间:
如果响应代码为 401,则问题源于原始 http 响应未在您的自定义拦截器类中使用。
增加连接池容量只会隐藏问题(并且可能“修复”应用程序运行的问题),但实际上并不会将连接释放回池并解决问题。
在您的拦截器类中,401 响应永远不会被消耗,仅用于 StatusCode 检查。此响应会阻塞池的一个连接。 401 响应需要手动使用,因为它从未被传递。
如果响应有 401 状态,您将执行一个新请求,而原始 401 响应只会永远阻止连接。这就是为什么在一定数量的 401 请求(默认为 5)之后不再有可用连接的原因。由于在这 5 401 个请求之后,ConnectionPool 中不再有可用的连接,因此线程会挂起,并且执行调用永远不会完成,因为它正在等待可用的空闲连接。
需要改变吗?一行代码可以让我们避免头痛和解决方法,在 401 检查后(或任何时候未返回/使用响应时)关闭响应:
public class RestTemplateHeaderInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
// ... omitted ...
// refresh token when expire
// THIS RESPONSE IS NEVER HANDLED
ClientHttpResponse response = execution.execute(request, body);
if (response.getStatusCode() == HttpStatus.UNAUTHORIZED) {
// Close response only needed for the status code and refresh
response.close();
// Continue on with your code.
Object refreshToken = session.getAttribute(AppConstants.Token.REFRESH_TOKEN);
if (refreshToken != null) {
// ... omitted ...
return execution.execute(request, body);
}
}
}
}
return execution.execute(request, body);
}
在使用 HttpEntites 时,还有一种方便的 HttpEntities 方法:org.apache.http.util.EntityUtils#consume。
请注意,此代码中针对 401 所做的拦截器中的重新请求不会通过添加到 httpclient 拦截器列表中的其他拦截器传递。
关于java - Spring RestTemplate拦截器不执行请求,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59244512/