简体   繁体   English

Spring RestTemplate拦截器不执行请求

[英]Spring RestTemplate Interceptor not execute request

When I configure RestTemplate use HttpClient then my interceptor only execute for first time, in second time it'll hang up when execute, in this block below.当我配置 RestTemplate 使用 HttpClient 时,我的拦截器只执行第一次,第二次执行时它会挂断,在下面的这个块中。 There is no exception, I don't know why!也不例外,不知道为什么! If I remove httpClient then no problem.如果我删除 httpClient 那就没问题了。 (My interceptor intention is catch 401 unauthorized status to refresh access token) (我的拦截器意图是捕获 401 未授权状态以刷新访问令牌)

if (url.contains("/auth") || url.contains("/custcare-common")) {
    return execution.execute(request, body);
}

Full code below:完整代码如下:

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;
}

} }

My RestTemplate config:我的 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;
    }
}

Finally, I done deal with this issue by setting DefaultMaxPerRoute, but I still confuse about this.最后,我通过设置 DefaultMaxPerRoute 来解决这个问题,但我仍然对此感到困惑。 :D :D

@Bean
public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
    PoolingHttpClientConnectionManager result = new PoolingHttpClientConnectionManager();
    result.setMaxTotal(100);
    result.setDefaultMaxPerRoute(100);
    return result;
}

Can you try this你能试试这个吗

instead of below block of code ie而不是下面的代码块即

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

try尝试

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

see if this works for you.看看这是否适合你。

This is an old question, but as people still come here via their search engine of choice and there was confusion about why the change "worked", this might save someone some time:这是一个老问题,但由于人们仍然通过他们选择的搜索引擎来到这里,并且对更改“有效”的原因感到困惑,这可能会节省一些时间:

The issue stems from the original http response not being consumed in your custom Interceptor class, if a 401 is the response code.如果 401 是响应代码,则问题源于原始 http 响应未在您的自定义拦截器类中使用。

Upping the ConnectionPool capacity only hides the problem (and may "fix" the issue for the application run), but does not actually release the connection and fix the problem.增加 ConnectionPool 容量只会隐藏问题(并可能“修复”应用程序运行的问题),但实际上并不会释放连接并修复问题。

In your interceptor class, the 401 response that is never consumed down the stream you do a request, the 401 response needs to be manually consumed as its never handed down.在您的拦截器类中,401 响应永远不会在您发出请求的流中消耗,401 响应需要手动消耗,因为它永远不会传递。

If a response has a 401 status, you execute a new request, and the original 401 response just blocks a connection forever.如果响应具有 401 状态,则您执行一个新请求,而原始 401 响应只会永远阻止连接。 That's why there are no more connections available after a certain number of 401 requests (5 is default asfaik).这就是为什么在一定数量的 401 请求(5 是默认 asfaik)之后没有更多可用连接的原因。 As no more connections are available in the ConnectionPool after those 5 401 requests, the thread just hangs around and the execute call never finishes since it's waiting for a free connection to use.由于在这些 5 401 请求之后 ConnectionPool 中没有更多连接可用,线程只是挂起并且执行调用永远不会完成,因为它正在等待可用的可用连接。

The required change?所需的更改? One line of code to save us the headache and the workaround, close the response after the 401 check (or any time a response is not returned):一行代码可以让我们省去头疼和解决方法,在 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);
}

I'll not comment on the response not being used/returned if the code is not 401, it may make sense to just use that response and return it if there is nothing in this response that you want/need to handle in this interceptor.如果代码不是 401,我不会评论未使用/返回的响应,如果此响应中没有您想要/需要在此拦截器中处理的任何内容,则仅使用该响应并返回它可能是有意义的。

Also note that a rerequest in an interceptor as done in this code for a 401 will not be passed through other interceptors added to the interceptorlist for the httpclient.另请注意,在此代码中为 401 所做的拦截器中的重新请求将不会通过添加到 httpclient 的拦截器列表的其他拦截器传递。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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