From ec28d222c412e733abf9b1131210cee183d64bc8 Mon Sep 17 00:00:00 2001
From: Joakim Erdfelt
Date: Thu, 27 Oct 2022 11:17:53 -0500
Subject: [PATCH 1/7] Example of using TryPathsHandler with PHP
+ This utilizes the matching logic
of the PathMappingsHandler
---
.../jetty/server/handler/TryPathsHandler.java | 16 ++-
.../server/handler/TryPathsHandlerTest.java | 131 +++++++++++++++---
2 files changed, 126 insertions(+), 21 deletions(-)
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
index a33b08e63de2..26f29aa7dd19 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
@@ -87,10 +87,22 @@ private static class TryPathsRequest extends Request.Wrapper
{
private final HttpURI _uri;
- public TryPathsRequest(Request wrapped, String pathInContext)
+ public TryPathsRequest(Request wrapped, String interpolated)
{
super(wrapped);
- _uri = Request.newHttpURIFrom(wrapped, URIUtil.canonicalPath(pathInContext));
+ int queryIdx = interpolated.indexOf('?');
+ if (queryIdx >= 0)
+ {
+ String path = interpolated.substring(0, queryIdx);
+ _uri = HttpURI.build(wrapped.getHttpURI())
+ .path(URIUtil.addPaths(Request.getContextPath(wrapped), path))
+ .query(interpolated.substring(queryIdx+1))
+ .asImmutable();
+ }
+ else
+ {
+ _uri = Request.newHttpURIFrom(wrapped, URIUtil.canonicalPath(interpolated));
+ }
}
@Override
diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java
index ea1d98f160cd..f868440b18ac 100644
--- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java
+++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java
@@ -16,22 +16,24 @@
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
import javax.net.ssl.SSLSocket;
+import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.toolchain.test.FS;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.LifeCycle;
@@ -41,12 +43,14 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TryPathsHandlerTest
{
+ public WorkDir workDir;
private Server server;
private SslContextFactory.Server sslContextFactory;
private ServerConnector connector;
@@ -68,19 +72,15 @@ private void start(List paths, Handler handler) throws Exception
contextPath = "/ctx";
ContextHandler context = new ContextHandler(contextPath);
- rootPath = Files.createDirectories(MavenTestingUtils.getTargetTestingPath(getClass().getSimpleName()));
- FS.cleanDirectory(rootPath);
+ rootPath = workDir.getEmptyPathDir();
context.setBaseResourceAsPath(rootPath);
server.setHandler(context);
TryPathsHandler tryPaths = new TryPathsHandler();
context.setHandler(tryPaths);
- tryPaths.setPaths(paths);
-
- ResourceHandler resourceHandler = new ResourceHandler();
- tryPaths.setHandler(resourceHandler);
- resourceHandler.setHandler(handler);
+ tryPaths.setPaths(paths);
+ tryPaths.setHandler(handler);
server.start();
}
@@ -94,16 +94,10 @@ public void dispose()
@Test
public void testTryPaths() throws Exception
{
- start(List.of("/maintenance.txt", "$path", "/forward?p=$path"), new Handler.Processor()
- {
- @Override
- public void process(Request request, Response response, Callback callback)
- {
- assertThat(Request.getPathInContext(request), equalTo("/forward%3Fp=/last"));
- response.setStatus(HttpStatus.NO_CONTENT_204);
- callback.succeeded();
- }
- });
+ ResourceHandler resourceHandler = new ResourceHandler();
+ resourceHandler.setHandler(new NoContentHandler());
+
+ start(List.of("/maintenance.txt", "$path", "/forward?p=$path"), resourceHandler);
try (SocketChannel channel = SocketChannel.open())
{
@@ -143,6 +137,66 @@ public void process(Request request, Response response, Callback callback)
}
}
+ @Test
+ public void testTryPathsPhpPathMappingsHandler() throws Exception
+ {
+ ResourceHandler resourceHandler = new ResourceHandler();
+
+ PathMappingsHandler pathMappingsHandler = new PathMappingsHandler();
+ pathMappingsHandler.addMapping(new ServletPathSpec("/"), resourceHandler);
+ pathMappingsHandler.addMapping(new ServletPathSpec("*.php"), new ExamplePhpHandler());
+ pathMappingsHandler.addMapping(new ServletPathSpec("/forward"), new NoContentHandler());
+
+ start(List.of("/maintenance.txt", "$path", "/forward?p=$path"), pathMappingsHandler);
+
+ try (SocketChannel channel = SocketChannel.open())
+ {
+ channel.connect(new InetSocketAddress("localhost", connector.getLocalPort()));
+
+ // Request something that doesn't exist
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setURI(contextPath + "/last");
+ channel.write(request.generate());
+ HttpTester.Response response = HttpTester.parseResponse(channel);
+ assertNotNull(response);
+ assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
+
+ // Create the specific static file that is requested.
+ String path = "idx.txt";
+ Files.writeString(rootPath.resolve(path), "hello", StandardOpenOption.CREATE);
+ // Make a second request with the specific file.
+ request = HttpTester.newRequest();
+ request.setURI(contextPath + "/" + path);
+ channel.write(request.generate());
+ response = HttpTester.parseResponse(channel);
+ assertNotNull(response);
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertEquals("hello", response.getContent());
+
+ // Request a php resource
+ Files.writeString(rootPath.resolve("index.php"), "raw-php-contents", StandardOpenOption.CREATE);
+ request = HttpTester.newRequest();
+ request.setURI(contextPath + "/index.php");
+ channel.write(request.generate());
+ response = HttpTester.parseResponse(channel);
+ assertNotNull(response);
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertThat(response.getContent(), startsWith("Example PHP: pathInContext=/index.php"));
+
+ // Create the "maintenance" file, it should be served first.
+ path = "maintenance.txt";
+ Files.writeString(rootPath.resolve(path), "maintenance", StandardOpenOption.CREATE);
+ // Make a second request with any path, we should get the maintenance file.
+ request = HttpTester.newRequest();
+ request.setURI(contextPath + "/whatever");
+ channel.write(request.generate());
+ response = HttpTester.parseResponse(channel);
+ assertNotNull(response);
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertEquals("maintenance", response.getContent());
+ }
+ }
+
@Test
public void testSecureRequestIsForwarded() throws Exception
{
@@ -175,4 +229,43 @@ public void process(Request request, Response response, Callback callback)
assertEquals(HttpStatus.OK_200, response.getStatus());
}
}
+
+ public static class ExamplePhpHandler extends Handler.Abstract
+ {
+ @Override
+ public Request.Processor handle(Request request) throws Exception
+ {
+ return new Handler.Processor()
+ {
+ @Override
+ public void process(Request request, Response response, Callback callback)
+ {
+ response.setStatus(HttpStatus.OK_200);
+ response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain; charset=utf-8");
+
+ String message = "Example PHP: pathInContext=%s, query=%s".formatted(Request.getPathInContext(request), request.getHttpURI().getQuery());
+
+ response.write(true, BufferUtil.toBuffer(message, StandardCharsets.UTF_8), callback);
+ }
+ };
+ }
+ }
+
+ public static class NoContentHandler extends Handler.Abstract
+ {
+ @Override
+ public Request.Processor handle(Request request) throws Exception
+ {
+ return new Handler.Processor()
+ {
+ public void process(Request request, Response response, Callback callback)
+ {
+ assertThat(Request.getPathInContext(request), equalTo("/forward"));
+ assertThat(request.getHttpURI().getQuery(), equalTo("p=/last"));
+ response.setStatus(HttpStatus.NO_CONTENT_204);
+ callback.succeeded();
+ }
+ };
+ }
+ }
}
From 63d0f5977f52effa13789e827921b7315d51b421 Mon Sep 17 00:00:00 2001
From: Joakim Erdfelt
Date: Thu, 27 Oct 2022 11:19:21 -0500
Subject: [PATCH 2/7] Removing fallback method (not used)
---
.../org/eclipse/jetty/server/handler/TryPathsHandler.java | 7 -------
1 file changed, 7 deletions(-)
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
index 26f29aa7dd19..079e8dcb6323 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
@@ -70,13 +70,6 @@ public Request.Processor handle(Request request) throws Exception
return result.wrapProcessor(super.handle(result));
}
- private Request.Processor fallback(Request request) throws Exception
- {
- String fallback = paths.isEmpty() ? "$path" : paths.get(paths.size() - 1);
- String interpolated = interpolate(request, fallback);
- return super.handle(new TryPathsRequest(request, interpolated));
- }
-
private String interpolate(Request request, String value)
{
String path = Request.getPathInContext(request);
From aff4c96d7cacf092bc1d0848bfd6d5bd6cccaf6d Mon Sep 17 00:00:00 2001
From: Joakim Erdfelt
Date: Thu, 27 Oct 2022 11:44:37 -0500
Subject: [PATCH 3/7] Fixing checkstyle
---
.../java/org/eclipse/jetty/server/handler/TryPathsHandler.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
index 079e8dcb6323..94208a772879 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
@@ -89,7 +89,7 @@ public TryPathsRequest(Request wrapped, String interpolated)
String path = interpolated.substring(0, queryIdx);
_uri = HttpURI.build(wrapped.getHttpURI())
.path(URIUtil.addPaths(Request.getContextPath(wrapped), path))
- .query(interpolated.substring(queryIdx+1))
+ .query(interpolated.substring(queryIdx + 1))
.asImmutable();
}
else
From 2c8be7ab26add02cd7b0fe97c7ba8aac3c993692 Mon Sep 17 00:00:00 2001
From: Joakim Erdfelt
Date: Thu, 27 Oct 2022 11:47:27 -0500
Subject: [PATCH 4/7] Easier to follow TryPathsRequest
---
.../jetty/server/handler/TryPathsHandler.java | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
index 94208a772879..f327746078d7 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
@@ -83,19 +83,21 @@ private static class TryPathsRequest extends Request.Wrapper
public TryPathsRequest(Request wrapped, String interpolated)
{
super(wrapped);
+
+ HttpURI.Mutable rewrittenUri = HttpURI.build(wrapped.getHttpURI());
+
int queryIdx = interpolated.indexOf('?');
if (queryIdx >= 0)
{
String path = interpolated.substring(0, queryIdx);
- _uri = HttpURI.build(wrapped.getHttpURI())
- .path(URIUtil.addPaths(Request.getContextPath(wrapped), path))
- .query(interpolated.substring(queryIdx + 1))
- .asImmutable();
+ rewrittenUri.path(URIUtil.addPaths(Request.getContextPath(wrapped), path));
+ rewrittenUri.query(interpolated.substring(queryIdx + 1));
}
else
{
- _uri = Request.newHttpURIFrom(wrapped, URIUtil.canonicalPath(interpolated));
+ rewrittenUri.path(URIUtil.addPaths(Request.getContextPath(wrapped), interpolated));
}
+ _uri = rewrittenUri.asImmutable();
}
@Override
From b67db99a2180dac3ecc724eed1d7b2b1d199cf7b Mon Sep 17 00:00:00 2001
From: Joakim Erdfelt
Date: Thu, 27 Oct 2022 15:40:38 -0500
Subject: [PATCH 5/7] Request.Processor for TryPathsHandler
---
.../jetty/server/handler/TryPathsHandler.java | 20 ++---
.../server/handler/TryPathsHandlerTest.java | 81 ++++++++++++-------
2 files changed, 57 insertions(+), 44 deletions(-)
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
index f327746078d7..d3b58c10a868 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
@@ -20,7 +20,6 @@
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.URIUtil;
-import org.eclipse.jetty.util.resource.Resource;
/**
* Inspired by nginx's {@code try_files} functionality.
@@ -54,20 +53,15 @@ public void setPaths(List paths)
@Override
public Request.Processor handle(Request request) throws Exception
{
- String interpolated = interpolate(request, "$path");
- Resource rootResource = request.getContext().getBaseResource();
- if (rootResource != null)
+ for (String path : paths)
{
- for (String path : paths)
- {
- interpolated = interpolate(request, path);
- Resource resource = rootResource.resolve(interpolated);
- if (resource != null && resource.exists())
- break;
- }
+ String interpolated = interpolate(request, path);
+ Request.WrapperProcessor result = new Request.WrapperProcessor(new TryPathsRequest(request, interpolated));
+ Request.Processor childProcessor = super.handle(result);
+ if (childProcessor != null)
+ return result.wrapProcessor(childProcessor);
}
- Request.WrapperProcessor result = new Request.WrapperProcessor(new TryPathsRequest(request, interpolated));
- return result.wrapProcessor(super.handle(result));
+ return null;
}
private String interpolate(Request request, String value)
diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java
index f868440b18ac..bf03c5643759 100644
--- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java
+++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java
@@ -51,12 +51,12 @@
public class TryPathsHandlerTest
{
public WorkDir workDir;
+ private static final String CONTEXT_PATH = "/ctx";
private Server server;
private SslContextFactory.Server sslContextFactory;
private ServerConnector connector;
private ServerConnector sslConnector;
private Path rootPath;
- private String contextPath;
private void start(List paths, Handler handler) throws Exception
{
@@ -70,8 +70,7 @@ private void start(List paths, Handler handler) throws Exception
sslConnector = new ServerConnector(server, 1, 1, sslContextFactory);
server.addConnector(sslConnector);
- contextPath = "/ctx";
- ContextHandler context = new ContextHandler(contextPath);
+ ContextHandler context = new ContextHandler(CONTEXT_PATH);
rootPath = workDir.getEmptyPathDir();
context.setBaseResourceAsPath(rootPath);
server.setHandler(context);
@@ -82,6 +81,7 @@ private void start(List paths, Handler handler) throws Exception
tryPaths.setPaths(paths);
tryPaths.setHandler(handler);
+ server.setDumpAfterStart(true);
server.start();
}
@@ -95,7 +95,27 @@ public void dispose()
public void testTryPaths() throws Exception
{
ResourceHandler resourceHandler = new ResourceHandler();
- resourceHandler.setHandler(new NoContentHandler());
+ resourceHandler.setDirAllowed(false);
+ resourceHandler.setHandler(new Handler.Abstract()
+ {
+ @Override
+ public Request.Processor handle(Request request)
+ {
+ if (!Request.getPathInContext(request).startsWith("/forward"))
+ return null;
+
+ return new Handler.Processor()
+ {
+ public void process(Request request, Response response, Callback callback)
+ {
+ assertThat(Request.getPathInContext(request), equalTo("/forward"));
+ assertThat(request.getHttpURI().getQuery(), equalTo("p=/last"));
+ response.setStatus(HttpStatus.NO_CONTENT_204);
+ callback.succeeded();
+ }
+ };
+ }
+ });
start(List.of("/maintenance.txt", "$path", "/forward?p=$path"), resourceHandler);
@@ -105,7 +125,7 @@ public void testTryPaths() throws Exception
// Make a first request without existing file paths.
HttpTester.Request request = HttpTester.newRequest();
- request.setURI(contextPath + "/last");
+ request.setURI(CONTEXT_PATH + "/last");
channel.write(request.generate());
HttpTester.Response response = HttpTester.parseResponse(channel);
assertNotNull(response);
@@ -116,7 +136,7 @@ public void testTryPaths() throws Exception
Files.writeString(rootPath.resolve(path), "hello", StandardOpenOption.CREATE);
// Make a second request with the specific file.
request = HttpTester.newRequest();
- request.setURI(contextPath + "/" + path);
+ request.setURI(CONTEXT_PATH + "/" + path);
channel.write(request.generate());
response = HttpTester.parseResponse(channel);
assertNotNull(response);
@@ -128,7 +148,7 @@ public void testTryPaths() throws Exception
Files.writeString(rootPath.resolve(path), "maintenance", StandardOpenOption.CREATE);
// Make a second request with any path, we should get the maintenance file.
request = HttpTester.newRequest();
- request.setURI(contextPath + "/whatever");
+ request.setURI(CONTEXT_PATH + "/whatever");
channel.write(request.generate());
response = HttpTester.parseResponse(channel);
assertNotNull(response);
@@ -141,11 +161,28 @@ public void testTryPaths() throws Exception
public void testTryPathsPhpPathMappingsHandler() throws Exception
{
ResourceHandler resourceHandler = new ResourceHandler();
+ resourceHandler.setDirAllowed(false);
PathMappingsHandler pathMappingsHandler = new PathMappingsHandler();
pathMappingsHandler.addMapping(new ServletPathSpec("/"), resourceHandler);
pathMappingsHandler.addMapping(new ServletPathSpec("*.php"), new ExamplePhpHandler());
- pathMappingsHandler.addMapping(new ServletPathSpec("/forward"), new NoContentHandler());
+ pathMappingsHandler.addMapping(new ServletPathSpec("/forward"), new Handler.Abstract()
+ {
+ @Override
+ public Request.Processor handle(Request request)
+ {
+ return new Handler.Processor()
+ {
+ public void process(Request request, Response response, Callback callback)
+ {
+ assertThat(Request.getPathInContext(request), equalTo("/forward"));
+ assertThat(request.getHttpURI().getQuery(), equalTo("p=/last"));
+ response.setStatus(HttpStatus.NO_CONTENT_204);
+ callback.succeeded();
+ }
+ };
+ }
+ });
start(List.of("/maintenance.txt", "$path", "/forward?p=$path"), pathMappingsHandler);
@@ -155,7 +192,7 @@ public void testTryPathsPhpPathMappingsHandler() throws Exception
// Request something that doesn't exist
HttpTester.Request request = HttpTester.newRequest();
- request.setURI(contextPath + "/last");
+ request.setURI(CONTEXT_PATH + "/last");
channel.write(request.generate());
HttpTester.Response response = HttpTester.parseResponse(channel);
assertNotNull(response);
@@ -166,7 +203,7 @@ public void testTryPathsPhpPathMappingsHandler() throws Exception
Files.writeString(rootPath.resolve(path), "hello", StandardOpenOption.CREATE);
// Make a second request with the specific file.
request = HttpTester.newRequest();
- request.setURI(contextPath + "/" + path);
+ request.setURI(CONTEXT_PATH + "/" + path);
channel.write(request.generate());
response = HttpTester.parseResponse(channel);
assertNotNull(response);
@@ -176,7 +213,7 @@ public void testTryPathsPhpPathMappingsHandler() throws Exception
// Request a php resource
Files.writeString(rootPath.resolve("index.php"), "raw-php-contents", StandardOpenOption.CREATE);
request = HttpTester.newRequest();
- request.setURI(contextPath + "/index.php");
+ request.setURI(CONTEXT_PATH + "/index.php");
channel.write(request.generate());
response = HttpTester.parseResponse(channel);
assertNotNull(response);
@@ -188,7 +225,7 @@ public void testTryPathsPhpPathMappingsHandler() throws Exception
Files.writeString(rootPath.resolve(path), "maintenance", StandardOpenOption.CREATE);
// Make a second request with any path, we should get the maintenance file.
request = HttpTester.newRequest();
- request.setURI(contextPath + "/whatever");
+ request.setURI(CONTEXT_PATH + "/whatever");
channel.write(request.generate());
response = HttpTester.parseResponse(channel);
assertNotNull(response);
@@ -219,7 +256,7 @@ public void process(Request request, Response response, Callback callback)
sslSocket.connect(new InetSocketAddress("localhost", sslConnector.getLocalPort()));
HttpTester.Request request = HttpTester.newRequest();
- request.setURI(contextPath + path);
+ request.setURI(CONTEXT_PATH + path);
OutputStream output = sslSocket.getOutputStream();
output.write(BufferUtil.toArray(request.generate()));
output.flush();
@@ -250,22 +287,4 @@ public void process(Request request, Response response, Callback callback)
};
}
}
-
- public static class NoContentHandler extends Handler.Abstract
- {
- @Override
- public Request.Processor handle(Request request) throws Exception
- {
- return new Handler.Processor()
- {
- public void process(Request request, Response response, Callback callback)
- {
- assertThat(Request.getPathInContext(request), equalTo("/forward"));
- assertThat(request.getHttpURI().getQuery(), equalTo("p=/last"));
- response.setStatus(HttpStatus.NO_CONTENT_204);
- callback.succeeded();
- }
- };
- }
- }
}
From bc2cf6e68de68e9d552bc4971f20a9bfe23488cb Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Fri, 28 Oct 2022 16:17:46 +0200
Subject: [PATCH 6/7] Updated TryPathsHandler.
Improved javadocs.
Introduced original[Path|Query]Attribute.
Signed-off-by: Simone Bordet
---
.../jetty/server/handler/TryPathsHandler.java | 132 ++++++++++++++----
.../server/handler/TryPathsHandlerTest.java | 54 ++++---
2 files changed, 133 insertions(+), 53 deletions(-)
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
index d3b58c10a868..e7f007aa724d 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
@@ -16,35 +16,110 @@
import java.util.List;
import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.URIUtil;
/**
* Inspired by nginx's {@code try_files} functionality.
- * This handler can be configured with a list of URI paths.
- * The special token {@code $path} represents the current request URI
- * path (the portion after the context path).
+ *
+ * This handler can be configured with a list of rewrite URI paths.
+ * The special token {@code $path} represents the current request
+ * {@code pathInContext} (the portion after the context path).
+ *
* Typical example of how this handler can be configured is the following:
* {@code
- * TryPathsHandler tryPaths = new TryPathsHandler();
- * tryPaths.setPaths("/maintenance.html", "$path", "/index.php?p=$path");
+ * TryPathsHandler tryPathsHandler = new TryPathsHandler();
+ * tryPathsHandler.setPaths("/maintenance.html", "$path", "/index.php?p=$path");
+ *
+ * PathMappingsHandler pathMappingsHandler = new PathMappingsHandler();
+ * tryPathsHandler.setHandler(pathMappingsHandler);
+ *
+ * pathMappingsHandler.addMapping(new ServletPathSpec("*.php"), new PHPHandler());
+ * pathMappingsHandler.addMapping(new ServletPathSpec("/"), new ResourceHandler());
* }
- * For a request such as {@code /context/path/to/resource.ext}, this
- * handler 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} to the next handler.
- * The last URI path specified in the list is therefore the "fallback" to
- * which the request is forwarded to in case no previous files can be found.
- * The file paths are resolved against {@link Context#getBaseResource()}
- * to make sure that only files visible to the application are served.
+ *
+ * For a request such as {@code /context/path/to/resource.ext}:
+ *
+ * - This handler rewrites the request {@code pathInContext} to
+ * {@code /maintenance.html} and forwards the request to the next handler,
+ * where it matches the {@code /} mapping, hitting the {@code ResourceHandler}
+ * that serves the file if it exists.
+ * - Otherwise, this handler rewrites the request {@code pathInContext} to
+ * {@code /path/to/resource.ext} and forwards the request to the next handler,
+ * where it matches the {@code /} mapping, hitting the {@code ResourceHandler}
+ * that serves the file if it exists.
+ * - Otherwise, this handler rewrites the request {@code pathInContext} to
+ * {@code /index.php?p=/path/to/resource.ext} and forwards the request to
+ * the next handler, where it matches the {@code *.php} mapping, hitting
+ * the {@code PHPHandler}.
+ *
+ *
+ * The original path and query may be stored as request attributes,
+ * under the names specified by {@link #setOriginalPathAttribute(String)}
+ * and {@link #setOriginalQueryAttribute(String)}.
*/
public class TryPathsHandler extends Handler.Wrapper
{
+ private String originalPathAttribute;
+ private String originalQueryAttribute;
private List paths;
+ /**
+ * @return the attribute name of the original request path
+ */
+ public String getOriginalPathAttribute()
+ {
+ return originalPathAttribute;
+ }
+
+ /**
+ * Sets the request attribute name to use to
+ * retrieve the original request path.
+ *
+ * @param originalPathAttribute the attribute name of the original
+ * request path
+ */
+ public void setOriginalPathAttribute(String originalPathAttribute)
+ {
+ this.originalPathAttribute = originalPathAttribute;
+ }
+
+ /**
+ * @return the attribute name of the original request query
+ */
+ public String getOriginalQueryAttribute()
+ {
+ return originalQueryAttribute;
+ }
+
+ /**
+ * Sets the request attribute name to use to
+ * retrieve the original request query.
+ *
+ * @param originalQueryAttribute the attribute name of the original
+ * request query
+ */
+ public void setOriginalQueryAttribute(String originalQueryAttribute)
+ {
+ this.originalQueryAttribute = originalQueryAttribute;
+ }
+
+ /**
+ * @return the rewrite URI paths
+ */
+ public List getPaths()
+ {
+ return paths;
+ }
+
+ /**
+ * Sets a list of rewrite URI paths.
+ * The special token {@code $path} represents the current request
+ * {@code pathInContext} (the portion after the context path).
+ *
+ * @param paths the rewrite URI paths
+ */
public void setPaths(List paths)
{
this.paths = paths;
@@ -70,28 +145,37 @@ private String interpolate(Request request, String value)
return value.replace("$path", path);
}
- private static class TryPathsRequest extends Request.Wrapper
+ private class TryPathsRequest extends Request.Wrapper
{
private final HttpURI _uri;
- public TryPathsRequest(Request wrapped, String interpolated)
+ public TryPathsRequest(Request wrapped, String newPathQuery)
{
super(wrapped);
- HttpURI.Mutable rewrittenUri = HttpURI.build(wrapped.getHttpURI());
+ HttpURI originalURI = wrapped.getHttpURI();
+
+ String originalPathAttribute = getOriginalPathAttribute();
+ if (originalPathAttribute != null)
+ setAttribute(originalPathAttribute, Request.getPathInContext(wrapped));
+ String originalQueryAttribute = getOriginalQueryAttribute();
+ if (originalQueryAttribute != null)
+ setAttribute(originalQueryAttribute, originalURI.getQuery());
- int queryIdx = interpolated.indexOf('?');
+ String originalContextPath = Request.getContextPath(wrapped);
+ HttpURI.Mutable rewrittenURI = HttpURI.build(originalURI);
+ int queryIdx = newPathQuery.indexOf('?');
if (queryIdx >= 0)
{
- String path = interpolated.substring(0, queryIdx);
- rewrittenUri.path(URIUtil.addPaths(Request.getContextPath(wrapped), path));
- rewrittenUri.query(interpolated.substring(queryIdx + 1));
+ String path = newPathQuery.substring(0, queryIdx);
+ rewrittenURI.path(URIUtil.addPaths(originalContextPath, path));
+ rewrittenURI.query(newPathQuery.substring(queryIdx + 1));
}
else
{
- rewrittenUri.path(URIUtil.addPaths(Request.getContextPath(wrapped), interpolated));
+ rewrittenURI.path(URIUtil.addPaths(originalContextPath, newPathQuery));
}
- _uri = rewrittenUri.asImmutable();
+ _uri = rewrittenURI.asImmutable();
}
@Override
diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java
index bf03c5643759..5453689d85f0 100644
--- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java
+++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/TryPathsHandlerTest.java
@@ -16,7 +16,6 @@
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
@@ -28,6 +27,7 @@
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
+import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
@@ -131,7 +131,7 @@ public void process(Request request, Response response, Callback callback)
assertNotNull(response);
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
- // Create the specific file that is requested.
+ // Create the specific static file that is requested.
String path = "idx.txt";
Files.writeString(rootPath.resolve(path), "hello", StandardOpenOption.CREATE);
// Make a second request with the specific file.
@@ -146,7 +146,7 @@ public void process(Request request, Response response, Callback callback)
// Create the "maintenance" file, it should be served first.
path = "maintenance.txt";
Files.writeString(rootPath.resolve(path), "maintenance", StandardOpenOption.CREATE);
- // Make a second request with any path, we should get the maintenance file.
+ // Make a third request with any path, we should get the maintenance file.
request = HttpTester.newRequest();
request.setURI(CONTEXT_PATH + "/whatever");
channel.write(request.generate());
@@ -158,14 +158,31 @@ public void process(Request request, Response response, Callback callback)
}
@Test
- public void testTryPathsPhpPathMappingsHandler() throws Exception
+ public void testTryPathsWithPathMappings() throws Exception
{
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setDirAllowed(false);
PathMappingsHandler pathMappingsHandler = new PathMappingsHandler();
pathMappingsHandler.addMapping(new ServletPathSpec("/"), resourceHandler);
- pathMappingsHandler.addMapping(new ServletPathSpec("*.php"), new ExamplePhpHandler());
+ pathMappingsHandler.addMapping(new ServletPathSpec("*.php"), new Handler.Abstract()
+ {
+ @Override
+ public Request.Processor handle(Request request)
+ {
+ return new Processor()
+ {
+ @Override
+ public void process(Request request, Response response, Callback callback)
+ {
+ response.setStatus(HttpStatus.OK_200);
+ response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain; charset=utf-8");
+ String message = "PHP: pathInContext=%s, query=%s".formatted(Request.getPathInContext(request), request.getHttpURI().getQuery());
+ Content.Sink.write(response, true, message, callback);
+ }
+ };
+ }
+ });
pathMappingsHandler.addMapping(new ServletPathSpec("/forward"), new Handler.Abstract()
{
@Override
@@ -190,7 +207,7 @@ public void process(Request request, Response response, Callback callback)
{
channel.connect(new InetSocketAddress("localhost", connector.getLocalPort()));
- // Request something that doesn't exist
+ // Make a first request without existing file paths.
HttpTester.Request request = HttpTester.newRequest();
request.setURI(CONTEXT_PATH + "/last");
channel.write(request.generate());
@@ -210,7 +227,7 @@ public void process(Request request, Response response, Callback callback)
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("hello", response.getContent());
- // Request a php resource
+ // Request an existing PHP file.
Files.writeString(rootPath.resolve("index.php"), "raw-php-contents", StandardOpenOption.CREATE);
request = HttpTester.newRequest();
request.setURI(CONTEXT_PATH + "/index.php");
@@ -218,7 +235,7 @@ public void process(Request request, Response response, Callback callback)
response = HttpTester.parseResponse(channel);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
- assertThat(response.getContent(), startsWith("Example PHP: pathInContext=/index.php"));
+ assertThat(response.getContent(), startsWith("PHP: pathInContext=/index.php"));
// Create the "maintenance" file, it should be served first.
path = "maintenance.txt";
@@ -266,25 +283,4 @@ public void process(Request request, Response response, Callback callback)
assertEquals(HttpStatus.OK_200, response.getStatus());
}
}
-
- public static class ExamplePhpHandler extends Handler.Abstract
- {
- @Override
- public Request.Processor handle(Request request) throws Exception
- {
- return new Handler.Processor()
- {
- @Override
- public void process(Request request, Response response, Callback callback)
- {
- response.setStatus(HttpStatus.OK_200);
- response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain; charset=utf-8");
-
- String message = "Example PHP: pathInContext=%s, query=%s".formatted(Request.getPathInContext(request), request.getHttpURI().getQuery());
-
- response.write(true, BufferUtil.toBuffer(message, StandardCharsets.UTF_8), callback);
- }
- };
- }
- }
}
From 7f1798c823f739937a5a93ba4c436a43dd813020 Mon Sep 17 00:00:00 2001
From: Simone Bordet
Date: Fri, 28 Oct 2022 16:48:04 +0200
Subject: [PATCH 7/7] Fixed javadoc errors.
Signed-off-by: Simone Bordet
---
.../org/eclipse/jetty/server/handler/TryPathsHandler.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
index e7f007aa724d..476758311991 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/TryPathsHandler.java
@@ -23,7 +23,7 @@
/**
* Inspired by nginx's {@code try_files} functionality.
*
- * This handler can be configured with a list of rewrite URI paths.
+ *
This handler can be configured with a list of rewrite URI paths.
* The special token {@code $path} represents the current request
* {@code pathInContext} (the portion after the context path).
*
@@ -115,7 +115,7 @@ public List getPaths()
/**
* Sets a list of rewrite URI paths.
- * The special token {@code $path} represents the current request
+ * The special token {@code $path} represents the current request
* {@code pathInContext} (the portion after the context path).
*
* @param paths the rewrite URI paths