1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.servlets;
20
21 import java.net.MalformedURLException;
22 import java.net.URI;
23 import java.net.URISyntaxException;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.LinkedList;
29 import java.util.List;
30 import java.util.Set;
31 import java.util.concurrent.atomic.AtomicInteger;
32
33 import javax.servlet.ServletConfig;
34 import javax.servlet.ServletException;
35 import javax.servlet.UnavailableException;
36 import javax.servlet.http.Cookie;
37 import javax.servlet.http.HttpServletRequest;
38
39 import org.eclipse.jetty.http.HttpURI;
40 import org.eclipse.jetty.server.Request;
41
42
43
44
45 public class BalancerServlet extends ProxyServlet
46 {
47
48 private static final class BalancerMember
49 {
50
51 private String _name;
52
53 private String _proxyTo;
54
55 private HttpURI _backendURI;
56
57 public BalancerMember(String name, String proxyTo)
58 {
59 super();
60 _name = name;
61 _proxyTo = proxyTo;
62 _backendURI = new HttpURI(_proxyTo);
63 }
64
65 public String getProxyTo()
66 {
67 return _proxyTo;
68 }
69
70 public HttpURI getBackendURI()
71 {
72 return _backendURI;
73 }
74
75 @Override
76 public String toString()
77 {
78 return "BalancerMember [_name=" + _name + ", _proxyTo=" + _proxyTo + "]";
79 }
80
81 @Override
82 public int hashCode()
83 {
84 final int prime = 31;
85 int result = 1;
86 result = prime * result + ((_name == null)?0:_name.hashCode());
87 return result;
88 }
89
90 @Override
91 public boolean equals(Object obj)
92 {
93 if (this == obj)
94 return true;
95 if (obj == null)
96 return false;
97 if (getClass() != obj.getClass())
98 return false;
99 BalancerMember other = (BalancerMember)obj;
100 if (_name == null)
101 {
102 if (other._name != null)
103 return false;
104 }
105 else if (!_name.equals(other._name))
106 return false;
107 return true;
108 }
109
110 }
111
112 private static final class RoundRobinIterator implements Iterator<BalancerMember>
113 {
114
115 private BalancerMember[] _balancerMembers;
116
117 private AtomicInteger _index;
118
119 public RoundRobinIterator(Collection<BalancerMember> balancerMembers)
120 {
121 _balancerMembers = (BalancerMember[])balancerMembers.toArray(new BalancerMember[balancerMembers.size()]);
122 _index = new AtomicInteger(-1);
123 }
124
125 public boolean hasNext()
126 {
127 return true;
128 }
129
130 public BalancerMember next()
131 {
132 BalancerMember balancerMember = null;
133 while (balancerMember == null)
134 {
135 int currentIndex = _index.get();
136 int nextIndex = (currentIndex + 1) % _balancerMembers.length;
137 if (_index.compareAndSet(currentIndex,nextIndex))
138 {
139 balancerMember = _balancerMembers[nextIndex];
140 }
141 }
142 return balancerMember;
143 }
144
145 public void remove()
146 {
147 throw new UnsupportedOperationException();
148 }
149
150 }
151
152 private static final String BALANCER_MEMBER_PREFIX = "BalancerMember.";
153
154 private static final List<String> FORBIDDEN_CONFIG_PARAMETERS;
155 static
156 {
157 List<String> params = new LinkedList<String>();
158 params.add("HostHeader");
159 params.add("whiteList");
160 params.add("blackList");
161 FORBIDDEN_CONFIG_PARAMETERS = Collections.unmodifiableList(params);
162 }
163
164 private static final List<String> REVERSE_PROXY_HEADERS;
165 static
166 {
167 List<String> params = new LinkedList<String>();
168 params.add("Location");
169 params.add("Content-Location");
170 params.add("URI");
171 REVERSE_PROXY_HEADERS = Collections.unmodifiableList(params);
172 }
173
174 private static final String JSESSIONID = "jsessionid";
175
176 private static final String JSESSIONID_URL_PREFIX = JSESSIONID + "=";
177
178 private boolean _stickySessions;
179
180 private Set<BalancerMember> _balancerMembers = new HashSet<BalancerMember>();
181
182 private boolean _proxyPassReverse;
183
184 private RoundRobinIterator _roundRobinIterator;
185
186 @Override
187 public void init(ServletConfig config) throws ServletException
188 {
189 validateConfig(config);
190 super.init(config);
191 initStickySessions(config);
192 initBalancers(config);
193 initProxyPassReverse(config);
194 postInit();
195 }
196
197 private void validateConfig(ServletConfig config) throws ServletException
198 {
199 @SuppressWarnings("unchecked")
200 List<String> initParameterNames = Collections.list(config.getInitParameterNames());
201 for (String initParameterName : initParameterNames)
202 {
203 if (FORBIDDEN_CONFIG_PARAMETERS.contains(initParameterName))
204 {
205 throw new UnavailableException(initParameterName + " not supported in " + getClass().getName());
206 }
207 }
208 }
209
210 private void initStickySessions(ServletConfig config) throws ServletException
211 {
212 _stickySessions = "true".equalsIgnoreCase(config.getInitParameter("StickySessions"));
213 }
214
215 private void initBalancers(ServletConfig config) throws ServletException
216 {
217 Set<String> balancerNames = getBalancerNames(config);
218 for (String balancerName : balancerNames)
219 {
220 String memberProxyToParam = BALANCER_MEMBER_PREFIX + balancerName + ".ProxyTo";
221 String proxyTo = config.getInitParameter(memberProxyToParam);
222 if (proxyTo == null || proxyTo.trim().length() == 0)
223 {
224 throw new UnavailableException(memberProxyToParam + " parameter is empty.");
225 }
226 _balancerMembers.add(new BalancerMember(balancerName,proxyTo));
227 }
228 }
229
230 private void initProxyPassReverse(ServletConfig config)
231 {
232 _proxyPassReverse = "true".equalsIgnoreCase(config.getInitParameter("ProxyPassReverse"));
233 }
234
235 private void postInit()
236 {
237 _roundRobinIterator = new RoundRobinIterator(_balancerMembers);
238 }
239
240 private Set<String> getBalancerNames(ServletConfig config) throws ServletException
241 {
242 Set<String> names = new HashSet<String>();
243 @SuppressWarnings("unchecked")
244 List<String> initParameterNames = Collections.list(config.getInitParameterNames());
245 for (String initParameterName : initParameterNames)
246 {
247 if (!initParameterName.startsWith(BALANCER_MEMBER_PREFIX))
248 {
249 continue;
250 }
251 int endOfNameIndex = initParameterName.lastIndexOf(".");
252 if (endOfNameIndex <= BALANCER_MEMBER_PREFIX.length())
253 {
254 throw new UnavailableException(initParameterName + " parameter does not provide a balancer member name");
255 }
256 names.add(initParameterName.substring(BALANCER_MEMBER_PREFIX.length(),endOfNameIndex));
257 }
258 return names;
259 }
260
261 @Override
262 protected HttpURI proxyHttpURI(HttpServletRequest request, String uri) throws MalformedURLException
263 {
264 BalancerMember balancerMember = selectBalancerMember(request);
265 try
266 {
267 URI dstUri = new URI(balancerMember.getProxyTo() + "/" + uri).normalize();
268 return new HttpURI(dstUri.toString());
269 }
270 catch (URISyntaxException e)
271 {
272 throw new MalformedURLException(e.getMessage());
273 }
274 }
275
276 private BalancerMember selectBalancerMember(HttpServletRequest request)
277 {
278 BalancerMember balancerMember = null;
279 if (_stickySessions)
280 {
281 String name = getBalancerMemberNameFromSessionId(request);
282 if (name != null)
283 {
284 balancerMember = findBalancerMemberByName(name);
285 if (balancerMember != null)
286 {
287 return balancerMember;
288 }
289 }
290 }
291 return _roundRobinIterator.next();
292 }
293
294 private BalancerMember findBalancerMemberByName(String name)
295 {
296 BalancerMember example = new BalancerMember(name,"");
297 for (BalancerMember balancerMember : _balancerMembers)
298 {
299 if (balancerMember.equals(example))
300 {
301 return balancerMember;
302 }
303 }
304 return null;
305 }
306
307 private String getBalancerMemberNameFromSessionId(HttpServletRequest request)
308 {
309 String name = getBalancerMemberNameFromSessionCookie(request);
310 if (name == null)
311 {
312 name = getBalancerMemberNameFromURL(request);
313 }
314 return name;
315 }
316
317 private String getBalancerMemberNameFromSessionCookie(HttpServletRequest request)
318 {
319 Cookie[] cookies = request.getCookies();
320 String name = null;
321 for (Cookie cookie : cookies)
322 {
323 if (JSESSIONID.equalsIgnoreCase(cookie.getName()))
324 {
325 name = extractBalancerMemberNameFromSessionId(cookie.getValue());
326 break;
327 }
328 }
329 return name;
330 }
331
332 private String getBalancerMemberNameFromURL(HttpServletRequest request)
333 {
334 String name = null;
335 String requestURI = request.getRequestURI();
336 int idx = requestURI.lastIndexOf(";");
337 if (idx != -1)
338 {
339 String requestURISuffix = requestURI.substring(idx);
340 if (requestURISuffix.startsWith(JSESSIONID_URL_PREFIX))
341 {
342 name = extractBalancerMemberNameFromSessionId(requestURISuffix.substring(JSESSIONID_URL_PREFIX.length()));
343 }
344 }
345 return name;
346 }
347
348 private String extractBalancerMemberNameFromSessionId(String sessionId)
349 {
350 String name = null;
351 int idx = sessionId.lastIndexOf(".");
352 if (idx != -1)
353 {
354 String sessionIdSuffix = sessionId.substring(idx + 1);
355 name = (sessionIdSuffix.length() > 0)?sessionIdSuffix:null;
356 }
357 return name;
358 }
359
360 @Override
361 protected String filterResponseHeaderValue(String headerName, String headerValue, HttpServletRequest request)
362 {
363 if (_proxyPassReverse && REVERSE_PROXY_HEADERS.contains(headerName))
364 {
365 HttpURI locationURI = new HttpURI(headerValue);
366 if (isAbsoluteLocation(locationURI) && isBackendLocation(locationURI))
367 {
368 Request jettyRequest = (Request)request;
369 URI reverseUri;
370 try
371 {
372 reverseUri = new URI(jettyRequest.getRootURL().append(locationURI.getCompletePath()).toString()).normalize();
373 return reverseUri.toURL().toString();
374 }
375 catch (Exception e)
376 {
377 _log.warn("Not filtering header response",e);
378 return headerValue;
379 }
380 }
381 }
382 return headerValue;
383 }
384
385 private boolean isBackendLocation(HttpURI locationURI)
386 {
387 for (BalancerMember balancerMember : _balancerMembers)
388 {
389 HttpURI backendURI = balancerMember.getBackendURI();
390 if (backendURI.getHost().equals(locationURI.getHost()) && backendURI.getScheme().equals(locationURI.getScheme())
391 && backendURI.getPort() == locationURI.getPort())
392 {
393 return true;
394 }
395 }
396 return false;
397 }
398
399 private boolean isAbsoluteLocation(HttpURI locationURI)
400 {
401 return locationURI.getHost() != null;
402 }
403
404 @Override
405 public String getHostHeader()
406 {
407 throw new UnsupportedOperationException("HostHeader not supported in " + getClass().getName());
408 }
409
410 @Override
411 public void setHostHeader(String hostHeader)
412 {
413 throw new UnsupportedOperationException("HostHeader not supported in " + getClass().getName());
414 }
415
416 @Override
417 public boolean validateDestination(String host, String path)
418 {
419 return true;
420 }
421
422 }