View | Details | Raw Unified | Return to bug 62988
Collapse All | Expand All

(-)java/org/apache/catalina/valves/LoadBalancerDrainingValve.java (-75 / +71 lines)
Lines 19-27 Link Here
19
import java.io.IOException;
19
import java.io.IOException;
20
20
21
import javax.servlet.ServletException;
21
import javax.servlet.ServletException;
22
import javax.servlet.SessionCookieConfig;
22
import javax.servlet.http.Cookie;
23
import javax.servlet.http.Cookie;
23
import javax.servlet.http.HttpServletResponse;
24
import javax.servlet.http.HttpServletResponse;
24
25
26
import org.apache.catalina.Context;
25
import org.apache.catalina.connector.Request;
27
import org.apache.catalina.connector.Request;
26
import org.apache.catalina.connector.Response;
28
import org.apache.catalina.connector.Response;
27
import org.apache.catalina.util.SessionConfig;
29
import org.apache.catalina.util.SessionConfig;
Lines 36-44 Link Here
36
 * and the client should be redirected to the same URI. This will cause the
38
 * and the client should be redirected to the same URI. This will cause the
37
 * load-balanced to re-balance the client to another server.</p>
39
 * load-balanced to re-balance the client to another server.</p>
38
 *
40
 *
39
 * <p>A request parameter is added to the redirect URI in order to avoid
40
 * repeated redirects in the event of an error or misconfiguration.</p>
41
 *
42
 * <p>All this work is required because when the activation state of a node is
41
 * <p>All this work is required because when the activation state of a node is
43
 * DISABLED, the load-balancer will still send requests to the node if they
42
 * DISABLED, the load-balancer will still send requests to the node if they
44
 * appear to have a session on that node. Since mod_jk doesn't actually know
43
 * appear to have a session on that node. Since mod_jk doesn't actually know
Lines 59-67 Link Here
59
 * @see <a href="https://tomcat.apache.org/connectors-doc/generic_howto/loadbalancers.html">Load
58
 * @see <a href="https://tomcat.apache.org/connectors-doc/generic_howto/loadbalancers.html">Load
60
 *      balancer documentation</a>
59
 *      balancer documentation</a>
61
 */
60
 */
62
public class LoadBalancerDrainingValve
61
public class LoadBalancerDrainingValve extends ValveBase {
63
    extends ValveBase
62
64
{
65
    /**
63
    /**
66
     * The request attribute key where the load-balancer's activation state
64
     * The request attribute key where the load-balancer's activation state
67
     * can be found.
65
     * can be found.
Lines 90-96 Link Here
90
     * The value of the cookie which can be set to ignore the "draining" action
88
     * The value of the cookie which can be set to ignore the "draining" action
91
     * of this Filter. This will allow a client to contact the server without
89
     * of this Filter. This will allow a client to contact the server without
92
     * being re-balanced to another server. The expected cookie name can be set
90
     * being re-balanced to another server. The expected cookie name can be set
93
     * in the {@link #_ignoreCookieValue}. The cookie name and value must match
91
     * in the {@link #_ignoreCookieName}. The cookie name and value must match
94
     * to avoid being re-balanced.
92
     * to avoid being re-balanced.
95
     */
93
     */
96
    private String _ignoreCookieValue;
94
    private String _ignoreCookieValue;
Lines 115-133 Link Here
115
    }
113
    }
116
114
117
    /**
115
    /**
118
     * Gets the name of the cookie that can be used to override the
119
     * re-balancing behavior of this Valve when the current node is
120
     * in the DISABLED activation state.
121
     *
122
     * @return The cookie name used to ignore normal processing rules.
123
     *
124
     * @see #setIgnoreCookieValue
125
     */
126
    public String getIgnoreCookieName() {
127
        return _ignoreCookieName;
128
    }
129
130
    /**
131
     * Sets the name of the cookie that can be used to override the
116
     * Sets the name of the cookie that can be used to override the
132
     * re-balancing behavior of this Valve when the current node is
117
     * re-balancing behavior of this Valve when the current node is
133
     * in the DISABLED activation state.
118
     * in the DISABLED activation state.
Lines 137-144 Link Here
137
     *
122
     *
138
     * @param cookieName The cookie name to use to ignore normal
123
     * @param cookieName The cookie name to use to ignore normal
139
     *                   processing rules.
124
     *                   processing rules.
140
     *
141
     * @see #getIgnoreCookieValue
142
     */
125
     */
143
    public void setIgnoreCookieName(String cookieName) {
126
    public void setIgnoreCookieName(String cookieName) {
144
        _ignoreCookieName = cookieName;
127
        _ignoreCookieName = cookieName;
Lines 145-163 Link Here
145
    }
128
    }
146
129
147
    /**
130
    /**
148
     * Gets the expected value of the cookie that can be used to override the
149
     * re-balancing behavior of this Valve when the current node is
150
     * in the DISABLED activation state.
151
     *
152
     * @return The cookie value used to ignore normal processing rules.
153
     *
154
     * @see #setIgnoreCookieValue
155
     */
156
    public String getIgnoreCookieValue() {
157
        return _ignoreCookieValue;
158
    }
159
160
    /**
161
     * Sets the expected value of the cookie that can be used to override the
131
     * Sets the expected value of the cookie that can be used to override the
162
     * re-balancing behavior of this Valve when the current node is
132
     * re-balancing behavior of this Valve when the current node is
163
     * in the DISABLED activation state. The "ignore" cookie's value
133
     * in the DISABLED activation state. The "ignore" cookie's value
Lines 166-173 Link Here
166
     *
136
     *
167
     * @param cookieValue The cookie value to use to ignore normal
137
     * @param cookieValue The cookie value to use to ignore normal
168
     *                    processing rules.
138
     *                    processing rules.
169
     *
170
     * @see #getIgnoreCookieValue
171
     */
139
     */
172
    public void setIgnoreCookieValue(String cookieValue) {
140
    public void setIgnoreCookieValue(String cookieValue) {
173
        _ignoreCookieValue = cookieValue;
141
        _ignoreCookieValue = cookieValue;
Lines 178-195 Link Here
178
        if("DIS".equals(request.getAttribute(ATTRIBUTE_KEY_JK_LB_ACTIVATION))
146
        if("DIS".equals(request.getAttribute(ATTRIBUTE_KEY_JK_LB_ACTIVATION))
179
           && !request.isRequestedSessionIdValid()) {
147
           && !request.isRequestedSessionIdValid()) {
180
148
181
            if(containerLog.isDebugEnabled())
149
            if(containerLog.isDebugEnabled()) {
182
                containerLog.debug("Load-balancer is in DISABLED state; draining this node");
150
                containerLog.debug("Load-balancer is in DISABLED state; draining this node");
151
            }
183
152
184
            boolean ignoreRebalance = false; // Allow certain clients
153
            boolean ignoreRebalance = false;
185
            Cookie sessionCookie = null;
154
            Cookie sessionCookie = null;
186
155
187
            // Kill any session cookie present
188
            final Cookie[] cookies = request.getCookies();
156
            final Cookie[] cookies = request.getCookies();
189
157
190
            final String sessionCookieName = request.getServletContext().getSessionCookieConfig().getName();
158
            final String sessionCookieName = SessionConfig.getSessionCookieName(request.getContext());
191
159
192
            // Kill any session cookie present
193
            if(null != cookies) {
160
            if(null != cookies) {
194
                for(Cookie cookie : cookies) {
161
                for(Cookie cookie : cookies) {
195
                    final String cookieName = cookie.getName();
162
                    final String cookieName = cookie.getName();
Lines 199-219 Link Here
199
                    if(sessionCookieName.equals(cookieName)
166
                    if(sessionCookieName.equals(cookieName)
200
                       && request.getRequestedSessionId().equals(cookie.getValue())) {
167
                       && request.getRequestedSessionId().equals(cookie.getValue())) {
201
                        sessionCookie = cookie;
168
                        sessionCookie = cookie;
202
                    } else
169
                    } else {
203
                    // Is the client presenting a valid ignore-cookie value?
170
                        // Is the client presenting a valid ignore-cookie value?
204
                    if(null != _ignoreCookieName
171
                        if (null != _ignoreCookieName
205
                            && _ignoreCookieName.equals(cookieName)
172
                                && _ignoreCookieName.equals(cookieName)
206
                            && null != _ignoreCookieValue
173
                                && null != _ignoreCookieValue
207
                            && _ignoreCookieValue.equals(cookie.getValue())) {
174
                                && _ignoreCookieValue.equals(cookie.getValue())) {
208
                        ignoreRebalance = true;
175
                            ignoreRebalance = true;
176
                        }
209
                    }
177
                    }
210
                }
178
                }
211
            }
179
            }
212
180
213
            if(ignoreRebalance) {
181
            if(ignoreRebalance) {
214
                if(containerLog.isDebugEnabled())
182
                if(containerLog.isDebugEnabled()) {
215
                    containerLog.debug("Client is presenting a valid " + _ignoreCookieName
183
                    containerLog.debug("Client is presenting a valid " + _ignoreCookieName
216
                                 + " cookie, re-balancing is being skipped");
184
                            + " cookie, re-balancing is being skipped");
185
                }
217
186
218
                getNext().invoke(request, response);
187
                getNext().invoke(request, response);
219
188
Lines 220-262 Link Here
220
                return;
189
                return;
221
            }
190
            }
222
191
223
            // Kill any session cookie that was found
192
            // Kill any session cookie that was found.
224
            // TODO: Consider implications of SSO cookies
193
            // TODO: Consider implications of SSO cookies.
225
            if(null != sessionCookie) {
194
            if (null != sessionCookie) {
226
                String cookiePath = request.getServletContext().getSessionCookieConfig().getPath();
195
                sessionCookie.setPath(determineSessionCookiePath(request));
227
196
                sessionCookie.setMaxAge(0); // Delete
228
                if(request.getContext().getSessionCookiePathUsesTrailingSlash()) {
197
                sessionCookie.setValue(""); // Purge the cookie's value
229
                    // Handle special case of ROOT context where cookies require a path of
198
                response.addCookie(sessionCookie);
230
                    // '/' but the servlet spec uses an empty string
231
                    // Also ensure the cookies for a context with a path of /foo don't get
232
                    // sent for requests with a path of /foobar
233
                    if (!cookiePath.endsWith("/"))
234
                        cookiePath = cookiePath + "/";
235
236
                    sessionCookie.setPath(cookiePath);
237
                    sessionCookie.setMaxAge(0); // Delete
238
                    sessionCookie.setValue(""); // Purge the cookie's value
239
                    response.addCookie(sessionCookie);
240
                }
241
            }
199
            }
242
200
243
            // Re-write the URI if it contains a ;jsessionid parameter
201
            // Re-write the URI if it contains a ;jsessionid parameter.
244
            String uri = request.getRequestURI();
202
            String uri = request.getRequestURI();
245
            String sessionURIParamName = SessionConfig.getSessionUriParamName(request.getContext());
203
            String sessionURIParamName = SessionConfig.getSessionUriParamName(request.getContext());
246
            if(uri.contains(";" + sessionURIParamName + "="))
204
            if(uri.contains(";" + sessionURIParamName + "=")) {
247
                uri = uri.replaceFirst(";" + sessionURIParamName + "=[^&?]*", "");
205
                uri = uri.replaceFirst(";" + sessionURIParamName + "=[^&?]*", "");
206
            }
248
207
249
            String queryString = request.getQueryString();
208
            String queryString = request.getQueryString();
250
209
251
            if(null != queryString)
210
            if(null != queryString) {
252
                uri = uri + "?" + queryString;
211
                uri = uri + "?" + queryString;
212
            }
253
213
254
            // NOTE: Do not call response.encodeRedirectURL or the bad
214
            // NOTE: Do not call response.encodeRedirectURL or the bad
255
            // sessionid will be restored
215
            // sessionid will be restored.
256
            response.setHeader("Location", uri);
216
            response.setHeader("Location", uri);
257
            response.setStatus(_redirectStatusCode);
217
            response.setStatus(_redirectStatusCode);
218
219
        } else {
220
            getNext().invoke(request, response);
258
        }
221
        }
259
        else
260
            getNext().invoke(request, response);
261
    }
222
    }
223
224
    // TODO This is basically copied from ApplicationSessionCookieConfig. Should be
225
    // refactored to avoid code duplication.
226
    private String determineSessionCookiePath(final Request request) {
227
        Context context = request.getContext();
228
        SessionCookieConfig scc = context.getServletContext().getSessionCookieConfig();
229
        String contextPath = context.getSessionCookiePath();
230
        if (!have(contextPath)) {
231
            contextPath = scc.getPath();
232
        }
233
        if (!have(contextPath)) {
234
            contextPath = context.getEncodedPath();
235
        }
236
        if (context.getSessionCookiePathUsesTrailingSlash()) {
237
            // Handle special case of ROOT context where cookies require a path of
238
            // '/' but the servlet spec uses an empty string
239
            // Also ensure the cookies for a context with a path of /foo don't get
240
            // sent for requests with a path of /foobar
241
            if (!contextPath.endsWith("/")) {
242
                contextPath = contextPath + "/";
243
            }
244
        } else {
245
            // Only handle special case of ROOT context where cookies require a
246
            // path of '/' but the servlet spec uses an empty string
247
            if (contextPath.length() == 0) {
248
                contextPath = "/";
249
            }
250
        }
251
        return contextPath;
252
    }
253
254
    private boolean have(final String s) {
255
        return s != null && !s.isEmpty();
256
    }
257
262
}
258
}
(-)test/org/apache/catalina/valves/TestLoadBalancerDrainingValve.java (-7 / +7 lines)
Lines 22-27 Link Here
22
import javax.servlet.SessionCookieConfig;
22
import javax.servlet.SessionCookieConfig;
23
import javax.servlet.http.Cookie;
23
import javax.servlet.http.Cookie;
24
24
25
import org.apache.catalina.util.SessionConfig;
25
import org.junit.Test;
26
import org.junit.Test;
26
27
27
import org.apache.catalina.Context;
28
import org.apache.catalina.Context;
Lines 224-245 Link Here
224
            EasyMock.expect(request.getRequestedSessionId()).andStubReturn(sessionId);
225
            EasyMock.expect(request.getRequestedSessionId()).andStubReturn(sessionId);
225
            EasyMock.expect(request.getRequestURI()).andStubReturn(requestURI);
226
            EasyMock.expect(request.getRequestURI()).andStubReturn(requestURI);
226
            EasyMock.expect(request.getCookies()).andStubReturn(cookies.toArray(new Cookie[cookies.size()]));
227
            EasyMock.expect(request.getCookies()).andStubReturn(cookies.toArray(new Cookie[cookies.size()]));
227
            EasyMock.expect(servletContext.getSessionCookieConfig()).andStubReturn(cookieConfig);
228
            EasyMock.expect(request.getServletContext()).andStubReturn(servletContext);
229
            EasyMock.expect(request.getContext()).andStubReturn(ctx);
228
            EasyMock.expect(request.getContext()).andStubReturn(ctx);
230
            EasyMock.expect(Boolean.valueOf(ctx.getSessionCookiePathUsesTrailingSlash())).andStubReturn(Boolean.TRUE);
229
            EasyMock.expect(ctx.getSessionCookieName()).andStubReturn(sessionCookieName);
231
            EasyMock.expect(servletContext.getSessionCookieConfig()).andStubReturn(cookieConfig);
230
            EasyMock.expect(servletContext.getSessionCookieConfig()).andStubReturn(cookieConfig);
232
            EasyMock.expect(request.getQueryString()).andStubReturn(queryString);
231
            EasyMock.expect(ctx.getSessionCookiePath()).andStubReturn("/");
233
232
234
           if(!enableIgnore) {
233
            if(!enableIgnore) {
234
                EasyMock.expect(Boolean.valueOf(ctx.getSessionCookiePathUsesTrailingSlash())).andStubReturn(Boolean.TRUE);
235
                EasyMock.expect(request.getQueryString()).andStubReturn(queryString);
236
235
                // Response will have cookie deleted
237
                // Response will have cookie deleted
236
                MyCookie expectedCookie = new MyCookie(cookieConfig.getName(), "");
238
                MyCookie expectedCookie = new MyCookie(cookieConfig.getName(), "");
237
                expectedCookie.setPath(cookieConfig.getPath());
239
                expectedCookie.setPath(cookieConfig.getPath());
238
                expectedCookie.setMaxAge(0);
240
                expectedCookie.setMaxAge(0);
239
241
240
                // These two lines just mean EasyMock.expect(response.addCookie) but for a void method
241
                response.addCookie(expectedCookie);
242
                response.addCookie(expectedCookie);
242
                EasyMock.expect(ctx.getSessionCookieName()).andReturn(sessionCookieName); // Indirect call
243
                String expectedRequestURI = requestURI;
243
                String expectedRequestURI = requestURI;
244
                if(null != queryString)
244
                if(null != queryString)
245
                    expectedRequestURI = expectedRequestURI + '?' + queryString;
245
                    expectedRequestURI = expectedRequestURI + '?' + queryString;

Return to bug 62988