View Javadoc

    1   // ========================================================================
    2   // Copyright 2006-2007 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // ========================================================================
    14
    15  package org.mortbay.proxy;
    16
    17
    18  import java.io.IOException;
    19  import java.io.InputStream;
    20  import java.io.OutputStream;
    21  import java.net.InetSocketAddress;
    22  import java.net.MalformedURLException;
    23  import java.net.Socket;
    24  import java.net.URL;
    25  import java.util.Enumeration;
    26  import java.util.HashSet;
    27
    28  import javax.servlet.Servlet;
    29  import javax.servlet.ServletConfig;
    30  import javax.servlet.ServletContext;
    31  import javax.servlet.ServletException;
    32  import javax.servlet.ServletRequest;
    33  import javax.servlet.ServletResponse;
    34  import javax.servlet.UnavailableException;
    35  import javax.servlet.http.HttpServletRequest;
    36  import javax.servlet.http.HttpServletResponse;
    37  import javax.servlet.http.HttpServletResponseWrapper;
    38
    39  import org.mortbay.io.Buffer;
    40  import org.mortbay.jetty.Connector;
    41  import org.mortbay.jetty.Handler;
    42  import org.mortbay.jetty.HttpSchemes;
    43  import org.mortbay.jetty.HttpURI;
    44  import org.mortbay.jetty.Server;
    45  import org.mortbay.jetty.bio.SocketConnector;
    46  import org.mortbay.jetty.client.Address;
    47  import org.mortbay.jetty.client.HttpClient;
    48  import org.mortbay.jetty.client.HttpExchange;
    49  import org.mortbay.jetty.handler.ContextHandlerCollection;
    50  import org.mortbay.jetty.handler.DefaultHandler;
    51  import org.mortbay.jetty.handler.HandlerCollection;
    52  import org.mortbay.jetty.servlet.Context;
    53  import org.mortbay.jetty.servlet.ServletHolder;
    54  import org.mortbay.jetty.webapp.WebAppContext;
    55  import org.mortbay.util.IO;
    56  import org.mortbay.util.ajax.Continuation;
    57  import org.mortbay.util.ajax.ContinuationSupport;
    58
    59
    60
    61  /**
    62   * Asynchronous Proxy Servlet.
    63   *
    64   * Forward requests to another server either as a standard web proxy (as defined by
    65   * RFC2616) or as a transparent proxy.
    66   *
    67   * This servlet needs the jetty-util and jetty-client classes to be available to
    68   * the web application.
    69   *
    70   */
    71  public class AsyncProxyServlet implements Servlet
    72  {
    73      HttpClient _client;
    74
    75      protected HashSet<String> _DontProxyHeaders = new HashSet<String>();
    76      {
    77          _DontProxyHeaders.add("proxy-connection");
    78          _DontProxyHeaders.add("connection");
    79          _DontProxyHeaders.add("keep-alive");
    80          _DontProxyHeaders.add("transfer-encoding");
    81          _DontProxyHeaders.add("te");
    82          _DontProxyHeaders.add("trailer");
    83          _DontProxyHeaders.add("proxy-authorization");
    84          _DontProxyHeaders.add("proxy-authenticate");
    85          _DontProxyHeaders.add("upgrade");
    86      }
    87
    88      private ServletConfig config;
    89      private ServletContext context;
    90
    91      /* (non-Javadoc)
    92       * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
    93       */
    94      public void init(ServletConfig config) throws ServletException
    95      {
    96          this.config=config;
    97          this.context=config.getServletContext();
    98
    99          _client=new HttpClient();
    100         //_client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
    101         _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
    102         try
    103         {
    104             _client.start();
    105         }
    106         catch (Exception e)
    107         {
    108             throw new ServletException(e);
    109         }
    110     }
    111
    112     /* (non-Javadoc)
    113      * @see javax.servlet.Servlet#getServletConfig()
    114      */
    115     public ServletConfig getServletConfig()
    116     {
    117         return config;
    118     }
    119
    120     /* (non-Javadoc)
    121      * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
    122      */
    123     public void service(ServletRequest req, ServletResponse res) throws ServletException,
    124             IOException
    125     {
    126         while (res instanceof HttpServletResponseWrapper)
    127             res=(HttpServletResponse)((HttpServletResponseWrapper)res).getResponse();
    128
    129         final HttpServletRequest request = (HttpServletRequest)req;
    130         final HttpServletResponse response = (HttpServletResponse)res;
    131
    132         if ("CONNECT".equalsIgnoreCase(request.getMethod()))
    133         {
    134             handleConnect(request,response);
    135         }
    136         else
    137         {
    138             final InputStream in=request.getInputStream();
    139             final OutputStream out=response.getOutputStream();
    140             final Continuation continuation = ContinuationSupport.getContinuation(request,request);
    141
    142
    143             if (!continuation.isPending())
    144             {
    145                 final byte[] buffer = new byte[4096]; // TODO avoid this!
    146                 String uri=request.getRequestURI();
    147                 if (request.getQueryString()!=null)
    148                     uri+="?"+request.getQueryString();
    149
    150                 HttpURI url=proxyHttpURI(request.getScheme(),
    151                         request.getServerName(),
    152                         request.getServerPort(),
    153                         uri);
    154
    155                 if (url==null)
    156                 {
    157                     response.sendError(HttpServletResponse.SC_FORBIDDEN);
    158                     return;
    159                 }
    160
    161                 HttpExchange exchange = new HttpExchange()
    162                 {
    163
    164                     protected void onRequestCommitted() throws IOException
    165                     {
    166                     }
    167
    168                     protected void onRequestComplete() throws IOException
    169                     {
    170                     }
    171
    172                     protected void onResponseComplete() throws IOException
    173                     {
    174                         continuation.resume();
    175                     }
    176
    177                     protected void onResponseContent(Buffer content) throws IOException
    178                     {
    179                         // TODO Avoid this copy
    180                         while (content.hasContent())
    181                         {
    182                             int len=content.get(buffer,0,buffer.length);
    183                             out.write(buffer,0,len);  // May block here for a little bit!
    184                         }
    185                     }
    186
    187                     protected void onResponseHeaderComplete() throws IOException
    188                     {
    189                     }
    190
    191                     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
    192                     {
    193                         if (reason!=null && reason.length()>0)
    194                             response.setStatus(status,reason.toString());
    195                         else
    196                             response.setStatus(status);
    197
    198                     }
    199
    200                     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
    201                     {
    202                         String s = name.toString().toLowerCase();
    203                         if (!_DontProxyHeaders.contains(s))
    204                             response.addHeader(name.toString(),value.toString());
    205                     }
    206
    207                 };
    208
    209                 exchange.setVersion(request.getProtocol());
    210                 exchange.setMethod(request.getMethod());
    211
    212                 exchange.setURL(url.toString());
    213
    214                 // check connection header
    215                 String connectionHdr = request.getHeader("Connection");
    216                 if (connectionHdr!=null)
    217                 {
    218                     connectionHdr=connectionHdr.toLowerCase();
    219                     if (connectionHdr.indexOf("keep-alive")<0  &&
    220                             connectionHdr.indexOf("close")<0)
    221                         connectionHdr=null;
    222                 }
    223
    224                 // copy headers
    225                 boolean xForwardedFor=false;
    226                 boolean hasContent=false;
    227                 long contentLength=-1;
    228                 Enumeration enm = request.getHeaderNames();
    229                 while (enm.hasMoreElements())
    230                 {
    231                     // TODO could be better than this!
    232                     String hdr=(String)enm.nextElement();
    233                     String lhdr=hdr.toLowerCase();
    234
    235                     if (_DontProxyHeaders.contains(lhdr))
    236                         continue;
    237                     if (connectionHdr!=null && connectionHdr.indexOf(lhdr)>=0)
    238                         continue;
    239
    240                     if ("content-type".equals(lhdr))
    241                         hasContent=true;
    242                     if ("content-length".equals(lhdr))
    243                         contentLength=request.getContentLength();
    244
    245                     Enumeration vals = request.getHeaders(hdr);
    246                     while (vals.hasMoreElements())
    247                     {
    248                         String val = (String)vals.nextElement();
    249                         if (val!=null)
    250                         {
    251                             exchange.setRequestHeader(lhdr,val);
    252                             xForwardedFor|="X-Forwarded-For".equalsIgnoreCase(hdr);
    253                         }
    254                     }
    255                 }
    256
    257                 // Proxy headers
    258                 exchange.setRequestHeader("Via","1.1 (jetty)");
    259                 if (!xForwardedFor)
    260                     exchange.addRequestHeader("X-Forwarded-For",
    261                             request.getRemoteAddr());
    262
    263                 if (hasContent)
    264                     exchange.setRequestContentSource(in);
    265
    266                 _client.send(exchange);
    267
    268                 continuation.suspend(30000);
    269             }
    270         }
    271     }
    272
    273
    274     /* ------------------------------------------------------------ */
    275     /**
    276     /** Resolve requested URL to the Proxied HttpURI
    277      * @param scheme The scheme of the received request.
    278      * @param serverName The server encoded in the received request(which
    279      * may be from an absolute URL in the request line).
    280      * @param serverPort The server port of the received request (which
    281      * may be from an absolute URL in the request line).
    282      * @param uri The URI of the received request.
    283      * @return The HttpURI to which the request should be proxied.
    284      * @throws MalformedURLException
    285      */
    286     protected HttpURI proxyHttpURI(String scheme, String serverName, int serverPort, String uri)
    287         throws MalformedURLException
    288     {
    289         return new HttpURI(scheme+"://"+serverName+":"+serverPort+uri);
    290     }
    291
    292     /* ------------------------------------------------------------ */
    293     public void handleConnect(HttpServletRequest request,
    294                               HttpServletResponse response)
    295         throws IOException
    296     {
    297         String uri = request.getRequestURI();
    298
    299         String port = "";
    300         String host = "";
    301
    302         int c = uri.indexOf(':');
    303         if (c>=0)
    304         {
    305             port = uri.substring(c+1);
    306             host = uri.substring(0,c);
    307             if (host.indexOf('/')>0)
    308                 host = host.substring(host.indexOf('/')+1);
    309         }
    310
    311         InetSocketAddress inetAddress = new InetSocketAddress (host, Integer.parseInt(port));
    312
    313         //if (isForbidden(HttpMessage.__SSL_SCHEME,addrPort.getHost(),addrPort.getPort(),false))
    314         //{
    315         //    sendForbid(request,response,uri);
    316         //}
    317         //else
    318         {
    319             InputStream in=request.getInputStream();
    320             OutputStream out=response.getOutputStream();
    321
    322             Socket socket = new Socket(inetAddress.getAddress(),inetAddress.getPort());
    323
    324             response.setStatus(200);
    325             response.setHeader("Connection","close");
    326             response.flushBuffer();
    327
    328
    329
    330             IO.copyThread(socket.getInputStream(),out);
    331             IO.copy(in,socket.getOutputStream());
    332         }
    333     }
    334
    335
    336
    337
    338     /* (non-Javadoc)
    339      * @see javax.servlet.Servlet#getServletInfo()
    340      */
    341     public String getServletInfo()
    342     {
    343         return "Proxy Servlet";
    344     }
    345
    346     /* (non-Javadoc)
    347      * @see javax.servlet.Servlet#destroy()
    348      */
    349     public void destroy()
    350     {
    351
    352     }
    353
    354     /**
    355      * Transparent Proxy.
    356      *
    357      * This convenience extension to AsyncProxyServlet configures the servlet
    358      * as a transparent proxy.   The servlet is configured with init parameter:<ul>
    359      * <li> ProxyTo - a URI like http://host:80/context to which the request is proxied.
    360      * <li> Prefix  - a URI prefix that is striped from the start of the forwarded URI.
    361      * </ul>
    362      * For example, if a request was received at /foo/bar and the ProxyTo was  http://host:80/context
    363      * and the Prefix was /foo, then the request would be proxied to http://host:80/context/bar
    364      *
    365      */
    366     public static class Transparent extends AsyncProxyServlet
    367     {
    368         String _prefix;
    369         String _proxyTo;
    370
    371         public Transparent()
    372         {
    373         }
    374
    375         public Transparent(String prefix,String server, int port)
    376         {
    377             _prefix=prefix;
    378             _proxyTo="http://"+server+":"+port;
    379         }
    380
    381         public void init(ServletConfig config) throws ServletException
    382         {
    383             if (config.getInitParameter("ProxyTo")!=null)
    384                 _proxyTo=config.getInitParameter("ProxyTo");
    385             if (config.getInitParameter("Prefix")!=null)
    386                 _prefix=config.getInitParameter("Prefix");
    387             if (_proxyTo==null)
    388                 throw new UnavailableException("No ProxyTo");
    389             super.init(config);
    390             config.getServletContext().log("Transparent AsyncProxyServlet @ "+(_prefix==null?"-":_prefix)+ " to "+_proxyTo);
    391
    392         }
    393
    394         protected HttpURI proxyHttpURI(final String scheme, final String serverName, int serverPort, final String uri) throws MalformedURLException
    395         {
    396             if (_prefix!=null && !uri.startsWith(_prefix))
    397                 return null;
    398
    399             if (_prefix!=null)
    400                 return new HttpURI(_proxyTo+uri.substring(_prefix.length()));
    401             return new HttpURI(_proxyTo+uri);
    402         }
    403     }
    404 }