0
点赞
收藏
分享

微信扫一扫

Tomcat Request Cookie 丢失问题

一、问题描述

生产环境偶尔(涉及到多线程处理)出现"前端传递`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会创建新的对象。简单看个案例:


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】请求处理完毕后:在Tomcat的org.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】连接关闭时:在Tomcat的org.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,这里解释下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.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);

   }

}


举报

相关推荐

0 条评论