【问题标题】:Spring RestTemplate Interceptor not execute requestSpring RestTemplate 拦截器不执行请求
【发布时间】:2019-12-09 07:46:23
【问题描述】:

当我配置 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;
}

【问题讨论】:

    标签: java spring resttemplate interceptor


    【解决方案1】:

    你可以试试这个

    而不是下面的代码块,即

    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient());
    RestTemplate restTemplate = new RestTemplate(requestFactory);
    

    试试

    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(clientHttpRequestFactory()));
    

    看看这是否适合你。

    【讨论】:

    • 谢谢!但我仍然有同样的问题:(如果我创建纯 HttpClient 事件(不设置任何内容 HttpClientBuilder.create().build();
    • 最后我通过设置池化 setDefaultMaxPerRoute(100) 来处理
    【解决方案2】:

    这是一个老问题,但由于人们仍然通过他们选择的搜索引擎来到这里,并且对于为什么更改“有效”存在困惑,这可能会节省一些时间:

    如果响应代码为 401,则问题源于未在您的自定义拦截器类中使用的原始 http 响应。

    增加 ConnectionPool 容量只会隐藏问题(并且可能会“修复”应用程序运行的问题),但不会真正将连接释放回池并修复问题。

    在您的拦截器类中,401 响应永远不会被消耗,仅用于 StatusCode 检查。此响应阻止池的一个连接。 401 响应需要手动使用,因为它从未传递过。

    如果响应具有 401 状态,则您执行一个新请求,而原始 401 响应只会永远阻止连接。这就是为什么在一定数量的 401 请求之后没有更多可用连接的原因(5 是默认的 afaik)。由于在这 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);
    }
    

    HttpEntities 还有一种方便的方式:使用 HttpEntites 时使用 org.apache.http.util.EntityUtils#consume。

    请注意,在此代码中为 401 所做的拦截器中的重新请求不会通过添加到 httpclient 的拦截器列表中的其他拦截器传递。

    【讨论】:

      猜你喜欢
      • 2013-03-17
      • 1970-01-01
      • 2019-12-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-11
      • 2018-08-09
      相关资源
      最近更新 更多