Java Weblet HttpSession未在Weblogic会话转发

时间:2015-08-13 20:48:55

标签: java session servlets cookies weblogic-10.x

我在这里提出了很长的问题并且稍微没有选项,所以我想我会转而参考这个网站,它似乎总能解决我的大部分问题。对于长篇文章感到抱歉,但有些背景很重要。

我们有一个java Web应用程序(.war),它可以托管在多个应用程序服务器上(Tomcat用于内部开发,Weblogic 10.3.5用于某些客户,Websphere用于其他客户,Glassfish用于其他客户)。对于它的价值,它是关于jdk 1.6

我们使用旧版本的struts(1.0.2)进行动作映射,在我们的web.xml文件中定义了几个过滤器,我们还为一些应用程序服务器提供了其他配置文件(如weblogic.xml)用于weblogic的文件,其中包含很少的元素)。视图层都是带有一些js的jsp。

我们有一个主入口点/类来处理扩展Struts ActionServlet类的http请求。

在weblogic(WLS)服务器10.3.5上部署和测试应用程序时,我遇到了这个问题,我将要讨论的问题从未在任何其他应用程序服务器上遇到过。

用户将通过调用初始struts操作(例如" / login")来尝试简单的初始日志记录操作。

<action path="/login"
        type="com.<...>.LoginAction"
        name="loginForm"
         scope="session"
        validate="false">
        <forward name="success" path="/completeLogin" internal="true"/>
        [...]
    </action>

应用过滤器,启动httpsession(检查request.getSession(false),如果HttpSession看起来为null,则调用request.getSession(),这在第一个过滤器中是预期的。)< / p>

在第一个操作成功结果之后,我们将内部请求如上所示转发到下一个Struts操作。此操作也成功完成,并将请求转发到下一个struts操作。

在继续之前,请务必注意这两个操作都会在HttpSession对象中设置重要属性,这些属性稍后会在我们的应用程序业务逻辑中使用。

下一个操作映射到.jsp页面(转换为非内部ActionForward):

<action path="/completeLogin"
        type="com.<...>.CompleteLoginAction"
        name="loginForm"
        scope="session">            
        <forward name="success" path="home.jsp"/>
        [...]
    </action>

在/ login和/ completeLogin操作完成之前,我打印HttpSession id以确保从一次调用到另一次调用重用相同的会话ID。

问题是当我的ActionServlet通过RequestDispatcher#forward(ServletRequest,ServletResponse)方法调度第三个请求时,第三个操作失败,因为我们希望从HttpSession中检索一些属性(之前已成功设置)但是不存在,因为,出乎意料的是,生成了一个新的HttpSession并将其传递给第三个动作而不是原始请求的HttpSession。 (我打印id并看到它与之前的2个打印的id不同),因为我无法访问这些属性,然后应用程序抛出异常而用户无法使用该应用程序,因为他/她根本无法登录。

现在,我来之前尝试了一些事情:

  • 我们有一个实现HttpSessionListener接口的类,它在创建或销毁HttpSession时通知我们。在我测试的所有情况下,我总是看到原始和第二次http会话创建的通知,但我从未收到会话销毁的通知。我假设weblogic必须在某个地方进行原始会话,并且从未销毁它,而是创建了一个新的。
  • 尽管如此,我确保检查对HttpSession#invalidate()方法的调用,并且在上面列出的流程中看不到我们的Filters,ActionServlet或Action类本身中调用的任何内容。
  • RequestDispatcher类是特定于容器的,即,在检索调度程序实例时,实际上在运行时调用供应商实现。我认为Oracle的调度程序可能有问题,因为其他供应商都没有问题(并且正在执行相同的代码),所以我向Oracle开了一个服务请求,我仍在和他们的一些工程师沟通找到一个解决方案,但我很难向他们证明我的问题,尽管我已经发送了无数的日志文件来解释这个问题。
  • 阅读Oracle的一些文档,我意识到像大多数servlet一样,HttpSession对象与浏览器Cookie密切相关。在我们的配置中,我们不使用任何形式的session persistence,我们也不创建任何其他cookie,而只是存储http会话属性。我们的客户还确认其浏览器已启用Cookie。我也能够在内部重现2个浏览器的问题。然后我开始调查cookie,它现在似乎是我的主要领导。我查看了初始过滤器后是否存在任何cookie。令我惊讶的是,我曾经期望没有cookie,因为我理解为会话创建的默认cookie在会话结束后设置为到期(浏览器关闭/离开应用程序)。

所以我继续尝试了以下内容,它似乎已经工作了一段时间,但现在用户回到我们这里说明用户仍然不时退出。 更糟糕的是,这个问题不一致,有时会发生,有时候也不会。我写的脏补丁是在最初的LoginAction。我在请求对象中扫描cookie并循环查看如果我找到任何名称为&#34; JSESSIONID&#34;并检查他们的价值。如果它们的值与当前http会话的会话ID不匹配,那么我会更新cookie。它工作了一段时间,但问题现在似乎已经回来了。

补丁的代码示例:

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 {
        [...]
}

补丁方法:

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

我们的weblogic.xml文件如下所示:

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

我还在weblogic管理控制台上启用了HttpDebug日志,并且在请求转发即将失败时经常看到此消息:

(创建初始会话)

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

[...]

(失败)

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

所以我猜我的问题是,有没有人曾经遇到过这个问题,底线是我的http会话在weblogic 10.3.5上运行后的RequestDispatcher#forward()之后是不一样的?或许同时有任何临时解决方案的想法?

我可能忘记了什么?

我的第3个动作调用request.getSession(),而不是request.getSession(false),因为假设它已经在此时创建,但是检索它的属性会显示一个空的地图/列表。

weblogic中有什么不同可能会导致这种行为?

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); [..]

第三步:

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

1 个答案:

答案 0 :(得分:0)

我自己解决了这个问题,不是一件容易的事,但事实证明,涉及到几个因素。首先,在代码中仍然存在一个地方,会话将被不必要地无效,我删除了它。 但其次,最有趣的是,我们的客户从不同WLS实例上的另一个应用程序访问我们的应用程序,但使用相同的域名。 I.E。:www.abc.com/customer_app重定向到www.abc.com/our_web_app,我了解到,这样做会导致浏览器在重定向请求中包含来自/ customer_app的所有cookie。其中还有一个JSESSIONID cookie(在/ our_web_app的上下文中不存在)。那就是问题。

WLS在第一次看到未知的JSESSIONID cookie时反应很好,它只是忽略它(因为它无法将它在内存中映射到任何http会话)并为our_web_app创建一个新的。问题是两个cookie都设置在cookie路径'/'上,这意味着每当我们的应用程序中发生转发时,WLS有时会读取旧的无意义的JSESSIONID cookie,有时会为our_web_app创建正确的新cookie。当它读取旧cookie时,它会 - 再次 - 不识别它并创建一个新的http会话(产生一个新的JSESSIONID cookie),从而丢失先前创建的http会话中的任何信息(登录后)。

一个好的临时解决方法是在通过我们的应用程序创建的cookie上设置更具体的cookie路径,这样,它们似乎首先出现在cookie列表中,并且在任何其他JSESSIONID cookie之前始终被认为是第一个。

即。在cookie-path'/'之前,cookie列表可能显示为: SOME_CUSTOMER_COOKIE1:value1,SOME_CUSTOMER_COOKIE2:value2,JSESSIONID:oldID,JSESSIONID:newCorrectID

为我们的网络应用中创建的Cookie设置cookie-path'/ our_web_app'后: JSESSIONID:newCorrectID,SOME_CUSTOMER_COOKIE1:value1,SOME_CUSTOMER_COOKIE2:value2,JSESSIONID:oldID

理想情况下,这可以通过确保不为两个应用程序使用相同的域nane甚至更改my_web_app的cookie-name属性(例如JSESSIOND - &gt; WEB_APP_SESSION_ID)来防止,但出于技术原因(主要是在客户方面,与负载平衡和成本问题相关)对我们来说都不是一种选择。

希望有一天能帮到某人。