blob: 9fb85f9e64fc7a1b0afc9ec335b6aa38ed4fdc17 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.fcgi.server.proxy;
import java.net.URI;
import java.util.List;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.proxy.AsyncProxyServlet;
/**
* Specific implementation of {@link org.eclipse.jetty.proxy.AsyncProxyServlet.Transparent} for FastCGI.
* <p>
* This servlet accepts a HTTP request and transforms it into a FastCGI request
* that is sent to the FastCGI server specified in the <code>proxyTo</code>
* init-param.
* <p>
* This servlet accepts two additional init-params:
* <ul>
* <li><code>scriptRoot</code>, mandatory, that must be set to the directory where
* the application that must be served via FastCGI is installed and corresponds to
* the FastCGI DOCUMENT_ROOT parameter</li>
* <li><code>scriptPattern</code>, optional, defaults to <code>(.+?\.php)</code>,
* that specifies a regular expression with at least 1 and at most 2 groups that specify
* respectively:
* <ul>
* <li>the FastCGI SCRIPT_NAME parameter</li>
* <li>the FastCGI PATH_INFO parameter</li>
* </ul></li>
* <li><code>fastCGI.HTTPS</code>, optional, defaults to false, that specifies whether
* to force the FastCGI <code>HTTPS</code> parameter to the value <code>on</code></li>
* </ul>
*
* @see TryFilesFilter
*/
public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
{
public static final String SCRIPT_ROOT_INIT_PARAM = "scriptRoot";
public static final String SCRIPT_PATTERN_INIT_PARAM = "scriptPattern";
public static final String ORIGINAL_URI_ATTRIBUTE_INIT_PARAM = "originalURIAttribute";
public static final String FASTCGI_HTTPS_INIT_PARAM = "fastCGI.HTTPS";
private static final String REMOTE_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remoteAddr";
private static final String REMOTE_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remotePort";
private static final String SERVER_NAME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverName";
private static final String SERVER_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverAddr";
private static final String SERVER_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverPort";
private static final String SCHEME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".scheme";
private static final String REQUEST_URI_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".requestURI";
private Pattern scriptPattern;
private String originalURIAttribute;
private boolean fcgiHTTPS;
@Override
public void init() throws ServletException
{
super.init();
String value = getInitParameter(SCRIPT_PATTERN_INIT_PARAM);
if (value == null)
value = "(.+?\\.php)";
scriptPattern = Pattern.compile(value);
originalURIAttribute = getInitParameter(ORIGINAL_URI_ATTRIBUTE_INIT_PARAM);
fcgiHTTPS = Boolean.parseBoolean(getInitParameter(FASTCGI_HTTPS_INIT_PARAM));
}
@Override
protected HttpClient newHttpClient()
{
ServletConfig config = getServletConfig();
String scriptRoot = config.getInitParameter(SCRIPT_ROOT_INIT_PARAM);
if (scriptRoot == null)
throw new IllegalArgumentException("Mandatory parameter '" + SCRIPT_ROOT_INIT_PARAM + "' not configured");
return new HttpClient(new ProxyHttpClientTransportOverFCGI(scriptRoot), null);
}
@Override
protected void sendProxyRequest(HttpServletRequest request, HttpServletResponse proxyResponse, Request proxyRequest)
{
proxyRequest.attribute(REMOTE_ADDR_ATTRIBUTE, request.getRemoteAddr());
proxyRequest.attribute(REMOTE_PORT_ATTRIBUTE, String.valueOf(request.getRemotePort()));
proxyRequest.attribute(SERVER_NAME_ATTRIBUTE, request.getServerName());
proxyRequest.attribute(SERVER_ADDR_ATTRIBUTE, request.getLocalAddr());
proxyRequest.attribute(SERVER_PORT_ATTRIBUTE, String.valueOf(request.getLocalPort()));
proxyRequest.attribute(SCHEME_ATTRIBUTE, request.getScheme());
// Has the original URI been rewritten ?
String originalURI = null;
if (originalURIAttribute != null)
originalURI = (String)request.getAttribute(originalURIAttribute);
if (originalURI == null)
{
// If we are forwarded or included, retain the original request URI.
String originalPath = (String)request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
String originalQuery = (String)request.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING);
if (originalPath == null)
{
originalPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI);
originalQuery = (String)request.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING);
}
if (originalPath != null)
{
originalURI = originalPath;
if (originalQuery != null)
originalURI += "?" + originalQuery;
}
}
if (originalURI != null)
proxyRequest.attribute(REQUEST_URI_ATTRIBUTE, originalURI);
// If the Host header is missing, add it.
if (!proxyRequest.getHeaders().containsKey(HttpHeader.HOST.asString()))
{
String host = request.getServerName();
int port = request.getServerPort();
if (!getHttpClient().isDefaultPort(request.getScheme(), port))
host += ":" + port;
proxyRequest.header(HttpHeader.HOST, host);
proxyRequest.header(HttpHeader.X_FORWARDED_HOST, host);
}
// PHP does not like multiple Cookie headers, coalesce into one.
List<String> cookies = proxyRequest.getHeaders().getValuesList(HttpHeader.COOKIE.asString());
if (cookies.size() > 1)
{
StringBuilder builder = new StringBuilder();
for (int i = 0; i < cookies.size(); ++i)
{
if (i > 0)
builder.append("; ");
String cookie = cookies.get(i);
builder.append(cookie);
}
proxyRequest.header(HttpHeader.COOKIE, null);
proxyRequest.header(HttpHeader.COOKIE, builder.toString());
}
super.sendProxyRequest(request, proxyResponse, proxyRequest);
}
protected void customizeFastCGIHeaders(Request proxyRequest, HttpFields fastCGIHeaders)
{
fastCGIHeaders.put(FCGI.Headers.REMOTE_ADDR, (String)proxyRequest.getAttributes().get(REMOTE_ADDR_ATTRIBUTE));
fastCGIHeaders.put(FCGI.Headers.REMOTE_PORT, (String)proxyRequest.getAttributes().get(REMOTE_PORT_ATTRIBUTE));
fastCGIHeaders.put(FCGI.Headers.SERVER_NAME, (String)proxyRequest.getAttributes().get(SERVER_NAME_ATTRIBUTE));
fastCGIHeaders.put(FCGI.Headers.SERVER_ADDR, (String)proxyRequest.getAttributes().get(SERVER_ADDR_ATTRIBUTE));
fastCGIHeaders.put(FCGI.Headers.SERVER_PORT, (String)proxyRequest.getAttributes().get(SERVER_PORT_ATTRIBUTE));
if (fcgiHTTPS || HttpScheme.HTTPS.is((String)proxyRequest.getAttributes().get(SCHEME_ATTRIBUTE)))
fastCGIHeaders.put(FCGI.Headers.HTTPS, "on");
URI proxyRequestURI = proxyRequest.getURI();
String rawPath = proxyRequestURI.getRawPath();
String rawQuery = proxyRequestURI.getRawQuery();
String requestURI = (String)proxyRequest.getAttributes().get(REQUEST_URI_ATTRIBUTE);
if (requestURI == null)
{
requestURI = rawPath;
if (rawQuery != null)
requestURI += "?" + rawQuery;
}
fastCGIHeaders.put(FCGI.Headers.REQUEST_URI, requestURI);
String scriptName = rawPath;
Matcher matcher = scriptPattern.matcher(rawPath);
if (matcher.matches())
{
// Expect at least one group in the regular expression.
scriptName = matcher.group(1);
// If there is a second group, map it to PATH_INFO.
if (matcher.groupCount() > 1)
fastCGIHeaders.put(FCGI.Headers.PATH_INFO, matcher.group(2));
}
fastCGIHeaders.put(FCGI.Headers.SCRIPT_NAME, scriptName);
String root = fastCGIHeaders.get(FCGI.Headers.DOCUMENT_ROOT);
fastCGIHeaders.put(FCGI.Headers.SCRIPT_FILENAME, root + scriptName);
}
private class ProxyHttpClientTransportOverFCGI extends HttpClientTransportOverFCGI
{
public ProxyHttpClientTransportOverFCGI(String scriptRoot)
{
super(scriptRoot);
}
@Override
protected void customize(Request request, HttpFields fastCGIHeaders)
{
super.customize(request, fastCGIHeaders);
customizeFastCGIHeaders(request, fastCGIHeaders);
if (_log.isDebugEnabled())
{
TreeMap<String, String> fcgi = new TreeMap<>();
for (HttpField field : fastCGIHeaders)
fcgi.put(field.getName(), field.getValue());
String eol = System.lineSeparator();
_log.debug("FastCGI variables{}{}", eol, fcgi.entrySet().stream()
.map(entry -> String.format("%s: %s", entry.getKey(), entry.getValue()))
.collect(Collectors.joining(eol)));
}
}
}
}