View Javadoc

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