I come a long way asking this question here and am slightly out of options so I thought I would refer instead to this site which seems to always have an answer to most of my problems. Sorry for the long post, but some context is important.
We have a java web application (.war) which can be hosted on several application servers (Tomcat for in-house development, Weblogic 10.3.5 for some customers, Websphere for other customers and Glassfish for others as well). For what it's worth, it's on jdk 1.6
We use an old version of struts (1.0.2) for action mapping, have a couple of filters defined in our web.xml file, we also have additional configuration files for some of the application servers (like a weblogic.xml file for weblogic with few elements in it). The view layer is all jsp with some js.
We have a main entry point / class to process http requests which extends the Struts ActionServlet class.
Here is my issue when deploying and testing the application on weblogic (WLS) server 10.3.5, the issue I am about to describe has never been encountered on any of the other application servers.
Users will try a simple initial logging action by invoking an initial struts action (like "/login").
<action path="/login"
type="com.<...>.LoginAction"
name="loginForm"
scope="session"
validate="false">
<forward name="success" path="/completeLogin" internal="true"/>
[...]
</action>
Filters are applied, an httpsession is initiated (a check on request.getSession(false) is done and then request.getSession() is called if the HttpSession appears to be null, which is expected in the first filters).
Following the successful result to this first action, we then forward internally the request as shown above to the next Struts action. This action also completes successfully and forwards the request to the next struts action.
Before moving on, it is important to note that both these actions set important attributes in the HttpSession object which are later used in our application business logic.
The next action maps to a .jsp page (which translates to non-internal ActionForward) :
<action path="/completeLogin"
type="com.<...>.CompleteLoginAction"
name="loginForm"
scope="session">
<forward name="success" path="home.jsp"/>
[...]
</action>
Right before both the /login and /completeLogin actions are completed, I print the HttpSession id to make sure the same session id is reused from one call to another.
The issue is when my ActionServlet dispatches the 3rd request via the RequestDispatcher#forward(ServletRequest,ServletResponse) method, the third action fails because we're expected to retrieve some attributes from the HttpSession (which were previously set successfully) but are not there because, surprise, a new HttpSession was generated and passed to that third action instead of the original request's HttpSession. (I print the id and see that it differs from the previous 2 printed ids), because I fail to access those attributes, the application then throws an Exception and the user cannot use the application because he/she simply cannot login.
Now, some things I have tried before coming here:
- We have a class implementing the HttpSessionListener interface, which notifies us when an HttpSession is created or destroyed. In all cases I have tested, I always see a notification for the original and second http session creation but i never get a notification for session destruction. I am assuming that weblogic has got to have the original session somewhere and never destroyed it but instead created a new one.
- I have nonetheless made sure to check for calls to HttpSession#invalidate() method and do not see any invoked in our Filters, ActionServlet or Action classes per se in the flow listed above.
- The RequestDispatcher class is container specific, i.e., a vendor implementation is actually invoked at runtime when retrieving the dispatcher instance. I assumed something may be wrong in Oracle's dispatcher since none of the other vendors have the issue (and the same code is being executed), so I opened a service request with Oracle and am still communicating with some of their engineers to find a solution but I am having a really difficult time proving to them my problem despite the countless log files I have sent them explaining the issue.
- Reading some of Oracle's documentation, I realized that like most servlets, HttpSession objects are intimately tied to browser Cookies. In our configuration, we do not use any form of session persistence, we also do not create any additional cookies, but merely store http session attributes. Our customer has also confirmed that cookies were enabled on their browsers. I have also been able to reproduce the issue on 2 browsers internally. I then started investigating cookies and it seems to be my main lead for now. I checked to see If any cookies were present after the initial filters. To my surprise, there were, I had expected no cookies because it was my understanding that the default cookie created for the session is set to expire after the session ends (browser closing / leaving the application).
So I went ahead and tried the following and it seemed to have worked for a while but now the user came back to us stating that the users are still logged out from time to time. To make matters worse, this issue is not consistent, it sometimes happen, sometimes doesn't. The dirty patch I wrote was at the initial LoginAction. I scan for cookies in the request object and loop to see If I find any cookies with name "JSESSIONID" and check their value. If their value doesn't match that of the session ID of the current http session, I then update the cookie. It worked for a while but the issue seems to be back now.
Code sample of the patch:
public class LoginAction extends GenericAction {
private static final Logger log = LoggerFactory.getLogger(LoginAction.class);
private static final String JSESSIONID_COOKIE_NAME = "JSESSIONID";
@Override
public ActionForward internalPerform(ActionMapping mapping, BaseForm form,
HttpServletRequest request, HttpServletResponse response) throws InvalidSessionException {
String clientOwner = null;
String registeredHost = null;
/*
* Temporary patch for Weblogic JAS. Will be removed eventually.
*/
verifyJSessionIdCookies(request, response);
try {
[...]
}
Patch method:
protected void verifyJSessionIdCookies(HttpServletRequest request, HttpServletResponse response) {
Cookie[] existingCookies = request.getCookies();
HttpSession session = request.getSession(false);
if (null != existingCookies && null != session) {
for (Cookie cookie : existingCookies) {
if (cookie.getName().equals(JSESSIONID_COOKIE_NAME) && !cookie.getValue().equals(session.getId())) {
log.debug("Updating current client JSESSIONID cookie from {} to value {}", cookie.getValue(),
session.getId());
cookie.setValue(session.getId());
}
}
}
}
}
Our weblogic.xml file looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<weblogic-web-app>
<container-descriptor>
<prefer-web-inf-classes>false</prefer-web-inf-classes>
<prefer-application-packages>
<package-name>org.apache.commons.lang.*</package-name>
</prefer-application-packages>
</container-descriptor>
I have also enabled HttpDebug logs on weblogic admin console itself and often see this message when the request forward is about to fail:
(Initial session creation)
<BEA-000000> <HttpRequest@17756711 - /ourwebapp/login.do: SessionID not found for WASC=ServletContext@2256126[app:ourwebapp module:ourwebapp path:/chapel spec-version:2.5]>
<BEA-000000> <HttpRequest@17756711 - /ourwebapp/login.do: Creating new session>
[...]
(failure)
<BEA-000000> <HttpRequest@11453786 - /ourwebapp/getHomePage.do: [RemoteSessionFetching] obtained workManager: null>
<BEA-000000> <HttpRequest@11453786 - /ourwebapp/getHomePage.do: Servername: localhost>
<BEA-000000> <HttpRequest@11453786 - /ourwebapp/getHomePage.do: Serverport: 7001>
[..]
<BEA-000000> <HttpRequest@11453786 - /ourwebapp/getHomePage.do: SessionID not found for WASC=ServletContext@2256126[app:ourwebapp module:ourwebapp path:/ourwebapp spec-version:2.5]>
<BEA-000000> <HttpRequest@11453786 - /ourwebapp/getHomePage.do: Creating new session>
So I guess my question is, has anyone ever encountered this issue before, the bottom line is my http session is not the same after a RequestDispatcher#forward() when running on weblogic 10.3.5 ? Or perhaps any ideas for a temporary workaround meanwhile ?
Anything I may have forgotten ?
My 3rd action invokes request.getSession(), not request.getSession(false), because it is assumed that it has been created at this point, but retrieving the attributes of it reveals an empty map/list.
what is so different in weblogic that may cause this behavior ?
Sample of the ActionServlet:
RequestDispatcher rd = getServletContext().getRequestDispatcher(path);
if (null == rd) {
String errorMessage = internal.getMessage(REQUEST_DISPATCHER, path);
log.debug(errorMessage);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errorMessage);
return;
}
if (null != request.getAttribute(Constants.INCLUDED_REQUEST)) {
rd.include(request, response);
} else {
try {
//fails after this call
rd.forward(request, response); [..]
Third action:
public ActionForward perform(ActionMapping mapping, ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
//displays a new id
log.debug("Http session after forward : {}", request.getSession(false).getId());
String mappingPath = mapping.getPath();
boolean outOfSessionAction = outOfSessionPaths.contains(mappingPath);
Attribute attr = null;
if (!outOfSessionAction) {
attr = request.getSession().getAttribute("attr1");
if (attr == null) {
//we should be retrieving this attribute but we fail because
//new HttpSession in the request object
return processException(request, mapping, new BusinessException(true));
}
}
I have resolved this issue myself, not an easy one but it turns out, several factors were involved. First and foremost, there was still a place in the code where the session would be needlessly invalidated, I removed that. But secondly, and most interestingly, our application was accessed by our customer from another application on a different WLS instance but using the same domain name. I.E.: www.abc.com/customer_app redirected to www.abc.com/our_web_app, I learned that doing so, causes the browser to include all cookies from /customer_app in the redirect request as well. Amongst which was also a JSESSIONID cookie (which does not exist in the context of /our_web_app). That right there was the problem.
WLS reacts just fine the first time it sees that unknown JSESSIONID cookie, it simply ignores it (because it cannot map it in memory to any http session) and creates a new one for our_web_app. The issue is that both cookies were set on cookie-path '/', meaning that whenever a forward occurred within our app, WLS sometimes read the old meaningless JSESSIONID cookie, sometimes the proper new cookie created for our_web_app. When it read the old cookie, it would - again - not recognize it and create a new http session (which yields a new JSESSIONID cookie) thereby losing any info from the previously created http session (after the login).
A good temporary workaround was to set a more specific cookie-path on cookies created via our application, this way, they seem to appear first on the list of cookies and are always considered first before any other JSESSIONID cookies.
I.E. Before when on cookie-path '/' the list of cookies might have appeared as: SOME_CUSTOMER_COOKIE1:value1,SOME_CUSTOMER_COOKIE2:value2, JSESSIONID:oldID, JSESSIONID: newCorrectID
After setting cookie-path '/our_web_app' for cookies created in our web app: JSESSIONID:newCorrectID,SOME_CUSTOMER_COOKIE1:value1,SOME_CUSTOMER_COOKIE2:value2, JSESSIONID:oldID
Ideally, this would've been preventable by either making sure not to use the same domain nane for both apps or even changing the cookie-name property for our_web_app (JSESSIOND -> WEB_APP_SESSION_ID for example) but for technical reasons (mainly on the customer side, related to load balancing and cost issues) neither were an option for us.
Hope this helps someone one day.