【发布时间】:2015-08-13 20:48:55
【问题描述】:
我在这里问这个问题已经走了很长一段路,但我有点没有选择,所以我想我会转而参考这个网站,它似乎总能解决我的大多数问题。很抱歉这篇文章很长,但一些背景很重要。
我们有一个 Java Web 应用程序 (.war),它可以托管在多个应用程序服务器上(Tomcat 用于内部开发,Weblogic 10.3.5 用于某些客户,Websphere 用于其他客户,Glassfish 也用于其他客户)。对于它的价值,它在 jdk 1.6 上
我们使用旧版本的 struts (1.0.2) 进行操作映射,在 web.xml 文件中定义了几个过滤器,我们还为一些应用程序服务器提供了额外的配置文件(如 weblogic.xml weblogic 的文件,其中包含很少的元素)。视图层都是jsp加一些js。
我们有一个主入口点/类来处理扩展了 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 显示为空,则调用 request.getSession(),这在第一个过滤器中是预期的)。
在第一个动作的成功结果之后,我们在内部将请求如上所示转发到下一个 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 个浏览器上重现该问题。然后我开始研究cookies,这似乎是我现在的主要线索。我检查了在初始过滤器之后是否存在任何 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 日志,并且在请求转发即将失败时经常看到此消息:
(初始会话创建)
<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>
所以我想我的问题是,以前有没有人遇到过这个问题,底线是在 weblogic 10.3.5 上运行 RequestDispatcher#forward() 之后我的 http 会话不一样?或者同时有什么临时解决方法的想法?
我可能忘记了什么?
我的第三个操作调用 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));
}
}
【问题讨论】:
-
对此进行更新,以防有人遇到同样的问题。就我而言,实际上问题似乎在链条上更高。托管在单独的 weblogic 实例上的另一个应用程序在初始登录请求中发送自己的 JSESSIONID cookie,我相信这是混淆 weblogic 的原因。尝试验证这一点,将再次更新问题。此外,最初建议的解决方法也行不通。
标签: java session servlets cookies weblogic-10.x