Fix/jetty 12 restore ee n fcgi (#9796)
* Restored ee9 FastCGIProxyServlet and TryFilesFilter. * Restored ee10 FastCGIProxyServlet and TryFilesFilter. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
1c01a49149
commit
22641f1267
|
@ -55,6 +55,11 @@
|
|||
<artifactId>jetty-ee10-cdi</artifactId>
|
||||
<version>12.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee10</groupId>
|
||||
<artifactId>jetty-ee10-fcgi-proxy</artifactId>
|
||||
<version>12.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee10</groupId>
|
||||
<artifactId>jetty-ee10-glassfish-jstl</artifactId>
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.ee10</groupId>
|
||||
<artifactId>jetty-ee10</artifactId>
|
||||
<version>12.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jetty-ee10-fcgi-proxy</artifactId>
|
||||
<name>EE10 :: FCGI Proxy</name>
|
||||
|
||||
<properties>
|
||||
<bundle-symbolic-name>${project.groupId}.fcgi.proxy</bundle-symbolic-name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee10</groupId>
|
||||
<artifactId>jetty-ee10-proxy</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.fcgi</groupId>
|
||||
<artifactId>jetty-fcgi-client</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee10</groupId>
|
||||
<artifactId>jetty-ee10-servlet</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-slf4j-impl</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,16 @@
|
|||
[description]
|
||||
Enables support for EE10 FastCGI proxying.
|
||||
|
||||
[environment]
|
||||
ee10
|
||||
|
||||
[tags]
|
||||
fcgi
|
||||
proxy
|
||||
|
||||
[depends]
|
||||
fcgi
|
||||
|
||||
[lib]
|
||||
lib/jetty-ee10-fcgi-proxy-${jetty.version}.jar
|
||||
lib/jetty-ee10-proxy-${jetty.version}.jar
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
module org.eclipse.jetty.ee10.fcgi.proxy
|
||||
{
|
||||
requires transitive org.eclipse.jetty.ee10.proxy;
|
||||
requires transitive org.eclipse.jetty.fcgi.client;
|
||||
requires transitive org.eclipse.jetty.server;
|
||||
|
||||
requires static jakarta.servlet;
|
||||
|
||||
exports org.eclipse.jetty.ee10.fcgi.proxy;
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee10.fcgi.proxy;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.ServletConfig;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.Request;
|
||||
import org.eclipse.jetty.ee10.proxy.AsyncProxyServlet;
|
||||
import org.eclipse.jetty.fcgi.FCGI;
|
||||
import org.eclipse.jetty.fcgi.client.transport.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.io.ClientConnector;
|
||||
import org.eclipse.jetty.util.ProcessorUtils;
|
||||
|
||||
/**
|
||||
* Specific implementation of {@link org.eclipse.jetty.ee10.proxy.AsyncProxyServlet.Transparent} for FastCGI.
|
||||
* <p>
|
||||
* This servlet accepts an HTTP request and transforms it into a FastCGI request
|
||||
* that is sent to the FastCGI server specified in the {@code proxyTo}
|
||||
* init-param.
|
||||
* <p>
|
||||
* This servlet accepts these additional {@code init-param}s:
|
||||
* <ul>
|
||||
* <li>{@code scriptRoot}, 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}, optional, defaults to {@code (.+?\.php)},
|
||||
* 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}, optional, defaults to false, that specifies whether
|
||||
* to force the FastCGI {@code HTTPS} parameter to the value {@code on}</li>
|
||||
* <li>{@code fastCGI.envNames}, optional, a comma separated list of environment variable
|
||||
* names read via {@link System#getenv(String)} that are forwarded as FastCGI parameters.</li>
|
||||
* <li>{@code unixDomainPath}, optional, that specifies the Unix-Domain path the FastCGI
|
||||
* server listens to.</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 ORIGINAL_QUERY_ATTRIBUTE_INIT_PARAM = "originalQueryAttribute";
|
||||
public static final String FASTCGI_HTTPS_INIT_PARAM = "fastCGI.HTTPS";
|
||||
public static final String FASTCGI_ENV_NAMES_INIT_PARAM = "fastCGI.envNames";
|
||||
|
||||
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 static final String REQUEST_QUERY_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".requestQuery";
|
||||
|
||||
private Pattern scriptPattern;
|
||||
private String originalURIAttribute;
|
||||
private String originalQueryAttribute;
|
||||
private boolean fcgiHTTPS;
|
||||
private Set<String> fcgiEnvNames;
|
||||
|
||||
@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);
|
||||
originalQueryAttribute = getInitParameter(ORIGINAL_QUERY_ATTRIBUTE_INIT_PARAM);
|
||||
|
||||
fcgiHTTPS = Boolean.parseBoolean(getInitParameter(FASTCGI_HTTPS_INIT_PARAM));
|
||||
|
||||
fcgiEnvNames = Collections.emptySet();
|
||||
String envNames = getInitParameter(FASTCGI_ENV_NAMES_INIT_PARAM);
|
||||
if (envNames != null)
|
||||
{
|
||||
fcgiEnvNames = Stream.of(envNames.split(","))
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
ClientConnector connector;
|
||||
String unixDomainPath = config.getInitParameter("unixDomainPath");
|
||||
if (unixDomainPath != null)
|
||||
{
|
||||
connector = ClientConnector.forUnixDomain(Path.of(unixDomainPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
int selectors = Math.max(1, ProcessorUtils.availableProcessors() / 2);
|
||||
String value = config.getInitParameter("selectors");
|
||||
if (value != null)
|
||||
selectors = Integer.parseInt(value);
|
||||
connector = new ClientConnector();
|
||||
connector.setSelectors(selectors);
|
||||
}
|
||||
return new HttpClient(new ProxyHttpClientTransportOverFCGI(connector, scriptRoot));
|
||||
}
|
||||
|
||||
@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;
|
||||
String originalQuery = null;
|
||||
if (originalURIAttribute != null)
|
||||
originalURI = (String)request.getAttribute(originalURIAttribute);
|
||||
if (originalURI != null && originalQueryAttribute != null)
|
||||
{
|
||||
originalQuery = (String)request.getAttribute(originalQueryAttribute);
|
||||
if (originalQuery != null)
|
||||
originalURI += "?" + originalQuery;
|
||||
}
|
||||
|
||||
if (originalURI == null)
|
||||
{
|
||||
// If we are forwarded or included, retain the original request URI.
|
||||
String originalPath = (String)request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
|
||||
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 (originalQuery != null)
|
||||
proxyRequest.attribute(REQUEST_QUERY_ATTRIBUTE, originalQuery);
|
||||
|
||||
// If the Host header is missing, add it.
|
||||
if (!proxyRequest.getHeaders().contains(HttpHeader.HOST))
|
||||
{
|
||||
String server = request.getServerName();
|
||||
int port = request.getServerPort();
|
||||
if (port != HttpScheme.getDefaultPort(request.getScheme()))
|
||||
server += ":" + port;
|
||||
String host = server;
|
||||
proxyRequest.headers(headers -> headers
|
||||
.put(HttpHeader.HOST, host)
|
||||
.put(HttpHeader.X_FORWARDED_HOST, host));
|
||||
}
|
||||
|
||||
// PHP does not like multiple Cookie headers, coalesce into one.
|
||||
List<String> cookies = proxyRequest.getHeaders().getValuesList(HttpHeader.COOKIE);
|
||||
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.headers(headers -> headers.put(HttpHeader.COOKIE, builder.toString()));
|
||||
}
|
||||
|
||||
super.sendProxyRequest(request, proxyResponse, proxyRequest);
|
||||
}
|
||||
|
||||
protected void customizeFastCGIHeaders(Request proxyRequest, HttpFields.Mutable fastCGIHeaders)
|
||||
{
|
||||
for (String envName : fcgiEnvNames)
|
||||
{
|
||||
String envValue = System.getenv(envName);
|
||||
if (envValue != null)
|
||||
fastCGIHeaders.put(envName, envValue);
|
||||
}
|
||||
|
||||
fastCGIHeaders.remove("HTTP_PROXY");
|
||||
|
||||
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 == null ? proxyRequest.getPath() : proxyRequestURI.getRawPath();
|
||||
String rawQuery = proxyRequestURI == null ? null : 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 requestQuery = (String)proxyRequest.getAttributes().get(REQUEST_QUERY_ATTRIBUTE);
|
||||
if (requestQuery != null)
|
||||
fastCGIHeaders.put(FCGI.Headers.QUERY_STRING, requestQuery);
|
||||
|
||||
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
|
||||
{
|
||||
private ProxyHttpClientTransportOverFCGI(ClientConnector connector, String scriptRoot)
|
||||
{
|
||||
super(connector, scriptRoot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(Request request, HttpFields.Mutable 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee10.fcgi.proxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.FilterConfig;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Inspired by nginx's try_files functionality.
|
||||
* <p>
|
||||
* This filter accepts the {@code files} init-param as a list of space-separated
|
||||
* file URIs. The special token {@code $path} represents the current request URL's
|
||||
* path (the portion after the context path).
|
||||
* <p>
|
||||
* Typical example of how this filter can be configured is the following:
|
||||
* <pre>
|
||||
* <filter>
|
||||
* <filter-name>try_files</filter-name>
|
||||
* <filter-class>org.eclipse.jetty.fcgi.server.proxy.TryFilesFilter</filter-class>
|
||||
* <init-param>
|
||||
* <param-name>files</param-name>
|
||||
* <param-value>/maintenance.html $path /index.php?p=$path</param-value>
|
||||
* </init-param>
|
||||
* </filter>
|
||||
* </pre>
|
||||
* For a request such as {@code /context/path/to/resource.ext}, this filter will
|
||||
* try to serve the {@code /maintenance.html} file if it finds it; failing that,
|
||||
* it will try to serve the {@code /path/to/resource.ext} file if it finds it;
|
||||
* failing that it will forward the request to {@code /index.php?p=/path/to/resource.ext}.
|
||||
* The last file URI specified in the list is therefore the "fallback" to which the request
|
||||
* is forwarded to in case no previous files can be found.
|
||||
* <p>
|
||||
* The files are resolved using {@link ServletContext#getResource(String)} to make sure
|
||||
* that only files visible to the application are served.
|
||||
*
|
||||
* @see FastCGIProxyServlet
|
||||
*/
|
||||
public class TryFilesFilter implements Filter
|
||||
{
|
||||
public static final String FILES_INIT_PARAM = "files";
|
||||
|
||||
private String[] files;
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig config) throws ServletException
|
||||
{
|
||||
String param = config.getInitParameter(FILES_INIT_PARAM);
|
||||
if (param == null)
|
||||
throw new ServletException(String.format("Missing mandatory parameter '%s'", FILES_INIT_PARAM));
|
||||
files = param.split(" ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
|
||||
{
|
||||
HttpServletRequest httpRequest = (HttpServletRequest)request;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse)response;
|
||||
|
||||
for (int i = 0; i < files.length - 1; ++i)
|
||||
{
|
||||
String file = files[i];
|
||||
String resolved = resolve(httpRequest, file);
|
||||
|
||||
URL url = request.getServletContext().getResource(resolved);
|
||||
if (url == null)
|
||||
continue;
|
||||
|
||||
if (Files.isReadable(toPath(url)))
|
||||
{
|
||||
chain.doFilter(httpRequest, httpResponse);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The last one is the fallback
|
||||
fallback(httpRequest, httpResponse, chain, files[files.length - 1]);
|
||||
}
|
||||
|
||||
private Path toPath(URL url) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return Paths.get(url.toURI());
|
||||
}
|
||||
catch (URISyntaxException x)
|
||||
{
|
||||
throw new IOException(x);
|
||||
}
|
||||
}
|
||||
|
||||
protected void fallback(HttpServletRequest request, HttpServletResponse response, FilterChain chain, String fallback) throws IOException, ServletException
|
||||
{
|
||||
String resolved = resolve(request, fallback);
|
||||
request.getServletContext().getRequestDispatcher(resolved).forward(request, response);
|
||||
}
|
||||
|
||||
private String resolve(HttpServletRequest request, String value)
|
||||
{
|
||||
String path = request.getServletPath();
|
||||
String info = request.getPathInfo();
|
||||
if (info != null)
|
||||
path += info;
|
||||
if (!path.startsWith("/"))
|
||||
path = "/" + path;
|
||||
return StringUtil.replace(value, "$path", path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee10.fcgi.proxy;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.client.ContentResponse;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.ee10.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.ee10.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class TryFilesFilterTest
|
||||
{
|
||||
private Server server;
|
||||
private ServerConnector connector;
|
||||
private ServerConnector sslConnector;
|
||||
private HttpClient client;
|
||||
private String forwardPath;
|
||||
|
||||
public void prepare(HttpServlet servlet) throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
connector = new ServerConnector(server);
|
||||
server.addConnector(connector);
|
||||
|
||||
SslContextFactory.Server serverSslContextFactory = new SslContextFactory.Server();
|
||||
serverSslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
|
||||
serverSslContextFactory.setKeyStorePassword("storepwd");
|
||||
sslConnector = new ServerConnector(server, serverSslContextFactory);
|
||||
server.addConnector(sslConnector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler(server, "/");
|
||||
|
||||
FilterHolder filterHolder = context.addFilter(TryFilesFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
forwardPath = "/index.php";
|
||||
filterHolder.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path " + forwardPath + "?p=$path");
|
||||
|
||||
context.addServlet(new ServletHolder(servlet), "/*");
|
||||
|
||||
ClientConnector clientConnector = new ClientConnector();
|
||||
SslContextFactory.Client clientSslContextFactory = new SslContextFactory.Client(true);
|
||||
// clientSslContextFactory.setEndpointIdentificationAlgorithm(null);
|
||||
// clientSslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
|
||||
// clientSslContextFactory.setKeyStorePassword("storepwd");
|
||||
clientConnector.setSslContextFactory(clientSslContextFactory);
|
||||
client = new HttpClient(new HttpClientTransportOverHTTP(clientConnector));
|
||||
server.addBean(client);
|
||||
|
||||
server.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void dispose() throws Exception
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHTTPSRequestIsForwarded() throws Exception
|
||||
{
|
||||
final String path = "/one/";
|
||||
prepare(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
{
|
||||
assertTrue("https".equalsIgnoreCase(req.getScheme()));
|
||||
assertTrue(req.isSecure());
|
||||
assertEquals(forwardPath, req.getRequestURI());
|
||||
assertTrue(req.getQueryString().endsWith(path));
|
||||
}
|
||||
});
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", sslConnector.getLocalPort())
|
||||
.scheme("https")
|
||||
.path(path)
|
||||
.send();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
#org.eclipse.jetty.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.fcgi.proxy.LEVEL=DEBUG
|
Binary file not shown.
|
@ -488,6 +488,11 @@
|
|||
<artifactId>jetty-ee10-jndi</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee10</groupId>
|
||||
<artifactId>jetty-ee10-fcgi-proxy</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- Demo EE10 Apps -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee10.demos</groupId>
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
<module>jetty-ee10-examples</module>
|
||||
<module>jetty-ee10-bom</module>
|
||||
<module>jetty-ee10-home</module>
|
||||
<module>jetty-ee10-fcgi-proxy</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
|
@ -84,6 +85,11 @@
|
|||
<artifactId>jetty-ee10-cdi</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee10</groupId>
|
||||
<artifactId>jetty-ee10-fcgi-proxy</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee10</groupId>
|
||||
<artifactId>jetty-ee10-glassfish-jstl</artifactId>
|
||||
|
|
|
@ -55,6 +55,11 @@
|
|||
<artifactId>jetty-ee9-cdi</artifactId>
|
||||
<version>12.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee9</groupId>
|
||||
<artifactId>jetty-ee9-fcgi-proxy</artifactId>
|
||||
<version>12.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee9</groupId>
|
||||
<artifactId>jetty-ee9-glassfish-jstl</artifactId>
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.ee9</groupId>
|
||||
<artifactId>jetty-ee9</artifactId>
|
||||
<version>12.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jetty-ee9-fcgi-proxy</artifactId>
|
||||
<name>EE9 :: FCGI Proxy</name>
|
||||
|
||||
<properties>
|
||||
<bundle-symbolic-name>${project.groupId}.fcgi.proxy</bundle-symbolic-name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-jakarta-servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee9</groupId>
|
||||
<artifactId>jetty-ee9-proxy</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.fcgi</groupId>
|
||||
<artifactId>jetty-fcgi-client</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee9</groupId>
|
||||
<artifactId>jetty-ee9-servlet</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-slf4j-impl</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,16 @@
|
|||
[description]
|
||||
Enables support for EE9 FastCGI proxying.
|
||||
|
||||
[environment]
|
||||
ee9
|
||||
|
||||
[tags]
|
||||
fcgi
|
||||
proxy
|
||||
|
||||
[depends]
|
||||
fcgi
|
||||
|
||||
[lib]
|
||||
lib/jetty-ee9-fcgi-proxy-${jetty.version}.jar
|
||||
lib/jetty-ee9-proxy-${jetty.version}.jar
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
module org.eclipse.jetty.ee9.fcgi.proxy
|
||||
{
|
||||
requires transitive org.eclipse.jetty.ee9.proxy;
|
||||
requires transitive org.eclipse.jetty.fcgi.client;
|
||||
requires transitive org.eclipse.jetty.server;
|
||||
|
||||
requires static jetty.servlet.api;
|
||||
|
||||
exports org.eclipse.jetty.ee9.fcgi.proxy;
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee9.fcgi.proxy;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.ServletConfig;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.Request;
|
||||
import org.eclipse.jetty.ee9.proxy.AsyncProxyServlet;
|
||||
import org.eclipse.jetty.fcgi.FCGI;
|
||||
import org.eclipse.jetty.fcgi.client.transport.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.io.ClientConnector;
|
||||
import org.eclipse.jetty.util.ProcessorUtils;
|
||||
|
||||
/**
|
||||
* Specific implementation of {@link org.eclipse.jetty.ee9.proxy.AsyncProxyServlet.Transparent} for FastCGI.
|
||||
* <p>
|
||||
* This servlet accepts an HTTP request and transforms it into a FastCGI request
|
||||
* that is sent to the FastCGI server specified in the {@code proxyTo}
|
||||
* init-param.
|
||||
* <p>
|
||||
* This servlet accepts these additional {@code init-param}s:
|
||||
* <ul>
|
||||
* <li>{@code scriptRoot}, 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}, optional, defaults to {@code (.+?\.php)},
|
||||
* 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}, optional, defaults to false, that specifies whether
|
||||
* to force the FastCGI {@code HTTPS} parameter to the value {@code on}</li>
|
||||
* <li>{@code fastCGI.envNames}, optional, a comma separated list of environment variable
|
||||
* names read via {@link System#getenv(String)} that are forwarded as FastCGI parameters.</li>
|
||||
* <li>{@code unixDomainPath}, optional, that specifies the Unix-Domain path the FastCGI
|
||||
* server listens to.</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 ORIGINAL_QUERY_ATTRIBUTE_INIT_PARAM = "originalQueryAttribute";
|
||||
public static final String FASTCGI_HTTPS_INIT_PARAM = "fastCGI.HTTPS";
|
||||
public static final String FASTCGI_ENV_NAMES_INIT_PARAM = "fastCGI.envNames";
|
||||
|
||||
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 static final String REQUEST_QUERY_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".requestQuery";
|
||||
|
||||
private Pattern scriptPattern;
|
||||
private String originalURIAttribute;
|
||||
private String originalQueryAttribute;
|
||||
private boolean fcgiHTTPS;
|
||||
private Set<String> fcgiEnvNames;
|
||||
|
||||
@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);
|
||||
originalQueryAttribute = getInitParameter(ORIGINAL_QUERY_ATTRIBUTE_INIT_PARAM);
|
||||
|
||||
fcgiHTTPS = Boolean.parseBoolean(getInitParameter(FASTCGI_HTTPS_INIT_PARAM));
|
||||
|
||||
fcgiEnvNames = Collections.emptySet();
|
||||
String envNames = getInitParameter(FASTCGI_ENV_NAMES_INIT_PARAM);
|
||||
if (envNames != null)
|
||||
{
|
||||
fcgiEnvNames = Stream.of(envNames.split(","))
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
ClientConnector connector;
|
||||
String unixDomainPath = config.getInitParameter("unixDomainPath");
|
||||
if (unixDomainPath != null)
|
||||
{
|
||||
connector = ClientConnector.forUnixDomain(Path.of(unixDomainPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
int selectors = Math.max(1, ProcessorUtils.availableProcessors() / 2);
|
||||
String value = config.getInitParameter("selectors");
|
||||
if (value != null)
|
||||
selectors = Integer.parseInt(value);
|
||||
connector = new ClientConnector();
|
||||
connector.setSelectors(selectors);
|
||||
}
|
||||
return new HttpClient(new ProxyHttpClientTransportOverFCGI(connector, scriptRoot));
|
||||
}
|
||||
|
||||
@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;
|
||||
String originalQuery = null;
|
||||
if (originalURIAttribute != null)
|
||||
originalURI = (String)request.getAttribute(originalURIAttribute);
|
||||
if (originalURI != null && originalQueryAttribute != null)
|
||||
{
|
||||
originalQuery = (String)request.getAttribute(originalQueryAttribute);
|
||||
if (originalQuery != null)
|
||||
originalURI += "?" + originalQuery;
|
||||
}
|
||||
|
||||
if (originalURI == null)
|
||||
{
|
||||
// If we are forwarded or included, retain the original request URI.
|
||||
String originalPath = (String)request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
|
||||
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 (originalQuery != null)
|
||||
proxyRequest.attribute(REQUEST_QUERY_ATTRIBUTE, originalQuery);
|
||||
|
||||
// If the Host header is missing, add it.
|
||||
if (!proxyRequest.getHeaders().contains(HttpHeader.HOST))
|
||||
{
|
||||
String server = request.getServerName();
|
||||
int port = request.getServerPort();
|
||||
if (port != HttpScheme.getDefaultPort(request.getScheme()))
|
||||
server += ":" + port;
|
||||
String host = server;
|
||||
proxyRequest.headers(headers -> headers
|
||||
.put(HttpHeader.HOST, host)
|
||||
.put(HttpHeader.X_FORWARDED_HOST, host));
|
||||
}
|
||||
|
||||
// PHP does not like multiple Cookie headers, coalesce into one.
|
||||
List<String> cookies = proxyRequest.getHeaders().getValuesList(HttpHeader.COOKIE);
|
||||
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.headers(headers -> headers.put(HttpHeader.COOKIE, builder.toString()));
|
||||
}
|
||||
|
||||
super.sendProxyRequest(request, proxyResponse, proxyRequest);
|
||||
}
|
||||
|
||||
protected void customizeFastCGIHeaders(Request proxyRequest, HttpFields.Mutable fastCGIHeaders)
|
||||
{
|
||||
for (String envName : fcgiEnvNames)
|
||||
{
|
||||
String envValue = System.getenv(envName);
|
||||
if (envValue != null)
|
||||
fastCGIHeaders.put(envName, envValue);
|
||||
}
|
||||
|
||||
fastCGIHeaders.remove("HTTP_PROXY");
|
||||
|
||||
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 == null ? proxyRequest.getPath() : proxyRequestURI.getRawPath();
|
||||
String rawQuery = proxyRequestURI == null ? null : 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 requestQuery = (String)proxyRequest.getAttributes().get(REQUEST_QUERY_ATTRIBUTE);
|
||||
if (requestQuery != null)
|
||||
fastCGIHeaders.put(FCGI.Headers.QUERY_STRING, requestQuery);
|
||||
|
||||
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
|
||||
{
|
||||
private ProxyHttpClientTransportOverFCGI(ClientConnector connector, String scriptRoot)
|
||||
{
|
||||
super(connector, scriptRoot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(Request request, HttpFields.Mutable 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee9.fcgi.proxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.FilterConfig;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Inspired by nginx's try_files functionality.
|
||||
* <p>
|
||||
* This filter accepts the {@code files} init-param as a list of space-separated
|
||||
* file URIs. The special token {@code $path} represents the current request URL's
|
||||
* path (the portion after the context path).
|
||||
* <p>
|
||||
* Typical example of how this filter can be configured is the following:
|
||||
* <pre>
|
||||
* <filter>
|
||||
* <filter-name>try_files</filter-name>
|
||||
* <filter-class>org.eclipse.jetty.fcgi.server.proxy.TryFilesFilter</filter-class>
|
||||
* <init-param>
|
||||
* <param-name>files</param-name>
|
||||
* <param-value>/maintenance.html $path /index.php?p=$path</param-value>
|
||||
* </init-param>
|
||||
* </filter>
|
||||
* </pre>
|
||||
* For a request such as {@code /context/path/to/resource.ext}, this filter will
|
||||
* try to serve the {@code /maintenance.html} file if it finds it; failing that,
|
||||
* it will try to serve the {@code /path/to/resource.ext} file if it finds it;
|
||||
* failing that it will forward the request to {@code /index.php?p=/path/to/resource.ext}.
|
||||
* The last file URI specified in the list is therefore the "fallback" to which the request
|
||||
* is forwarded to in case no previous files can be found.
|
||||
* <p>
|
||||
* The files are resolved using {@link ServletContext#getResource(String)} to make sure
|
||||
* that only files visible to the application are served.
|
||||
*
|
||||
* @see FastCGIProxyServlet
|
||||
*/
|
||||
public class TryFilesFilter implements Filter
|
||||
{
|
||||
public static final String FILES_INIT_PARAM = "files";
|
||||
|
||||
private String[] files;
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig config) throws ServletException
|
||||
{
|
||||
String param = config.getInitParameter(FILES_INIT_PARAM);
|
||||
if (param == null)
|
||||
throw new ServletException(String.format("Missing mandatory parameter '%s'", FILES_INIT_PARAM));
|
||||
files = param.split(" ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
|
||||
{
|
||||
HttpServletRequest httpRequest = (HttpServletRequest)request;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse)response;
|
||||
|
||||
for (int i = 0; i < files.length - 1; ++i)
|
||||
{
|
||||
String file = files[i];
|
||||
String resolved = resolve(httpRequest, file);
|
||||
|
||||
URL url = request.getServletContext().getResource(resolved);
|
||||
if (url == null)
|
||||
continue;
|
||||
|
||||
if (Files.isReadable(toPath(url)))
|
||||
{
|
||||
chain.doFilter(httpRequest, httpResponse);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The last one is the fallback
|
||||
fallback(httpRequest, httpResponse, chain, files[files.length - 1]);
|
||||
}
|
||||
|
||||
private Path toPath(URL url) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return Paths.get(url.toURI());
|
||||
}
|
||||
catch (URISyntaxException x)
|
||||
{
|
||||
throw new IOException(x);
|
||||
}
|
||||
}
|
||||
|
||||
protected void fallback(HttpServletRequest request, HttpServletResponse response, FilterChain chain, String fallback) throws IOException, ServletException
|
||||
{
|
||||
String resolved = resolve(request, fallback);
|
||||
request.getServletContext().getRequestDispatcher(resolved).forward(request, response);
|
||||
}
|
||||
|
||||
private String resolve(HttpServletRequest request, String value)
|
||||
{
|
||||
String path = request.getServletPath();
|
||||
String info = request.getPathInfo();
|
||||
if (info != null)
|
||||
path += info;
|
||||
if (!path.startsWith("/"))
|
||||
path = "/" + path;
|
||||
return StringUtil.replace(value, "$path", path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee9.fcgi.proxy;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.client.ContentResponse;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.ee9.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.ee9.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.ee9.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class TryFilesFilterTest
|
||||
{
|
||||
private Server server;
|
||||
private ServerConnector connector;
|
||||
private ServerConnector sslConnector;
|
||||
private HttpClient client;
|
||||
private String forwardPath;
|
||||
|
||||
public void prepare(HttpServlet servlet) throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
connector = new ServerConnector(server);
|
||||
server.addConnector(connector);
|
||||
|
||||
SslContextFactory.Server serverSslContextFactory = new SslContextFactory.Server();
|
||||
serverSslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
|
||||
serverSslContextFactory.setKeyStorePassword("storepwd");
|
||||
sslConnector = new ServerConnector(server, serverSslContextFactory);
|
||||
server.addConnector(sslConnector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler(server, "/");
|
||||
|
||||
FilterHolder filterHolder = context.addFilter(TryFilesFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
forwardPath = "/index.php";
|
||||
filterHolder.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path " + forwardPath + "?p=$path");
|
||||
|
||||
context.addServlet(new ServletHolder(servlet), "/*");
|
||||
|
||||
ClientConnector clientConnector = new ClientConnector();
|
||||
SslContextFactory.Client clientSslContextFactory = new SslContextFactory.Client(true);
|
||||
// clientSslContextFactory.setEndpointIdentificationAlgorithm(null);
|
||||
// clientSslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
|
||||
// clientSslContextFactory.setKeyStorePassword("storepwd");
|
||||
clientConnector.setSslContextFactory(clientSslContextFactory);
|
||||
client = new HttpClient(new HttpClientTransportOverHTTP(clientConnector));
|
||||
server.addBean(client);
|
||||
|
||||
server.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void dispose() throws Exception
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHTTPSRequestIsForwarded() throws Exception
|
||||
{
|
||||
final String path = "/one/";
|
||||
prepare(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
{
|
||||
assertTrue("https".equalsIgnoreCase(req.getScheme()));
|
||||
assertTrue(req.isSecure());
|
||||
assertEquals(forwardPath, req.getRequestURI());
|
||||
assertTrue(req.getQueryString().endsWith(path));
|
||||
}
|
||||
});
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", sslConnector.getLocalPort())
|
||||
.scheme("https")
|
||||
.path(path)
|
||||
.send();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
#org.eclipse.jetty.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.fcgi.proxy.LEVEL=DEBUG
|
Binary file not shown.
|
@ -472,6 +472,11 @@
|
|||
<artifactId>jetty-ee9-jndi</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee9</groupId>
|
||||
<artifactId>jetty-ee9-fcgi-proxy</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- Demo ee9 Apps -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee9.demos</groupId>
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
<module>jetty-ee9-tests</module>
|
||||
<module>jetty-ee9-bom</module>
|
||||
<module>jetty-ee9-home</module>
|
||||
<module>jetty-ee9-fcgi-proxy</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
|
@ -92,6 +93,11 @@
|
|||
<artifactId>jetty-ee9-cdi</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee9</groupId>
|
||||
<artifactId>jetty-ee9-fcgi-proxy</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee9</groupId>
|
||||
<artifactId>jetty-ee9-glassfish-jstl</artifactId>
|
||||
|
|
|
@ -157,7 +157,7 @@ public class DistributionTests extends AbstractJettyHomeTest
|
|||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"ee9", "ee10"})
|
||||
public void testSimpleWebAppWithJSPandJSTL(String env) throws Exception
|
||||
public void testSimpleWebAppWithJSPAndJSTL(String env) throws Exception
|
||||
{
|
||||
Path jettyBase = newTestJettyBaseDirectory();
|
||||
String jettyVersion = System.getProperty("jettyVersion");
|
||||
|
@ -1301,6 +1301,131 @@ public class DistributionTests extends AbstractJettyHomeTest
|
|||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"ee9", "ee10"})
|
||||
public void testEEFastCGIProxying(String env) throws Exception
|
||||
{
|
||||
Path jettyBase = newTestJettyBaseDirectory();
|
||||
String jettyVersion = System.getProperty("jettyVersion");
|
||||
JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
|
||||
.jettyVersion(jettyVersion)
|
||||
.jettyBase(jettyBase)
|
||||
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
|
||||
.build();
|
||||
|
||||
String mods = String.join(",",
|
||||
"resources", "http", "fcgi", "core-deploy",
|
||||
toEnvironment("deploy", env),
|
||||
toEnvironment("fcgi-proxy", env)
|
||||
);
|
||||
try (JettyHomeTester.Run run1 = distribution.start(List.of("--add-modules=" + mods)))
|
||||
{
|
||||
assertTrue(run1.awaitFor(START_TIMEOUT, TimeUnit.SECONDS));
|
||||
assertEquals(0, run1.getExitValue());
|
||||
|
||||
Path jettyLogging = distribution.getJettyBase().resolve("resources/jetty-logging.properties");
|
||||
String loggingConfig = """
|
||||
org.eclipse.jetty.LEVEL=DEBUG
|
||||
""";
|
||||
Files.writeString(jettyLogging, loggingConfig, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
|
||||
// Add a FastCGI connector to simulate, for example, php-fpm.
|
||||
int fcgiPort = distribution.freePort();
|
||||
Path jettyBaseEtc = jettyBase.resolve("etc");
|
||||
Files.createDirectories(jettyBaseEtc);
|
||||
Path fcgiConnectorXML = jettyBaseEtc.resolve("fcgi-connector.xml");
|
||||
Files.writeString(fcgiConnectorXML, """
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
|
||||
<Configure id="Server">
|
||||
<Call name="addConnector">
|
||||
<Arg>
|
||||
<New id="fcgiConnector" class="org.eclipse.jetty.server.ServerConnector">
|
||||
<Arg><Ref refid="Server" /></Arg>
|
||||
<Arg type="int">1</Arg>
|
||||
<Arg type="int">1</Arg>
|
||||
<Arg>
|
||||
<Array type="org.eclipse.jetty.server.ConnectionFactory">
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory">
|
||||
<Arg><Ref refid="httpConfig" /></Arg>
|
||||
</New>
|
||||
</Item>
|
||||
</Array>
|
||||
</Arg>
|
||||
<Set name="port">$P</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
</Configure>
|
||||
""".replace("$P", String.valueOf(fcgiPort)), StandardOpenOption.CREATE);
|
||||
|
||||
// Deploy a Jetty context XML file that is only necessary for the test,
|
||||
// as it simulates, for example, what the php-fpm server would return.
|
||||
Path jettyBaseWork = jettyBase.resolve("work");
|
||||
Path phpXML = jettyBase.resolve("webapps").resolve("php.xml");
|
||||
Files.writeString(phpXML, """
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
|
||||
<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
|
||||
<Set name="contextPath">/php</Set>
|
||||
<Set name="baseResourceAsPath">
|
||||
<Call class="java.nio.file.Path" name="of">
|
||||
<Arg>$R</Arg>
|
||||
</Call>
|
||||
</Set>
|
||||
<Set name="handler">
|
||||
<New class="org.eclipse.jetty.server.handler.ResourceHandler" />
|
||||
</Set>
|
||||
</Configure>
|
||||
""".replace("$R", jettyBaseWork.toAbsolutePath().toString()), StandardOpenOption.CREATE);
|
||||
// Save a file in $JETTY_BASE/work so that it can be requested.
|
||||
String testFileContent = "hello";
|
||||
Files.writeString(jettyBaseWork.resolve("test.txt"), testFileContent, StandardOpenOption.CREATE);
|
||||
|
||||
// Deploy a Jetty context XML file that sets up the FastCGIProxyServlet.
|
||||
// Converts URIs from http://host:<httpPort>/proxy/foo to http://host:<fcgiPort>/php/foo.
|
||||
Path proxyXML = jettyBase.resolve("webapps").resolve("proxy.xml");
|
||||
Files.writeString(proxyXML, """
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
|
||||
<Configure class="org.eclipse.jetty.$ENV.servlet.ServletContextHandler">
|
||||
<Set name="contextPath">/proxy</Set>
|
||||
<Call name="addServlet">
|
||||
<Arg>org.eclipse.jetty.$ENV.fcgi.proxy.FastCGIProxyServlet</Arg>
|
||||
<Arg>*.txt</Arg>
|
||||
<Call name="setInitParameter">
|
||||
<Arg>proxyTo</Arg>
|
||||
<Arg>http://localhost:$P/php</Arg>
|
||||
</Call>
|
||||
<Call name="setInitParameter">
|
||||
<Arg>scriptRoot</Arg>
|
||||
<Arg>/var/wordpress</Arg>
|
||||
</Call>
|
||||
</Call>
|
||||
</Configure>
|
||||
""".replace("$ENV", env).replace("$P", String.valueOf(fcgiPort)), StandardOpenOption.CREATE);
|
||||
|
||||
Path proxyProps = jettyBase.resolve("webapps").resolve("proxy.properties");
|
||||
Files.writeString(proxyProps, """
|
||||
environment=$ENV
|
||||
""".replace("$ENV", env), StandardOpenOption.CREATE);
|
||||
|
||||
int httpPort = distribution.freePort();
|
||||
try (JettyHomeTester.Run run2 = distribution.start("jetty.http.port=" + httpPort, "etc/fcgi-connector.xml"))
|
||||
{
|
||||
assertTrue(run2.awaitConsoleLogsFor("Started oejs.Server@", START_TIMEOUT, TimeUnit.SECONDS));
|
||||
|
||||
startHttpClient();
|
||||
// Make a request to the /proxy context on the httpPort; it should be converted to FastCGI
|
||||
// and reverse proxied to the simulated php-fpm /php context on the fcgiPort.
|
||||
ContentResponse response = client.GET("http://localhost:" + httpPort + "/proxy/test.txt");
|
||||
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||
assertThat(response.getContentAsString(), is(testFileContent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(max = JRE.JAVA_18)
|
||||
@Tag("flaky")
|
||||
|
|
Loading…
Reference in New Issue