/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.undertow.session;
import java.io.IOException;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.servlet.api.Deployment;
import io.undertow.servlet.spec.HttpServletRequestImpl;
import org.jboss.as.clustering.web.OutgoingDistributableSessionData;
import org.jboss.logging.Logger;
/**
* Web request handler to specifically handle Tomcat jvmRoute using mod_jk(2) module. We assume that the session id has a format
* of id.jvmRoute where jvmRoute is used by JK module to determine sticky session during load balancing. Checks for failover by
* matching session and request jvmRoute to the session manager's, updating the session and session cookie if a failover is
* detected.
* <p/>
* This handler is inserted in the pipeline only when mod_jk is configured.
*
* @author Ben Wang
* @author Brian Stansberry
* @version $Revision: 108925 $
*/
public class JvmRouteHandler implements HttpHandler {
// The info string for this Valve
private static final String info = "JvmRouteValve/1.0";
private static Logger log = Logger.getLogger(JvmRouteHandler.class);
private final SessionManager manager;
private final HttpHandler next;
private final Deployment deployment;
/**
* Create a new handler.
*/
public JvmRouteHandler(SessionManager manager, final HttpHandler next, final Deployment deployment) {
super();
this.manager = manager;
this.next = next;
this.deployment = deployment;
}
public String getInfo() {
return info;
}
@Override
public void handleRequest(final HttpServerExchange exchange) throws Exception {
if (log.isTraceEnabled()) {
log.tracef("handling request %s", exchange.getRequestURI());
}
// Need to check it before let request through.
checkJvmRoute(exchange);
// let the servlet invocation go through
next.handleRequest(exchange);
}
public void checkJvmRoute(final HttpServerExchange exchange) throws IOException, ServletException {
String requestedId = deployment.getServletContext().getSessionCookieConfig().findSessionId(exchange);
HttpServletRequestImpl req = (HttpServletRequestImpl) exchange.getAttachment(HttpServletRequestImpl.ATTACHMENT_KEY);
HttpSession session = req.getSession(false);
if (session != null) {
String sessionId = session.getId();
String jvmRoute = manager.getJvmRoute();
if (log.isTraceEnabled()) {
log.tracef("checkJvmRoute(): check if need to re-route based on JvmRoute. Session id: %s jvmRoute: %s", sessionId, jvmRoute);
}
if (jvmRoute != null) {
// Check if incoming session id has JvmRoute appended. If not, append it.
boolean setCookie = !req.isRequestedSessionIdFromURL();
handleJvmRoute(requestedId, sessionId, jvmRoute, exchange, setCookie);
}
}
}
protected void handleJvmRoute(String requestedId, String sessionId, String jvmRoute, HttpServerExchange exchange, boolean setCookie) throws IOException {
// The new id we'll give the session if we detect a failover
String newId = null;
// First, check if the session object's jvmRoute matches ours
// TODO. The current format is assumed to be id.jvmRoute. Can be generalized later.
Map.Entry<String, String> sessionEntry = this.manager.parse(sessionId);
String realId = sessionEntry.getKey();
String sessionJvmRoute = sessionEntry.getValue();
if (sessionJvmRoute == null) {
newId = this.manager.createSessionId(realId, jvmRoute);
} else if (!jvmRoute.equals(sessionJvmRoute)) {
if (log.isTraceEnabled()) {
log.tracef("handleJvmRoute(): We have detected a failover with different jvmRoute. old one: %s, new one: %s. Will reset the session id.", sessionJvmRoute, jvmRoute);
}
newId = this.manager.createSessionId(realId, this.manager.locateJvmRoute(realId));
}
if (newId != null) {
// Fix the session's id
resetSessionId(exchange, sessionId, newId);
}
// Now we know the session object has a correct id
// Also need to ensure any session cookie is correct
if (setCookie) {
// Check if the jvmRoute of the requested session id matches ours.
// Only bother if we haven't already spotted a mismatch above
if (newId == null) {
String requestedJvmRoute = (requestedId != null) ? this.manager.parse(requestedId).getValue() : null;
if (!jvmRoute.equals(requestedJvmRoute)) {
if (log.isTraceEnabled()) {
log.tracef("handleJvmRoute(): We have detected a failover with different jvmRoute. received one: %s, new one: %s. Will reset the session id.", requestedJvmRoute, jvmRoute);
}
newId = this.manager.createSessionId(realId, this.manager.locateJvmRoute(realId));
}
}
/* Change the sessionid cookie if needed */
if (newId != null) {
deployment.getServletContext().getSessionCookieConfig().setSessionId(exchange, newId);
}
}
}
/**
* Update the id of the given session
*
* @param oldId id of the session to change
* @param newId new session id the session object should have
*/
private void resetSessionId(HttpServerExchange exchange, String oldId, String newId) throws IOException {
ClusteredSession<? extends OutgoingDistributableSessionData> session = (ClusteredSession<?>) manager.getSession(exchange, deployment.getServletContext().getSessionCookieConfig());
// change session id with the new one using local jvmRoute.
if (session != null) {
session.resetIdWithRouteInfo(newId);
if (log.isTraceEnabled()) {
log.tracef("resetSessionId(): changed catalina session to= [%s] old one= [%s]", newId, oldId);
}
} else if (log.isTraceEnabled()) {
log.tracef("resetSessionId(): no session with id %s found", newId);
}
}
}