View Javadoc

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  
19  package org.eclipse.jetty.fcgi.server.proxy;
20  
21  import java.net.URI;
22  import java.util.regex.Matcher;
23  import java.util.regex.Pattern;
24  import javax.servlet.RequestDispatcher;
25  import javax.servlet.ServletConfig;
26  import javax.servlet.ServletException;
27  import javax.servlet.http.HttpServletRequest;
28  
29  import org.eclipse.jetty.client.HttpClient;
30  import org.eclipse.jetty.client.api.Request;
31  import org.eclipse.jetty.fcgi.FCGI;
32  import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI;
33  import org.eclipse.jetty.http.HttpFields;
34  import org.eclipse.jetty.http.HttpScheme;
35  import org.eclipse.jetty.proxy.AsyncProxyServlet;
36  import org.eclipse.jetty.proxy.ProxyServlet;
37  
38  /**
39   * Specific implementation of {@link ProxyServlet.Transparent} for FastCGI.
40   * <p />
41   * This servlet accepts a HTTP request and transforms it into a FastCGI request
42   * that is sent to the FastCGI server specified in the <code>proxyTo</code>
43   * init-param.
44   * <p />
45   * This servlet accepts two additional init-params:
46   * <ul>
47   *     <li><code>scriptRoot</code>, mandatory, that must be set to the directory where
48   *     the application that must be served via FastCGI is installed and corresponds to
49   *     the FastCGI DOCUMENT_ROOT parameter</li>
50   *     <li><code>scriptPattern</code>, optional, defaults to <code>(.+?\.php)</code>,
51   *     that specifies a regular expression with at least 1 and at most 2 groups that specify
52   *     respectively:
53   *     <ul>
54   *         <li>the FastCGI SCRIPT_NAME parameter</li>
55   *         <li>the FastCGI PATH_INFO parameter</li>
56   *     </ul></li>
57   *     <li><code>fastCGI.HTTPS</code>, optional, defaults to false, that specifies whether
58   *     to force the FastCGI <code>HTTPS</code> parameter to the value <code>on</code></li>
59   * </ul>
60   *
61   * @see TryFilesFilter
62   */
63  public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
64  {
65      public static final String SCRIPT_ROOT_INIT_PARAM = "scriptRoot";
66      public static final String SCRIPT_PATTERN_INIT_PARAM = "scriptPattern";
67      public static final String FASTCGI_HTTPS_INIT_PARAM = "fastCGI.HTTPS";
68  
69      private static final String REMOTE_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remoteAddr";
70      private static final String REMOTE_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remotePort";
71      private static final String SERVER_NAME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverName";
72      private static final String SERVER_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverAddr";
73      private static final String SERVER_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverPort";
74      private static final String SCHEME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".scheme";
75      private static final String REQUEST_URI_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".requestURI";
76  
77      private Pattern scriptPattern;
78      private boolean fcgiHTTPS;
79  
80      @Override
81      public void init() throws ServletException
82      {
83          super.init();
84  
85          String value = getInitParameter(SCRIPT_PATTERN_INIT_PARAM);
86          if (value == null)
87              value = "(.+?\\.php)";
88          scriptPattern = Pattern.compile(value);
89  
90          fcgiHTTPS = Boolean.parseBoolean(getInitParameter(FASTCGI_HTTPS_INIT_PARAM));
91      }
92  
93      @Override
94      protected HttpClient newHttpClient()
95      {
96          ServletConfig config = getServletConfig();
97          String scriptRoot = config.getInitParameter(SCRIPT_ROOT_INIT_PARAM);
98          if (scriptRoot == null)
99              throw new IllegalArgumentException("Mandatory parameter '" + SCRIPT_ROOT_INIT_PARAM + "' not configured");
100         return new HttpClient(new ProxyHttpClientTransportOverFCGI(scriptRoot), null);
101     }
102 
103     @Override
104     protected void customizeProxyRequest(Request proxyRequest, HttpServletRequest request)
105     {
106         proxyRequest.attribute(REMOTE_ADDR_ATTRIBUTE, request.getRemoteAddr());
107         proxyRequest.attribute(REMOTE_PORT_ATTRIBUTE, String.valueOf(request.getRemotePort()));
108         proxyRequest.attribute(SERVER_NAME_ATTRIBUTE, request.getServerName());
109         proxyRequest.attribute(SERVER_ADDR_ATTRIBUTE, request.getLocalAddr());
110         proxyRequest.attribute(SERVER_PORT_ATTRIBUTE, String.valueOf(request.getLocalPort()));
111 
112         proxyRequest.attribute(SCHEME_ATTRIBUTE, request.getScheme());
113 
114         // If we are forwarded or included, retain the original request URI.
115         String originalPath = (String)request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
116         String originalQuery = (String)request.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING);
117         if (originalPath == null)
118         {
119             originalPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI);
120             originalQuery = (String)request.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING);
121         }
122         if (originalPath != null)
123         {
124             String originalURI = originalPath;
125             if (originalQuery != null)
126                 originalURI += "?" + originalQuery;
127             proxyRequest.attribute(REQUEST_URI_ATTRIBUTE, originalURI);
128         }
129 
130         super.customizeProxyRequest(proxyRequest, request);
131     }
132 
133     protected void customizeFastCGIHeaders(Request proxyRequest, HttpFields fastCGIHeaders)
134     {
135         fastCGIHeaders.put(FCGI.Headers.REMOTE_ADDR, (String)proxyRequest.getAttributes().get(REMOTE_ADDR_ATTRIBUTE));
136         fastCGIHeaders.put(FCGI.Headers.REMOTE_PORT, (String)proxyRequest.getAttributes().get(REMOTE_PORT_ATTRIBUTE));
137         fastCGIHeaders.put(FCGI.Headers.SERVER_NAME, (String)proxyRequest.getAttributes().get(SERVER_NAME_ATTRIBUTE));
138         fastCGIHeaders.put(FCGI.Headers.SERVER_ADDR, (String)proxyRequest.getAttributes().get(SERVER_ADDR_ATTRIBUTE));
139         fastCGIHeaders.put(FCGI.Headers.SERVER_PORT, (String)proxyRequest.getAttributes().get(SERVER_PORT_ATTRIBUTE));
140 
141         if (fcgiHTTPS || HttpScheme.HTTPS.is((String)proxyRequest.getAttributes().get(SCHEME_ATTRIBUTE)))
142             fastCGIHeaders.put(FCGI.Headers.HTTPS, "on");
143 
144         URI proxyRequestURI = proxyRequest.getURI();
145         String rawPath = proxyRequestURI.getRawPath();
146         String rawQuery = proxyRequestURI.getRawQuery();
147 
148         String requestURI = (String)proxyRequest.getAttributes().get(REQUEST_URI_ATTRIBUTE);
149         if (requestURI == null)
150         {
151             requestURI = rawPath;
152             if (rawQuery != null)
153                 requestURI += "?" + rawQuery;
154         }
155         fastCGIHeaders.put(FCGI.Headers.REQUEST_URI, requestURI);
156 
157         String scriptName = rawPath;
158         Matcher matcher = scriptPattern.matcher(rawPath);
159         if (matcher.matches())
160         {
161             // Expect at least one group in the regular expression.
162             scriptName = matcher.group(1);
163 
164             // If there is a second group, map it to PATH_INFO.
165             if (matcher.groupCount() > 1)
166                 fastCGIHeaders.put(FCGI.Headers.PATH_INFO, matcher.group(2));
167         }
168         fastCGIHeaders.put(FCGI.Headers.SCRIPT_NAME, scriptName);
169 
170         String root = fastCGIHeaders.get(FCGI.Headers.DOCUMENT_ROOT);
171         fastCGIHeaders.put(FCGI.Headers.SCRIPT_FILENAME, root + scriptName);
172     }
173 
174     private class ProxyHttpClientTransportOverFCGI extends HttpClientTransportOverFCGI
175     {
176         public ProxyHttpClientTransportOverFCGI(String scriptRoot)
177         {
178             super(scriptRoot);
179         }
180 
181         @Override
182         protected void customize(Request request, HttpFields fastCGIHeaders)
183         {
184             super.customize(request, fastCGIHeaders);
185             customizeFastCGIHeaders(request, fastCGIHeaders);
186         }
187     }
188 }