Skip to content

Commit

Permalink
Made the mechanism for creating Unix-Domain sockets and addresses pri…
Browse files Browse the repository at this point in the history
…vate.

This would make easier to fit QUIC in the future.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
  • Loading branch information
sbordet committed Jul 27, 2021
1 parent d1797e8 commit 1b5ce37
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 61 deletions.
6 changes: 6 additions & 0 deletions jetty-fcgi/fcgi-server/pom.xml
Expand Up @@ -69,5 +69,11 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-unixdomain-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Expand Up @@ -14,6 +14,7 @@
package org.eclipse.jetty.fcgi.server.proxy;

import java.net.URI;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Set;
Expand All @@ -36,6 +37,7 @@
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.proxy.AsyncProxyServlet;
import org.eclipse.jetty.util.ProcessorUtils;

Expand All @@ -62,6 +64,8 @@
* 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
Expand Down Expand Up @@ -122,11 +126,23 @@ protected HttpClient newHttpClient()
String scriptRoot = config.getInitParameter(SCRIPT_ROOT_INIT_PARAM);
if (scriptRoot == null)
throw new IllegalArgumentException("Mandatory parameter '" + SCRIPT_ROOT_INIT_PARAM + "' not configured");
int selectors = Math.max(1, ProcessorUtils.availableProcessors() / 2);
String value = config.getInitParameter("selectors");
if (value != null)
selectors = Integer.parseInt(value);
return new HttpClient(new ProxyHttpClientTransportOverFCGI(selectors, scriptRoot));

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
Expand Down Expand Up @@ -261,9 +277,9 @@ protected void customizeFastCGIHeaders(Request proxyRequest, HttpFields.Mutable

private class ProxyHttpClientTransportOverFCGI extends HttpClientTransportOverFCGI
{
private ProxyHttpClientTransportOverFCGI(int selectors, String scriptRoot)
private ProxyHttpClientTransportOverFCGI(ClientConnector connector, String scriptRoot)
{
super(selectors, scriptRoot);
super(connector, scriptRoot);
}

@Override
Expand Down
Expand Up @@ -14,9 +14,12 @@
package org.eclipse.jetty.fcgi.server.proxy;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
Expand All @@ -29,18 +32,22 @@
import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
Expand All @@ -49,19 +56,13 @@

public class FastCGIProxyServletTest
{
public static Stream<Arguments> factories()
{
return Stream.of(
true, // send status 200
false // don't send status 200
).map(Arguments::of);
}

private final Map<String, String> fcgiParams = new HashMap<>();
private Server server;
private ServerConnector httpConnector;
private ServerConnector fcgiConnector;
private Connector fcgiConnector;
private ServletContextHandler context;
private HttpClient client;
private Path unixDomainPath;

public void prepare(boolean sendStatus200, HttpServlet servlet) throws Exception
{
Expand All @@ -71,26 +72,40 @@ public void prepare(boolean sendStatus200, HttpServlet servlet) throws Exception
httpConnector = new ServerConnector(server);
server.addConnector(httpConnector);

fcgiConnector = new ServerConnector(server, new ServerFCGIConnectionFactory(new HttpConfiguration(), sendStatus200));
ServerFCGIConnectionFactory fcgi = new ServerFCGIConnectionFactory(new HttpConfiguration(), sendStatus200);
if (unixDomainPath == null)
{
fcgiConnector = new ServerConnector(server, fcgi);
}
else
{
UnixDomainServerConnector connector = new UnixDomainServerConnector(server, fcgi);
connector.setUnixDomainPath(unixDomainPath);
fcgiConnector = connector;
}
server.addConnector(fcgiConnector);

final String contextPath = "/";
String contextPath = "/";
context = new ServletContextHandler(server, contextPath);

final String servletPath = "/script";
String servletPath = "/script";
FastCGIProxyServlet fcgiServlet = new FastCGIProxyServlet()
{
@Override
protected String rewriteTarget(HttpServletRequest request)
{
return "http://localhost:" + fcgiConnector.getLocalPort() + servletPath + request.getServletPath();
String uri = "http://localhost";
if (unixDomainPath == null)
uri += ":" + ((ServerConnector)fcgiConnector).getLocalPort();
return uri + servletPath + request.getServletPath();
}
};
ServletHolder fcgiServletHolder = new ServletHolder(fcgiServlet);
fcgiServletHolder.setName("fcgi");
fcgiServletHolder.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, "/scriptRoot");
fcgiServletHolder.setInitParameter("proxyTo", "http://localhost");
fcgiServletHolder.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "(.+?\\.php)");
fcgiParams.forEach(fcgiServletHolder::setInitParameter);
context.addServlet(fcgiServletHolder, "*.php");

context.addServlet(new ServletHolder(servlet), servletPath + "/*");
Expand All @@ -111,36 +126,36 @@ public void dispose() throws Exception
}

@ParameterizedTest(name = "[{index}] sendStatus200={0}")
@MethodSource("factories")
@ValueSource(booleans = {true, false})
public void testGETWithSmallResponseContent(boolean sendStatus200) throws Exception
{
testGETWithResponseContent(sendStatus200, 1024, 0);
}

@ParameterizedTest(name = "[{index}] sendStatus200={0}")
@MethodSource("factories")
@ValueSource(booleans = {true, false})
public void testGETWithLargeResponseContent(boolean sendStatus200) throws Exception
{
testGETWithResponseContent(sendStatus200, 16 * 1024 * 1024, 0);
}

@ParameterizedTest(name = "[{index}] sendStatus200={0}")
@MethodSource("factories")
@ValueSource(booleans = {true, false})
public void testGETWithLargeResponseContentWithSlowClient(boolean sendStatus200) throws Exception
{
testGETWithResponseContent(sendStatus200, 16 * 1024 * 1024, 1);
}

private void testGETWithResponseContent(boolean sendStatus200, int length, final long delay) throws Exception
private void testGETWithResponseContent(boolean sendStatus200, int length, long delay) throws Exception
{
final byte[] data = new byte[length];
byte[] data = new byte[length];
new Random().nextBytes(data);

final String path = "/foo/index.php";
String path = "/foo/index.php";
prepare(sendStatus200, new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
{
assertTrue(request.getRequestURI().endsWith(path));
response.setContentLength(data.length);
Expand Down Expand Up @@ -173,28 +188,27 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t
}

@ParameterizedTest(name = "[{index}] sendStatus200={0}")
@MethodSource("factories")
@ValueSource(booleans = {true, false})
public void testURIRewrite(boolean sendStatus200) throws Exception
{
String originalPath = "/original/index.php";
String originalQuery = "foo=bar";
String remotePath = "/remote/index.php";
String pathAttribute = "_path_attribute";
String queryAttribute = "_query_attribute";
fcgiParams.put(FastCGIProxyServlet.ORIGINAL_URI_ATTRIBUTE_INIT_PARAM, pathAttribute);
fcgiParams.put(FastCGIProxyServlet.ORIGINAL_QUERY_ATTRIBUTE_INIT_PARAM, queryAttribute);
prepare(sendStatus200, new HttpServlet()
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
protected void service(HttpServletRequest request, HttpServletResponse response)
{
assertThat((String)request.getAttribute(FCGI.Headers.REQUEST_URI), Matchers.startsWith(originalPath));
assertEquals(originalQuery, request.getAttribute(FCGI.Headers.QUERY_STRING));
assertThat(request.getRequestURI(), Matchers.endsWith(remotePath));
}
});
context.stop();
String pathAttribute = "_path_attribute";
String queryAttribute = "_query_attribute";
ServletHolder fcgi = context.getServletHandler().getServlet("fcgi");
fcgi.setInitParameter(FastCGIProxyServlet.ORIGINAL_URI_ATTRIBUTE_INIT_PARAM, pathAttribute);
fcgi.setInitParameter(FastCGIProxyServlet.ORIGINAL_QUERY_ATTRIBUTE_INIT_PARAM, queryAttribute);
context.insertHandler(new HandlerWrapper()
{
@Override
Expand All @@ -216,4 +230,34 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest,

assertEquals(HttpStatus.OK_200, response.getStatus());
}

@Test
@EnabledForJreRange(min = JRE.JAVA_16)
public void testUnixDomain() throws Exception
{
int maxUnixDomainPathLength = 108;
Path path = Files.createTempFile("unix", ".sock");
if (path.normalize().toAbsolutePath().toString().length() > maxUnixDomainPathLength)
path = Files.createTempFile(Path.of("/tmp"), "unix", ".sock");
assertTrue(Files.deleteIfExists(path));
unixDomainPath = path;
fcgiParams.put("unixDomainPath", path.toString());
byte[] content = new byte[512];
new Random().nextBytes(content);
prepare(true, new HttpServlet()
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
{
response.getOutputStream().write(content);
}
});

ContentResponse response = client.newRequest("localhost", httpConnector.getLocalPort())
.path("/index.php")
.send();

assertEquals(HttpStatus.OK_200, response.getStatus());
assertArrayEquals(content, response.getContent());
}
}
@@ -1,3 +1,3 @@
# Jetty Logging using jetty-slf4j-impl
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG
#org.eclipse.jetty.fcgi.LEVEL=DEBUG
19 changes: 12 additions & 7 deletions jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
Expand Up @@ -49,6 +49,11 @@ public class ClientConnector extends ContainerLifeCycle
public static final String CONNECTION_PROMISE_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".connectionPromise";
private static final Logger LOG = LoggerFactory.getLogger(ClientConnector.class);

public static ClientConnector forUnixDomain(Path path)
{
return new ClientConnector(SocketChannelWithAddress.Factory.forUnixDomain(path));
}

private final SocketChannelWithAddress.Factory factory;
private Executor executor;
private Scheduler scheduler;
Expand All @@ -67,7 +72,7 @@ public ClientConnector()
this((address, context) -> new SocketChannelWithAddress(SocketChannel.open(), address));
}

public ClientConnector(SocketChannelWithAddress.Factory factory)
private ClientConnector(SocketChannelWithAddress.Factory factory)
{
this.factory = Objects.requireNonNull(factory);
}
Expand Down Expand Up @@ -393,33 +398,33 @@ protected void connectionFailed(SelectableChannel channel, Throwable failure, Ob
/**
* <p>A pair/record holding a {@link SocketChannel} and a {@link SocketAddress} to connect to.</p>
*/
public static class SocketChannelWithAddress
private static class SocketChannelWithAddress
{
private final SocketChannel channel;
private final SocketAddress address;

public SocketChannelWithAddress(SocketChannel channel, SocketAddress address)
private SocketChannelWithAddress(SocketChannel channel, SocketAddress address)
{
this.channel = channel;
this.address = address;
}

public SocketChannel getSocketChannel()
private SocketChannel getSocketChannel()
{
return channel;
}

public SocketAddress getSocketAddress()
private SocketAddress getSocketAddress()
{
return address;
}

/**
* <p>A factory for {@link SocketChannelWithAddress} instances.</p>
*/
public interface Factory
private interface Factory
{
public static Factory forUnixDomain(Path path)
private static Factory forUnixDomain(Path path)
{
return (address, context) ->
{
Expand Down

0 comments on commit 1b5ce37

Please sign in to comment.