From 1b5ce377545abf933d5c4f66bdad74b29cbafd96 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 27 Jul 2021 17:43:23 +0200 Subject: [PATCH] Made the mechanism for creating Unix-Domain sockets and addresses private. This would make easier to fit QUIC in the future. Signed-off-by: Simone Bordet --- jetty-fcgi/fcgi-server/pom.xml | 6 + .../server/proxy/FastCGIProxyServlet.java | 30 +++-- .../server/proxy/FastCGIProxyServletTest.java | 104 +++++++++++++----- .../test/resources/jetty-logging.properties | 2 +- .../org/eclipse/jetty/io/ClientConnector.java | 19 ++-- .../unixdomain/server/UnixDomainTest.java | 17 +-- .../tests/distribution/DistributionTests.java | 4 +- 7 files changed, 121 insertions(+), 61 deletions(-) diff --git a/jetty-fcgi/fcgi-server/pom.xml b/jetty-fcgi/fcgi-server/pom.xml index bed9361cede4..0a2226e4b81d 100644 --- a/jetty-fcgi/fcgi-server/pom.xml +++ b/jetty-fcgi/fcgi-server/pom.xml @@ -69,5 +69,11 @@ ${project.version} test + + org.eclipse.jetty + jetty-unixdomain-server + ${project.version} + test + diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java index a543c22df221..e22b81c70d62 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java @@ -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; @@ -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; @@ -62,6 +64,8 @@ * to force the FastCGI {@code HTTPS} parameter to the value {@code on} *
  • {@code fastCGI.envNames}, optional, a comma separated list of environment variable * names read via {@link System#getenv(String)} that are forwarded as FastCGI parameters.
  • + *
  • {@code unixDomainPath}, optional, that specifies the Unix-Domain path the FastCGI + * server listens to.
  • * * * @see TryFilesFilter @@ -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 @@ -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 diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java index 6df325b47312..1498be1b98fc 100644 --- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java +++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java @@ -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; @@ -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; @@ -49,19 +56,13 @@ public class FastCGIProxyServletTest { - public static Stream factories() - { - return Stream.of( - true, // send status 200 - false // don't send status 200 - ).map(Arguments::of); - } - + private final Map 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 { @@ -71,19 +72,32 @@ 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); @@ -91,6 +105,7 @@ protected String rewriteTarget(HttpServletRequest request) 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 + "/*"); @@ -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); @@ -173,16 +188,20 @@ 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)); @@ -190,11 +209,6 @@ protected void service(HttpServletRequest request, HttpServletResponse response) } }); 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 @@ -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()); + } } diff --git a/jetty-fcgi/fcgi-server/src/test/resources/jetty-logging.properties b/jetty-fcgi/fcgi-server/src/test/resources/jetty-logging.properties index 4e7406f1b548..9d915cb3b003 100644 --- a/jetty-fcgi/fcgi-server/src/test/resources/jetty-logging.properties +++ b/jetty-fcgi/fcgi-server/src/test/resources/jetty-logging.properties @@ -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 diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java index b7f3b4bbeac8..f0a4c987e17f 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java @@ -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; @@ -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); } @@ -393,23 +398,23 @@ protected void connectionFailed(SelectableChannel channel, Throwable failure, Ob /** *

    A pair/record holding a {@link SocketChannel} and a {@link SocketAddress} to connect to.

    */ - 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; } @@ -417,9 +422,9 @@ public SocketAddress getSocketAddress() /** *

    A factory for {@link SocketChannelWithAddress} instances.

    */ - public interface Factory + private interface Factory { - public static Factory forUnixDomain(Path path) + private static Factory forUnixDomain(Path path) { return (address, context) -> { diff --git a/jetty-unixdomain-server/src/test/java/org/eclipse/jetty/unixdomain/server/UnixDomainTest.java b/jetty-unixdomain-server/src/test/java/org/eclipse/jetty/unixdomain/server/UnixDomainTest.java index bf08d6d4ec97..29e770eaa2ba 100644 --- a/jetty-unixdomain-server/src/test/java/org/eclipse/jetty/unixdomain/server/UnixDomainTest.java +++ b/jetty-unixdomain-server/src/test/java/org/eclipse/jetty/unixdomain/server/UnixDomainTest.java @@ -13,8 +13,6 @@ package org.eclipse.jetty.unixdomain.server; -import java.io.IOException; -import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.file.Files; import java.nio.file.Path; @@ -49,7 +47,6 @@ import static org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V1; import static org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V2; -import static org.eclipse.jetty.io.ClientConnector.SocketChannelWithAddress.Factory.forUnixDomain; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -133,7 +130,7 @@ public void handle(String target, Request jettyRequest, HttpServletRequest reque } }); - ClientConnector clientConnector = new ClientConnector(forUnixDomain(unixDomainPath)); + ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath); HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector)); httpClient.start(); try @@ -168,15 +165,7 @@ public void handle(String target, Request jettyRequest, HttpServletRequest reque } }); - ClientConnector clientConnector = new ClientConnector((address, context) -> - { - if (address instanceof InetSocketAddress) - { - if (((InetSocketAddress)address).getPort() == fakeProxyPort) - return forUnixDomain(unixDomainPath).newSocketChannelWithAddress(address, context); - } - throw new IOException("request was not proxied"); - }); + ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath); HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector)); httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", fakeProxyPort)); @@ -230,7 +219,7 @@ else if ("/v2".equals(target)) }); // Java 11+ portable way to implement SocketChannelWithAddress.Factory. - ClientConnector clientConnector = new ClientConnector(forUnixDomain(unixDomainPath)); + ClientConnector clientConnector = ClientConnector.forUnixDomain(unixDomainPath); HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector)); httpClient.start(); diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java index 2df7decafe4c..1cc0b7d61351 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java @@ -313,7 +313,7 @@ public void testUnixSocket() throws Exception File war = distribution.resolveArtifact("org.eclipse.jetty.demos:demo-jsp-webapp:war:" + jettyVersion); distribution.installWarFile(war, "test"); - try (JettyHomeTester.Run run2 = distribution.start("jetty.unixsocket.path=" + sockFile.toString())) + try (JettyHomeTester.Run run2 = distribution.start("jetty.unixsocket.path=" + sockFile)) { assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); @@ -933,7 +933,7 @@ public void testUnixDomain() throws Exception { assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); - ClientConnector connector = new ClientConnector(ClientConnector.SocketChannelWithAddress.Factory.forUnixDomain(path)); + ClientConnector connector = ClientConnector.forUnixDomain(path); client = new HttpClient(new HttpClientTransportDynamic(connector)); client.start(); ContentResponse response = client.GET("http://localhost/path");