Bug 390381 - Provide different HttpSesssions for servlets registered with different HTTPContexts
Summary: Provide different HttpSesssions for servlets registered with different HTTPCo...
Status: RESOLVED FIXED
Alias: None
Product: Equinox
Classification: Eclipse Project
Component: Server-Side (show other bugs)
Version: 4.0   Edit
Hardware: PC Linux
: P3 enhancement with 3 votes (vote)
Target Milestone: ---   Edit
Assignee: equinox.server-side-inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords: helpwanted
Depends on:
Blocks: 386504
  Show dependency tree
 
Reported: 2012-09-25 14:44 EDT by Ralf Sternberg CLA
Modified: 2018-01-22 23:13 EST (History)
9 users (show)

See Also:


Attachments
example project (6.89 KB, application/x-zip)
2012-09-26 03:13 EDT, Ralf Sternberg CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Ralf Sternberg CLA 2012-09-25 14:44:18 EDT
When registering servlets with the HttpService, all registered servlets share the same ServletContext, even when they are registered with different HttpContexts.

It would be nice if the Equinox implementation could return different ServletContexts for different HttpContexts, as Jetty does [1]. This would allow to isolate different web applications within one OSGi container.

Example:

  HttpContext httpContext1 = httpService.createDefaultHttpContext();
  httpService.registerServlet( "/foo", servlet1, null, httpContext1 );

  HttpContext httpContext2 = httpService.createDefaultHttpContext();
  httpService.registerServlet( "/bar", servlet2, null, httpContext2 );

Running this code in Equinox, servlet1.getServletContext() == servlet2.getServletContext(). Trying the same with Apache Karaf 2.2.9 using PAX Web, both servlet contexts are different, and consequently, both servlets also get different HttpSessions.

[1] http://felix.apache.org/site/apache-felix-http-service.html#ApacheFelixHTTPService-ServletContextNotes
Comment 1 Thomas Watson CLA 2012-09-25 16:12:08 EDT
Your message in the equinox forum provides a bit more information:

http://www.eclipse.org/forums/index.php/t/383351/

(In reply to comment #0)
> Running this code in Equinox, servlet1.getServletContext() ==
> servlet2.getServletContext(). Trying the same with Apache Karaf 2.2.9 using
> PAX Web, both servlet contexts are different, and consequently, both
> servlets also get different HttpSessions.
> 

You mention servlet1.getServletContext() == servlet2.getServletContext() here but in the forum message you mention the hashCode() values being equal.  Are they really the identical objects (using identity ==) or do they have the same hashCode and perhaps return true from the equals() method?

In our implementation of the ServletContext it does not appear that we overwrite the hashCode or equals methods and instead these methods would simply delegate up to the container's SerlvetContext implementation making it appear that the two ServletContext objects are identical when they really are not.
Comment 2 Ralf Sternberg CLA 2012-09-25 17:28:52 EDT
(In reply to comment #1)
> Your message in the equinox forum provides a bit more information:

I skipped these details because I thought there were distracting.

> You mention servlet1.getServletContext() == servlet2.getServletContext()
> here but in the forum message you mention the hashCode() values being equal.
> Are they really the identical objects (using identity ==) or do they have
> the same hashCode and perhaps return true from the equals() method?

In a first test I printed out the hashCode because the ServletContext's toString() output doesn't include a hash. But I also made sure that they are really identical by comparing the references (==).
Comment 3 Thomas Watson CLA 2012-09-25 18:04:59 EDT
(In reply to comment #2)
> In a first test I printed out the hashCode because the ServletContext's
> toString() output doesn't include a hash. But I also made sure that they are
> really identical by comparing the references (==).

In my tests I did not find the ServletContext objects to be idetical (==).  Is this in a servlet bridge environment or just a plain equinox framework with the jetty bundles installed?
Comment 4 Ralf Sternberg CLA 2012-09-26 03:13:28 EDT
Created attachment 221496 [details]
example project

(In reply to comment #3)
> (In reply to comment #2)
> > In a first test I printed out the hashCode because the ServletContext's
> > toString() output doesn't include a hash. But I also made sure that they are
> > really identical by comparing the references (==).
> 
> In my tests I did not find the ServletContext objects to be idetical (==). 
> Is this in a servlet bridge environment or just a plain equinox framework
> with the jetty bundles installed?

That's interesting. I use plain equinox with jetty:

id	State       Bundle
0	ACTIVE      org.eclipse.osgi_3.8.0.v20120529-1548
1	ACTIVE      org.eclipse.jetty.security_8.1.3.v20120522
2	ACTIVE      org.eclipse.jetty.continuation_8.1.3.v20120522
3	ACTIVE      org.apache.felix.gogo.shell_0.8.0.v201110170705
4	ACTIVE      org.eclipse.jetty.util_8.1.3.v20120522
5	ACTIVE      org.eclipse.equinox.http.jetty_3.0.0.v20120522-1841
6	ACTIVE      org.apache.felix.gogo.runtime_0.8.0.v201108120515
7	ACTIVE      org.eclipse.jetty.servlet_8.1.3.v20120522
8	ACTIVE      org.eclipse.jetty.io_8.1.3.v20120522
9	ACTIVE      org.eclipse.jetty.server_8.1.3.v20120522
10	ACTIVE      org.apache.felix.gogo.command_0.8.0.v201108120515
11	ACTIVE      test.servletcontext_1.0.0.qualifier
12	ACTIVE      org.eclipse.equinox.console_1.0.0.v20120522-1841
13	ACTIVE      org.eclipse.jetty.http_8.1.3.v20120522
14	ACTIVE      org.eclipse.equinox.http.servlet_1.1.300.v20120522-1841
15	ACTIVE      javax.servlet_3.0.0.v201112011016
16	ACTIVE      org.eclipse.osgi.services_3.3.100.v20120522-1822

I've also attached my example project.
Comment 5 Thomas Watson CLA 2012-09-26 09:57:38 EDT
(In reply to comment #4)
> That's interesting. I use plain equinox with jetty:
> 

Thanks Ralf.  I agree the hashcode values of the two ServletContext objects are equal.  That is a bug we should fix (also the equals method is needs to be fixed since it currently returns false even for the same ServletContext object!  But the two ServletContext are not ==

Add the following lines to your test:


  System.out.println( "ServletContexts ==: "
     + ( servlet1.getServletContext() == servlet2.getServletContext() ) );
  System.out.println( "ServletContext1 equals ServletContext1: "
     + ( servlet1.getServletContext().equals(servlet1.getServletContext() )));
  System.out.println( "ServletContext1 equals ServletContext2: "
     + ( servlet1.getServletContext().equals(servlet1.getServletContext() )));

So assuming we fix the hashcode and equals methods would that solve your issue?  It is unclear to me if it would.
Comment 6 Thomas Watson CLA 2012-09-26 10:00:04 EDT
(In reply to comment #5)
> Add the following lines to your test:
> 
> 
>   System.out.println( "ServletContexts ==: "
>      + ( servlet1.getServletContext() == servlet2.getServletContext() ) );
>   System.out.println( "ServletContext1 equals ServletContext1: "
>      + ( servlet1.getServletContext().equals(servlet1.getServletContext()
> )));
>   System.out.println( "ServletContext1 equals ServletContext2: "
>      + ( servlet1.getServletContext().equals(servlet1.getServletContext()
                                                      ^
I had a typo here. that should really be serlvet2 NOT servlet1.

> )));
> 


At any rate I get the following output with this test:

HttpContext: false
ServletContexts hashcode equals: true
ServletContexts ==: false
ServletContext1 equals ServletContext1: false
ServletContext1 equals ServletContext2: false
Comment 7 Gunnar Wagenknecht CLA 2012-09-26 10:31:15 EDT
(In reply to comment #5)
> So assuming we fix the hashcode and equals methods would that solve your
> issue?  It is unclear to me if it would.

Although they will be two different ServletContext objects they still wrap the same actual Jetty ServletContext. Thus, they also share the same HttpSession. I think the request is more about having different contexts underneath so that you also get different HttpSession objects (isolation of the HttpSession).
Comment 8 Ralf Sternberg CLA 2012-09-26 10:47:46 EDT
Now I see they aren't identical. I forgot to remove the .hashCode() in my example and thought I was comparing the real objects. Sorry for that.

Now I also checked that both servlet contexts maintain separate attribute stores, which is good.

> So assuming we fix the hashcode and equals methods would that solve your
> issue?  It is unclear to me if it would.

I don't think it would, because we obtain the servlet context from the http session, using request.getSession().getServletContext(). Since both servlets share the same HttpSession (don't they?), I'm afraid it's undefined which servlet context we get?
Comment 9 Thomas Watson CLA 2012-09-26 11:05:59 EDT
I think this is more about enhancing our HttpSessionAdaptor, but I must admit I don't know the implications of changing that to be HttpContext specific.

http://git.eclipse.org/c/equinox/rt.equinox.bundles.git/tree/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/HttpSessionAdaptor.java?h=master

I'm going to need help to make progress here.
Comment 10 Ralf Sternberg CLA 2012-09-26 11:24:42 EDT
I was wrong again. The HttpSession is also wrapped, the requests will return different instances of HttpSession, but with the same session ID and the same attribute store, because they are backed by the same HttpSession.

Now I'm getting to the root of our problem. We basically store a reference in the HttpSession to something that is stored in the ServletContext. In a the other context, this reference in the session is not valid anymore.

It seems that the following assertion of the HttpSession contract is not valid in Equinox:

  Session information is scoped only to the current web application
  (ServletContext), so information stored in one context will not
  be directly visible in another.
Comment 11 Thomas Watson CLA 2012-09-26 11:31:50 EDT
(In reply to comment #10)
> It seems that the following assertion of the HttpSession contract is not
> valid in Equinox:
> 
>   Session information is scoped only to the current web application
>   (ServletContext), so information stored in one context will not
>   be directly visible in another.

Right, so I am wondering if it is valid to somehow store that information in a way that is HttpContext specific in our HttpSessionAdaptor.
Comment 12 Ralf Sternberg CLA 2012-09-26 12:26:41 EDT
(In reply to comment #11)
> Right, so I am wondering if it is valid to somehow store that information in
> a way that is HttpContext specific in our HttpSessionAdaptor.

That would fix our problem, and sounds like a good idea to me, but I'm also not sure about the implications. There may be code that initializes a session when isNew() returns true and relies on the existence of certain attributes when isNew() is false - but I'm sure if this is really an issue...
Comment 13 Gunnar Wagenknecht CLA 2012-09-26 13:04:52 EDT
(In reply to comment #12)
> That would fix our problem, and sounds like a good idea to me, but I'm also
> not sure about the implications. 

Oh, there may also be code that relies on the current behavior, i.e. two servlets registered with different HttpContext each (for security reasons) but which share common data in the session (eg. user authentication info).
Comment 14 Thomas Watson CLA 2012-09-26 16:08:18 EDT
(In reply to comment #13)
> (In reply to comment #12)
> > That would fix our problem, and sounds like a good idea to me, but I'm also
> > not sure about the implications. 
> 
> Oh, there may also be code that relies on the current behavior, i.e. two
> servlets registered with different HttpContext each (for security reasons)
> but which share common data in the session (eg. user authentication info).

True, but I'm not sure we should keep the default behavior, it seems more like a bug to me than the intended design.  If we decide to add this enhancement perhaps we should have a flag to disable it, but I would be for enabling it by default.
Comment 15 Thomas Watson CLA 2012-09-26 16:09:40 EDT
Changing title to reflect the current discussion.
Comment 16 Gunnar Wagenknecht CLA 2012-09-26 16:19:54 EDT
Great! I'm willing to craft a patch over the next few weeks. Just have to align it with the upcoming conference travel.
Comment 17 Gunnar Wagenknecht CLA 2012-09-26 16:19:54 EDT
Great! I'm willing to craft a patch over the next few weeks. Just have to align it with the upcoming conference travel.
Comment 18 Simon Kaegi CLA 2012-09-26 18:02:35 EDT
Bizarre as it might sounds I think we're going to have to do the logic in the HttpSessionAdaptor instead of asking Jetty for multiple new ServletContexts in order to have consistent behavior with the ServletBridge. But apart from that ... go for it as this indeed does sound correct to me. Please add/flag me as a reviewer when the time comes.
Comment 19 Gunnar Wagenknecht CLA 2012-09-27 01:40:06 EDT
Is Gerrit available for Equinox? Or do you want a classic patch?
Comment 20 Gunnar Wagenknecht CLA 2012-09-27 02:22:21 EDT
So it seems the easiest way is to simply "namespace" the session attributes, i.e. the HttpSessionAdaptor will prefex each attributes using the same method that ProxyContext uses ("hc_" + httpContext.hashCode()). This avoids tracking sessions ourselves and leaves session handling (expiration, etc.) and fail-over logic to the container. 

However, we still have:
getCreationTime
invalidate
isNew
setMaxInactiveInterval

I'm not much concerned about getCreationTime() and isNew(). I'm more concerned about invalidate() and setMaxInactiveInterval(). Those are shared between contexts and a servlet invalidating a session will also invalidate it for all others sharing the same session.

Note, Servlet/Filter registrations without a HttpContext do get a new default context on each registration. Thus, they will never share any session data after the change. Is this ok or should registrations with a 'null' context of the same bundle share the same context? The contract states: "null if a default HttpContext is to be created and used". It can be read both ways, i.e. to be created but not necessarily for every call.
Comment 21 Gunnar Wagenknecht CLA 2012-09-27 04:09:34 EDT
I started with a test case:
https://github.com/eclipseguru/rt.equinox.bundles/commits/gunnar/bug390381
Comment 22 Gunnar Wagenknecht CLA 2012-09-27 04:52:05 EDT
Branch updated with commit to scope session attributes to HttpContext. Simon, you want a pull request on GitHub? :)
Comment 23 Gunnar Wagenknecht CLA 2012-09-27 04:58:31 EDT
One minor questions: should I use "httpContext.hashCode()" for scoping or "System.identidyHashCode(httpContext)"?
Comment 24 Holger Staudacher CLA 2012-09-27 08:52:23 EDT
Can this fix also go into Juno SR2?
Comment 25 Thomas Watson CLA 2012-09-27 10:08:20 EDT
(In reply to comment #20)
> So it seems the easiest way is to simply "namespace" the session attributes,
> i.e. the HttpSessionAdaptor will prefex each attributes using the same
> method that ProxyContext uses ("hc_" + httpContext.hashCode()). 

ProxyContext uses a Map<HttpContext, Dictionary> where the Dictionary provides the attributes of a ServletContext for a specific HttpContext.  Is there a reason you did not use that approach here?  Is there an advantage to doing this prefix mapping and storing all the attributes for all the HttpContext in the same Map

> This avoids
> tracking sessions ourselves and leaves session handling (expiration, etc.)
> and fail-over logic to the container. 
> 
> However, we still have:
> getCreationTime
> invalidate
> isNew
> setMaxInactiveInterval
> 
> I'm not much concerned about getCreationTime() and isNew(). I'm more
> concerned about invalidate() and setMaxInactiveInterval(). Those are shared
> between contexts and a servlet invalidating a session will also invalidate
> it for all others sharing the same session.

I don't have a good feeling on this one.  As a first pass could we ignore this issue?  Otherwise it seems we would have to start tracking each session validity ourselves, but I am not sure how we could go about invalidating just a single session and keeping the others valid since we all know we are sharing the same actual session impl from the container.  I'm definitely not an expert here.

> 
> Note, Servlet/Filter registrations without a HttpContext do get a new
> default context on each registration. Thus, they will never share any
> session data after the change. Is this ok or should registrations with a
> 'null' context of the same bundle share the same context? The contract
> states: "null if a default HttpContext is to be created and used". It can be
> read both ways, i.e. to be created but not necessarily for every call.

Could we change our DefaultHttpContext impl to overwrite equals and hashCode such that two DefaultHttpContext for the same bundle are equal?

(In reply to comment #21)
> I started with a test case:
> https://github.com/eclipseguru/rt.equinox.bundles/commits/gunnar/bug390381

Thanks Gunnar!


(In reply to comment #23)
> One minor questions: should I use "httpContext.hashCode()" for scoping or
> "System.identidyHashCode(httpContext)"?

I think I would prefer to use the Map<HttpContext,Hashtable<String,Object>> approach instead to track the session attributes.  Suggesting Hashtable to avoid Enumeration implementation, but you could easily use a Map and use:

Collections.enumeration(map.keySet())

OK, I just wrote all this up questioning why you were using the prefix approach, and I think I know why.  Is it because you need to actually store the values in the container's implementation of session so that they are "persisted" across servlet requests?  If so I am sorry for the noise, but I thought it better to record my thought process while reviewing your work.

Question: are HttpSessions pre-populated with attributes from the container when they are created?  If so do we need to let these pre-populated values bleed through to the HttpContext specific sessions?
Comment 26 Thomas Watson CLA 2012-09-27 10:22:51 EDT
(In reply to comment #24)
> Can this fix also go into Juno SR2?

I think we should wait until we have the proper solution in hand so that we can properly assess the risk.  I am open to the idea though.
Comment 27 Gunnar Wagenknecht CLA 2012-09-27 10:43:17 EDT
(In reply to comment #25)
> OK, I just wrote all this up questioning why you were using the prefix
> approach, and I think I know why.  Is it because you need to actually store
> the values in the container's implementation of session so that they are
> "persisted" across servlet requests?  If so I am sorry for the noise, but I
> thought it better to record my thought process while reviewing your work.

Yes. The relation HttpContext (or ProxyContext) to HttpSession is N:M. There will be many sessions for each context. The container (eg., Jetty) assigns and maintains session for each connected client (eg., browser). Sessions are bound to requests using cookies or request parameters.

Remember the memory leak in the help center? This was because sessions were never expired by Jetty (because of some default setting) and they grew to millions.
 
> Question: are HttpSessions pre-populated with attributes from the container
> when they are created?  If so do we need to let these pre-populated values
> bleed through to the HttpContext specific sessions?

A container may be configured to persist sessions into a database (or some other store) or it may just replicate the sessions to other nodes in a cluster. Thus, session data will be serialized and may be restored at a later point in time and/or on a different container node in the cluster. Using the container session as backing store for session attributes allows the HttpService to participate in the logic and capabilities offered by containers. 

Bahhh, wait a second, I just figured that session replication won't work reliable. Hash codes will be different across JVMs. D'oh! 

Just thinking out load ... I could define an extension to the HttpContext interface which allows to define a custom prefix.
Comment 28 Gunnar Wagenknecht CLA 2012-09-27 10:46:00 EDT
(In reply to comment #27)
> Just thinking out load ... I could define an extension to the HttpContext
> interface which allows to define a custom prefix.

s/load/loud
Comment 29 Simon Kaegi CLA 2012-09-27 11:33:48 EDT
re: Github pull request -- that's great for me for reviewing as I can do it in Orion. Not sure that's so great for the actual commit though.

re: session serialization *sigh* that does indeed sound tricky and I suspect context attributes also need some love.

I seem to recall String hashcodes are safe across VMs because they're spec'ed so if we can get to a string as a stable representation...
Comment 30 Gunnar Wagenknecht CLA 2012-09-28 04:41:59 EDT
Simon, you need to fork it so that I can send you a pull request. I also pushed a proposal for customizable session namespaces.

The workflow is afaik: You pull my commits, and review them and you commit them into the Equinox repo with you committer id and push that.
Comment 31 Thomas Watson CLA 2012-09-28 08:37:30 EDT
(In reply to comment #30)
> Simon, you need to fork it so that I can send you a pull request. I also
> pushed a proposal for customizable session namespaces.

The idea seems reasonable to me:

- Should HttpContextWithSessionNamespace be a marker interface instead of extending HttpContext?  Perhaps this concept may apply to other areas of the container?

- Not sure I like the term Namespace, only because we have the ill-named NamespaceException in HttpService API (I think that exception should have been called AliasException).  Perhaps HttpContextIdentity with a method getIdentity()

- Should we consider having DefaultHttpContext implement HttpContextIdentity and have the identity be some string that includes the Bundle ID for the HttpContext?

> 
> The workflow is afaik: You pull my commits, and review them and you commit
> them into the Equinox repo with you committer id and push that.

See the following for the complete flow:

http://wiki.eclipse.org/Development_Resources/Handling_Git_Contributions#Git
Comment 32 Gunnar Wagenknecht CLA 2012-09-28 12:47:01 EDT
(In reply to comment #31)
> - Should HttpContextWithSessionNamespace be a marker interface instead of
> extending HttpContext?  Perhaps this concept may apply to other areas of the
> container?

I've no strong opinion  But it is coupled to the HttpContext.

> - Not sure I like the term Namespace, only because we have the ill-named
> NamespaceException in HttpService API (I think that exception should have
> been called AliasException).  Perhaps HttpContextIdentity with a method
> getIdentity()

Technically it's a prefix. getSessionAttributePrefix would be better than getId. I just feel the term id is not precise enough and also overloaded.
 
> - Should we consider having DefaultHttpContext implement HttpContextIdentity
> and have the identity be some string that includes the Bundle ID for the
> HttpContext?

Yepp, likely. I think another candidate is the registry bundle. I have to check, but I believe it already defines ids for registered contexts.
Comment 33 Cristiano Gaviao CLA 2013-01-12 23:30:16 EST
Hi, could someone inform me what is the status of this improvement?
Comment 34 Gunnar Wagenknecht CLA 2013-03-08 02:20:49 EST
(In reply to comment #33)
> Hi, could someone inform me what is the status of this improvement?

I'm currently waiting on an an update to the HttpService spec. I talked to the spec lead at ECE and it seems that the Felix behavior is not intended by the spec.

http://www.mail-archive.com/osgi-dev@mail.osgi.org/msg02500.html
Comment 35 Ralf Sternberg CLA 2013-03-15 10:05:24 EDT
Thanks for the link, Gunnar. Felix Meschberger is going to present the new spec at EclipseCon 2013 [1]. That sounds like a good opportunity to meet and discuss this issue.

[1] http://www.eclipsecon.org/2013/sessions/whats-new-http-service-specification
Comment 36 Raymond Auge CLA 2015-03-23 09:33:44 EDT
Considerable work has taken place on equinox's http.servlet to implement RFC 189 and the Http Whiteboard draft spec.

I believe the issues described here should be cleared up.

Could someone be willing to test it?

Futher, a test case would be appreciated.