All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.javakaffee.web.msm.RequestTrackingHostValve Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2009 Martin Grotzke
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package de.javakaffee.web.msm;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;

import org.apache.catalina.Context;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;

/**
 * This valve is used for tracking requests for that the session must be sent to
 * memcached, on host level. This encapsulates/surrounds als container request
 * processing like e.g. authentication and ServletRequestListener notification.
 *
 * @author Martin Grotzke
 * @version $Id$
 */
public abstract class RequestTrackingHostValve extends ValveBase {

    private static final String REQUEST_IGNORED = "de.javakaffee.msm.request.ignored";

    public static final String REQUEST_PROCESS = "de.javakaffee.msm.request.process";

    public static final String SESSION_ID_CHANGED = "de.javakaffee.msm.sessionIdChanged";

    public static final String REQUEST_PROCESSED = "de.javakaffee.msm.request.processed";

    static final String RELOCATE = "session.relocate";

    protected static final Log _log = LogFactory.getLog( RequestTrackingHostValve.class );

    private final Pattern _ignorePattern;
    private final MemcachedSessionService _sessionBackupService;
    private final Statistics _statistics;
    private final AtomicBoolean _enabled;
    protected final String _sessionCookieName;
    private final CurrentRequest _currentRequest;
    private final Context _msmContext;

    private static final String MSM_REQUEST_ID = "msm.requestId";

    /**
     * Creates a new instance with the given ignore pattern and
     * {@link SessionBackupService}.
     *
     * @param ignorePattern
     *            the regular expression for request uris to ignore
     * @param context
     *            the catalina context of this valve
     * @param sessionBackupService
     *            the service that actually backups sessions
     * @param statistics
     *            used to store statistics
     * @param enabled
     *            specifies if memcached-session-manager is enabled or not.
     *            If false, each request is just processed without doing anything further.
     */
    public RequestTrackingHostValve( @Nullable final String ignorePattern, @Nonnull final String sessionCookieName,
            @Nonnull final MemcachedSessionService sessionBackupService,
            @Nonnull final Statistics statistics,
            @Nonnull final AtomicBoolean enabled,
            @Nonnull final CurrentRequest currentRequest) {
        if ( ignorePattern != null ) {
            _log.info( "Setting ignorePattern to " + ignorePattern );
            _ignorePattern = Pattern.compile( ignorePattern );
        } else {
            _ignorePattern = null;
        }
        _sessionCookieName = sessionCookieName;
        _sessionBackupService = sessionBackupService;
        _statistics = statistics;
        _enabled = enabled;
        _currentRequest = currentRequest;

        _msmContext = _sessionBackupService.getManager().getContext();
    }

    /**
     * Returns the actually used name for the session cookie.
     * @return the cookie name, never null.
     */
    protected String getSessionCookieName() {
        return _sessionCookieName;
    }

    public boolean isIgnoredRequest() {
        final Request request = _currentRequest.get();
        return request != null && request.getNote(REQUEST_IGNORED) == Boolean.TRUE;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void invoke( final Request request, final Response response ) throws IOException, ServletException {

        final String requestId = getURIWithQueryString( request );
        if(!_enabled.get() || !_msmContext.equals(request.getContext())) {
            getNext().invoke( request, response );
        } else if ( _ignorePattern != null && _ignorePattern.matcher( requestId ).matches() ) {
            if(_log.isDebugEnabled()) {
                _log.debug( ">>>>>> Ignoring: " + requestId + " (requestedSessionId "+ request.getRequestedSessionId() +") ==================" );
            }

            try {
                storeRequestThreadLocal( request );
                request.setNote(REQUEST_IGNORED, Boolean.TRUE);
                getNext().invoke( request, response );
            } finally {
                if(request.getNote(REQUEST_PROCESSED) == Boolean.TRUE) {
                    final String sessionId = getSessionId(request, response);
                    if(sessionId != null) {
                        _sessionBackupService.requestFinished(sessionId, requestId);
                    }
                }
                resetRequestThreadLocal();
            }
            if(_log.isDebugEnabled()) {
                _log.debug( "<<<<<< Ignored: " + requestId + " ==================" );
            }
        } else {

            request.setNote(REQUEST_PROCESS, Boolean.TRUE);

            if ( _log.isDebugEnabled() ) {
                _log.debug( ">>>>>> Request starting: " + requestId + " (requestedSessionId "+ request.getRequestedSessionId() +") ==================" );
            }

            try {
                storeRequestThreadLocal( request );
                getNext().invoke( request, response );
            } finally {
                final Boolean sessionIdChanged = (Boolean) request.getNote(SESSION_ID_CHANGED);
                backupSession( request, response, sessionIdChanged == null ? false : sessionIdChanged.booleanValue() );
                resetRequestThreadLocal();
            }

            if ( _log.isDebugEnabled() ) {
                logDebugRequestSessionCookie( request );
                logDebugResponseCookie( response );
                _log.debug( "<<<<<< Request finished: " + requestId + " ==================" );
            }

        }
    }

    protected void logDebugRequestSessionCookie( final Request request ) {
        final Cookie[] cookies = request.getCookies();
        if ( cookies == null ) {
            return;
        }
        for( final javax.servlet.http.Cookie cookie : cookies ) {
            if ( cookie.getName().equals( _sessionCookieName ) ) {
                _log.debug( "Have request session cookie: domain=" + cookie.getDomain() + ", maxAge=" + cookie.getMaxAge() +
                        ", path=" + cookie.getPath() + ", value=" + cookie.getValue() +
                        ", version=" + cookie.getVersion() + ", secure=" + cookie.getSecure() );
            }
        }
    }

    @Nonnull
    protected static String getURIWithQueryString( @Nonnull final Request request ) {
        final Object note = request.getNote(MSM_REQUEST_ID);
        if(note != null) {
            // we have a string and want to save cast
            return note.toString();
        }
        final StringBuilder sb = new StringBuilder(30);
        sb.append(request.getMethod())
        .append(' ')
        .append(request.getRequestURI());
        if(!isPostMethod(request) && request.getQueryString() != null) {
            sb.append('?').append(request.getQueryString());
        }
        final String result = sb.toString();
        request.setNote(MSM_REQUEST_ID, result);
        return result;
    }

	protected static boolean isPostMethod(final Request request) {
		final String method = request.getMethod();
		if ( method == null && _log.isDebugEnabled() ) {
			_log.debug("No method set for request " + request.getRequestURI() +
					(request.getQueryString() != null ? "?" + request.getQueryString() : ""));
		}
		return method != null ? method.toLowerCase().equals( "post" ) : false;
	}

	void resetRequestThreadLocal() {
        _currentRequest.reset();
    }

    void storeRequestThreadLocal( @Nonnull final Request request ) {
        _currentRequest.set( request );
    }

    private void backupSession( final Request request, final Response response, final boolean sessionIdChanged ) {

        /*
         * Do we have a session?
         */
        final String sessionId = getSessionId(request, response);
        if ( sessionId != null ) {
            _statistics.requestWithSession();
            _sessionBackupService.backupSession( sessionId, sessionIdChanged, getURIWithQueryString( request ) );
        }
        else {
            _statistics.requestWithoutSession();
        }

    }

    private String getSessionId(final Request request, final Response response) {
        // If the context is configured with cookie="false" there's no session cookie
        // sent so we must rely on data from MemcachedSessionService
        String sessionId = (String) request.getNote(MemcachedSessionService.NEW_SESSION_ID);
        if(sessionId == null) {
            sessionId = getSessionIdFromResponseSessionCookie( response );
        }
        return sessionId != null ? sessionId : request.getRequestedSessionId();
    }

    private String getSessionIdFromResponseSessionCookie(final Response response) {
        final String[] headers = getSetCookieHeaders(response);
        if (headers == null) {
            return null;
        }
        List sessionCookies = new ArrayList(headers.length);
        for (final String header : headers) {
            if (header != null && header.contains(_sessionCookieName)) {
                final String sessionIdPrefix = _sessionCookieName + "=";
                final int idxNameStart = header.indexOf(sessionIdPrefix);
                final int idxValueStart = idxNameStart + sessionIdPrefix.length();
                int idxValueEnd = header.indexOf(';', idxNameStart);
                if (idxValueEnd == -1) {
                    idxValueEnd = header.indexOf(' ', idxValueStart);
                }
                if (idxValueEnd == -1) {
                    idxValueEnd = header.length();
                }
                String result = header.substring(idxValueStart, idxValueEnd);

                if(header.substring(idxValueEnd - 1).contains(sessionIdPrefix)) {
                    _log.warn("Response contains Set-Cookie header with multiple "+ _sessionCookieName+" entries: " + header +
                            ". Session handling might be negatively affected, you should investigate this.");
                }

                sessionCookies.add(result);
            }
        }

        if (sessionCookies.size() == 1)
            return sessionCookies.get(0);
        else if (sessionCookies.size() > 1) {
            _log.warn("Response contains multiple Set-Cookie headers with a "+ _sessionCookieName+", returning the first one from: " + sessionCookies.toString() +
                    ". Session handling might be negatively affected, you should investigate this.");
            return sessionCookies.get(0);
        }

        return null;
    }

    /**
     * Read the Set-Cookie header from the given response.
     */
    protected abstract String[] getSetCookieHeaders(final Response response);

    private void logDebugResponseCookie( final Response response ) {
        final String[] headers = getSetCookieHeaders(response);
        if ( headers != null ) {
            for (final String header : headers) {
                if (header != null && header.contains(_sessionCookieName)) {
                    _log.debug( "Request finished, with Set-Cookie header: " + header );
                }
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy