Hi Chris,

On Wed, Jun 21, 2017 at 9:05 PM, <schu...@apache.org> wrote:

> Author: schultz
> Date: Wed Jun 21 19:05:38 2017
> New Revision: 1799498
>
> URL: http://svn.apache.org/viewvc?rev=1799498&view=rev
> Log:
> Add LoadBalancerDrainingValve.
>
> Added:
>     
> tomcat/trunk/java/org/apache/catalina/valves/LoadBalancerDrainingValve.java
>  (with props)
>     tomcat/trunk/test/org/apache/catalina/valves/
> TestLoadBalancerDrainingValve.java   (with props)
> Modified:
>     tomcat/trunk/webapps/docs/changelog.xml
>     tomcat/trunk/webapps/docs/config/valve.xml
>
> Added: tomcat/trunk/java/org/apache/catalina/valves/
> LoadBalancerDrainingValve.java
> URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/
> catalina/valves/LoadBalancerDrainingValve.java?rev=1799498&view=auto
> ============================================================
> ==================
> --- 
> tomcat/trunk/java/org/apache/catalina/valves/LoadBalancerDrainingValve.java
> (added)
> +++ 
> tomcat/trunk/java/org/apache/catalina/valves/LoadBalancerDrainingValve.java
> Wed Jun 21 19:05:38 2017
> @@ -0,0 +1,277 @@
> +/*
> + * Licensed to the Apache Software Foundation (ASF) under one or more
> + * contributor license agreements.  See the NOTICE file distributed with
> + * this work for additional information regarding copyright ownership.
> + * The ASF licenses this file to You 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 org.apache.catalina.valves;
> +
> +import java.io.IOException;
> +
> +import javax.servlet.ServletException;
> +import javax.servlet.http.Cookie;
> +import javax.servlet.http.HttpServletResponse;
> +
> +import org.apache.catalina.LifecycleException;
> +import org.apache.catalina.connector.Request;
> +import org.apache.catalina.connector.Response;
> +import org.apache.catalina.util.SessionConfig;
> +import org.apache.juli.logging.Log;
> +
> +/**
> + * <p>A Valve to detect situations where a load-balanced node receiving a
> + * request has been deactivated by the load balancer
> (JK_LB_ACTIVATION=DIS)
> + * and the incoming request has no valid session.</p>
> + *
> + * <p>In these cases, the user's session cookie should be removed if it
> exists,
> + * any ";jsessionid" parameter should be removed from the request URI,
> + * and the client should be redirected to the same URI. This will cause
> the
> + * load-balanced to re-balance the client to another server.</p>
> + *
> + * <p>A request parameter is added to the redirect URI in order to avoid
> + * repeated redirects in the event of an error or misconfiguration.</p>
> + *
> + * <p>All this work is required because when the activation state of a
> node is
> + * DISABLED, the load-balancer will still send requests to the node if
> they
> + * appear to have a session on that node. Since mod_jk doesn't actually
> know
> + * whether the session id is valid, it will send the request blindly to
> + * the disabled node, which makes it take much longer to drain the node
> + * than strictly necessary.</p>
> + *
> + * <p>For testing purposes, a special cookie can be configured and used
> + * by a client to ignore the normal behavior of this Valve and allow
> + * a client to get a new session on a DISABLED node. See
> + * {@link #setIgnoreCookieName} and {@link #setIgnoreCookieValue}
> + * to configure those values.</p>
> + *
> + * <p>This Valve should be installed earlier in the Valve pipeline than
> any
> + * authentication valves, as the redirection should take place before an
> + * authentication valve would save a request to a protected resource.</p>
> + *
> + * @see http://tomcat.apache.org/connectors-doc/generic_howto/
> loadbalancers.html
> + */
> +public class LoadBalancerDrainingValve
> +    extends ValveBase
> +{
> +    /**
> +     * The request attribute key where the load-balancer's activation
> state
> +     * can be found.
> +     */
> +    static final String ATTRIBUTE_KEY_JK_LB_ACTIVATION =
> "JK_LB_ACTIVATION";
>

Any objection to make this constant public and visible from outside ?
I find it useful to be able to refer the constant by name than its value
when integrating.


> +
> +    /**
> +     * The HTTP response code that will be used to redirect the request
> +     * back to the load-balancer for re-balancing. Defaults to 307
> +     * (TEMPORARY_REDIRECT).
> +     *
> +     * HTTP status code 305 (USE_PROXY) might be an option, here. too.
> +     */
> +    private int _redirectStatusCode = HttpServletResponse.SC_
> TEMPORARY_REDIRECT;
> +
> +    /**
> +     * The name of the cookie which can be set to ignore the "draining"
> action
> +     * of this Filter. This will allow a client to contact the server
> without
> +     * being re-balanced to another server. The expected cookie value can
> be set
> +     * in the {@link #_ignoreCookieValue}. The cookie name and value must
> match
> +     * to avoid being re-balanced.
> +     */
> +    private String _ignoreCookieName;
> +
> +    /**
> +     * The value of the cookie which can be set to ignore the "draining"
> action
> +     * of this Filter. This will allow a client to contact the server
> without
> +     * being re-balanced to another server. The expected cookie name can
> be set
> +     * in the {@link #_ignoreCookieValue}. The cookie name and value must
> match
> +     * to avoid being re-balanced.
> +     */
> +    private String _ignoreCookieValue;
> +
> +    /**
> +     * Local reference to the container log.
> +     */
> +    protected Log containerLog = null;
> +
> +    public LoadBalancerDrainingValve()
> +    {
> +        super(true); // Supports async
> +    }
> +
> +    //
> +    // Configuration parameters
> +    //
> +
> +    /**
> +     * Sets the HTTP response code that will be used to redirect the
> request
> +     * back to the load-balancer for re-balancing. Defaults to 307
> +     * (TEMPORARY_REDIRECT).
> +     */
> +    public void setRedirectStatusCode(int code) {
> +        _redirectStatusCode = code;
> +    }
> +
> +    /**
> +     * Gets the name of the cookie that can be used to override the
> +     * re-balancing behavior of this Valve when the current node is
> +     * in the DISABLED activation state.
> +     *
> +     * @return The cookie name used to ignore normal processing rules.
> +     *
> +     * @see #setIgnoreCookieValue
> +     */
> +    public String getIgnoreCookieName() {
> +        return _ignoreCookieName;
> +    }
> +
> +    /**
> +     * Sets the name of the cookie that can be used to override the
> +     * re-balancing behavior of this Valve when the current node is
> +     * in the DISABLED activation state.
> +     *
> +     * There is no default value for this setting: the ability to override
> +     * the re-balancing behavior of this Valve is <i>disabled</i> by
> default.
> +     *
> +     * @param cookieName The cookie name to use to ignore normal
> +     *                   processing rules.
> +     *
> +     * @see #getIgnoreCookieValue
> +     */
> +    public void setIgnoreCookieName(String cookieName) {
> +        _ignoreCookieName = cookieName;
> +    }
> +
> +    /**
> +     * Gets the expected value of the cookie that can be used to override
> the
> +     * re-balancing behavior of this Valve when the current node is
> +     * in the DISABLED activation state.
> +     *
> +     * @return The cookie value used to ignore normal processing rules.
> +     *
> +     * @see #setIgnoreCookieValue
> +     */
> +    public String getIgnoreCookieValue() {
> +        return _ignoreCookieValue;
> +    }
> +
> +    /**
> +     * Sets the expected value of the cookie that can be used to override
> the
> +     * re-balancing behavior of this Valve when the current node is
> +     * in the DISABLED activation state. The "ignore" cookie's value
> +     * <b>must</b> be exactly equal to this value in order to allow
> +     * the client to override the re-balancing behavior.
> +     *
> +     * @param cookieValue The cookie value to use to ignore normal
> +     *                    processing rules.
> +     *
> +     * @see #getIgnoreCookieValue
> +     */
> +    public void setIgnoreCookieValue(String cookieValue) {
> +        _ignoreCookieValue = cookieValue;
> +    }
> +
> +    @Override
> +    public void initInternal()
> +        throws LifecycleException
> +    {
> +        super.initInternal();
> +
> +        containerLog = getContainer().getLogger();
> +    }
> +
> +    @Override
> +    public void invoke(Request request, Response response) throws
> IOException, ServletException {
> +        if("DIS".equals(request.getAttribute(ATTRIBUTE_KEY_JK_
> LB_ACTIVATION))
> +           && !request.isRequestedSessionIdValid()) {
> +
> +            if(containerLog.isDebugEnabled())
> +                containerLog.debug("Load-balancer is in DISABLED state;
> draining this node");
> +
> +            boolean ignoreRebalance = false; // Allow certain clients
> +            Cookie sessionCookie = null;
> +
> +            // Kill any session cookie present
> +            final Cookie[] cookies = request.getCookies();
> +
> +            final String sessionCookieName = request.getServletContext().
> getSessionCookieConfig().getName();
> +
> +            // Kill any session cookie present
> +            if(null != cookies) {
> +                for(Cookie cookie : cookies) {
> +                    final String cookieName = cookie.getName();
> +                    if(containerLog.isTraceEnabled())
> +                        containerLog.trace("Checking cookie " +
> cookieName + "=" + cookie.getValue());
> +
> +                    if(sessionCookieName.equals(cookieName)
> +                       && 
> request.getRequestedSessionId().equals(cookie.getValue()))
> {
> +                        sessionCookie = cookie;
>

Is it a good idea to 'break' here ?
Do you expect more than one session cookies ?!


> +                    } else
> +                    // Is the client presenting a valid ignore-cookie
> value?
> +                    if(null != _ignoreCookieName
> +                            && _ignoreCookieName.equals(cookieName)
> +                            && null != _ignoreCookieValue
> +                            && _ignoreCookieValue.equals(cookie.getValue()))
> {
> +                        ignoreRebalance = true;
> +                    }
> +                }
> +            }
> +
> +            if(ignoreRebalance) {
> +                if(containerLog.isDebugEnabled())
> +                    containerLog.debug("Client is presenting a valid " +
> _ignoreCookieName
> +                                 + " cookie, re-balancing is being
> skipped");
> +
> +                getNext().invoke(request, response);
> +
> +                return;
> +            }
> +
> +            // Kill any session cookie that was found
> +            // TODO: Consider implications of SSO cookies
> +            if(null != sessionCookie) {
> +                String cookiePath = request.getServletContext().
> getSessionCookieConfig().getPath();
> +
> +                
> if(request.getContext().getSessionCookiePathUsesTrailingSlash())
> {
> +                    // Handle special case of ROOT context where cookies
> require a path of
> +                    // '/' but the servlet spec uses an empty string
> +                    // Also ensure the cookies for a context with a path
> of /foo don't get
> +                    // sent for requests with a path of /foobar
> +                    if (!cookiePath.endsWith("/"))
> +                        cookiePath = cookiePath + "/";
> +
> +                    sessionCookie.setPath(cookiePath);
> +                    sessionCookie.setMaxAge(0); // Delete
> +                    sessionCookie.setValue(""); // Purge the cookie's
> value
> +                    response.addCookie(sessionCookie);
> +                }
> +            }
> +
> +            // Re-write the URI if it contains a ;jsessionid parameter
> +            String uri = request.getRequestURI();
> +            String sessionURIParamName = "jsessionid";
> +            SessionConfig.getSessionUriParamName(request.getContext());
>

It seems this bug has been inroduced during testing/debugging.
The return value of
"SessionConfig.getSessionUriParamName(request.getContext());"
is ignored and the sessionURIParamName is always "jsessionid".


> +            if(uri.contains(";" + sessionURIParamName + "="))
> +                uri = uri.replaceFirst(";" + sessionURIParamName +
> "=[^&?]*", "");
> +
> +            String queryString = request.getQueryString();
> +
> +            if(null != queryString)
> +                uri = uri + "?" + queryString;
> +
> +            // NOTE: Do not call response.encodeRedirectURL or the bad
> +            // sessionid will be restored
> +            response.setHeader("Location", uri);
> +            response.setStatus(_redirectStatusCode);
> +        }
> +        else
> +            getNext().invoke(request, response);
> +    }
> +}
>
> Propchange: tomcat/trunk/java/org/apache/catalina/valves/
> LoadBalancerDrainingValve.java
> ------------------------------------------------------------
> ------------------
>     svn:eol-style = native
>
> Added: tomcat/trunk/test/org/apache/catalina/valves/
> TestLoadBalancerDrainingValve.java
> URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/
> catalina/valves/TestLoadBalancerDrainingValve.java?rev=1799498&view=auto
> ============================================================
> ==================
> --- tomcat/trunk/test/org/apache/catalina/valves/
> TestLoadBalancerDrainingValve.java (added)
> +++ tomcat/trunk/test/org/apache/catalina/valves/
> TestLoadBalancerDrainingValve.java Wed Jun 21 19:05:38 2017
> @@ -0,0 +1,257 @@
> +/* Licensed to the Apache Software Foundation (ASF) under one or more
> + * contributor license agreements.  See the NOTICE file distributed with
> + * this work for additional information regarding copyright ownership.
> + * The ASF licenses this file to You 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 org.apache.catalina.valves;
> +
> +import java.util.ArrayList;
> +import java.util.List;
> +
> +import javax.servlet.ServletContext;
> +import javax.servlet.SessionCookieConfig;
> +import javax.servlet.http.Cookie;
> +
> +import org.junit.Test;
> +
> +import org.apache.catalina.Context;
> +import org.apache.catalina.Valve;
> +import org.apache.catalina.connector.Request;
> +import org.apache.catalina.connector.Response;
> +import org.apache.catalina.core.StandardPipeline;
> +import org.easymock.EasyMock;
> +import org.easymock.IMocksControl;
> +
> +public class TestLoadBalancerDrainingValve {
> +
> +    static class MockResponse extends Response {
> +        private List<Cookie> cookies;
> +        @Override
> +        public boolean isCommitted() {
> +            return false;
> +        }
> +        @Override
> +        public void addCookie(Cookie cookie)
> +        {
> +            if(null == cookies)
> +                cookies = new ArrayList<Cookie>(1);
> +            cookies.add(cookie);
> +        }
> +        public List<Cookie> getCookies() {
> +            return cookies;
> +        }
> +    }
> +
> +    static class CookieConfig implements SessionCookieConfig {
> +
> +        private String name;
> +        private String domain;
> +        private String path;
> +        private String comment;
> +        private boolean httpOnly;
> +        private boolean secure;
> +        private int maxAge;
> +
> +        @Override
> +        public String getName() {
> +            return name;
> +        }
> +        @Override
> +        public void setName(String name) {
> +            this.name = name;
> +        }
> +        @Override
> +        public String getDomain() {
> +            return domain;
> +        }
> +        @Override
> +        public void setDomain(String domain) {
> +            this.domain = domain;
> +        }
> +        @Override
> +        public String getPath() {
> +            return path;
> +        }
> +        @Override
> +        public void setPath(String path) {
> +            this.path = path;
> +        }
> +        @Override
> +        public String getComment() {
> +            return comment;
> +        }
> +        @Override
> +        public void setComment(String comment) {
> +            this.comment = comment;
> +        }
> +        @Override
> +        public boolean isHttpOnly() {
> +            return httpOnly;
> +        }
> +        @Override
> +        public void setHttpOnly(boolean httpOnly) {
> +            this.httpOnly = httpOnly;
> +        }
> +        @Override
> +        public boolean isSecure() {
> +            return secure;
> +        }
> +        @Override
> +        public void setSecure(boolean secure) {
> +            this.secure = secure;
> +        }
> +        @Override
> +        public int getMaxAge() {
> +            return maxAge;
> +        }
> +        @Override
> +        public void setMaxAge(int maxAge) {
> +            this.maxAge = maxAge;
> +        }
> +    }
> +
> +    // A Cookie subclass that knows how to compare itself to other Cookie
> objects
> +    static class MyCookie extends Cookie {
> +      public MyCookie(String name, String value) { super(name, value); }
> +
> +      @Override
> +      public boolean equals(Object o) {
> +        if(null == o) return false;
> +        MyCookie mc = (MyCookie)o;
> +
> +        return mc.getName().equals(this.getName())
> +            && mc.getPath().equals(this.getPath())
> +            && mc.getValue().equals(this.getValue())
> +            && mc.getMaxAge() == this.getMaxAge();
> +      }
> +
> +      @Override
> +      public String toString() {
> +          return "Cookie { name=" + getName() + ", value=" + getValue() +
> ", path=" + getPath() + ", maxAge=" + getMaxAge() + " }";
> +      }
> +    }
> +
> +    @Test
> +    public void testNormalRequest() throws Exception {
> +        runValve("ACT", true, true, false, null);
> +    }
> +
> +    @Test
> +    public void testDisabledValidSession() throws Exception {
> +        runValve("DIS", true, true, false, null);
> +    }
> +
> +    @Test
> +    public void testDisabledInvalidSession() throws Exception {
> +        runValve("DIS", false, false, false, "foo=bar");
> +    }
> +
> +    @Test
> +    public void testDisabledInvalidSessionWithIgnore() throws Exception {
> +        runValve("DIS", false, true, true, "foo=bar");
> +    }
> +
> +    private void runValve(String jkActivation,
> +                          boolean validSessionId,
> +                          boolean expectInvokeNext,
> +                          boolean enableIgnore,
> +                          String queryString) throws Exception {
> +        IMocksControl control = EasyMock.createControl();
> +        ServletContext servletContext = control.createMock(
> ServletContext.class);
> +        Context ctx = control.createMock(Context.class);
> +        Request request = control.createMock(Request.class);
> +        Response response = control.createMock(Response.class);
> +
> +        String sessionCookieName = "JSESSIONID";
> +        String sessionId = "cafebabe";
> +        String requestURI = "/test/path";
> +        SessionCookieConfig cookieConfig = new CookieConfig();
> +        cookieConfig.setDomain("example.com");
> +        cookieConfig.setName(sessionCookieName);
> +        cookieConfig.setPath("/");
> +
> +        // Valve.init requires all of this stuff
> +        EasyMock.expect(ctx.getMBeanKeyProperties()).andStubReturn("");
> +        EasyMock.expect(ctx.getName()).andStubReturn("");
> +        EasyMock.expect(ctx.getPipeline()).andStubReturn(new
> StandardPipeline());
> +        EasyMock.expect(ctx.getDomain()).andStubReturn("foo");
> +        EasyMock.expect(ctx.getLogger()).andStubReturn(org.apache.
> juli.logging.LogFactory.getLog(LoadBalancerDrainingValve.class));
> +        EasyMock.expect(ctx.getServletContext()).
> andStubReturn(servletContext);
> +
> +        // Set up the actual test
> +        EasyMock.expect(request.getAttribute(LoadBalancerDrainingValve.
> ATTRIBUTE_KEY_JK_LB_ACTIVATION)).andStubReturn(jkActivation);
> +        EasyMock.expect(request.isRequestedSessionIdValid()).
> andStubReturn(validSessionId);
> +
> +        ArrayList<Cookie> cookies = new ArrayList<Cookie>();
> +        if(enableIgnore) {
> +            cookies.add(new Cookie("ignore", "true"));
> +        }
> +
> +        if(!validSessionId) {
> +            MyCookie cookie = new MyCookie(cookieConfig.getName(),
> sessionId);
> +            cookie.setPath(cookieConfig.getPath());
> +            cookie.setValue(sessionId);
> +
> +            cookies.add(cookie);
> +
> +            EasyMock.expect(request.getRequestedSessionId()).
> andStubReturn(sessionId);
> +            EasyMock.expect(request.getRequestURI()).
> andStubReturn(requestURI);
> +            
> EasyMock.expect(request.getCookies()).andStubReturn(cookies.toArray(new
> Cookie[cookies.size()]));
> +            EasyMock.expect(servletContext.getSessionCookieConfig()).
> andStubReturn(cookieConfig);
> +            EasyMock.expect(request.getServletContext()).
> andStubReturn(servletContext);
> +            EasyMock.expect(request.getContext()).andStubReturn(ctx);
> +            EasyMock.expect(ctx.getSessionCookiePathUsesTraili
> ngSlash()).andStubReturn(true);
> +            EasyMock.expect(servletContext.getSessionCookieConfig()).
> andStubReturn(cookieConfig);
> +            EasyMock.expect(request.getQueryString()).
> andStubReturn(queryString);
> +
> +           if(!enableIgnore) {
> +                // Response will have cookie deleted
> +                MyCookie expectedCookie = new
> MyCookie(cookieConfig.getName(), "");
> +                expectedCookie.setPath(cookieConfig.getPath());
> +                expectedCookie.setMaxAge(0);
> +
> +                // These two lines just mean 
> EasyMock.expect(response.addCookie)
> but for a void method
> +                response.addCookie(expectedCookie);
> +                
> EasyMock.expect(ctx.getSessionCookieName()).andReturn(sessionCookieName);
> // Indirect call
> +                String expectedRequestURI = requestURI;
> +                if(null != queryString)
> +                    expectedRequestURI = expectedRequestURI + '?' +
> queryString;
> +                response.setHeader("Location", expectedRequestURI);
> +                response.setStatus(307);
> +            }
> +        }
> +
> +        Valve next = control.createMock(Valve.class);
> +
> +        if(expectInvokeNext) {
> +            // Expect the "next" Valve to fire
> +            // Next 2 lines are basically 
> EasyMock.expect(next.invoke(req,res))
> but for a void method
> +            next.invoke(request, response);
> +            EasyMock.expectLastCall();
> +        }
> +
> +        // Get set to actually test
> +        control.replay();
> +
> +        LoadBalancerDrainingValve valve = new LoadBalancerDrainingValve();
> +        valve.setContainer(ctx);
> +        valve.init();
> +        valve.setNext(next);
> +        valve.setIgnoreCookieName("ignore");
> +        valve.setIgnoreCookieValue("true");
> +
> +        valve.invoke(request, response);
> +
> +        control.verify();
> +    }
> +}
>
> Propchange: tomcat/trunk/test/org/apache/catalina/valves/
> TestLoadBalancerDrainingValve.java
> ------------------------------------------------------------
> ------------------
>     svn:eol-style = native
>
> Modified: tomcat/trunk/webapps/docs/changelog.xml
> URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/
> changelog.xml?rev=1799498&r1=1799497&r2=1799498&view=diff
> ============================================================
> ==================
> --- tomcat/trunk/webapps/docs/changelog.xml (original)
> +++ tomcat/trunk/webapps/docs/changelog.xml Wed Jun 21 19:05:38 2017
> @@ -138,6 +138,10 @@
>          variable for CGI executables is populated in a consistent way
> regardless
>          of how the CGI servlet is mapped to a request. (markt)
>        </fix>
> +      <add>
> +        Add LoadBalancerDrainingValve, a Valve designed to reduce the
> amount of
> +        time required for a node to drain its authenticated users.
> (schultz)
> +      </add>
>      </changelog>
>    </subsection>
>    <subsection name="Coyote">
>
> Modified: tomcat/trunk/webapps/docs/config/valve.xml
> URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/
> config/valve.xml?rev=1799498&r1=1799497&r2=1799498&view=diff
> ============================================================
> ==================
> --- tomcat/trunk/webapps/docs/config/valve.xml (original)
> +++ tomcat/trunk/webapps/docs/config/valve.xml Wed Jun 21 19:05:38 2017
> @@ -700,6 +700,81 @@
>
>
>  <section name="Proxies Support">
> +  <subsection name="Load Balancer Draining Valve">
> +    <subsection name="Introduction">
> +      <p>
> +        When using mod_jk or mod_proxy_ajp, the client's session id is
> used to
> +        determine which back-end server will be used to serve the
> request. If the
> +        target node is being "drained" (in mod_jk, this is the
> <i>DISABLED</i>
> +        state; in mod_proxy_ajp, this is the <i>Drain (N)</i> state),
> requests
> +        for expired sessions can actually cause the draining node to fail
> to
> +        drain.
> +      </p>
> +      <p>
> +        Unfortunately, AJP-based load-balancers cannot prove whether the
> +        client-provided session id is valid or not and therefore will
> send any
> +        requests for a session that appears to be targeted to that node
> to the
> +        disabled (or "draining") node, causing the "draining" process to
> take
> +        longer than necessary.
> +      </p>
> +      <p>
> +        This Valve detects requests for invalid sessions, strips the
> session
> +        information from the request, and redirects back to the same URL,
> where
> +        the load-balancer should choose a different (active)  node to
> handle the
> +        request. This will accelerate the "draining" process for the
> disabled
> +        node(s).
> +      </p>
> +
> +      <p>
> +        The activation state of the node is sent by the load-balancer in
> the
> +        request, so no state change on the node being disabled is
> necessary. Simply
> +        configure this Valve in your valve pipeline and it will take
> action when
> +        the activation state is set to "disabled".
> +      </p>
> +
> +      <p>
> +        You should take care to register this Valve earlier in the Valve
> pipeline
> +        than any authentication Valves, because this Valve should be able
> to
> +        redirect a request before any authentication Valve saves a
> request to a
> +        protected resource. If this happens, a new session will be
> created and
> +        the draining process will stall because a new, valid session will
> be
> +        established.
> +      </p>
> +    </subsection><!-- / Introduction -->
> +
> +    <subsection name="Attributes">
> +      <p>The <strong>Load Balancer Draining Valve</strong> supports the
> +      following configuration attributes:</p>
> +
> +      <attributes>
> +        <attribute name="className" required="true">
> +          <p>Java class name of the implementation to use. This MUST be
> set to
> +          <strong>org.apache.catalina.valves.LoadBalancerDrainingValve</
> strong>.
> +          </p>
> +        </attribute>
> +
> +        <attribute name="redirectStatusCode" required="false">
> +          <p>Allows setting a custom redirect code to be used when the
> client
> +          is redirected to be re-balanced by the load-balancer. The
> default is
> +          307 TEMPORARY_REDIRECT.</p>
> +        </attribute>
> +
> +        <attribute name="ignoreCookieName" required="false">
> +          <p>When used with <code>ignoreCookieValue</code>, a client can
> present
> +          this cookie (and accompanying value) that will cause this Valve
> to
> +          do nothing. This will allow you to probe your <i>disabled</i>
> node
> +          before re-enabling it to make sure that it is working as
> expected.</p>
> +        </attribute>
> +
> +        <attribute name="ignoreCookieValue" required="false">
> +          <p>When used with <code>ignoreCookieName</code>, a client can
> present
> +          a cookie (and accompanying value) that will cause this Valve to
> +          do nothing. This will allow you to probe your <i>disabled</i>
> node
> +          before re-enabling it to make sure that it is working as
> expected.</p>
> +        </attribute>
> +      </attributes>
> +    </subsection><!-- /Attributes -->
> +  </subsection><!-- /Load Balancer Draining Valve -->
>
>  <subsection name="Remote IP Valve">
>
>
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
> For additional commands, e-mail: dev-h...@tomcat.apache.org
>
>

Reply via email to