Java Servlet HttpSession 未在 Weblogic 会话转发上维护
Java Servlet HttpSession not maintained on Weblogic session forward
我在这里问这个问题已经走了很长一段路,但我有点不知所措,所以我想我应该转而参考这个网站,它似乎总能解决我的大部分问题。很抱歉 post,但一些上下文很重要。
我们有一个 java Web 应用程序 (.war),它可以托管在多个应用程序服务器上(Tomcat 用于内部开发,Weblogic 10.3.5 用于某些客户,其他客户的 Websphere 和其他客户的 Glassfish)。对于它的价值,它在 jdk 1.6
我们使用旧版本的 struts (1.0.2) 进行动作映射,在我们的 web.xml 文件中定义了几个过滤器,我们还有一些额外的配置文件应用程序服务器(比如 weblogic 的 weblogic.xml 文件,里面只有很少的元素)。视图层都是jsp加上一些js.
我们有一个主入口点/class 来处理扩展 Struts ActionServlet class.
的 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 似乎为空,则调用 request.getSession(),这在第一个过滤器)。
在第一个操作的成功结果之后,我们然后 ward 在内部请求如上所示到下一个 Struts 操作。此操作也成功完成,并且 wards 请求下一个 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 不同),因为我无法访问这些属性,应用程序随后抛出异常,用户无法使用该应用程序,因为 he/she 根本无法登录。
现在,我来这里之前尝试过一些事情:
- 我们有一个 class 实现 HttpSessionListener 接口,它会在创建或销毁 HttpSession 时通知我们。在我测试过的所有情况下,我总是看到有关创建原始和第二个 http 会话的通知,但我从未收到有关会话 销毁 的通知。我假设 weblogic 必须在某个地方拥有原始会话并且从未销毁它而是创建了一个新会话。
- 我 nonetheless 确保检查对 HttpSession#invalidate() 方法的调用,并且在我们的过滤器、ActionServlet 或 Action classes 本身中没有看到任何调用以上所列。
- RequestDispatcher class 是特定于容器的,即,在检索调度程序实例时实际上会在运行时调用供应商实现。我假设 Oracle 的调度程序可能有问题,因为 none 的其他供应商有问题(并且正在执行相同的代码),所以我向 Oracle 提出了服务请求,并且仍在与他们的一些工程师沟通以找到一个解决方案,但我很难向他们证明我的问题,尽管我已经向他们发送了无数的日志文件来解释这个问题。
- 阅读一些 Oracle 文档后,我意识到与大多数 servlet 一样,HttpSession 对象与浏览器 Cookie 密切相关。在我们的配置中,我们不使用任何形式的 session persistence,我们也不创建任何额外的 cookie,而只是存储 http 会话属性。我们的客户还确认在他们的浏览器上启用了 cookie。我也能够在内部的 2 个浏览器上重现该问题。然后我开始研究 cookie,现在它似乎是我的主要线索。我检查了初始过滤器后是否存在任何 cookie。令我惊讶的是,我没有预料到 cookie,因为据我所知,为会话创建的默认 cookie 设置为在会话结束(浏览器关闭/离开应用程序)后过期。
所以我继续尝试以下方法,它似乎已经工作了一段时间,但现在用户回来告诉我们,用户仍然不时注销。 更糟糕的是,这个问题并不一致,有时会发生,有时不会。 我写的脏补丁是在初始的 LoginAction 处。我扫描请求对象中的 cookie 并循环查看是否找到名称为 "JSESSIONID" 的任何 cookie 并检查它们的值。如果它们的值与当前 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 日志,当 ward 的请求即将失败时经常看到此消息:
(初始会话创建)
<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 会话在 RequestDispatcher#forward() 之后不一样 运行在 weblogic 10.3.5 上?或者也许同时有临时解决方法的想法?
我可能忘记了什么?
我的第 3 个操作调用 request.getSession(),而不是 request.getSession(false),因为假定它已在此时创建,但检索它的属性会显示一个空的 map/list.
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));
}
}
我自己解决了这个问题,这不是一件容易的事,但事实证明,这涉及到几个因素。首先也是最重要的是,代码中仍有一个地方会不必要地使会话无效,我将其删除。
但其次,也是最有趣的是,我们的应用程序被我们的客户从不同 WLS 实例上的另一个应用程序访问,但使用相同的域名。即: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 都设置在 cookie 路径“/”上,这意味着每当我们的应用程序中发生转发时,WLS 有时会读取旧的无意义的 JSESSIONID cookie,有时会读取为 our_web_app 创建的正确的新 cookie。当它读取旧的 cookie 时,它会再次无法识别它并创建一个新的 http 会话(产生一个新的 JSESSIONID cookie),从而丢失先前创建的 http 会话(登录后)的任何信息。
一个很好的临时解决方法是在通过我们的应用程序创建的 cookie 上设置更具体的 cookie 路径,这样,它们似乎首先出现在 cookie 列表中,并且总是在任何其他 JSESSIONID cookie 之前首先被考虑。
I.E.在 cookie 路径 '/' 之前,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
理想情况下,这可以通过确保两个应用程序不使用相同的域名或什至更改 our_web_app 的 cookie 名称 属性(JSESSIOND -> WEB_APP_SESSION_ID 例如)但出于技术原因(主要是在客户端,与负载平衡和成本问题相关)我们都不是一个选择。
希望有一天这对某人有所帮助。
我在这里问这个问题已经走了很长一段路,但我有点不知所措,所以我想我应该转而参考这个网站,它似乎总能解决我的大部分问题。很抱歉 post,但一些上下文很重要。
我们有一个 java Web 应用程序 (.war),它可以托管在多个应用程序服务器上(Tomcat 用于内部开发,Weblogic 10.3.5 用于某些客户,其他客户的 Websphere 和其他客户的 Glassfish)。对于它的价值,它在 jdk 1.6
我们使用旧版本的 struts (1.0.2) 进行动作映射,在我们的 web.xml 文件中定义了几个过滤器,我们还有一些额外的配置文件应用程序服务器(比如 weblogic 的 weblogic.xml 文件,里面只有很少的元素)。视图层都是jsp加上一些js.
我们有一个主入口点/class 来处理扩展 Struts ActionServlet class.
的 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 似乎为空,则调用 request.getSession(),这在第一个过滤器)。
在第一个操作的成功结果之后,我们然后 ward 在内部请求如上所示到下一个 Struts 操作。此操作也成功完成,并且 wards 请求下一个 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 不同),因为我无法访问这些属性,应用程序随后抛出异常,用户无法使用该应用程序,因为 he/she 根本无法登录。
现在,我来这里之前尝试过一些事情:
- 我们有一个 class 实现 HttpSessionListener 接口,它会在创建或销毁 HttpSession 时通知我们。在我测试过的所有情况下,我总是看到有关创建原始和第二个 http 会话的通知,但我从未收到有关会话 销毁 的通知。我假设 weblogic 必须在某个地方拥有原始会话并且从未销毁它而是创建了一个新会话。
- 我 nonetheless 确保检查对 HttpSession#invalidate() 方法的调用,并且在我们的过滤器、ActionServlet 或 Action classes 本身中没有看到任何调用以上所列。
- RequestDispatcher class 是特定于容器的,即,在检索调度程序实例时实际上会在运行时调用供应商实现。我假设 Oracle 的调度程序可能有问题,因为 none 的其他供应商有问题(并且正在执行相同的代码),所以我向 Oracle 提出了服务请求,并且仍在与他们的一些工程师沟通以找到一个解决方案,但我很难向他们证明我的问题,尽管我已经向他们发送了无数的日志文件来解释这个问题。
- 阅读一些 Oracle 文档后,我意识到与大多数 servlet 一样,HttpSession 对象与浏览器 Cookie 密切相关。在我们的配置中,我们不使用任何形式的 session persistence,我们也不创建任何额外的 cookie,而只是存储 http 会话属性。我们的客户还确认在他们的浏览器上启用了 cookie。我也能够在内部的 2 个浏览器上重现该问题。然后我开始研究 cookie,现在它似乎是我的主要线索。我检查了初始过滤器后是否存在任何 cookie。令我惊讶的是,我没有预料到 cookie,因为据我所知,为会话创建的默认 cookie 设置为在会话结束(浏览器关闭/离开应用程序)后过期。
所以我继续尝试以下方法,它似乎已经工作了一段时间,但现在用户回来告诉我们,用户仍然不时注销。 更糟糕的是,这个问题并不一致,有时会发生,有时不会。 我写的脏补丁是在初始的 LoginAction 处。我扫描请求对象中的 cookie 并循环查看是否找到名称为 "JSESSIONID" 的任何 cookie 并检查它们的值。如果它们的值与当前 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 日志,当 ward 的请求即将失败时经常看到此消息:
(初始会话创建)
<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 会话在 RequestDispatcher#forward() 之后不一样 运行在 weblogic 10.3.5 上?或者也许同时有临时解决方法的想法?
我可能忘记了什么?
我的第 3 个操作调用 request.getSession(),而不是 request.getSession(false),因为假定它已在此时创建,但检索它的属性会显示一个空的 map/list.
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));
}
}
我自己解决了这个问题,这不是一件容易的事,但事实证明,这涉及到几个因素。首先也是最重要的是,代码中仍有一个地方会不必要地使会话无效,我将其删除。 但其次,也是最有趣的是,我们的应用程序被我们的客户从不同 WLS 实例上的另一个应用程序访问,但使用相同的域名。即: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 都设置在 cookie 路径“/”上,这意味着每当我们的应用程序中发生转发时,WLS 有时会读取旧的无意义的 JSESSIONID cookie,有时会读取为 our_web_app 创建的正确的新 cookie。当它读取旧的 cookie 时,它会再次无法识别它并创建一个新的 http 会话(产生一个新的 JSESSIONID cookie),从而丢失先前创建的 http 会话(登录后)的任何信息。
一个很好的临时解决方法是在通过我们的应用程序创建的 cookie 上设置更具体的 cookie 路径,这样,它们似乎首先出现在 cookie 列表中,并且总是在任何其他 JSESSIONID cookie 之前首先被考虑。
I.E.在 cookie 路径 '/' 之前,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
理想情况下,这可以通过确保两个应用程序不使用相同的域名或什至更改 our_web_app 的 cookie 名称 属性(JSESSIOND -> WEB_APP_SESSION_ID 例如)但出于技术原因(主要是在客户端,与负载平衡和成本问题相关)我们都不是一个选择。
希望有一天这对某人有所帮助。