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的使用存在缺陷,导致这个偶发性问题

在分析原因前,先需要搞懂一个概念:requesttomcat里面是循环使用的

二、Tomcat 中 Reqeust 复用机制

Request对象的复用机制是为了提高性能和减少垃圾收集压力而设计的。Tomcat使用了一种对象池的机制来管理Request对象和Response对象。通过复用这些对象,Tomcat可以避免频繁地创建和销毁对象,从而提高系统的效率。

复用机制的工作原理
【1】对象池:Tomcat维护一个对象池,用于存储Request对象和Response对象。当一个新的HTTP请求到达时,Tomcat从对象池中获取一个空闲的Request对象和Response对象。如果对象池中没有空闲的对象,Tomcat会创建新的对象。简单看个案例:

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

【2】对象重置:当一个请求处理完毕后,Request对象会被重置(通过调用recycle方法),以清除上一次请求的状态,使其可以安全地用于下一个请求。以下是org.apache.catalina.connector.Request类中recycle方法的简化源码和解释:

public class Request {
    // Various fields representing the state of the request
    private String protocol;
    private String method;
    private String requestURI;
    private String queryString;
    private String remoteAddr;
    private String remoteHost;
    private String serverName;
    private int serverPort;
    private boolean secure;
    private InputStream inputStream;
    private Reader reader;
    private ServletInputStream servletInputStream;
    private BufferedReader bufferedReader;
    private Map<String, Object> attributes;
    private Map<String, String[]> parameters;
    private Cookie[] cookies;
    private HttpSession session;

    // Other fields and methods...

    /**
     * Recycle this request object.
     */
    public void recycle() {
        // 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】请求处理完毕后:在Tomcatorg.apache.coyote.Request类中,recycle方法通常在请求处理完毕后被调用。例如,在AbstractProcessorLight类中处理请求和响应的逻辑中,recycle方法被调用来重置Request对象。

// org.apache.coyote.AbstractProcessorLight
public class AbstractProcessorLight<S> implements Processor {
    // Various fields and methods...

    @Override
    public SocketState process(SocketWrapperBase<S> socketWrapper, SocketEvent status) throws IOException {
        // Process the request and response
        try {
            // Request processing logic...
        } finally {
            // Recycle the request and response objects
            request.recycle();
            response.recycle();
        }
        return SocketState.CLOSED;
    }
}

【2】连接关闭时:在Tomcatorg.apache.coyote.http11.Http11Processor类中,当连接关闭时,recycle方法也会被调用。例如,当处理完一个请求并决定关闭连接时,会调用recycle方法。

// org.apache.coyote.http11.Http11Processor
public class Http11Processor extends AbstractProcessorLight<SocketChannel> {
    // Various fields and methods...

    @Override
    public SocketState service(SocketWrapperBase<SocketChannel> socketWrapper) throws IOException {
        // Service the request and response
        try {
            // Request servicing logic...
        } finally {
            // Recycle the request and response objects
            request.recycle();
            response.recycle();
        }
        return SocketState.CLOSED;
    }
}

【3】异常处理:在处理请求的过程中,如果发生异常,Tomcat也会确保调用recycle方法来重置Request对象。例如:

// org.apache.coyote.http11.Http11Processor
public class Http11Processor extends AbstractProcessorLight<SocketChannel> {
    // Various fields and methods...

    @Override
    public SocketState service(SocketWrapperBase<SocketChannel> socketWrapper) throws IOException {
        try {
            // Request servicing logic...
        } catch (Exception e) {
            // Handle exception and recycle request
            request.recycle();
            response.recycle();
            throw e;
        }
    }
}

后期原因分析中需要使用到RequestFacade,这里解释下RequestFacadeRequest之间的关系:RequestFacade是一个包装类Facade,用于保护底层的Request对象,确保应用程序无法直接访问和修改内部实现细节。
【1】Request类: Request类是Tomcat内部用来表示HTTP请求的类,包含了请求的所有详细信息。该类提供了许多方法来访问和操作请求的各个部分,例如请求头、请求参数、输入流等。
【2】RequestFacade 类: RequestFacade类是一个包装器,用于保护Request对象。它实现了javax.servlet.http.HttpServletRequest接口,并将方法调用委托给内部的Request对象。通过使用RequestFacadeTomcat确保了应用程序只能通过标准的HttpServletRequest接口访问请求数据,而不能直接访问或修改Request对象的内部实现。

具体实现:在Tomcat中,RequestFacade类通常包含一个Request对象的引用,并将所有的接口方法调用委托给这个内部的Request对象。例如:

// org.apache.catalina.connector.RequestFacade
public class RequestFacade implements HttpServletRequest {
    private final Request request;

    public RequestFacade(Request request) {
        this.request = request;
    }

    @Override
    public String getParameter(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.CoyoteAdapter
public class CoyoteAdapter implements Adapter {
    @Override
    public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception {
        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);

        // Create a RequestFacade to pass to the application
        HttpServletRequest 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 ACookies已经被解析过了。同时需要注意,此时第一次请求的生命周期已经结束了,所以重置cookiesParsed的操作就不复存在了,Tomcat重新复用RequestFacade A的时候Cookies就会获取到一个null

@Override
public Cookie[] getCookies() {
    if (!cookiesParsed) {
        parseCookies();
    }
    return cookies;
}

protected void parseCookies() {
    cookiesParsed = true;

    Cookies serverCookies = coyoteRequest.getCookies();
    int count = serverCookies.getCookieCount();
    if (count <= 0) {
        returnl
    }

    cookies = new Cookie[count];
}

【3】第三次请求时,Tomcat复用了RequestFacade A,当正常解析Cookies的时候发现cookiesParsedtrue就跳过了正确解析的环节,当需要使用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异常,以此就能定位到问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/874050.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大屏地图区域显示、复选框多选打点,自定义窗体信息(vue3+TS)

效果图&#xff1a; NPM 安装 Loader&#xff1a; npm i amap/amap-jsapi-loader --save 并设置 key 和安全密钥&#xff1a; import AMapLoader from amap/amap-jsapi-loader;//引入高德地图window._AMapSecurityConfig {securityJsCode: "「你申请的安全密钥」"…

[数据集][目标检测]百事可乐可口可乐瓶子检测数据集VOC+YOLO格式195张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;195 标注数量(xml文件个数)&#xff1a;195 标注数量(txt文件个数)&#xff1a;195 标注类别…

前端登录鉴权——以若依Ruoyi前后端分离项目为例解读

权限模型 Ruoyi框架学习——权限管理_若依框架权限-CSDN博客 用户-角色-菜单&#xff08;User-Role-Menu&#xff09;模型是一种常用于权限管理的设计模式&#xff0c;用于实现系统中的用户权限控制。该模型主要包含以下几个要素&#xff1a; 用户&#xff08;User&#xff09;…

【VSCode v1.93.0】手动配置远程remote-ssh

开发环境 VS Code版本&#xff1a;1.93.0 (Windows) Ubuntu版本&#xff1a;20.04 使用VS Code 插件remote-ssh远程访问Ubuntu服务器中的代码&#xff0c;若Ubuntu无法联网&#xff0c;在连接的时候会报错&#xff1a; Could not establish connection to "xxxx": F…

店匠科技携手Stripe共谋电商支付新篇章

在全球电商行业蓬勃发展的背景下,支付环节作为交易闭环的核心,其重要性日益凸显。随着消费者对支付体验要求的不断提高,以及跨境电商的迅猛发展,支付市场正经历着前所未有的变革与挑战。在这一充满机遇与竞争的领域,店匠科技(Shoplazza)凭借其创新的嵌入式支付解决方案—— Sho…

【Obsidian】同步插件 Remotely Save 的应用介绍

背景 引言&#xff0c;大多数的笔记软件数据都存储在云端&#xff0c;这样如果是没网络&#xff0c;或者笔记服务器崩溃故障&#xff0c;那么你就无法访问自己的笔记了&#xff0c;前段时间的语雀系统崩溃&#xff0c;不知道你有没有了解&#xff1f; 所以笔记在自己手里&…

第四届“长城杯”网络安全大赛 暨京津冀网络安全技能竞赛(初赛) 全方向 题解WriteUp

战队名称&#xff1a;TeamGipsy 战队排名&#xff1a;18 SQLUP 题目描述&#xff1a;a website developed by a novice developer. 开题&#xff0c;是个登录界面。 账号admin&#xff0c;随便什么密码都能登录 点击头像可以进行文件上传 先简单上传个木马试试 测一下&…

ComfyUI-图片表情控制工作流整合包,相关软件包及工作流均已打包好,一键启动即可

前言 本期给大家带来基于LivePortrait的ComfyUI图片表情控制工作流&#xff0c;还可以通过控制表情变化及参考视频生成动态肖像。 此工作流操作简单&#xff0c;相关ComfyUI软件包、模型、节点、工作流均已打包在内&#xff0c;一键启动即可。 # 配置要求&#xff1a; 建议电…

基于 TiDB 资源管控 + TiCDC 实现多业务融合容灾测试

导读 随着金融行业的不断发展&#xff0c;多个业务系统的整合成为了趋势&#xff0c;分布式数据库的应用也愈发广泛。为了应对多业务融合带来的复杂性&#xff0c;金融机构需要在保障各业务系统高效运行的同时&#xff0c;确保 IT 系统的高可用性和稳定性。本文将介绍 TiDB 如…

媒体服务器软件BUG说明及改进方案

媒体服务器软件BUG说明及改进方案 一、BUG描述二、问题分析三、改进方案四、实施计划五、预期效果六、总结一、BUG描述 在当前版本的媒体服务器中,存在一个关于静音媒体流处理的问题。具体表现为:当主叫连续发送静音帧到媒体服务器时,媒体服务器并未将这些静音帧转发给被叫…

2. Python之注释及缩进以及print和input函数使用

一. Python代码中的注释及缩进 Python中注释有单行注释&#xff0c;多行注释&#xff0c;声明注释 单行注释使用符号#&#xff0c;从#开始&#xff0c;后面到本行末尾都是注释内容。多行注释可以使用’‘’ ‘’三个引号&#xff0c;三个引号内的内容都是注释声明注释&#x…

相机内存卡格式化了照片怎么恢复?格式化恢复详解

摄影爱好者们都知道&#xff0c;相机内存卡是记录我们美好瞬间的重要媒介。然而&#xff0c;在使用过程中&#xff0c;有时我们会因操作不当或设备故障&#xff0c;不小心格式化了内存卡&#xff0c;从而导致珍贵的照片丢失。面对这种情况&#xff0c;我们该如何恢复这些被格式…

深入FastAPI:掌握使用多个关联模型的高级用法[Union类型]

在FastAPI中&#xff0c;响应模型可以声明为Union类型&#xff0c;这允许你为同一个端点定义多种可能的响应模型。这种灵活性使得API可以根据不同的情况返回不同类型的数据结构。 例如&#xff0c;根据请求中的查询参数或数据库中的数据&#xff0c;一个API端点可能有时返回一…

STM32 HAL DMA 中断碰到的问题

流程 串口收数据—>dma搬运到变量—>空闲中断----->接收完成 配置 dma中断全部去掉 串口中断开启 freertos中断全部去掉 时钟配置 代码 开启中断 // DMA 空闲检查 void receives_uaru_7(void) {RXU7 0;//清除中断标志HAL_UARTEx_ReceiveToIdle_DMA(&hua…

【学习笔记11】如何找到twitter中自己的cookie?

步骤 在浏览器中打开twitter网站&#xff0c;按下CtrlShiftI(i)按下面步骤以此点击 参考 如何找到自己的Facebook XS Cookie和X/Twitter Auth_Token? 一張圖秒懂

二、Maven工程的创建--JavaSEJavaEE

1、idea创建Maven JavaSE工程&#xff1a; 2、idea创建Maven JavaEE工程&#xff1a; &#xff08;1&#xff09;手动创建 &#xff08;2&#xff09;插件方式创建 在idea里安装插件JBLJavaToWeb&#xff1b; 选择需要生成的项目文件后&#xff0c;右击&#xff1a; 项目…

【实战篇】MySQL为什么有时候会选错索引?

场景分析 1 如下数据库表&#xff1a; CREATE TABLE t (id int(11) NOT NULL AUTO_INCREMENT,a int(11) DEFAULT NULL,b int(11) DEFAULT NULL,PRIMARY KEY (id),KEY a (a),KEY b (b) ) ENGINEInnoDB;然后&#xff0c;我们往表 t 中插入 10 万行记录&#xff0c;取值按整数递…

那些35岁以上的产品经理都去做什么了?

前言 升成管理层的一批&#xff0c;改行去开店的一批&#xff0c;剩下来在干产品经理的也不少&#xff0c;能在35岁之后还干产品经理的&#xff0c;都是人精&#xff0c;知道适者生存&#xff0c;知道要跟着大势走。 现在的大势是啥&#xff1f;AI呀&#xff01; 我那些干了…

【delphi】判断多显示器下,程序在那个显示其中

在 Delphi 中&#xff0c;如果你的电脑连接了多个显示器&#xff0c;可以通过以下步骤判断某个程序在哪个显示器上运行。 方法概述&#xff1a; 获取程序窗口的位置&#xff08;例如窗体的 Left、Top 坐标&#xff09;。使用 Screen.MonitorFromWindow 函数来确定该窗口所属的…

面了 5 家知名企业的NLP算法岗(大模型方向),被问麻了。。。。。

最近一位同学&#xff0c;给我分享了他面试 NLP 算法工程师(大模型方向)的经历与经验。直呼最近找工作太难了。。。。 今天我整理后分享给大家&#xff0c;希望对后续找工作的有所帮助。 这位同学为面试刷了 leetcode200-300 题左右&#xff0c;侧重刷中高频hard题&#xff0…