【问题标题】:HttpServletRequest headers disappear when using CompletableFuture使用 CompletableFuture 时,HttpServletRequest 标头消失
【发布时间】:2020-06-07 11:35:02
【问题描述】:

首先,对于如此模糊的问题标题,我深表歉意。我是 Java 异步编程的新手(因此 CompletableFuture),我正在尝试使用 Spring 的 @Async 功能来玩弄它。

我有以下 AsyncConfig.java

@Configuration
public class AsyncConfiguration {

    private static final Logger LOG = LogManager.getLogger(AsyncConfiguration.class);

    @Bean("asyncExecutor")
    public Executor asyncExecutor() {

        LOG.info("Configuring ASYNC Executor");

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(1);
        executor.setMaxPoolSize(2);
        executor.setQueueCapacity(2);
        executor.setThreadNamePrefix("AsyncThread--");
        executor.initialize();

        LOG.info("ASYNC Executor Configuration Complete");

        return executor;
    }

}

现在我有两个过滤器,ApiUsageLimitFilter@Order(1)RequestResponseLoggingFilter@Order(2)

ApiUsageLimitFilter.javadoFilter中,我有以下代码:

@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        LOG.info("API Filter Request handled by Thread {} -> {}", Thread.currentThread().getName(),
                Thread.currentThread().getId());
        final HttpServletRequest httpServletRequest = HttpServletRequest.class.cast(request);
        HttpServletResponse httpServletResponse = HttpServletResponse.class.cast(response);
        try {
            LOG.info("Checking usgae limit for IP: {}", httpServletRequest.getHeader("X-Real-IP"));
            apiUsageMonitorService.isAllowed(httpServletRequest)  //Some method that returns a CompletableFuture<Boolean>
            .thenCompose((allowed) -> {
                if (allowed.equals(Boolean.TRUE)) {
                    LOG.info("IP: {} allowed", httpServletRequest.getHeader("X-Real-IP")); // Value is NULL
                    try {
                        chain.doFilter(httpServletRequest, httpServletResponse);
                    } catch (IOException | ServletException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    return null;
                } else {
                    LOG.warn("IP: {} reached its usage limit. Blocking any further calls",
                            httpServletRequest.getHeader("X-Real-IP"));
                    return apiUsageMonitorService.remainingTTL(httpServletRequest);
                }
            }).thenAccept((remainingTTL) -> {

                System.out.println("This gets executed");
                if (remainingTTL != null) {
                    System.out.println("SOme of this gets executed");
                    try {

                        ApiResponse errorResponse = new ApiResponse();
                        errorResponse.setStatus(ApiRequestStatus.FAILURE);
                        errorResponse.setMessage(
                                "You have reached the API usage limit. Only 10 requests allowed per hour. Please try after the time specified in Retry-After header");
                        errorResponse.setErrorCode(ApiRequestErrorCode.API_USAGE_LIMIT_REACHED);
                        String errorResponseString = getResponseAsString(errorResponse);
                        LOG.info("Error response as String:{}", errorResponseString);
                        httpServletResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());

                        Long remainingSeconds = TimeUnit.MILLISECONDS.toSeconds(remainingTTL);
                        httpServletResponse.setHeader("Retry-After", remainingSeconds.toString() + " seconds");
                        httpServletResponse.getWriter().write(errorResponseString);
                    } catch (Exception e) {
                        // TODO: handle exception
                    }

                }
            });

上述方法调用的服务基本上返回booleanCompletableFuture,并基于该值,我要么调用链中的下一个过滤器,要么返回响应。

发生的情况是,在调用转到apiUsageMonitorService.isAllowed(httpServletRequest) 行之前,请求对象存在并且我可以看到其中的标头。但是,当在apiUsageMonitorService.isAllowed(httpServletRequest) 方法的thenCompose 中返回CompletableFuture 时,我再也看不到标头值 X-Real-IP 了。它打印一个空值。

我在没有异步代码的情况下尝试了相同的操作,即使在该特定调用之后我也能够访问请求标头。此外,该调用也将转到链中的下一个过滤器。但是当使用CompletableFuture 时,在调用之后的那个点根本不存在标头,它也不会转到链中的下一个过滤器RequestResponseLoggingFilter,因为我没有看到任何日志由那个过滤器完成。

以下是日志:

2020-02-23 23:12:53.721 信息 28774 --- [nio-8080-exec-3]

i.turls.lib.filters.ApiUsageLimitFilter  : API Filter Request handled by Thread http-nio-8080-exec-3 -> 58
2020-02-23 23:12:53.721  INFO 28774 --- [nio-8080-exec-3] i.turls.lib.filters.ApiUsageLimitFilter  : Checking usgae limit for IP: 122.168.23.274
2020-02-23 23:12:53.721  INFO 28774 --- [ AsyncThread--1] i.t.l.s.i.ApiUsageMonitorServiceImpl     : Running Async Method on Thread AsyncThread--1 -> 70
2020-02-23 23:12:53.721  INFO 28774 --- [ AsyncThread--1] i.t.l.s.i.ApiUsageMonitorServiceImpl     : Checking API usage for IP: 122.168.23.274
2020-02-23 23:12:53.729  INFO 28774 --- [ AsyncThread--1] i.t.l.s.i.ApiUsageMonitorServiceImpl     : Creating new entry with IP 122.168.23.274
2020-02-23 23:12:53.738  INFO 28774 --- [ AsyncThread--1] i.turls.lib.filters.ApiUsageLimitFilter  : IP: null allowed

这里出了什么问题?是不是当CompletableFuture完成后,请求对象早就消失了,因为Filters实现了同步方法?如果是这样,我该如何处理这个问题?我必须以异步方式使用它

【问题讨论】:

  • 在调用异步 lambda 之前,您是否尝试过获取标头值?无论如何,恐怕你需要调用 request.getAsyncContext() 并实现 AsyncListener 才能使异步逻辑工作。

标签: java asynchronous servlets completable-future


【解决方案1】:

您可能需要使用 Spring 的RequestContextHolder

Holder 类以线程绑定的 RequestAttributes 对象的形式公开 Web 请求。如果可继承标志设置为 true,则该请求将被当前线程产生的任何子线程继承。

以下sn-p(曾经)为我工作,但现在已经有几年了。

HttpHeaders headers = new HttpHeaders();

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof WebRequest) {
    WebRequest request = (WebRequest) requestAttributes;

    Iterator<String> headerNames = request.getHeaderNames();

    while (headerNames.hasNext()) {

        String headerName = headerNames.next();
        String[] headerValues = request.getHeaderValues(headerName);
        Arrays.stream(headerValues)
            .forEach(headerValue -> headers.add(headerName, headerValue));
    }
}

【讨论】:

    猜你喜欢
    • 2018-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-23
    • 2016-03-28
    • 1970-01-01
    相关资源
    最近更新 更多