14

我发送一个$.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");

如果我为我得到的两个请求打印这些变量,
request1:

isSessionNew:true
cookieFromRequestHeader:JSESSIONID=FD0D502635EEB67E3D36203E26CBB59A
session.getId():9212B14094AB92D0F7F10EE21F593E52

请求2:

isSessionNew:true
cookieFromRequestHeader:JSESSIONID=FD0D502635EEB67E3D36203E26CBB59A
session.getId(): E8734E413FA3D3FEBD4E38A7BF27BA58

如您所见,服务器清楚地为 request2 创建了一个新会话request.getSession()。但它为什么这样做呢?它理论上应该是同步的,并为您提供与第一个请求(首先到达此代码)创建的相同会话。现在,为了确保会话创建是同步的,我执行了以下操作:

@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'),我会得到
request1':

isSessionNew:false
cookieFromRequestHeader:JSESSIONID=E8734E413FA3D3FEBD4E38A7BF27BA58 session.getId():E8734E413FA3D3FEBD4E38A7BF27BA58

请求2':

isSessionNew:false
cookieFromRequestHeader:JSESSIONID=E8734E413FA3D3FEBD4E38A7BF27BA58
session.getId():E8734E413FA3D3FEBD4E38A7BF27BA58

If you see closely now, the session id is the same (in request1' and request2') and is the last one created from the request2. Is there a way of me getting the same session from multiple subsequent requests that come to the server in very short time frames?

I am not using any special features - I am using Spring's out of the box session strategy. Also, it looks like the cookie JSESSIONID from the frist 2 requests (request1 and request2) come from the first time I visit the page (lets say there was a request0 sent to the server when it created this JSESSIONID). But it also looks like unless you explicitly call request.getSession(), the backend/server will always create a new JSESSIONID for every response and send it back to the client. So when a new request is sent from the client after a response comes, its going to have a new JSESSIONID. It looks like Spring out of the box session handling is not working appropriately.

Kind Regards,
despot

ADDITIONAL RESEARCH:

I wanted to see if I can register the session creation with a HttpSessionListner. This way I can see when the session with id FD0D502635EEB67E3D36203E26CBB59A (the cookie that is being sent in request1 and request2) is created. And also, weather using the listener (the SessionProcessor) I can store the sessions in a map by id and later on retrieve them by the id from the cookie (so I don't need to create another session).
So here is the code:

public interface ISessionProcessor extends ISessionRetriever, ISessionPopulator {
}

public interface ISessionRetriever {

    HttpSession getSession(String sessionId);
}

public interface ISessionPopulator {

    HttpSession setSession(String sessionId, HttpSession session);
}

The reason for separating these was because I only wanted to allow the listener to add sessions to the map, and the controllers only to be able to create a session through request.getSession() - so the listner's sessionCreated method was invoked always (as you'll see below).

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});
    }
}  

in 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>

in my-servlet-context.xml:

<bean class="mypackage.listener.SessionProcessor"/>
<bean class="mypackage.SomeController"/>

in my controller:

                    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()});
                            }
                    }

This gave me the same results. It appeared that the session creation by means other than request.getSession (when spring itself out of box created the session), was either not registered by the listener or the cookie/jsessionID came from somewhere else. Look the answer for more.

Other sources that helped me go through the HttpSession issues:
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
some spring reference stuff:
session management
session management in security
discussions on how to get session when you have a sessionId (what I did above):
coderanch discussion
stackoverflow
the post that helped me finalize my listener autowiring

4

3 回答 3

1

it looks like the cookie JSESSIONID from the frist 2 requests (request1 and request2) come from the first time I visit the page (lets say there was a request0 sent to the server when it created this JSESSIONID).

This was not true. I have 2 applications deployed under the same domain on the same server. So when I was calling http://mydomain.com/app1/initpage the server created a session for app1 with id FD0D502635EEB67E3D36203E26CBB59A and sent this JSESSIONID in a cookie to the client. The client saved the cookie under the mydomain.com and the second time when I executed http://mydomain.com/app2/executeService, the client browser sent the JSESSIONID from the cookie in the request header. I received it on the server but this was not a session in the other app2.

This explains the fact that when I send the other two requests (request1' and request2') they have a sessionID created on the appropriate application.

Have a look more here:
Deploying multiple web apps in same server
Under what conditions is a JSESSIONID created?

As for the concrete answer to my question, it appears that you need to make the 1st request synchronized so you are always sure that you have the same session id in the following requests. The following requests after the 1st one, can be asynchronous though.

于 2012-10-24T10:51:03.543 回答
0

Just store your cookie(with JESSIONID) in Client , when your send subsequent request to Server , put the stored cookie in your request header field and send, then you will get the same session in the server end.

CLIENT(IOS) store your cookie from response:

    NSHTTPURLResponse* httpURLReqponse = (NSHTTPURLResponse*) response;
    NSDictionary* allHeaders = [httpURLReqponse allHeaderFields];
    NSLog(@"Response Headers : %@ ", allHeaders);
    NSString* cookie = [allHeaders objectForKey: @"Set-Cookie"];
    DATA.cookies = cookie;      // Store the cookie

CLIENT(IOS) send your subsequent request with cookie:

// Put the stored cookie in your request header
[(NSMutableURLRequest*)request addValue: DATA.cookies forHTTPHeaderField:@"cookie"];
[NSURLConnection sendAsynchronousRequest: request queue:[NSOperationQueue mainQueue] completionHandler:nil];

Not only for IOS client . Then , in the server end , you will get the same session:

Server(JavaEE) GET HttpSession:

// Get the session, print its hashCode. You will find out that it's same as previous.
HttpSession session = ServletActionContext.getRequest().getSession();
于 2013-10-19T02:27:04.403 回答
0

I noticed that it happens when cookies are disabled in ...web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app error-url="">

    <context-root>/some-app</context-root>

    <class-loader delegate="true"/>

    <jsp-config>
        <property name="keepgenerated" value="true">
            <description>Keep a copy of the generated servlet class' java code.</description>
        </property>
    </jsp-config>

    <session-config>
        <session-properties>
            <property name="timeoutSeconds" value="600"/>
            <property name="enableCookies" value="false"/> 
        </session-properties>
    </session-config>

</glassfish-web-app>

It should be <property name="enableCookies" value="false"/> to retain session ID from the same connection.

于 2015-10-12T13:22:02.780 回答