View Javadoc

1   // ========================================================================
2   // Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.servlets;
15  
16  
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.io.OutputStream;
20  import java.net.InetSocketAddress;
21  import java.net.MalformedURLException;
22  import java.net.Socket;
23  import java.util.Enumeration;
24  import java.util.HashSet;
25  
26  import javax.servlet.Servlet;
27  import javax.servlet.ServletConfig;
28  import javax.servlet.ServletContext;
29  import javax.servlet.ServletException;
30  import javax.servlet.ServletRequest;
31  import javax.servlet.ServletResponse;
32  import javax.servlet.http.HttpServletRequest;
33  import javax.servlet.http.HttpServletResponse;
34  
35  import org.eclipse.jetty.client.HttpClient;
36  import org.eclipse.jetty.client.HttpExchange;
37  import org.eclipse.jetty.continuation.Continuation;
38  import org.eclipse.jetty.continuation.ContinuationSupport;
39  import org.eclipse.jetty.http.HttpHeaders;
40  import org.eclipse.jetty.http.HttpSchemes;
41  import org.eclipse.jetty.http.HttpURI;
42  import org.eclipse.jetty.io.Buffer;
43  import org.eclipse.jetty.io.EofException;
44  import org.eclipse.jetty.util.IO;
45  import org.eclipse.jetty.util.TypeUtil;
46  import org.eclipse.jetty.util.log.Log;
47  
48  
49  
50  /**
51   * EXPERIMENTAL Proxy servlet.
52   * 
53   *
54   */
55  public class ProxyServlet implements Servlet
56  {
57      HttpClient _client;
58  
59      protected HashSet<String> _DontProxyHeaders = new HashSet<String>();
60      {
61          _DontProxyHeaders.add("proxy-connection");
62          _DontProxyHeaders.add("connection");
63          _DontProxyHeaders.add("keep-alive");
64          _DontProxyHeaders.add("transfer-encoding");
65          _DontProxyHeaders.add("te");
66          _DontProxyHeaders.add("trailer");
67          _DontProxyHeaders.add("proxy-authorization");
68          _DontProxyHeaders.add("proxy-authenticate");
69          _DontProxyHeaders.add("upgrade");
70      }
71  
72      private ServletConfig config;
73      private ServletContext context;
74  
75      /* (non-Javadoc)
76       * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
77       */
78      public void init(ServletConfig config) throws ServletException
79      {
80          this.config=config;
81          this.context=config.getServletContext();
82  
83          _client=new HttpClient();
84          //_client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
85          _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
86          try
87          {
88              _client.start();
89          }
90          catch (Exception e)
91          {
92              throw new ServletException(e);
93          }
94      }
95  
96      /* (non-Javadoc)
97       * @see javax.servlet.Servlet#getServletConfig()
98       */
99      public ServletConfig getServletConfig()
100     {
101         return config;
102     }
103 
104     /* (non-Javadoc)
105      * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
106      */
107     public void service(ServletRequest req, ServletResponse res) throws ServletException,
108             IOException
109     {
110         final HttpServletRequest request = (HttpServletRequest)req;
111         final HttpServletResponse response = (HttpServletResponse)res;
112         if ("CONNECT".equalsIgnoreCase(request.getMethod()))
113         {
114             handleConnect(request,response);
115         }
116         else
117         {
118             final InputStream in=request.getInputStream();
119             final OutputStream out=response.getOutputStream();
120 
121             final Continuation continuation = ContinuationSupport.getContinuation(request);
122             
123             if (!continuation.isInitial())
124                 response.sendError(HttpServletResponse.SC_GATEWAY_TIMEOUT); // Need better test that isInitial
125             else
126             {
127                 String uri=request.getRequestURI();
128                 if (request.getQueryString()!=null)
129                     uri+="?"+request.getQueryString();
130 
131 		HttpURI url=proxyHttpURI(request.getScheme(),
132 		                         request.getServerName(),
133 		                         request.getServerPort(),
134 		                         uri);
135 		if (url==null)
136 		{
137 		    response.sendError(HttpServletResponse.SC_FORBIDDEN);
138 		    return;
139 		}
140 
141 
142                 HttpExchange exchange = new HttpExchange()
143                 {
144                     protected void onRequestCommitted() throws IOException
145                     {
146                     }
147 
148                     protected void onRequestComplete() throws IOException
149                     {
150                     }
151 
152                     protected void onResponseComplete() throws IOException
153                     {
154                         continuation.complete();
155                     }
156 
157                     protected void onResponseContent(Buffer content) throws IOException
158                     {
159                         content.writeTo(out);
160                     }
161 
162                     protected void onResponseHeaderComplete() throws IOException
163                     {
164                     }
165 
166                     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
167                     {
168                         if (reason!=null && reason.length()>0)
169                             response.setStatus(status,reason.toString());
170                         else
171                             response.setStatus(status);
172                     }
173 
174                     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
175                     {
176                         String s = name.toString().toLowerCase();
177                         if (!_DontProxyHeaders.contains(s))
178                             response.addHeader(name.toString(),value.toString());
179                     }
180 
181                     protected void onConnectionFailed(Throwable ex)
182                     {
183                         onException(ex);
184                     }
185 
186                     protected void onException(Throwable ex)
187                     {
188                         if (ex instanceof EofException)
189                         {
190                             Log.ignore(ex);
191                             return;
192                         }
193                         Log.warn(ex.toString());
194                         Log.debug(ex);
195                         if (!response.isCommitted())
196                             response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
197                         continuation.complete();
198                     }
199 
200                     protected void onExpire()
201                     {
202                         if (!response.isCommitted())
203                             response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
204                         continuation.complete();
205                     }
206 
207                 };
208 
209                 exchange.setScheme(HttpSchemes.HTTPS.equals(request.getScheme())?HttpSchemes.HTTPS_BUFFER:HttpSchemes.HTTP_BUFFER);
210                 exchange.setMethod(request.getMethod());
211 		exchange.setURL(url.toString());
212                 exchange.setVersion(request.getProtocol());
213 
214                 if (Log.isDebugEnabled())
215                     Log.debug("PROXY TO "+url);
216 
217                 // check connection header
218                 String connectionHdr = request.getHeader("Connection");
219                 if (connectionHdr!=null)
220                 {
221                     connectionHdr=connectionHdr.toLowerCase();
222                     if (connectionHdr.indexOf("keep-alive")<0  &&
223                             connectionHdr.indexOf("close")<0)
224                         connectionHdr=null;
225                 }
226 
227                 // copy headers
228                 boolean xForwardedFor=false;
229                 boolean hasContent=false;
230                 long contentLength=-1;
231                 Enumeration<?> enm = request.getHeaderNames();
232                 while (enm.hasMoreElements())
233                 {
234                     // TODO could be better than this!
235                     String hdr=(String)enm.nextElement();
236                     String lhdr=hdr.toLowerCase();
237 
238                     if (_DontProxyHeaders.contains(lhdr))
239                         continue;
240                     if (connectionHdr!=null && connectionHdr.indexOf(lhdr)>=0)
241                         continue;
242 
243                     if ("content-type".equals(lhdr))
244                         hasContent=true;
245                     if ("content-length".equals(lhdr))
246                     {
247                         contentLength=request.getContentLength();
248                         exchange.setRequestHeader(HttpHeaders.CONTENT_LENGTH,TypeUtil.toString(contentLength));
249                         if (contentLength>0)
250                             hasContent=true;
251                     }
252 
253                     Enumeration<?> vals = request.getHeaders(hdr);
254                     while (vals.hasMoreElements())
255                     {
256                         String val = (String)vals.nextElement();
257                         if (val!=null)
258                         {
259                             exchange.setRequestHeader(lhdr,val);
260                             xForwardedFor|="X-Forwarded-For".equalsIgnoreCase(hdr);
261                         }
262                     }
263                 }
264 
265                 // Proxy headers
266                 exchange.setRequestHeader("Via","1.1 (jetty)");
267                 if (!xForwardedFor)
268                     exchange.addRequestHeader("X-Forwarded-For",
269                             request.getRemoteAddr());
270 
271                 if (hasContent)
272                     exchange.setRequestContentSource(in);
273 
274                 continuation.suspend(); 
275                 continuation.keepWrappers();
276                 _client.send(exchange);
277 
278             }
279         }
280     }
281 
282 
283     /* ------------------------------------------------------------ */
284     public void handleConnect(HttpServletRequest request,
285                               HttpServletResponse response)
286         throws IOException
287     {
288         String uri = request.getRequestURI();
289 
290         context.log("CONNECT: "+uri);
291 
292         String port = "";
293         String host = "";
294 
295         int c = uri.indexOf(':');
296         if (c>=0)
297         {
298             port = uri.substring(c+1);
299             host = uri.substring(0,c);
300             if (host.indexOf('/')>0)
301                 host = host.substring(host.indexOf('/')+1);
302         }
303 
304         // TODO - make this async!
305 
306 
307         InetSocketAddress inetAddress = new InetSocketAddress (host, Integer.parseInt(port));
308 
309         //if (isForbidden(HttpMessage.__SSL_SCHEME,addrPort.getHost(),addrPort.getPort(),false))
310         //{
311         //    sendForbid(request,response,uri);
312         //}
313         //else
314         {
315             InputStream in=request.getInputStream();
316             OutputStream out=response.getOutputStream();
317 
318             Socket socket = new Socket(inetAddress.getAddress(),inetAddress.getPort());
319             context.log("Socket: "+socket);
320 
321             response.setStatus(200);
322             response.setHeader("Connection","close");
323             response.flushBuffer();
324             // TODO prevent real close!
325 
326             context.log("out<-in");
327             IO.copyThread(socket.getInputStream(),out);
328             context.log("in->out");
329             IO.copy(in,socket.getOutputStream());
330         }
331     }
332 
333     /* ------------------------------------------------------------ */
334     protected HttpURI proxyHttpURI(String scheme, String serverName, int serverPort, String uri)
335         throws MalformedURLException
336     {
337         return new HttpURI(scheme+"://"+serverName+":"+serverPort+uri);
338     }
339 
340 
341     /* (non-Javadoc)
342      * @see javax.servlet.Servlet#getServletInfo()
343      */
344     public String getServletInfo()
345     {
346         return "Proxy Servlet";
347     }
348 
349     /* (non-Javadoc)
350      * @see javax.servlet.Servlet#destroy()
351      */
352     public void destroy()
353     {
354 
355     }
356 
357 }