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";
+
+ /**
+ * 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;
+ } 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());
+ 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.getSessionCookiePathUsesTrailingSlash()).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: [email protected]
For additional commands, e-mail: [email protected]