View Javadoc

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