0


Tomcat Request Cookie 丢失问题

优质博文:IT-BLOG-CN

一、问题描述

生产环境偶尔(涉及到多线程处理)出现"前端传递`Cookie为空"的告警,导致前端请求丢失,出现请求失败问题。告警内容如下

前端传递Cookie为空
告警内容:服务端获取request Cookie为空,请尽快处理!!!
AppId:xxxxxx
ip:xx.xx.xxx.xx
告警事件:2024-03-15

背景:为什么要加

Cookie

告警:项目出海,需要保证多语言,语言信息从

Cookie

中获取,所以添加了

Cookie

告警,告警后发到工作群中,但是相关开发人员告知自己能够正常访问,没有问题,因为正好周五,自己觉得偶发性肯定和并发相关,所以周末研究了下代码,发现和

Tomcat Rquest

复用机制和

ThreadLocal

的使用存在缺陷,导致这个偶发性问题

在分析原因前,先需要搞懂一个概念:

request

tomcat

里面是循环使用的

二、Tomcat 中 Reqeust 复用机制

Request

对象的复用机制是为了提高性能和减少垃圾收集压力而设计的。

Tomcat

使用了一种对象池的机制来管理

Request

对象和

Response

对象。通过复用这些对象,

Tomcat

可以避免频繁地创建和销毁对象,从而提高系统的效率。

复用机制的工作原理
【1】对象池:

Tomcat

维护一个对象池,用于存储

Request

对象和

Response

对象。当一个新的

HTTP

请求到达时,

Tomcat

从对象池中获取一个空闲的

Request

对象和

Response

对象。如果对象池中没有空闲的对象,

Tomcat

会创建新的对象。简单看个案例:

publicclassRequestPool{privateStack<Request> pool =newStack<>();// 获取对象:getRequest 方法从对象池中获取一个 Request 对象。如果对象池为空,则创建一个新的 Request 对象。publicRequestgetRequest(){if(pool.isEmpty()){returnnewRequest();}else{return pool.pop();}}// 释放对象:releaseRequest 方法将 Request 对象重置(调用 recycle 方法)并放回对象池中。publicvoidreleaseRequest(Request request){
        request.recycle();
        pool.push(request);}}

【2】对象重置:当一个请求处理完毕后,

Request

对象会被重置(通过调用

recycle

方法),以清除上一次请求的状态,使其可以安全地用于下一个请求。以下是

org.apache.catalina.connector.Request

类中

recycle

方法的简化源码和解释:

publicclassRequest{// Various fields representing the state of the requestprivateString protocol;privateString method;privateString requestURI;privateString queryString;privateString remoteAddr;privateString remoteHost;privateString serverName;privateint serverPort;privateboolean secure;privateInputStream inputStream;privateReader reader;privateServletInputStream servletInputStream;privateBufferedReader bufferedReader;privateMap<String,Object> attributes;privateMap<String,String[]> parameters;privateCookie[] cookies;privateHttpSession session;// Other fields and methods.../**
     * Recycle this request object.
     */publicvoidrecycle(){// Reset the state of the request object// 重置基本属性:recycle 方法将 Request 对象的基本属性(如 protocol、method、requestURI 等)重置为初始状态(通常为 null 或默认值)。// 清空集合和数组:attributes 和 parameters 集合被清空,以确保没有残留的请求数据。cookies 数组也被重置为 null。// 重置流和读者:inputStream、reader、servletInputStream 和 bufferedReader 被重置为 null,以确保没有残留的输入流和读者对象。// 重置会话:session 被重置为 null,以确保没有残留的会话信息。
        protocol =null;
        method =null;
        requestURI =null;
        queryString =null;
        remoteAddr =null;
        remoteHost =null;
        serverName =null;
        serverPort =0;
        secure =false;
        inputStream =null;
        reader =null;
        servletInputStream =null;
        bufferedReader =null;
        attributes.clear();
        parameters.clear();
        cookies =null;
        session =null;// Other reset logic...}}

**

recycle

执行的时机:**

recycle

方法在

Tomcat

源码中的调用时机主要是在请求处理完毕之后,

Request

对象被返回到对象池之前。具体来说,

recycle

方法通常在以下几个场景中被调用:
【1】请求处理完毕后:在

Tomcat

org.apache.coyote.Request

类中,

recycle

方法通常在请求处理完毕后被调用。例如,在

AbstractProcessorLight

类中处理请求和响应的逻辑中,

recycle

方法被调用来重置

Request

对象。

// org.apache.coyote.AbstractProcessorLightpublicclassAbstractProcessorLight<S>implementsProcessor{// Various fields and methods...@OverridepublicSocketStateprocess(SocketWrapperBase<S> socketWrapper,SocketEvent status)throwsIOException{// Process the request and responsetry{// Request processing logic...}finally{// Recycle the request and response objects
            request.recycle();
            response.recycle();}returnSocketState.CLOSED;}}

【2】连接关闭时:在

Tomcat

org.apache.coyote.http11.Http11Processor

类中,当连接关闭时,

recycle

方法也会被调用。例如,当处理完一个请求并决定关闭连接时,会调用

recycle

方法。

// org.apache.coyote.http11.Http11ProcessorpublicclassHttp11ProcessorextendsAbstractProcessorLight<SocketChannel>{// Various fields and methods...@OverridepublicSocketStateservice(SocketWrapperBase<SocketChannel> socketWrapper)throwsIOException{// Service the request and responsetry{// Request servicing logic...}finally{// Recycle the request and response objects
            request.recycle();
            response.recycle();}returnSocketState.CLOSED;}}

【3】异常处理:在处理请求的过程中,如果发生异常,

Tomcat

也会确保调用

recycle

方法来重置

Request

对象。例如:

// org.apache.coyote.http11.Http11ProcessorpublicclassHttp11ProcessorextendsAbstractProcessorLight<SocketChannel>{// Various fields and methods...@OverridepublicSocketStateservice(SocketWrapperBase<SocketChannel> socketWrapper)throwsIOException{try{// Request servicing logic...}catch(Exception e){// Handle exception and recycle request
            request.recycle();
            response.recycle();throw e;}}}

后期原因分析中需要使用到

RequestFacade

,这里解释下

RequestFacade

Request

之间的关系:

RequestFacade

是一个包装类

Facade

,用于保护底层的

Request

对象,确保应用程序无法直接访问和修改内部实现细节。
**【1】

Request

类:**

Request

类是

Tomcat

内部用来表示

HTTP

请求的类,包含了请求的所有详细信息。该类提供了许多方法来访问和操作请求的各个部分,例如请求头、请求参数、输入流等。
**【2】

RequestFacade

类:**

RequestFacade

类是一个包装器,用于保护

Request

对象。它实现了

javax.servlet.http.HttpServletRequest

接口,并将方法调用委托给内部的

Request

对象。通过使用

RequestFacade

Tomcat

确保了应用程序只能通过标准的

HttpServletRequest

接口访问请求数据,而不能直接访问或修改

Request

对象的内部实现。

具体实现:在

Tomcat

中,

RequestFacade

类通常包含一个

Request

对象的引用,并将所有的接口方法调用委托给这个内部的

Request

对象。例如:

// org.apache.catalina.connector.RequestFacadepublicclassRequestFacadeimplementsHttpServletRequest{privatefinalRequest request;publicRequestFacade(Request request){this.request = request;}@OverridepublicStringgetParameter(String name){return request.getParameter(name);}// Other methods from HttpServletRequest interface// All methods delegate to the internal Request object}

使用场景:在

Tomcat

处理请求的过程中,当需要将

HttpServletRequest

对象传递给应用程序时,

Tomcat

会创建一个

RequestFacade

实例,并将内部的

Request

对象传递给它。例如

// org.apache.catalina.connector.CoyoteAdapterpublicclassCoyoteAdapterimplementsAdapter{@Overridepublicvoidservice(org.apache.coyote.Request req,org.apache.coyote.Response res)throwsException{Request request =(Request) req.getNote(ADAPTER_NOTES);Response response =(Response) res.getNote(ADAPTER_NOTES);// Create a RequestFacade to pass to the applicationHttpServletRequest requestFacade = request.getRequest();// Pass the RequestFacade to the application
        context.getPipeline().getFirst().invoke(requestFacade, response);}}
ThreadLocal

的原理不清楚,可以参考ThreadLocal 类

三、原因分析

【1】第一次请求由线程

A

正常执行,执行完成后执行

recycle

方法,将

RequestFacade

中的属性修改为

null

,准备下次复用,但是当前线程的

ThreadLocal

没有被清理。
【2】第二次请求恰好也由线程

A

执行(这也是偶发的原因),通过

ThreadLocal

获取

RequestFacade

对象,并通过

getCookies

获取

Cookie

,因为第一次请求结束后将

Cookie

置为

null

并将

cookiesParsed

修改为了

false

,但是这次请求再次调用

getCookies

的时候,将

cookiesParsed

修改为了

true

。用来表示

RequestFacade A

Cookies

已经被解析过了。同时需要注意,此时第一次请求的生命周期已经结束了,所以重置

cookiesParsed

的操作就不复存在了,

Tomcat

重新复用

RequestFacade A

的时候

Cookies

就会获取到一个

null

@OverridepublicCookie[]getCookies(){if(!cookiesParsed){parseCookies();}return cookies;}protectedvoidparseCookies(){
    cookiesParsed =true;Cookies serverCookies = coyoteRequest.getCookies();int count = serverCookies.getCookieCount();if(count <=0){
        returnl
    }

    cookies =newCookie[count];}

【3】第三次请求时,

Tomcat

复用了

RequestFacade A

,当正常解析

Cookies

的时候发现

cookiesParsed

true

就跳过了正确解析的环节,当需要使用

Cookie

的时候发现为空,本次请求直接被中止。(灵异事件)

解决方案:
【1】

ThreadLocal

使用完后一定需要

clean


【2】不要在跨线程中使用

request

对象。可以使用

-Dorg.apache.catalina.connector.RECYCLE_FACADES=true

禁止复用。在项目的

extraenv.sh

中设置参数后,如果有访问已经被回收的

request

对象,就会抛出

The request object has been recycled and is no longer associated with this facade

异常,以此就能定位到问题

标签: tomcat firefox java

本文转载自: https://blog.csdn.net/zhengzhaoyang122/article/details/142035371
版权归原作者 程序猿进阶 所有, 如有侵权,请联系我们删除。

“Tomcat Request Cookie 丢失问题”的评论:

还没有评论