SpringSession系列-请求与响应重写
我们知道,HttpServletRequset
和HttpServletResponse
是Servlet
标准所指定的Java
语言与Web
容器进行交互的接口。接口本身只规定java
语言对web
容器进行访问的行为方式,而具体的实现是由不同的web
容器在其内部实现的。
那么在运行期,当我们需要对HttpServletRequset
和HttpServletResponse
的默认实例进行扩展时,我们就可以继承HttpServletRequestWrapper
和HttpServletResponseWrapper
来实现。
在 SpringSession
中因为我们要实现不依赖容器本身的getSession
实现,因此需要扩展 HttpServletRequset
,通过重写getSession
来实现分布式session
的能力。下面就来看下SpringSession
中对于HttpServletRequset
的扩展。
1、请求重写
SpringSession
中对于请求重写,在能力上主要体现在存储方面,也就是getSession
方法上。在 SessionRepositoryFilter
这个类中,是通过内部类的方式实现了对HttpServletRequset
和HttpServletResponse
的扩展。
1.1 HttpServletRequset 扩展实现
1 | private final class SessionRepositoryRequestWrapper |
1.2 构造方法
1 | private SessionRepositoryRequestWrapper(HttpServletRequest request, |
构造方法里面将 HttpServletRequest
、HttpServletResponse
以及 ServletContext
实例传递进来,以便于后续扩展使用。
1.3 getSession 方法
1 |
|
上面这段代码有几个点,这里单独来解释下。
getCurrentSession
- 这是为了在同一个请求过程中不需要重复的去从存储中获取session,在一个新的进来时,将当前的 session 设置到当前请求中,在后续处理过程如果需要getSession就不需要再去存储介质中再拿一次。
getRequestedSession
- 这个是根据请求信息去取
session
,这里面就包括了sessionId
解析,从存储获取session
对象等过程。
- 这个是根据请求信息去取
- 是否创建新的
session
对象- 在当前请求中和存储中都没有获取到
session
信息的情况下,这里会根据create
参数来判断是否创建新的session
。这里一般用户首次登录时或者session
失效时会走到。
- 在当前请求中和存储中都没有获取到
1.4 getRequestedSession
根据请求信息来获取session
对象
1 | private S getRequestedSession() { |
这段代码还是很有意思的,这里获取sessionId
返回的是个列表。当然这里是SpringSession
的实现策略,因为支持session
,所以这里以列表的形式返回的。OK,继续来看如何解析sessionId
的:
[外链图片转存中…(img-RttDZ5t0-1650764287870)]
这里可以看到SpringSession
对于sessionId
获取的两种策略,一种是基于cookie
,一种是基于header
;分别来看下具体实现。
1.4.1 CookieHttpSessionIdResolver 获取 sessionId
CookieHttpSessionIdResolver
中获取sessionId
的核心代码如下:
[外链图片转存中…(img-jN2CqgSr-1650764287871)]
其实这里没啥好说的,就是读cookie
。从request
将cookie
信息拿出来,然后遍历找当前sessionId
对应的cookie
,这里的判断也很简单, 如果是以SESSION
开头,则表示是 SessionId
,毕竟cookie
是共享的,不只有sessionId
,还有可能存储其他内容。
另外这里面有个 jvmRoute,这个东西实际上很少能够用到,因为大多数情况下这个值都是null。这个我们在分析CookieSerializer
时再来解释。
1.4.2 HeaderHttpSessionIdResolver 获取 sessionId
[外链图片转存中…(img-GinHnGhX-1650764287872)]
这个获取更直接粗暴,就是根据 headerName
从 header
中取值。
回到getRequestedSession
,剩下的代码中核心的都是和sessionRepository
这个有关系,这部分就会涉及到存储部分。不在本篇的分析范围之内,会在存储实现部分来分析。
1.5 HttpSessionWrapper
[外链图片转存中…(img-NLT9vKvP-1650764287872)]
上面的代码中当我们拿到session
实例是通常会包装下,那么用到的就是这个HttpSessionWrapper
。
HttpSessionWrapper
继承了 HttpSessionAdapter
,这个HttpSessionAdapter
就是将SpringSession 转换成一个标准HttpSession
的适配类。HttpSessionAdapter
实现了标准servlet
规范的HttpSession
接口。
1.5.1 HttpSessionWrapper
HttpSessionWrapper
重写了 invalidate
方法。从代码来看,调用该方法产生的影响是:
requestedSessionInvalidated
置为true
,标识当前session
失效。- 将当前请求中的
session
设置为null
,那么在请求的后续调用中通过getCurrentSession
将拿不到session
信息。 - 当前缓存的 session 清楚,包括sessionId,session实例等。
- 删除存储介质中的session对象。
1.5.2 HttpSessionAdapter
SpringSession
和标准HttpSession
的配置器类。这个怎么理解呢,来看下一段代码:
1 |
|
对于基于容器本身实现的HttpSession
来说,getAttribute
的实现也是有容器本身决定。但是这里做了转换之后,getAttribute
将会通过SpringSession
中实现的方案来获取。其他的API
适配也是基于此实现。
SessionCommittingRequestDispatcher
实现了 RequestDispatcher
接口。关于RequestDispatcher
可以参考这篇文章【Servlet】关于RequestDispatcher的原理。SessionCommittingRequestDispatcher
对forward
的行为并没有改变。
对于include
则是在include
之前提交session
。为什么这么做呢?
因为include
方法使原先的Servlet
和转发到的Servlet
都可以输出响应信息,即原先的Servlet
还可以继续输出响应信息;即请求转发后,原先的Servlet
还可以继续输出响应信息,转发到的Servlet
对请求做出的响应将并入原先Servlet
的响应对象中。
所以这个在include
调用之前调用commit
,这样可以确保被包含的Servlet
程序不能改变响应消息的状态码和响应头。
2 响应重写
响应重写的目的是确保在请求提交时能够把session保存起来。来看下SessionRepositoryResponseWrapper
类的实现:
[外链图片转存中…(img-GQr4aCqq-1650764287873)]
这里面实现还就是重写onResponseCommitted
,也就是上面说的,在请求提交时能够通过这个回调函数将session
保存到存储容器中。
2.1 session 提交
最后来看下 commitSession
[外链图片转存中…(img-LfCJyYua-1650764287874)]
这个过程不会再去存储容器中拿session
信息,而是直接从当前请求中拿。如果拿不到,则在回写cookie
时会将当前session
对应的cookie
值设置为空,这样下次请求过来时携带的sessionCookie
就是空,这样就会重新触发登陆。
如果拿到,则清空当前请求中的session
信息,然后将session
保存到存储容器中,并且将sessionId
回写到cookie
中。
小结
本篇主要对SpringSession
中重写Request
和Response
进行了分析。通过重写Request
请求来将session
的存储与存储容器关联起来,通过重写Response
来处理session
提交,将session
保存到存储容器中。
后面我们会继续来分析SpringSession
的源码。最近也在学习链路跟踪相关的技术,也准备写一写,有兴趣的同学可以一起讨论。
SpringSession系列-请求与响应重写
http://www.glmapper.com/2018/12/10/spring/spring-session-rewrite-req-resp/