View Javadoc

1   /*
2    * Copyright 2001-2004 The Apache Software Foundation.
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    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.portletbridge.web;
17  
18  import java.io.BufferedInputStream;
19  import java.io.BufferedOutputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.PrintWriter;
23  import java.lang.reflect.InvocationTargetException;
24  import java.util.Enumeration;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.Set;
28  
29  import javax.servlet.ServletException;
30  import javax.servlet.http.HttpServlet;
31  import javax.servlet.http.HttpServletRequest;
32  import javax.servlet.http.HttpServletResponse;
33  import javax.servlet.http.HttpSession;
34  
35  import org.apache.commons.beanutils.BeanMap;
36  import org.apache.commons.beanutils.BeanUtils;
37  import org.apache.commons.httpclient.Header;
38  import org.apache.commons.httpclient.HttpClient;
39  import org.apache.commons.httpclient.HttpMethodBase;
40  import org.apache.commons.httpclient.HttpState;
41  import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
42  import org.apache.commons.httpclient.NTCredentials;
43  import org.apache.commons.httpclient.UsernamePasswordCredentials;
44  import org.apache.commons.httpclient.auth.AuthScope;
45  import org.apache.commons.httpclient.methods.GetMethod;
46  import org.apache.commons.httpclient.methods.PostMethod;
47  import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
48  import org.apache.xml.serialize.OutputFormat;
49  import org.apache.xml.serialize.Serializer;
50  import org.apache.xml.serialize.SerializerFactory;
51  import org.portletbridge.rewriter.FullUrlRewriter;
52  import org.portletbridge.rewriter.RegExStyleSheetRewriter;
53  import org.portletbridge.xsl.LinkRewriterXmlFilter;
54  import org.xml.sax.InputSource;
55  import org.xml.sax.SAXException;
56  import org.xml.sax.SAXNotRecognizedException;
57  import org.xml.sax.SAXNotSupportedException;
58  import org.xml.sax.XMLReader;
59  import org.xml.sax.helpers.XMLReaderFactory;
60  
61  /***
62   * @author JMcCrindle
63   */
64  public class ProxyHttpServlet extends HttpServlet {
65  
66      /***
67       * default serial version id 
68       */
69      private static final long serialVersionUID = 7594239856795649323L;
70  
71      private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
72              .getLog(ProxyHttpServlet.class);
73  
74      private HttpClient client = null;
75  
76      private String httpStateSessionKey = ProxyHttpServlet.class + ".httpStateSessionKey";
77  
78      private XMLReader parser = null;
79  
80      private String configProxyEnabled = null;
81  
82      private String configProxyHost = null;
83  
84      private String configProxyPort = null;
85  
86      private String configProxyAuthentication = null;
87  
88      private String configProxyAuthenticationUsername = null;
89  
90      private String configProxyAuthenticationPassword = null;
91  
92      private String configProxyAuthenticationHost = null;
93  
94      private String configProxyAuthenticationDomain = null;
95  
96      private String configConnectionTimeout = null;
97  
98      private String configConnectionMaxTotal = null;
99  
100     private String configConnectionMaxPerHost = null;
101 
102     private static Set BLOCK_REQUEST_HEADERS = new HashSet();
103 
104     private static Set BLOCK_RESPONSE_HEADERS = new HashSet();
105 
106     static {
107         BLOCK_REQUEST_HEADERS.add("cookie");
108         BLOCK_REQUEST_HEADERS.add("accept-encoding");
109         BLOCK_REQUEST_HEADERS.add("connection");
110         BLOCK_REQUEST_HEADERS.add("keep-alive");
111         BLOCK_RESPONSE_HEADERS.add("connection");
112         BLOCK_RESPONSE_HEADERS.add("proxy-connection");
113         BLOCK_RESPONSE_HEADERS.add("content-length");
114         BLOCK_RESPONSE_HEADERS.add("location");
115         BLOCK_RESPONSE_HEADERS.add("set-cookie");
116         BLOCK_RESPONSE_HEADERS.add("content-encoding");
117         BLOCK_RESPONSE_HEADERS.add("transfer-encoding");
118     }
119 
120     /*
121      * (non-Javadoc)
122      * 
123      * @see javax.servlet.GenericServlet#init()
124      */
125     public void init() throws ServletException {
126         try {
127             initProperties();
128         } catch (IllegalAccessException e1) {
129             throw new ServletException(e1);
130         } catch (InvocationTargetException e1) {
131             throw new ServletException(e1);
132         }
133         MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
134         HttpConnectionManagerParams httpConnectionManagerParams = new HttpConnectionManagerParams();
135         if (configConnectionTimeout != null) {
136             httpConnectionManagerParams.setConnectionTimeout(Integer
137                     .parseInt(configConnectionTimeout));
138         }
139         if (configConnectionMaxTotal != null) {
140             httpConnectionManagerParams.setMaxTotalConnections(Integer
141                     .parseInt(configConnectionMaxTotal));
142         }
143         connectionManager.setParams(httpConnectionManagerParams);
144         client = new HttpClient(connectionManager);
145         if (configConnectionMaxPerHost != null) {
146             httpConnectionManagerParams.setMaxConnectionsPerHost(client
147                     .getHostConfiguration(), Integer
148                     .parseInt(configConnectionMaxPerHost));
149         }
150         if (configProxyEnabled != null
151                 && Boolean.valueOf(configProxyEnabled).booleanValue()) {
152             client.getHostConfiguration().setProxy(configProxyHost,
153                     Integer.parseInt(configProxyPort));
154         }
155         try {
156             parser = XMLReaderFactory
157                     .createXMLReader("org.cyberneko.html.parsers.SAXParser");
158             parser.setFeature(
159                     "http://cyberneko.org/html/features/balance-tags", true);
160         } catch (SAXNotRecognizedException e) {
161             throw new ServletException(e);
162         } catch (SAXNotSupportedException e) {
163             throw new ServletException(e);
164         } catch (SAXException e) {
165             throw new ServletException(e);
166         }
167     }
168 
169     /***
170      * noop
171      * 
172      * @throws InvocationTargetException
173      * @throws IllegalAccessException
174      */
175     protected void initProperties() throws IllegalAccessException,
176             InvocationTargetException {
177         BeanMap map = new BeanMap(this);
178         String servletName = getServletConfig().getServletName();
179         log.debug(servletName);
180         for (Iterator iter = map.keyIterator(); iter.hasNext();) {
181             String property = (String) iter.next();
182             String initParameter = getServletContext().getInitParameter(
183                     servletName + "." + property);
184             if (initParameter != null) {
185                 log.debug(property + "=" + initParameter);
186                 BeanUtils.setProperty(this, property, initParameter);
187             }
188         }
189     }
190 
191     /*
192      * (non-Javadoc)
193      * 
194      * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
195      *      javax.servlet.http.HttpServletResponse)
196      */
197     protected void doGet(HttpServletRequest request,
198             HttpServletResponse response) throws ServletException, IOException {
199         String queryString = request.getQueryString();
200         // TODO: worry about the context path being 0 length
201         // TODO: code duplication
202         String url = request.getRequestURI().substring(
203                 request.getContextPath().length() + 1)
204                 + (queryString != null ? '?' + queryString : "");
205         log.debug(url);
206         GetMethod getMethod = new GetMethod(url);
207         process(request, response, getMethod);
208     }
209 
210     /*
211      * (non-Javadoc)
212      * 
213      * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
214      *      javax.servlet.http.HttpServletResponse)
215      */
216     protected void doPost(HttpServletRequest request,
217             HttpServletResponse response) throws ServletException, IOException {
218         String queryString = request.getQueryString();
219         String url = request.getRequestURI().substring(
220                 request.getContextPath().length() + 1)
221                 + (queryString != null ? '?' + queryString : "");
222         log.debug(url);
223         PostMethod postMethod = new PostMethod(url);
224         process(request, response, postMethod);
225     }
226 
227     /*
228      * (non-Javadoc)
229      * 
230      * @see javax.servlet.http.HttpServlet#doHead(javax.servlet.http.HttpServletRequest,
231      *      javax.servlet.http.HttpServletResponse)
232      */
233     protected void doHead(HttpServletRequest arg0, HttpServletResponse arg1)
234             throws ServletException, IOException {
235         throw new ServletException("HEAD Unsupported");
236     }
237 
238     /*
239      * (non-Javadoc)
240      * 
241      * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest,
242      *      javax.servlet.http.HttpServletResponse)
243      */
244     protected void process(HttpServletRequest request,
245             HttpServletResponse response, HttpMethodBase method)
246             throws ServletException, IOException {
247         
248         long startTime = System.currentTimeMillis();
249 
250         String methodUrl = method
251             .getURI().toString();
252 
253         try {
254             HttpSession session = request.getSession(true);
255             int statusCode = 0;
256             FullUrlRewriter fullUrlRewriter = new FullUrlRewriter(methodUrl, request.getRequestURI());
257             RegExStyleSheetRewriter styleSheetRewriter = new RegExStyleSheetRewriter(
258                     fullUrlRewriter);
259 
260             method.setFollowRedirects(false);
261             Enumeration headerNames = request.getHeaderNames();
262             while (headerNames.hasMoreElements()) {
263                 String headerName = (String) headerNames.nextElement();
264                 if (!BLOCK_REQUEST_HEADERS.contains(headerName.toLowerCase())) {
265                     String headerValue = request.getHeader(headerName);
266                     log.debug("RequestHeader:" + headerName + "=" + headerValue);
267                     method.setRequestHeader(headerName, headerValue);
268                 }
269             }
270 
271             HttpState state = null;
272             
273             synchronized (session) {
274                 state = (HttpState) session
275                         .getAttribute(httpStateSessionKey);
276                 if (state == null) {
277                     state = createState();
278                     session.setAttribute(httpStateSessionKey, state);
279                 }
280             }
281 
282             statusCode = client.executeMethod(
283                     client.getHostConfiguration(), method, state);
284             response.setStatus(statusCode);
285 
286             Header[] responseHeaders = method.getResponseHeaders();
287             for (int i = 0; i < responseHeaders.length; i++) {
288                 Header header = responseHeaders[i];
289                 String headerName = header.getName();
290                 if (!BLOCK_RESPONSE_HEADERS.contains(headerName
291                         .toLowerCase())) {
292                     String headerValue = header.getValue();
293                     log.debug("ResponseHeader:" + headerName + "="
294                             + headerValue);
295                     response.setHeader(headerName, headerValue);
296                 }
297             }
298 
299             if ((int) Math.floor(statusCode / 100) == 3) {
300                 Header responseHeader = method.getResponseHeader("Location");
301                 if (responseHeader != null) {
302                     response.setHeader("Location", fullUrlRewriter
303                             .rewrite(responseHeader.getValue()));
304                 }
305             } else {
306                 Header responseHeader = method
307                         .getResponseHeader("Content-Type");
308                 String contentType = responseHeader.getValue();
309                 if (contentType.indexOf("text/html") != -1) {
310                     PrintWriter responseWriter = response.getWriter();
311                     InputStream responseBodyAsStream = method
312                             .getResponseBodyAsStream();
313                     SerializerFactory factory = SerializerFactory
314                             .getSerializerFactory("html");
315                     Serializer writer = factory
316                             .makeSerializer(new OutputFormat());
317                     writer.setOutputCharStream(responseWriter);
318                     LinkRewriterXmlFilter filter = new LinkRewriterXmlFilter();
319                     filter.setParent(parser);
320                     filter.setContentHandler(writer.asContentHandler());
321                     log.debug(request.getRequestURI());
322                     filter.setUrlRewriter(fullUrlRewriter);
323                     filter.setStyleSheetRewriter(styleSheetRewriter);
324 
325                     try {
326                         InputSource inputSource = new InputSource(
327                                 responseBodyAsStream);
328                         filter.parse(inputSource);
329                     } catch (SAXException e) {
330                         throw new ServletException(e);
331                     } catch (IOException e) {
332                         throw new ServletException(e);
333                     } finally {
334                         responseWriter.flush();
335                         responseBodyAsStream.close();
336                     }
337                 } else if (contentType.indexOf("text/css") != -1) {
338                     String responseBodyAsString = method
339                             .getResponseBodyAsString();
340                     String rewrittenCss = styleSheetRewriter
341                             .rewrite(responseBodyAsString);
342                     response.getWriter().write(rewrittenCss);
343                 } else {
344                     BufferedOutputStream outputStream = new BufferedOutputStream(response
345                             .getOutputStream());
346                     InputStream responseBodyAsStream = method
347                             .getResponseBodyAsStream();
348                     BufferedInputStream in = new BufferedInputStream(
349                             responseBodyAsStream);
350                     byte[] b = new byte[4096];
351                     int i = -1;
352                     while ((i = in.read(b)) != -1) {
353                         outputStream.write(b, 0, i);
354                     }
355                     outputStream.flush();
356                     responseBodyAsStream.close();
357                 }
358             }
359         } catch (Throwable t) {
360             log.warn(t, t);
361         } finally {
362             method.releaseConnection();
363         }
364         
365         log.info(methodUrl + " finished in " + (System.currentTimeMillis() - startTime) + "ms");
366     }
367 
368     /***
369      * @return
370      * @throws ServletException
371      */
372     protected HttpState createState() throws ServletException {
373         HttpState state;
374         state = new HttpState();
375         if (configProxyEnabled != null
376                 && Boolean.valueOf(configProxyEnabled)
377                         .booleanValue()) {
378             if (configProxyAuthentication != null) {
379                 if ("ntlm"
380                         .equalsIgnoreCase(configProxyAuthentication)) {
381                     state
382                             .setProxyCredentials(
383                                     AuthScope.ANY,
384                                     new NTCredentials(
385                                             configProxyAuthenticationUsername,
386                                             configProxyAuthenticationPassword,
387                                             configProxyAuthenticationHost,
388                                             configProxyAuthenticationDomain));
389                 } else if ("basic"
390                         .equalsIgnoreCase(configProxyAuthentication) || "digest"
391                             .equalsIgnoreCase(configProxyAuthentication)) {
392                     state
393                             .setProxyCredentials(
394                                     AuthScope.ANY,
395                                     new UsernamePasswordCredentials(
396                                             configProxyAuthenticationUsername,
397                                             configProxyAuthenticationPassword));
398                 } else {
399                     throw new ServletException(
400                             "configProxyAuthentication can only be either 'basic' or 'ntlm'");
401                 }
402             } else {
403                 throw new ServletException(
404                         "If configProxyEnabled is try, configProxyAuthentication must be set");
405             }
406         }
407         return state;
408     }
409 
410     public void setConfigConnectionMaxPerHost(String configConnectionMaxPerHost) {
411         this.configConnectionMaxPerHost = configConnectionMaxPerHost;
412     }
413 
414     public void setConfigConnectionMaxTotal(String configConnectionMaxTotal) {
415         this.configConnectionMaxTotal = configConnectionMaxTotal;
416     }
417 
418     public void setConfigConnectionTimeout(String configConnectionTimeout) {
419         this.configConnectionTimeout = configConnectionTimeout;
420     }
421 
422     public void setConfigProxyAuthentication(String configProxyAuthentication) {
423         this.configProxyAuthentication = configProxyAuthentication;
424     }
425 
426     public void setConfigProxyAuthenticationHost(
427             String configProxyAuthenticationHost) {
428         this.configProxyAuthenticationHost = configProxyAuthenticationHost;
429     }
430 
431     public void setConfigProxyAuthenticationPassword(
432             String configProxyAuthenticationPassword) {
433         this.configProxyAuthenticationPassword = configProxyAuthenticationPassword;
434     }
435 
436     public void setConfigProxyAuthenticationUsername(
437             String configProxyAuthenticationUsername) {
438         this.configProxyAuthenticationUsername = configProxyAuthenticationUsername;
439     }
440 
441     public void setConfigProxyEnabled(String configProxyEnabled) {
442         this.configProxyEnabled = configProxyEnabled;
443     }
444 
445     public void setConfigProxyHost(String configProxyHost) {
446         this.configProxyHost = configProxyHost;
447     }
448 
449     public void setConfigProxyPort(String configProxyPort) {
450         this.configProxyPort = configProxyPort;
451     }
452 
453     public void setHttpStateSessionKey(String httpStateSessionKey) {
454         this.httpStateSessionKey = httpStateSessionKey;
455     }
456 
457     public String getConfigProxyAuthenticationDomain() {
458         return configProxyAuthenticationDomain;
459     }
460 
461     public void setConfigProxyAuthenticationDomain(
462             String configProxyAuthenticationDomain) {
463         this.configProxyAuthenticationDomain = configProxyAuthenticationDomain;
464     }
465 
466     public String getConfigConnectionMaxPerHost() {
467         return configConnectionMaxPerHost;
468     }
469 
470     public String getConfigConnectionMaxTotal() {
471         return configConnectionMaxTotal;
472     }
473 
474     public String getConfigConnectionTimeout() {
475         return configConnectionTimeout;
476     }
477 
478     public String getConfigProxyAuthentication() {
479         return configProxyAuthentication;
480     }
481 
482     public String getConfigProxyAuthenticationHost() {
483         return configProxyAuthenticationHost;
484     }
485 
486     public String getConfigProxyAuthenticationPassword() {
487         return configProxyAuthenticationPassword;
488     }
489 
490     public String getConfigProxyAuthenticationUsername() {
491         return configProxyAuthenticationUsername;
492     }
493 
494     public String getConfigProxyEnabled() {
495         return configProxyEnabled;
496     }
497 
498     public String getConfigProxyHost() {
499         return configProxyHost;
500     }
501 
502     public String getConfigProxyPort() {
503         return configProxyPort;
504     }
505 
506     public String getHttpStateSessionKey() {
507         return httpStateSessionKey;
508     }
509 }