1// 2// ======================================================================== 3// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. 4// ------------------------------------------------------------------------ 5// All rights reserved. This program and the accompanying materials 6// are made available under the terms of the Eclipse Public License v1.0 7// and Apache License v2.0 which accompanies this distribution. 8// 9// The Eclipse Public License is available at 10// http://www.eclipse.org/legal/epl-v10.html 11// 12// The Apache License v2.0 is available at 13// http://www.opensource.org/licenses/apache2.0.php 14// 15// You may elect to redistribute this code under either of these licenses. 16// ======================================================================== 17// 18 19package org.eclipse.jetty.servlets; 20 21import java.net.MalformedURLException; 22import java.net.URI; 23import java.net.URISyntaxException; 24import java.util.Collection; 25import java.util.Collections; 26import java.util.HashSet; 27import java.util.Iterator; 28import java.util.LinkedList; 29import java.util.List; 30import java.util.Set; 31import java.util.concurrent.atomic.AtomicInteger; 32 33import javax.servlet.ServletConfig; 34import javax.servlet.ServletException; 35import javax.servlet.UnavailableException; 36import javax.servlet.http.Cookie; 37import javax.servlet.http.HttpServletRequest; 38 39import org.eclipse.jetty.http.HttpURI; 40import org.eclipse.jetty.server.Request; 41 42/** 43 * 6 44 */ 45public 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} 423