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.io.IOException; 22import java.io.InputStream; 23import java.io.OutputStream; 24import java.net.InetSocketAddress; 25import java.net.MalformedURLException; 26import java.net.Socket; 27import java.net.URI; 28import java.net.URISyntaxException; 29import java.util.Collections; 30import java.util.Enumeration; 31import java.util.HashSet; 32import java.util.List; 33import java.util.Locale; 34import java.util.Map; 35import java.util.StringTokenizer; 36 37import javax.servlet.Servlet; 38import javax.servlet.ServletConfig; 39import javax.servlet.ServletContext; 40import javax.servlet.ServletException; 41import javax.servlet.ServletRequest; 42import javax.servlet.ServletResponse; 43import javax.servlet.UnavailableException; 44import javax.servlet.http.HttpServletRequest; 45import javax.servlet.http.HttpServletResponse; 46 47import org.eclipse.jetty.client.HttpClient; 48import org.eclipse.jetty.client.HttpExchange; 49import org.eclipse.jetty.continuation.Continuation; 50import org.eclipse.jetty.continuation.ContinuationSupport; 51import org.eclipse.jetty.http.HttpHeaderValues; 52import org.eclipse.jetty.http.HttpHeaders; 53import org.eclipse.jetty.http.HttpSchemes; 54import org.eclipse.jetty.http.HttpURI; 55import org.eclipse.jetty.http.PathMap; 56import org.eclipse.jetty.io.Buffer; 57import org.eclipse.jetty.io.EofException; 58import org.eclipse.jetty.util.HostMap; 59import org.eclipse.jetty.util.IO; 60import org.eclipse.jetty.util.log.Log; 61import org.eclipse.jetty.util.log.Logger; 62import org.eclipse.jetty.util.thread.QueuedThreadPool; 63 64/** 65 * Asynchronous Proxy Servlet. 66 * 67 * Forward requests to another server either as a standard web proxy (as defined by RFC2616) or as a transparent proxy. 68 * <p> 69 * This servlet needs the jetty-util and jetty-client classes to be available to the web application. 70 * <p> 71 * To facilitate JMX monitoring, the "HttpClient" and "ThreadPool" are set as context attributes prefixed with the servlet name. 72 * <p> 73 * The following init parameters may be used to configure the servlet: 74 * <ul> 75 * <li>name - Name of Proxy servlet (default: "ProxyServlet" 76 * <li>maxThreads - maximum threads 77 * <li>maxConnections - maximum connections per destination 78 * <li>timeout - the period in ms the client will wait for a response from the proxied server 79 * <li>idleTimeout - the period in ms a connection to proxied server can be idle for before it is closed 80 * <li>requestHeaderSize - the size of the request header buffer (d. 6,144) 81 * <li>requestBufferSize - the size of the request buffer (d. 12,288) 82 * <li>responseHeaderSize - the size of the response header buffer (d. 6,144) 83 * <li>responseBufferSize - the size of the response buffer (d. 32,768) 84 * <li>HostHeader - Force the host header to a particular value 85 * <li>whiteList - comma-separated list of allowed proxy destinations 86 * <li>blackList - comma-separated list of forbidden proxy destinations 87 * </ul> 88 * 89 * @see org.eclipse.jetty.server.handler.ConnectHandler 90 */ 91public class ProxyServlet implements Servlet 92{ 93 protected Logger _log; 94 protected HttpClient _client; 95 protected String _hostHeader; 96 97 protected HashSet<String> _DontProxyHeaders = new HashSet<String>(); 98 { 99 _DontProxyHeaders.add("proxy-connection"); 100 _DontProxyHeaders.add("connection"); 101 _DontProxyHeaders.add("keep-alive"); 102 _DontProxyHeaders.add("transfer-encoding"); 103 _DontProxyHeaders.add("te"); 104 _DontProxyHeaders.add("trailer"); 105 _DontProxyHeaders.add("proxy-authorization"); 106 _DontProxyHeaders.add("proxy-authenticate"); 107 _DontProxyHeaders.add("upgrade"); 108 } 109 110 protected ServletConfig _config; 111 protected ServletContext _context; 112 protected HostMap<PathMap> _white = new HostMap<PathMap>(); 113 protected HostMap<PathMap> _black = new HostMap<PathMap>(); 114 115 /* ------------------------------------------------------------ */ 116 /* 117 * (non-Javadoc) 118 * 119 * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig) 120 */ 121 public void init(ServletConfig config) throws ServletException 122 { 123 _config = config; 124 _context = config.getServletContext(); 125 126 _hostHeader = config.getInitParameter("HostHeader"); 127 128 try 129 { 130 _log = createLogger(config); 131 132 _client = createHttpClient(config); 133 134 if (_context != null) 135 { 136 _context.setAttribute(config.getServletName() + ".ThreadPool",_client.getThreadPool()); 137 _context.setAttribute(config.getServletName() + ".HttpClient",_client); 138 } 139 140 String white = config.getInitParameter("whiteList"); 141 if (white != null) 142 { 143 parseList(white,_white); 144 } 145 String black = config.getInitParameter("blackList"); 146 if (black != null) 147 { 148 parseList(black,_black); 149 } 150 } 151 catch (Exception e) 152 { 153 throw new ServletException(e); 154 } 155 } 156 157 public void destroy() 158 { 159 try 160 { 161 _client.stop(); 162 } 163 catch (Exception x) 164 { 165 _log.debug(x); 166 } 167 } 168 169 170 /** 171 * Create and return a logger based on the ServletConfig for use in the 172 * proxy servlet 173 * 174 * @param config 175 * @return Logger 176 */ 177 protected Logger createLogger(ServletConfig config) 178 { 179 return Log.getLogger("org.eclipse.jetty.servlets." + config.getServletName()); 180 } 181 182 /** 183 * Create and return an HttpClientInstance 184 * 185 * @return HttpClient 186 */ 187 protected HttpClient createHttpClientInstance() 188 { 189 return new HttpClient(); 190 } 191 192 /** 193 * Create and return an HttpClient based on ServletConfig 194 * 195 * By default this implementation will create an instance of the 196 * HttpClient for use by this proxy servlet. 197 * 198 * @param config 199 * @return HttpClient 200 * @throws Exception 201 */ 202 protected HttpClient createHttpClient(ServletConfig config) throws Exception 203 { 204 HttpClient client = createHttpClientInstance(); 205 client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); 206 207 String t = config.getInitParameter("maxThreads"); 208 209 if (t != null) 210 { 211 client.setThreadPool(new QueuedThreadPool(Integer.parseInt(t))); 212 } 213 else 214 { 215 client.setThreadPool(new QueuedThreadPool()); 216 } 217 218 ((QueuedThreadPool)client.getThreadPool()).setName(config.getServletName()); 219 220 t = config.getInitParameter("maxConnections"); 221 222 if (t != null) 223 { 224 client.setMaxConnectionsPerAddress(Integer.parseInt(t)); 225 } 226 227 t = config.getInitParameter("timeout"); 228 229 if ( t != null ) 230 { 231 client.setTimeout(Long.parseLong(t)); 232 } 233 234 t = config.getInitParameter("idleTimeout"); 235 236 if ( t != null ) 237 { 238 client.setIdleTimeout(Long.parseLong(t)); 239 } 240 241 t = config.getInitParameter("requestHeaderSize"); 242 243 if ( t != null ) 244 { 245 client.setRequestHeaderSize(Integer.parseInt(t)); 246 } 247 248 t = config.getInitParameter("requestBufferSize"); 249 250 if ( t != null ) 251 { 252 client.setRequestBufferSize(Integer.parseInt(t)); 253 } 254 255 t = config.getInitParameter("responseHeaderSize"); 256 257 if ( t != null ) 258 { 259 client.setResponseHeaderSize(Integer.parseInt(t)); 260 } 261 262 t = config.getInitParameter("responseBufferSize"); 263 264 if ( t != null ) 265 { 266 client.setResponseBufferSize(Integer.parseInt(t)); 267 } 268 269 client.start(); 270 271 return client; 272 } 273 274 /* ------------------------------------------------------------ */ 275 /** 276 * Helper function to process a parameter value containing a list of new entries and initialize the specified host map. 277 * 278 * @param list 279 * comma-separated list of new entries 280 * @param hostMap 281 * target host map 282 */ 283 private void parseList(String list, HostMap<PathMap> hostMap) 284 { 285 if (list != null && list.length() > 0) 286 { 287 int idx; 288 String entry; 289 290 StringTokenizer entries = new StringTokenizer(list,","); 291 while (entries.hasMoreTokens()) 292 { 293 entry = entries.nextToken(); 294 idx = entry.indexOf('/'); 295 296 String host = idx > 0?entry.substring(0,idx):entry; 297 String path = idx > 0?entry.substring(idx):"/*"; 298 299 host = host.trim(); 300 PathMap pathMap = hostMap.get(host); 301 if (pathMap == null) 302 { 303 pathMap = new PathMap(true); 304 hostMap.put(host,pathMap); 305 } 306 if (path != null) 307 { 308 pathMap.put(path,path); 309 } 310 } 311 } 312 } 313 314 /* ------------------------------------------------------------ */ 315 /** 316 * Check the request hostname and path against white- and blacklist. 317 * 318 * @param host 319 * hostname to check 320 * @param path 321 * path to check 322 * @return true if request is allowed to be proxied 323 */ 324 public boolean validateDestination(String host, String path) 325 { 326 if (_white.size() > 0) 327 { 328 boolean match = false; 329 330 Object whiteObj = _white.getLazyMatches(host); 331 if (whiteObj != null) 332 { 333 List whiteList = (whiteObj instanceof List)?(List)whiteObj:Collections.singletonList(whiteObj); 334 335 for (Object entry : whiteList) 336 { 337 PathMap pathMap = ((Map.Entry<String, PathMap>)entry).getValue(); 338 if (match = (pathMap != null && (pathMap.size() == 0 || pathMap.match(path) != null))) 339 break; 340 } 341 } 342 343 if (!match) 344 return false; 345 } 346 347 if (_black.size() > 0) 348 { 349 Object blackObj = _black.getLazyMatches(host); 350 if (blackObj != null) 351 { 352 List blackList = (blackObj instanceof List)?(List)blackObj:Collections.singletonList(blackObj); 353 354 for (Object entry : blackList) 355 { 356 PathMap pathMap = ((Map.Entry<String, PathMap>)entry).getValue(); 357 if (pathMap != null && (pathMap.size() == 0 || pathMap.match(path) != null)) 358 return false; 359 } 360 } 361 } 362 363 return true; 364 } 365 366 /* ------------------------------------------------------------ */ 367 /* 368 * (non-Javadoc) 369 * 370 * @see javax.servlet.Servlet#getServletConfig() 371 */ 372 public ServletConfig getServletConfig() 373 { 374 return _config; 375 } 376 377 /* ------------------------------------------------------------ */ 378 /** 379 * Get the hostHeader. 380 * 381 * @return the hostHeader 382 */ 383 public String getHostHeader() 384 { 385 return _hostHeader; 386 } 387 388 /* ------------------------------------------------------------ */ 389 /** 390 * Set the hostHeader. 391 * 392 * @param hostHeader 393 * the hostHeader to set 394 */ 395 public void setHostHeader(String hostHeader) 396 { 397 _hostHeader = hostHeader; 398 } 399 400 /* ------------------------------------------------------------ */ 401 /* 402 * (non-Javadoc) 403 * 404 * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse) 405 */ 406 public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException 407 { 408 final int debug = _log.isDebugEnabled()?req.hashCode():0; 409 410 final HttpServletRequest request = (HttpServletRequest)req; 411 final HttpServletResponse response = (HttpServletResponse)res; 412 413 if ("CONNECT".equalsIgnoreCase(request.getMethod())) 414 { 415 handleConnect(request,response); 416 } 417 else 418 { 419 final InputStream in = request.getInputStream(); 420 final OutputStream out = response.getOutputStream(); 421 422 final Continuation continuation = ContinuationSupport.getContinuation(request); 423 424 if (!continuation.isInitial()) 425 response.sendError(HttpServletResponse.SC_GATEWAY_TIMEOUT); // Need better test that isInitial 426 else 427 { 428 429 String uri = request.getRequestURI(); 430 if (request.getQueryString() != null) 431 uri += "?" + request.getQueryString(); 432 433 HttpURI url = proxyHttpURI(request,uri); 434 435 if (debug != 0) 436 _log.debug(debug + " proxy " + uri + "-->" + url); 437 438 if (url == null) 439 { 440 response.sendError(HttpServletResponse.SC_FORBIDDEN); 441 return; 442 } 443 444 HttpExchange exchange = new HttpExchange() 445 { 446 @Override 447 protected void onRequestCommitted() throws IOException 448 { 449 } 450 451 @Override 452 protected void onRequestComplete() throws IOException 453 { 454 } 455 456 @Override 457 protected void onResponseComplete() throws IOException 458 { 459 if (debug != 0) 460 _log.debug(debug + " complete"); 461 continuation.complete(); 462 } 463 464 @Override 465 protected void onResponseContent(Buffer content) throws IOException 466 { 467 if (debug != 0) 468 _log.debug(debug + " content" + content.length()); 469 content.writeTo(out); 470 } 471 472 @Override 473 protected void onResponseHeaderComplete() throws IOException 474 { 475 } 476 477 @Override 478 protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException 479 { 480 if (debug != 0) 481 _log.debug(debug + " " + version + " " + status + " " + reason); 482 483 if (reason != null && reason.length() > 0) 484 response.setStatus(status,reason.toString()); 485 else 486 response.setStatus(status); 487 } 488 489 @Override 490 protected void onResponseHeader(Buffer name, Buffer value) throws IOException 491 { 492 String nameString = name.toString(); 493 String s = nameString.toLowerCase(Locale.ENGLISH); 494 if (!_DontProxyHeaders.contains(s) || (HttpHeaders.CONNECTION_BUFFER.equals(name) && HttpHeaderValues.CLOSE_BUFFER.equals(value))) 495 { 496 if (debug != 0) 497 _log.debug(debug + " " + name + ": " + value); 498 499 String filteredHeaderValue = filterResponseHeaderValue(nameString,value.toString(),request); 500 if (filteredHeaderValue != null && filteredHeaderValue.trim().length() > 0) 501 { 502 if (debug != 0) 503 _log.debug(debug + " " + name + ": (filtered): " + filteredHeaderValue); 504 response.addHeader(nameString,filteredHeaderValue); 505 } 506 } 507 else if (debug != 0) 508 _log.debug(debug + " " + name + "! " + value); 509 } 510 511 @Override 512 protected void onConnectionFailed(Throwable ex) 513 { 514 handleOnConnectionFailed(ex,request,response); 515 516 // it is possible this might trigger before the 517 // continuation.suspend() 518 if (!continuation.isInitial()) 519 { 520 continuation.complete(); 521 } 522 } 523 524 @Override 525 protected void onException(Throwable ex) 526 { 527 if (ex instanceof EofException) 528 { 529 _log.ignore(ex); 530 //return; 531 } 532 handleOnException(ex,request,response); 533 534 // it is possible this might trigger before the 535 // continuation.suspend() 536 if (!continuation.isInitial()) 537 { 538 continuation.complete(); 539 } 540 } 541 542 @Override 543 protected void onExpire() 544 { 545 handleOnExpire(request,response); 546 continuation.complete(); 547 } 548 549 }; 550 551 exchange.setScheme(HttpSchemes.HTTPS.equals(request.getScheme())?HttpSchemes.HTTPS_BUFFER:HttpSchemes.HTTP_BUFFER); 552 exchange.setMethod(request.getMethod()); 553 exchange.setURL(url.toString()); 554 exchange.setVersion(request.getProtocol()); 555 556 557 if (debug != 0) 558 _log.debug(debug + " " + request.getMethod() + " " + url + " " + request.getProtocol()); 559 560 // check connection header 561 String connectionHdr = request.getHeader("Connection"); 562 if (connectionHdr != null) 563 { 564 connectionHdr = connectionHdr.toLowerCase(Locale.ENGLISH); 565 if (connectionHdr.indexOf("keep-alive") < 0 && connectionHdr.indexOf("close") < 0) 566 connectionHdr = null; 567 } 568 569 // force host 570 if (_hostHeader != null) 571 exchange.setRequestHeader("Host",_hostHeader); 572 573 // copy headers 574 boolean xForwardedFor = false; 575 boolean hasContent = false; 576 long contentLength = -1; 577 Enumeration<?> enm = request.getHeaderNames(); 578 while (enm.hasMoreElements()) 579 { 580 // TODO could be better than this! 581 String hdr = (String)enm.nextElement(); 582 String lhdr = hdr.toLowerCase(Locale.ENGLISH); 583 584 if ("transfer-encoding".equals(lhdr)) 585 { 586 if (request.getHeader("transfer-encoding").indexOf("chunk")>=0) 587 hasContent = true; 588 } 589 590 if (_DontProxyHeaders.contains(lhdr)) 591 continue; 592 if (connectionHdr != null && connectionHdr.indexOf(lhdr) >= 0) 593 continue; 594 if (_hostHeader != null && "host".equals(lhdr)) 595 continue; 596 597 if ("content-type".equals(lhdr)) 598 hasContent = true; 599 else if ("content-length".equals(lhdr)) 600 { 601 contentLength = request.getContentLength(); 602 exchange.setRequestHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(contentLength)); 603 if (contentLength > 0) 604 hasContent = true; 605 } 606 else if ("x-forwarded-for".equals(lhdr)) 607 xForwardedFor = true; 608 609 Enumeration<?> vals = request.getHeaders(hdr); 610 while (vals.hasMoreElements()) 611 { 612 String val = (String)vals.nextElement(); 613 if (val != null) 614 { 615 if (debug != 0) 616 _log.debug(debug + " " + hdr + ": " + val); 617 618 exchange.setRequestHeader(hdr,val); 619 } 620 } 621 } 622 623 // Proxy headers 624 exchange.setRequestHeader("Via","1.1 (jetty)"); 625 if (!xForwardedFor) 626 { 627 exchange.addRequestHeader("X-Forwarded-For",request.getRemoteAddr()); 628 exchange.addRequestHeader("X-Forwarded-Proto",request.getScheme()); 629 exchange.addRequestHeader("X-Forwarded-Host",request.getHeader("Host")); 630 exchange.addRequestHeader("X-Forwarded-Server",request.getLocalName()); 631 } 632 633 if (hasContent) 634 { 635 exchange.setRequestContentSource(in); 636 } 637 638 customizeExchange(exchange, request); 639 640 /* 641 * we need to set the timeout on the continuation to take into 642 * account the timeout of the HttpClient and the HttpExchange 643 */ 644 long ctimeout = (_client.getTimeout() > exchange.getTimeout()) ? _client.getTimeout() : exchange.getTimeout(); 645 646 // continuation fudge factor of 1000, underlying components 647 // should fail/expire first from exchange 648 if ( ctimeout == 0 ) 649 { 650 continuation.setTimeout(0); // ideally never times out 651 } 652 else 653 { 654 continuation.setTimeout(ctimeout + 1000); 655 } 656 657 customizeContinuation(continuation); 658 659 continuation.suspend(response); 660 _client.send(exchange); 661 662 } 663 } 664 } 665 666 /* ------------------------------------------------------------ */ 667 public void handleConnect(HttpServletRequest request, HttpServletResponse response) throws IOException 668 { 669 String uri = request.getRequestURI(); 670 671 String port = ""; 672 String host = ""; 673 674 int c = uri.indexOf(':'); 675 if (c >= 0) 676 { 677 port = uri.substring(c + 1); 678 host = uri.substring(0,c); 679 if (host.indexOf('/') > 0) 680 host = host.substring(host.indexOf('/') + 1); 681 } 682 683 // TODO - make this async! 684 685 InetSocketAddress inetAddress = new InetSocketAddress(host,Integer.parseInt(port)); 686 687 // if (isForbidden(HttpMessage.__SSL_SCHEME,addrPort.getHost(),addrPort.getPort(),false)) 688 // { 689 // sendForbid(request,response,uri); 690 // } 691 // else 692 { 693 InputStream in = request.getInputStream(); 694 OutputStream out = response.getOutputStream(); 695 696 Socket socket = new Socket(inetAddress.getAddress(),inetAddress.getPort()); 697 698 response.setStatus(200); 699 response.setHeader("Connection","close"); 700 response.flushBuffer(); 701 // TODO prevent real close! 702 703 IO.copyThread(socket.getInputStream(),out); 704 IO.copy(in,socket.getOutputStream()); 705 } 706 } 707 708 /* ------------------------------------------------------------ */ 709 protected HttpURI proxyHttpURI(HttpServletRequest request, String uri) throws MalformedURLException 710 { 711 return proxyHttpURI(request.getScheme(), request.getServerName(), request.getServerPort(), uri); 712 } 713 714 protected HttpURI proxyHttpURI(String scheme, String serverName, int serverPort, String uri) throws MalformedURLException 715 { 716 if (!validateDestination(serverName,uri)) 717 return null; 718 719 return new HttpURI(scheme + "://" + serverName + ":" + serverPort + uri); 720 } 721 722 /* 723 * (non-Javadoc) 724 * 725 * @see javax.servlet.Servlet#getServletInfo() 726 */ 727 public String getServletInfo() 728 { 729 return "Proxy Servlet"; 730 } 731 732 733 /** 734 * Extension point for subclasses to customize an exchange. Useful for setting timeouts etc. The default implementation does nothing. 735 * 736 * @param exchange 737 * @param request 738 */ 739 protected void customizeExchange(HttpExchange exchange, HttpServletRequest request) 740 { 741 742 } 743 744 /** 745 * Extension point for subclasses to customize the Continuation after it's initial creation in the service method. Useful for setting timeouts etc. The 746 * default implementation does nothing. 747 * 748 * @param continuation 749 */ 750 protected void customizeContinuation(Continuation continuation) 751 { 752 753 } 754 755 /** 756 * Extension point for custom handling of an HttpExchange's onConnectionFailed method. The default implementation delegates to 757 * {@link #handleOnException(Throwable, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)} 758 * 759 * @param ex 760 * @param request 761 * @param response 762 */ 763 protected void handleOnConnectionFailed(Throwable ex, HttpServletRequest request, HttpServletResponse response) 764 { 765 handleOnException(ex,request,response); 766 } 767 768 /** 769 * Extension point for custom handling of an HttpExchange's onException method. The default implementation sets the response status to 770 * HttpServletResponse.SC_INTERNAL_SERVER_ERROR (503) 771 * 772 * @param ex 773 * @param request 774 * @param response 775 */ 776 protected void handleOnException(Throwable ex, HttpServletRequest request, HttpServletResponse response) 777 { 778 if (ex instanceof IOException) 779 { 780 _log.warn(ex.toString()); 781 _log.debug(ex); 782 } 783 else 784 _log.warn(ex); 785 786 if (!response.isCommitted()) 787 { 788 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 789 } 790 } 791 792 /** 793 * Extension point for custom handling of an HttpExchange's onExpire method. The default implementation sets the response status to 794 * HttpServletResponse.SC_GATEWAY_TIMEOUT (504) 795 * 796 * @param request 797 * @param response 798 */ 799 protected void handleOnExpire(HttpServletRequest request, HttpServletResponse response) 800 { 801 if (!response.isCommitted()) 802 { 803 response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT); 804 } 805 } 806 807 /** 808 * Extension point for remote server response header filtering. The default implementation returns the header value as is. If null is returned, this header 809 * won't be forwarded back to the client. 810 * 811 * @param headerName 812 * @param headerValue 813 * @param request 814 * @return filteredHeaderValue 815 */ 816 protected String filterResponseHeaderValue(String headerName, String headerValue, HttpServletRequest request) 817 { 818 return headerValue; 819 } 820 821 /** 822 * Transparent Proxy. 823 * 824 * This convenience extension to ProxyServlet configures the servlet as a transparent proxy. The servlet is configured with init parameters: 825 * <ul> 826 * <li>ProxyTo - a URI like http://host:80/context to which the request is proxied. 827 * <li>Prefix - a URI prefix that is striped from the start of the forwarded URI. 828 * </ul> 829 * For example, if a request was received at /foo/bar and the ProxyTo was http://host:80/context and the Prefix was /foo, then the request would be proxied 830 * to http://host:80/context/bar 831 * 832 */ 833 public static class Transparent extends ProxyServlet 834 { 835 String _prefix; 836 String _proxyTo; 837 838 public Transparent() 839 { 840 } 841 842 public Transparent(String prefix, String host, int port) 843 { 844 this(prefix,"http",host,port,null); 845 } 846 847 public Transparent(String prefix, String schema, String host, int port, String path) 848 { 849 try 850 { 851 if (prefix != null) 852 { 853 _prefix = new URI(prefix).normalize().toString(); 854 } 855 _proxyTo = new URI(schema,null,host,port,path,null,null).normalize().toString(); 856 } 857 catch (URISyntaxException ex) 858 { 859 _log.debug("Invalid URI syntax",ex); 860 } 861 } 862 863 @Override 864 public void init(ServletConfig config) throws ServletException 865 { 866 super.init(config); 867 868 String prefix = config.getInitParameter("Prefix"); 869 _prefix = prefix == null?_prefix:prefix; 870 871 // Adjust prefix value to account for context path 872 String contextPath = _context.getContextPath(); 873 _prefix = _prefix == null?contextPath:(contextPath + _prefix); 874 875 String proxyTo = config.getInitParameter("ProxyTo"); 876 _proxyTo = proxyTo == null?_proxyTo:proxyTo; 877 878 if (_proxyTo == null) 879 throw new UnavailableException("ProxyTo parameter is requred."); 880 881 if (!_prefix.startsWith("/")) 882 throw new UnavailableException("Prefix parameter must start with a '/'."); 883 884 _log.info(config.getServletName() + " @ " + _prefix + " to " + _proxyTo); 885 } 886 887 @Override 888 protected HttpURI proxyHttpURI(final String scheme, final String serverName, int serverPort, final String uri) throws MalformedURLException 889 { 890 try 891 { 892 if (!uri.startsWith(_prefix)) 893 return null; 894 895 URI dstUri = new URI(_proxyTo + uri.substring(_prefix.length())).normalize(); 896 897 if (!validateDestination(dstUri.getHost(),dstUri.getPath())) 898 return null; 899 900 return new HttpURI(dstUri.toString()); 901 } 902 catch (URISyntaxException ex) 903 { 904 throw new MalformedURLException(ex.getMessage()); 905 } 906 } 907 } 908} 909