[英]Calling execution.execute() twice in RestTemplate interceptor
[英]Spring RestTemplate Interceptor not execute request
当我配置 RestTemplate 使用 HttpClient 时,我的拦截器只执行第一次,第二次执行时它会挂断,在下面的这个块中。 也不例外,不知道为什么! 如果我删除 httpClient 那就没问题了。 (我的拦截器意图是捕获 401 未授权状态以刷新访问令牌)
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;
}
你能试试这个吗
而不是下面的代码块即
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient());
RestTemplate restTemplate = new RestTemplate(requestFactory);
尝试
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(clientHttpRequestFactory()));
看看这是否适合你。
这是一个老问题,但由于人们仍然通过他们选择的搜索引擎来到这里,并且对更改“有效”的原因感到困惑,这可能会节省一些时间:
如果 401 是响应代码,则问题源于原始 http 响应未在您的自定义拦截器类中使用。
增加 ConnectionPool 容量只会隐藏问题(并可能“修复”应用程序运行的问题),但实际上并不会释放连接并修复问题。
在您的拦截器类中,401 响应永远不会在您发出请求的流中消耗,401 响应需要手动消耗,因为它永远不会传递。
如果响应具有 401 状态,则您执行一个新请求,而原始 401 响应只会永远阻止连接。 这就是为什么在一定数量的 401 请求(5 是默认 asfaik)之后没有更多可用连接的原因。 由于在这些 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);
}
如果代码不是 401,我不会评论未使用/返回的响应,如果此响应中没有您想要/需要在此拦截器中处理的任何内容,则仅使用该响应并返回它可能是有意义的。
另请注意,在此代码中为 401 所做的拦截器中的重新请求将不会通过添加到 httpclient 的拦截器列表的其他拦截器传递。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.