【发布时间】:2012-10-12 12:11:21
【问题描述】:
我正在发送一个$.getJSON (HTTP GET) 请求两次(使用不同的数据),一个接一个(假设我们有 request1 和 request2)。我可以在 FF 和 Chrome 的开发人员工具中看到我有相同的 cookie:JSESSIONID=FD0D502635EEB67E3D36203E26CBB59A 标头字段。
在服务器端我尝试获取会话:
HttpSession session = request.getSession();
boolean isSessionNew = session.isNew();
String sessionId = session.getId();
String cookieFromRequestHeader = request.getHeader("cookie");
如果我为我得到的两个请求打印这些变量,
请求1:
isSessionNew:true
cookieFromRequestHeader:JSESSIONID=FD0D502635EEB67E3D36203E26CBB59A
session.getId():9212B14094AB92D0F7F10EE21F593E52
请求2:
isSessionNew:true
cookieFromRequestHeader:JSESSIONID=FD0D502635EEB67E3D36203E26CBB59A
session.getId(): E8734E413FA3D3FEBD4E38A7BF27BA58
如您所见,服务器显然在request.getSession() 上为request2 创建了一个新会话。但它为什么这样做呢?从理论上讲,它应该是同步的,并为您提供与第一个请求(首先到达此代码)创建的相同会话。 现在,为了确保会话创建是同步的,我执行了以下操作:
@Autowired
private ServletContext servletContext;
...
synchronized (servletContext) {
HttpSession session = request.getSession();
boolean isSessionNew = session.isNew();
String sessionId = session.getId();
String cookieFromRequestHeader = request.getHeader("cookie");
}
我得到了相同的结果。
如果我稍后再次发送相同的请求(比如说 request1' 和 request2'),我会得到,
请求1':
isSessionNew:false
cookieFromRequestHeader:JSESSIONID=E8734E413FA3D3FEBD4E38A7BF27BA58 session.getId():E8734E413FA3D3FEBD4E38A7BF27BA58
请求2':
isSessionNew:false
cookieFromRequestHeader:JSESSIONID=E8734E413FA3D3FEBD4E38A7BF27BA58
session.getId():E8734E413FA3D3FEBD4E38A7BF27BA58
如果您现在仔细观察,会话 ID 是相同的(在 request1' 和 request2' 中)并且是从 request2 创建的最后一个。有没有办法让我在很短的时间内从多个后续请求中获得相同的会话?
我没有使用任何特殊功能——我使用的是 Spring 开箱即用的会话策略。此外,前 2 个请求(request1 和 request2)中的 cookie JSESSIONID 似乎来自我第一次访问该页面时(假设在创建此 JSESSIONID 时向服务器发送了一个 request0)。但看起来除非你显式调用 request.getSession(),否则后端/服务器总是会为每个响应创建一个新的 JSESSIONID 并将其发送回客户端。因此,当响应到来后从客户端发送新请求时,它将有一个新的 JSESSIONID。开箱即用的 Spring 会话处理似乎无法正常工作。
亲切的问候,
暴君
附加研究:
我想看看是否可以使用 HttpSessionListner 注册会话创建。这样我可以看到 ID 为 FD0D502635EEB67E3D36203E26CBB59A 的会话(在 request1 和 request2 中发送的 cookie)何时创建。而且,使用监听器(SessionProcessor)的天气,我可以通过 id 将会话存储在地图中,然后通过 cookie 中的 id 检索它们(因此我不需要创建另一个会话)。
所以这里是代码:
public interface ISessionProcessor extends ISessionRetriever, ISessionPopulator {
}
public interface ISessionRetriever {
HttpSession getSession(String sessionId);
}
public interface ISessionPopulator {
HttpSession setSession(String sessionId, HttpSession session);
}
分离这些的原因是因为我只想允许侦听器向地图添加会话,而控制器只能通过 request.getSession() 创建会话 - 所以总是调用侦听器的 sessionCreated 方法(如下所示)。
public class SessionProcessor implements ISessionProcessor {
private Map<String, HttpSession> sessions = new HashMap<String, HttpSession>();
@Override
public HttpSession getSession(String sessionId) {
return sessions.get(sessionId);
}
@Override
public HttpSession setSession(String sessionId, HttpSession session) {
return sessions.put(sessionId, session);
}
}
public class SessionRetrieverHttpSessionListener implements HttpSessionListener {
private static final Logger LOGGER = LoggerFactory.getLogger(SessionRetrieverHttpSessionListener.class);
@Autowired
private ISessionPopulator sessionPopulator;
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSession session = se.getSession();
LOGGER.debug("Session with id {} created. MaxInactiveInterval: {} session:{}", new Object[]{session.getId(), session.getMaxInactiveInterval(), session});
sessionPopulator.setSession(session.getId(), session);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
// session has been invalidated and all session data (except Id) is no longer available
LOGGER.debug("Session with id {} destroyed. MaxInactiveInterval: {}, LastAccessedTime: {}, session:{}",
new Object[]{session.getId(), session.getMaxInactiveInterval(), session.getLastAccessedTime(), session});
}
}
在 web.xml 中: org.springframework.web.context.ContextLoaderListener
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/my-servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<listener>
<listener-class>mypackage.listener.SessionRetrieverHttpSessionListener</listener-class>
</listener>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
在 my-servlet-context.xml 中:
<bean class="mypackage.listener.SessionProcessor"/>
<bean class="mypackage.SomeController"/>
在我的控制器中:
synchronized (servletContext) {
String cookieFromRequestHeader = request.getHeader("cookie");
LOG.debug("cookieFromRequestHeader:{}", new Object[] {cookieFromRequestHeader});
String jsessionIdFromCookieFromRequestHeader = cookieFromRequestHeader.substring(cookieFromRequestHeader.indexOf("=") + 1);
LOG.debug("jsessionIdFromCookieFromRequestHeader:{}", new Object[] {jsessionIdFromCookieFromRequestHeader});
session = sessionRetriever.getSession(jsessionIdFromCookieFromRequestHeader);
LOG.debug("session:{}", new Object[] {session});
if (session == null) {
LOG.debug("request.isRequestedSessionIdFromCookie():{}, request.isRequestedSessionIdFromURL():{}, WebUtils.getSessionId(request):{}.", new Object[] {request.isRequestedSessionIdFromCookie(), request.isRequestedSessionIdFromURL(), WebUtils.getSessionId(request)});
session = request.getSession();
boolean isSessionNew = session.isNew();
LOG.debug("Is session new? - {}. The session should not be new after the first fingerprint part is received - check if this occured in the logs - if that happend than there is an error!", isSessionNew);
LOG.debug("request.isRequestedSessionIdFromCookie():{}, request.isRequestedSessionIdFromURL():{}, WebUtils.getSessionId(request):{}.", new Object[] {request.isRequestedSessionIdFromCookie(), request.isRequestedSessionIdFromURL(), WebUtils.getSessionId(request)});
//read https://stackoverflow.com/a/2066883 and think about using ServletContextAware also.
LOG.debug("cookieFromRequestHeader:{} session.getId(): {}", new Object[]{cookieFromRequestHeader, session.getId()});
}
}
这给了我同样的结果。似乎通过 request.getSession 以外的方式创建会话(当 spring 本身创建会话时),要么没有被侦听器注册,要么 cookie/jsessionID 来自其他地方。寻找更多答案。
帮助我解决 HttpSession 问题的其他来源:
servlet context injection in controller
overview of concurrency when you have to work with HttpSession
using HttpSession object to do synchronization (avoid this)
the "best" way to do synchronization when working with HttpSession
一些弹簧参考资料:
session management
session management in security
讨论当你有 sessionId 时如何获取会话(我在上面做了什么):
coderanch discussion
stackoverflow
the post that helped me finalize my listener autowiring
【问题讨论】:
标签: java jquery spring http-get httpsession