繁体   English   中英

Spring RestTemplate拦截器不执行请求

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM