SpringSession 请求与响应重写的实现

我们知道, HttpServletRequsetHttpServletResponseServlet 标准所指定的 Java 语言与 Web 容器进行交互的接口。接口本身只规定 java 语言对 web 容器进行访问的行为方式,而具体的实现是由不同的 web 容器在其内部实现的。

那么在运行期,当我们需要对 HttpServletRequsetHttpServletResponse 的默认实例进行扩展时,我们就可以继承 HttpServletRequestWrapperHttpServletResponseWrapper 来实现。

SpringSession 中因为我们要实现不依赖容器本身的 getSession 实现,因此需要扩展 HttpServletRequset ,通过重写 getSession 来实现分布式 session 的能力。下面就来看下 SpringSession 中对于 HttpServletRequset 的扩展。

1、请求重写

SpringSession 中对于请求重写,在能力上主要体现在存储方面,也就是 getSession 方法上。在 SessionRepositoryFilter 这个类中,是通过内部类的方式实现了对 HttpServletRequsetHttpServletResponse 的扩展。

1.1 HttpServletRequset 扩展实现

private final class SessionRepositoryRequestWrapper
			extends HttpServletRequestWrapper {
	// HttpServletResponse 实例
	private final HttpServletResponse response;
	// ServletContext 实例
	private final ServletContext servletContext;
 // requestedSession session对象
 private S requestedSession;
 // 是否缓存 session
 private boolean requestedSessionCached;
	// sessionId
	private String requestedSessionId;
	// sessionId 是否有效
	private Boolean requestedSessionIdValid;
	// sessionId 是否失效
	private boolean requestedSessionInvalidated;

	// 省略方法
}

1.2 构造方法

private SessionRepositoryRequestWrapper(HttpServletRequest request,
		HttpServletResponse response, ServletContext servletContext) {
	super(request);
	this.response = response;
	this.servletContext = servletContext;
}

构造方法里面将 HttpServletRequestHttpServletResponse 以及 ServletContext 实例传递进来,以便于后续扩展使用。

1.3 getSession 方法

@Override
public HttpSessionWrapper getSession(boolean create) {
 // 从当前请求线程中获取 session
	HttpSessionWrapper currentSession = getCurrentSession();
	// 如果有直接返回
	if (currentSession != null) {
		return currentSession;
	}
	// 从请求中获取 session,这里面会涉及到从缓存中拿session的过程
	S requestedSession = getRequestedSession();
	if (requestedSession != null) {
	 // 无效的会话id(不支持的会话存储库)请求属性名称。
	 // 这里看下当前的sessionId是否有效
		if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
		 // 设置当前session的最后访问时间,用于延迟session的有效期
			requestedSession.setLastAccessedTime(Instant.now());
			// 将requestedSessionIdValid置为true
			this.requestedSessionIdValid = true;
			// 包装session
			currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
			// 不是新的session,如果是新的session则需要改变sessionId
			currentSession.setNew(false);
			// 将session设置到当前请求上下文
			setCurrentSession(currentSession);
			// 返回session
			return currentSession;
		}
	}
	else {
		// 这里处理的是无效的sessionId的情况,但是当前请求线程 session有效
		if (SESSION_LOGGER.isDebugEnabled()) {
			SESSION_LOGGER.debug(
					"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
		}
		// 将invalidSessionId置为true
		setAttribute(INVALID_SESSION_ID_ATTR, "true");
	}
	// 是否需要创建新的session
	if (!create) {
		return null;
	}
	if (SESSION_LOGGER.isDebugEnabled()) {
		SESSION_LOGGER.debug(
				"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
						+ SESSION_LOGGER_NAME,
				new RuntimeException(
						"For debugging purposes only (not an error)"));
	}
	// 创建新的session
	S session = SessionRepositoryFilter.this.sessionRepository.createSession();
	// 设置最后访问时间,也就是指定了当前session的有效期限
	session.setLastAccessedTime(Instant.now());
	// 包装下当前session
	currentSession = new HttpSessionWrapper(session, getServletContext());
	//设置到当前请求线程
	setCurrentSession(currentSession);
	return currentSession;
}

上面这段代码有几个点,这里单独来解释下。

getCurrentSession

这是为了在同一个请求过程中不需要重复的去从存储中获取session,在一个新的进来时,将当前的 session 设置到当前请求中,在后续处理过程如果需要getSession就不需要再去存储介质中再拿一次。

getRequestedSession

这个是根据请求信息去取 session ,这里面就包括了 sessionId 解析,从存储获取 session 对象等过程。

是否创建新的 session 对象

在当前请求中和存储中都没有获取到 session 信息的情况下,这里会根据 create 参数来判断是否创建新的 session 。这里一般用户首次登录时或者 session 失效时会走到。

1.4 getRequestedSession

根据请求信息来获取 session 对象

private S getRequestedSession() {
 // 缓存的请求session是否存在
	if (!this.requestedSessionCached) {
  // 获取 sessionId
  List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver
  		.resolveSessionIds(this);
  // 通过sessionId来从存储中获取session
  for (String sessionId : sessionIds) {
  	if (this.requestedSessionId == null) {
  		this.requestedSessionId = sessionId;
  	}
  	S session = SessionRepositoryFilter.this.sessionRepository
  			.findById(sessionId);
  	if (session != null) {
  		this.requestedSession = session;
  		this.requestedSessionId = sessionId;
  		break;
  	}
  }
  this.requestedSessionCached = true;
	}
	return this.requestedSession;
}

这段代码还是很有意思的,这里获取 sessionId 返回的是个列表。当然这里是 SpringSession 的实现策略,因为支持 session ,所以这里以列表的形式返回的。OK,继续来看如何解析 sessionId 的:

这里可以看到 SpringSession 对于 sessionId 获取的两种策略,一种是基于 cookie ,一种是基于 header ;分别来看下具体实现。

1.4.1 CookieHttpSessionIdResolver 获取 sessionId

CookieHttpSessionIdResolver 中获取 sessionId 的核心代码如下:

其实这里没啥好说的,就是读 cookie 。从 requestcookie 信息拿出来,然后遍历找当前 sessionId 对应的 cookie ,这里的判断也很简单, 如果是以 SESSION 开头,则表示是 SessionId ,毕竟 cookie 是共享的,不只有 sessionId,还有可能存储其他内容。

另外这里面有个 jvmRoute,这个东西实际上很少能够用到,因为大多数情况下这个值都是null。这个我们在分析 CookieSerializer 时再来解释。

1.4.2 HeaderHttpSessionIdResolver 获取 sessionId

这个获取更直接粗暴,就是根据 headerNameheader中取值。

回到 getRequestedSession ,剩下的代码中核心的都是和 sessionRepository 这个有关系,这部分就会涉及到存储部分。不在本篇的分析范围之内,会在存储实现部分来分析。

1.5 HttpSessionWrapper

上面的代码中当我们拿到 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 的配置器类。这个怎么理解呢,来看下一段代码:

@Override
public Object getAttribute(String name) {
	checkState();
	return this.session.getAttribute(name);
}

对于基于容器本身实现的 HttpSession 来说, getAttribute 的实现也是有容器本身决定。但是这里做了转换之后, getAttribute 将会通过 SpringSession 中实现的方案来获取。其他的 API 适配也是基于此实现。

SessionCommittingRequestDispatcher

实现了 RequestDispatcher 接口。关于 RequestDispatcher 可以参考这篇文章【Servlet】关于RequestDispatcher的原理 。 SessionCommittingRequestDispatcherforward 的行为并没有改变。 对于 include 则是在 include 之前提交 session 。为什么这么做呢?

因为 include 方法使原先的 Servlet 和转发到的 Servlet 都可以输出响应信息,即原先的 Servlet 还可以继续输出响应信息;即请求转发后,原先的 Servlet 还可以继续输出响应信息,转发到的 Servlet 对请求做出的响应将并入原先 Servlet 的响应对象中。

所以这个在 include 调用之前调用 commit ,这样可以确保被包含的 Servlet 程序不能改变响应消息的状态码和响应头。

2 响应重写

响应重写的目的是确保在请求提交时能够把session保存起来。来看下 SessionRepositoryResponseWrapper 类的实现:

这里面实现还就是重写 onResponseCommitted ,也就是上面说的,在请求提交时能够通过这个回调函数将 session

保存到存储容器中。

2.1 session 提交

最后来看下 commitSession

这个过程不会再去存储容器中拿 session 信息,而是直接从当前请求中拿。如果拿不到,则在回写 cookie 时会将当前 session 对应的 cookie 值设置为空,这样下次请求过来时携带的 sessionCookie 就是空,这样就会重新触发登陆。

如果拿到,则清空当前请求中的 session 信息,然后将 session 保存到存储容器中,并且将 sessionId 回写到 cookie 中。

小结

本篇主要对 SpringSession 中重写 RequestResponse 进行了分析。通过重写 Request 请求来将 session 的存储与存储容器关联起来,通过重写 Response 来处理 session 提交,将 session 保存到存储容器中。

后面我们会继续来分析 SpringSession 的源码。最近也在学习链路跟踪相关的技术,也准备写一写,有兴趣的同学可以一起讨论。 希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • SpringSession 请求与响应重写的实现

    我们知道, HttpServletRequset 和 HttpServletResponse 是 Servlet 标准所指定的 Java 语言与 Web 容器进行交互的接口.接口本身只规定 java 语言对 web 容器进行访问的行为方式,而具体的实现是由不同的 web 容器在其内部实现的. 那么在运行期,当我们需要对 HttpServletRequset 和 HttpServletResponse 的默认实例进行扩展时,我们就可以继承 HttpServletRequestWrapper 和 H

  • DRF之请求与响应的实现

    目录 1 请求和响应 1.1 请求 1.2 响应 2 视图 2.1 基于APIView写接口 2.2 基于GenericAPIView写的接口 2.3 基于GenericAPIView和5个视图扩展类写的接口 2.4 使用ModelViewSet编写5个接口 2.5 源码分析ViewSetMixin 2.6 继承ViewSetMixin的视图类 1 请求和响应 1.1 请求 # 请求对象 # from rest_framework.request import Request def __ini

  • 全局记录Feign的请求和响应日志方式

    目录 1.项目里定义FeignClient接口 2.单个FeignClient接口开启日志 3.所有FeignClient接口 开启日志 3.1.修改FeignConfiguration 3.2.还是修改 application.yml 配置 2.3.OK了,此时项目里 4.重写FeignClient输出日志 5.使用Aspect切面输出日志 项目里使用了Feign进行远程调用,有时为了问题排查,需要开启请求和响应日志 下面简介一下如何开启Feign日志: 注:本文基于 spring-boot-

  • django rest framework之请求与响应(详解)

    前言:在上一篇文章,已经实现了访问指定URL就返回了指定的数据,这也体现了RESTful API的一个理念,每一个URL代表着一个资源.当然我们还知道RESTful API的另一个特性就是,发送不同的请求动作,会返还不同的响应,这篇文章就讲一下django-rest-framework这个工具在这方面给我们带来的便捷操作. 一.Request对象 平时我们在写Django的视图函数的时候,都会带上一个request参数,这样就能处理平时搭建网站时,浏览器访问网页时发出的常规的HttpReques

  • 详解AngularJS用Interceptors来统一处理HTTP请求和响应

    Web 开发中,除了数据操作之外,最频繁的就是发起和处理各种 HTTP 请求了,加上 HTTP 请求又是异步的,如果在每个请求中来单独捕获各种常规错误,处理各类自定义错误,那将会有大量的功能类似的代码,或者使用丑陋的方法在每个请求中调用某几个自定义的函数来处理.这两种方法基本都不是靠谱之选.好在 AngularJS 提供了 Interceptors --拦截战斗机--来对应用内所有的 XHR 请求进行统一处理. 主要功能 Interceptors 有两个处理时机,分别是: 其它程序代码执行 HT

  • angular 用拦截器统一处理http请求和响应的方法

    想使用angularjs里的htpp向后台发送请求,现在有个用户唯一识别的token想要放到headers里面去,也就是{headres:{'token':1}} index.html里引入以下js: angular.module('app.factorys',[]) .factory('httpInterceptor',['$q','$injector','$localStorage',function ($q,$injector,$localStorage) { var httpInterc

  • javaweb如何实现请求和响应

    先来看一个流程图: 服务器处理请求的流程: (1)服务器每次收到请求时,都会为这个请求开辟一个新的线程.   (2)服务器会把客户端的请求数据封装到request对象中,request就是请求数据的载体!   (3)服务器还会创建response对象,这个对象与客户端连接在一起,它可以用来向客户端发送响应. 由流程图可以看出,在JavaWeb的请求与响应中,最重要的两个参数为request以及response,这两参数在Servlet的service( )方法中. 1.response概念: r

  • Java Web请求与响应实例详解

    Servlet最主要作用就是处理客户端请求并作出回应,为此,针对每次请求,Web容器在调用service()之前都会创建两个对象,分别是HttpServletRequest和HttpServletResponse.其中HttpServletRequest封装HTTP请求消息,HttpServletResponse封装HTTP响应消息.需要注意的是,Web服务器运行过程中,每个Servlet都会只创建一个实例对象,不过每次请求都会调用Servlet实例的service(ServletRequest

  • 详解HTTP请求与响应基础及实例

    详解HTTP请求与响应基础及实例 一.HTTP的请求与响应 二.HttpServletRequest和HttpServletResponse对象获取HTTP响应和请求 一.HTTP的请求与响应 HTTP协议(HyperText Transfer Protocol,超文本传输协议)是用于从WWW服务器传输超文本到本地浏览器的传输协议.是客户端和服务器端之间数据传输的格式规范. 通常,由HTTP客户端发起一个请求,服务端一旦收到请求,向客户端返回一个相应(一个请求的发出,有且只有一个响应). (一)

  • 从请求到响应过程中django都做了哪些处理

    前言 最近面试的时候,被面试官问道一个问题,就是 request.user 里面的 user 是怎样得到的,这个问题当时没有回答上来,可以说是非常的尴尬,所以赶快查了一些资料,看了一些源码,特地来总结一下这个问题. 要想回答为什么可以直接通过 request.user 得到请求的用户,应该先来看看请求被处理以及如何返回响应的流程.今天先总结一下 django 从请求到响应都进行了哪些过程. WSGI 当客户端发送一次请求后,最先处理请求的实际上是 web 服务器就是我们经常说的 nginx.Ap

随机推荐