enhanced router middleware support; added pre-fused middleware handling and improved handler registration
This commit is contained in:
Generated
+1
@@ -2,5 +2,6 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/nuxt-shadcn-dashboard" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
Generated
+27
-27
@@ -4,37 +4,19 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="fc757130-fe3e-4ea9-8d69-c26ffb8545a4" name="Changes" comment="optimized dynamic body size impl, decluttering javadocs/comments">
|
<list default="true" id="fc757130-fe3e-4ea9-8d69-c26ffb8545a4" name="Changes" comment="multipart parsing, request body access, and chunked input stream support">
|
||||||
<change afterPath="$PROJECT_DIR$/flash-bench/src/main/java/dev/relism/bench/StaticResourceManager.java" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/flash-bench/pom.xml" beforeDir="false" afterPath="$PROJECT_DIR$/flash-bench/pom.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/flash-bench/pom.xml" beforeDir="false" afterPath="$PROJECT_DIR$/flash-bench/pom.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/flash-bench/src/main/java/dev/relism/bench/Main.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash-bench/src/main/java/dev/relism/bench/Main.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/HttpServer.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/HttpServer.java" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/HttpServer.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/HttpServer.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/HttpServerConfiguration.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/HttpServerConfiguration.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/Main.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/Main.java" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/Main.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/Main.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/RequestParser.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/RequestParser.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/http/ContentType.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/http/ContentType.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/http/HttpStatus.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/http/HttpStatus.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/HeaderMap.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/HeaderMap.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/LazyBody.java" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/QueryParams.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/QueryParams.java" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/QueryParams.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/QueryParams.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/Request.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/Request.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/RequestHandler.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/RequestHandler.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/Response.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/Response.java" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/Response.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/Response.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/SimpleHandler.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/models/SimpleHandler.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/routing/AbstractRouter.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/routing/AbstractRouter.java" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/routing/AbstractRouter.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/routing/AbstractRouter.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/routing/routers/fastpathrouter/FastPathViews.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/routing/routers/fastpathrouter/FastPathViews.java" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/routing/GlobalRouter.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/routing/GlobalRouter.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/resources/assets/html/default_404.html" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/resources/assets/html/default_404.html" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/routing/Route.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/routing/Route.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/main/resources/assets/html/default_exception.html" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/resources/assets/html/default_exception.html" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/flash/src/main/java/dev/relism/routing/routers/fastpathrouter/FastPathRouterImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/main/java/dev/relism/routing/routers/fastpathrouter/FastPathRouterImpl.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/test/java/dev/relism/HttpServerConcurrencyTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/test/java/dev/relism/HttpServerConcurrencyTest.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/test/java/dev/relism/HttpServerTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/test/java/dev/relism/HttpServerTest.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/test/java/dev/relism/RequestParserTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/test/java/dev/relism/RequestParserTest.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/test/java/dev/relism/models/HeaderMapTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/test/java/dev/relism/models/HeaderMapTest.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/test/java/dev/relism/models/RequestLineTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/test/java/dev/relism/models/RequestLineTest.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/test/java/dev/relism/models/RequestTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/test/java/dev/relism/models/RequestTest.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/test/java/dev/relism/models/ResponseTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/test/java/dev/relism/models/ResponseTest.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/test/java/dev/relism/routing/GlobalRouterTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/test/java/dev/relism/routing/GlobalRouterTest.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/test/java/dev/relism/routing/routers/fastpathrouter/FastPathRouterImplTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/test/java/dev/relism/routing/routers/fastpathrouter/FastPathRouterImplTest.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/flash/src/test/java/dev/relism/template/ErrorPagesTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/flash/src/test/java/dev/relism/template/ErrorPagesTest.java" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -247,7 +229,7 @@
|
|||||||
<workItem from="1773837116508" duration="4255000" />
|
<workItem from="1773837116508" duration="4255000" />
|
||||||
<workItem from="1773847734658" duration="911000" />
|
<workItem from="1773847734658" duration="911000" />
|
||||||
<workItem from="1773875652058" duration="5481000" />
|
<workItem from="1773875652058" duration="5481000" />
|
||||||
<workItem from="1773915392332" duration="1223000" />
|
<workItem from="1773915392332" duration="24154000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="Initial">
|
<task id="LOCAL-00001" summary="Initial">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -281,7 +263,23 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1773591259108</updated>
|
<updated>1773591259108</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="5" />
|
<task id="LOCAL-00005" summary="enhanced HTTP server configuration and response handling; added acceptorThreads, improved header management, and refined error page titles">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1773920675327</created>
|
||||||
|
<option name="number" value="00005" />
|
||||||
|
<option name="presentableId" value="LOCAL-00005" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1773920675327</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00006" summary="multipart parsing, request body access, and chunked input stream support">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1773920735659</created>
|
||||||
|
<option name="number" value="00006" />
|
||||||
|
<option name="presentableId" value="LOCAL-00006" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1773920735659</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="7" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -303,7 +301,9 @@
|
|||||||
<MESSAGE value="pre-module refactor" />
|
<MESSAGE value="pre-module refactor" />
|
||||||
<MESSAGE value="refactored, pre-buffer reuse" />
|
<MESSAGE value="refactored, pre-buffer reuse" />
|
||||||
<MESSAGE value="optimized dynamic body size impl, decluttering javadocs/comments" />
|
<MESSAGE value="optimized dynamic body size impl, decluttering javadocs/comments" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="optimized dynamic body size impl, decluttering javadocs/comments" />
|
<MESSAGE value="enhanced HTTP server configuration and response handling; added acceptorThreads, improved header management, and refined error page titles" />
|
||||||
|
<MESSAGE value="multipart parsing, request body access, and chunked input stream support" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="multipart parsing, request body access, and chunked input stream support" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
|
|||||||
+8
-3
@@ -31,9 +31,14 @@
|
|||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.asynchttpclient</groupId>
|
<groupId>org.hibernate.orm</groupId>
|
||||||
<artifactId>async-http-client</artifactId>
|
<artifactId>hibernate-core</artifactId>
|
||||||
<version>${ahc.version}</version>
|
<version>6.6.4.Final</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<version>2.3.232</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import dev.relism.http.HttpStatus;
|
|||||||
import dev.relism.models.*;
|
import dev.relism.models.*;
|
||||||
import dev.relism.routing.AbstractRouter;
|
import dev.relism.routing.AbstractRouter;
|
||||||
import dev.relism.routing.GlobalRouter;
|
import dev.relism.routing.GlobalRouter;
|
||||||
|
import dev.relism.routing.Middleware;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
public class HttpServer {
|
public class HttpServer {
|
||||||
private final HttpServerConfiguration configuration;
|
private final HttpServerConfiguration configuration;
|
||||||
private final ServerSocket serverSocket;
|
private final ServerSocket serverSocket;
|
||||||
private final GlobalRouter globalRouter = new GlobalRouter();
|
private final GlobalRouter globalRouter;
|
||||||
private final ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
|
private final ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
|
||||||
private final Set<Socket> activeSockets = ConcurrentHashMap.newKeySet();
|
private final Set<Socket> activeSockets = ConcurrentHashMap.newKeySet();
|
||||||
private volatile boolean stopped = false;
|
private volatile boolean stopped = false;
|
||||||
@@ -52,9 +53,14 @@ public class HttpServer {
|
|||||||
DIGITS[i] = String.valueOf(i).getBytes(StandardCharsets.UTF_8);
|
DIGITS[i] = String.valueOf(i).getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpServer(HttpServerConfiguration configuration) throws IOException {
|
/**
|
||||||
|
* Creates the server with optional server-level middlewares applied to every registered route.
|
||||||
|
* Middlewares are pre-fused at construction time; the first element executes outermost.
|
||||||
|
*/
|
||||||
|
public HttpServer(HttpServerConfiguration configuration, Middleware... middlewares) throws IOException {
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.serverSocket = new ServerSocket(configuration.getPort());
|
this.serverSocket = new ServerSocket(configuration.getPort());
|
||||||
|
this.globalRouter = new GlobalRouter(middlewares);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a future that completes once the accept loop is running and the server is ready. */
|
/** Returns a future that completes once the accept loop is running and the server is ready. */
|
||||||
@@ -119,21 +125,18 @@ public class HttpServer {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpServer register(RequestHandler handler) {
|
public HttpServer register(RequestHandler handler, Middleware... m) { globalRouter.register(handler, m); return this; }
|
||||||
globalRouter.register(handler);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpServer get(String path, SimpleHandler.FunctionalHandler h) { globalRouter.get(path, h); return this; }
|
public HttpServer get (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { globalRouter.get (path, h, m); return this; }
|
||||||
public HttpServer post(String path, SimpleHandler.FunctionalHandler h) { globalRouter.post(path, h); return this; }
|
public HttpServer post (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { globalRouter.post (path, h, m); return this; }
|
||||||
public HttpServer put(String path, SimpleHandler.FunctionalHandler h) { globalRouter.put(path, h); return this; }
|
public HttpServer put (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { globalRouter.put (path, h, m); return this; }
|
||||||
public HttpServer delete(String path, SimpleHandler.FunctionalHandler h) { globalRouter.delete(path, h); return this; }
|
public HttpServer delete (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { globalRouter.delete (path, h, m); return this; }
|
||||||
public HttpServer patch(String path, SimpleHandler.FunctionalHandler h) { globalRouter.patch(path, h); return this; }
|
public HttpServer patch (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { globalRouter.patch (path, h, m); return this; }
|
||||||
public HttpServer options(String path, SimpleHandler.FunctionalHandler h) { globalRouter.options(path, h); return this; }
|
public HttpServer options(String path, SimpleHandler.FunctionalHandler h, Middleware... m) { globalRouter.options(path, h, m); return this; }
|
||||||
public HttpServer head(String path, SimpleHandler.FunctionalHandler h) { globalRouter.head(path, h); return this; }
|
public HttpServer head (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { globalRouter.head (path, h, m); return this; }
|
||||||
public HttpServer trace(String path, SimpleHandler.FunctionalHandler h) { globalRouter.trace(path, h); return this; }
|
public HttpServer trace (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { globalRouter.trace (path, h, m); return this; }
|
||||||
public HttpServer connect(String path, SimpleHandler.FunctionalHandler h) { globalRouter.connect(path, h); return this; }
|
public HttpServer connect(String path, SimpleHandler.FunctionalHandler h, Middleware... m) { globalRouter.connect(path, h, m); return this; }
|
||||||
public HttpServer purge(String path, SimpleHandler.FunctionalHandler h) { globalRouter.purge(path, h); return this; }
|
public HttpServer purge (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { globalRouter.purge (path, h, m); return this; }
|
||||||
|
|
||||||
|
|
||||||
private void process(Socket socket) {
|
private void process(Socket socket) {
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Lazy query parameter access ({@code ?key=value&...}). Backed by a zero-copy {@link ByteView}
|
* Lazy query parameter access ({@code ?key=value&...}). Backed by a zero-copy {@link ByteView}
|
||||||
* over the raw query string bytes, no parsing at construction, values decoded on demand.
|
* over the raw query string bytes, no parsing at construction, values percent-decoded on demand.
|
||||||
|
*
|
||||||
|
* <p>Percent-decoding ({@code %XX} → byte, {@code +} → space) follows the
|
||||||
|
* {@code application/x-www-form-urlencoded} convention used by all browsers.
|
||||||
*/
|
*/
|
||||||
public class QueryParams {
|
public class QueryParams {
|
||||||
public static final QueryParams EMPTY = new QueryParams(null);
|
public static final QueryParams EMPTY = new QueryParams(null);
|
||||||
@@ -22,10 +25,7 @@ public class QueryParams {
|
|||||||
public String get(String name) {
|
public String get(String name) {
|
||||||
long r = findFirst(name);
|
long r = findFirst(name);
|
||||||
if (r < 0) return null;
|
if (r < 0) return null;
|
||||||
int s = (int) (r >> 32), l = (int) (r & 0xFFFFFFFFL);
|
return decode((int) (r >> 32), (int) (r & 0xFFFFFFFFL));
|
||||||
byte[] bytes = new byte[l];
|
|
||||||
for (int i = 0; i < l; i++) bytes[i] = raw.byteAt(s + i);
|
|
||||||
return new String(bytes, StandardCharsets.UTF_8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteView view(String name) {
|
ByteView view(String name) {
|
||||||
@@ -51,11 +51,8 @@ public class QueryParams {
|
|||||||
int valStart = i;
|
int valStart = i;
|
||||||
while (i < len && raw.byteAt(i) != '&') i++;
|
while (i < len && raw.byteAt(i) != '&') i++;
|
||||||
if (keyMatches(keyStart, keyLen, name)) {
|
if (keyMatches(keyStart, keyLen, name)) {
|
||||||
int valLen = i - valStart;
|
|
||||||
byte[] bytes = new byte[valLen];
|
|
||||||
for (int j = 0; j < valLen; j++) bytes[j] = raw.byteAt(valStart + j);
|
|
||||||
if (result == null) result = new ArrayList<>();
|
if (result == null) result = new ArrayList<>();
|
||||||
result.add(new String(bytes, StandardCharsets.UTF_8));
|
result.add(decode(valStart, i - valStart));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i < len && raw.byteAt(i) == '&') i++;
|
if (i < len && raw.byteAt(i) == '&') i++;
|
||||||
@@ -63,6 +60,8 @@ public class QueryParams {
|
|||||||
return result != null ? result : List.of();
|
return result != null ? result : List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Internals ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** Returns (valStart << 32) | valLen, or -1 if not found. */
|
/** Returns (valStart << 32) | valLen, or -1 if not found. */
|
||||||
private long findFirst(String name) {
|
private long findFirst(String name) {
|
||||||
if (raw == null) return -1L;
|
if (raw == null) return -1L;
|
||||||
@@ -87,4 +86,39 @@ public class QueryParams {
|
|||||||
for (int i = 0; i < len; i++) if (raw.byteAt(start + i) != (byte) name.charAt(i)) return false;
|
for (int i = 0; i < len; i++) if (raw.byteAt(start + i) != (byte) name.charAt(i)) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Percent-decodes a value slice from {@code raw} into a UTF-8 String.
|
||||||
|
* {@code %XX} triplets are decoded to their byte values; {@code +} decodes as space.
|
||||||
|
* Invalid {@code %} sequences are passed through as-is.
|
||||||
|
*/
|
||||||
|
private String decode(int start, int length) {
|
||||||
|
byte[] out = new byte[length]; // upper bound — decoded is never longer
|
||||||
|
int w = 0;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
byte b = raw.byteAt(start + i);
|
||||||
|
if (b == '+') {
|
||||||
|
out[w++] = ' ';
|
||||||
|
} else if (b == '%' && i + 2 < length) {
|
||||||
|
int hi = hexVal(raw.byteAt(start + i + 1));
|
||||||
|
int lo = hexVal(raw.byteAt(start + i + 2));
|
||||||
|
if (hi >= 0 && lo >= 0) {
|
||||||
|
out[w++] = (byte) ((hi << 4) | lo);
|
||||||
|
i += 2;
|
||||||
|
} else {
|
||||||
|
out[w++] = b; // not a valid escape — keep literal %
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out[w++] = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new String(out, 0, w, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int hexVal(byte b) {
|
||||||
|
if (b >= '0' && b <= '9') return b - '0';
|
||||||
|
if (b >= 'a' && b <= 'f') return b - 'a' + 10;
|
||||||
|
if (b >= 'A' && b <= 'F') return b - 'A' + 10;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,12 +112,6 @@ public class Response {
|
|||||||
|
|
||||||
public boolean isStreaming() { return stream != null; }
|
public boolean isStreaming() { return stream != null; }
|
||||||
|
|
||||||
/**
|
|
||||||
* Pre-encoded status bytes (e.g. {@code "200 OK"}), or {@code null} if the status was set
|
|
||||||
* via {@link #status(int)} — in which case {@link HttpStatus#bytesForCode} is used as fallback.
|
|
||||||
*/
|
|
||||||
public byte[] getStatusBytes() { return statusBytes; }
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Internal setters used by HttpServer for handler return values
|
// Internal setters used by HttpServer for handler return values
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -12,9 +12,16 @@ import lombok.Getter;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base router. Each router has a namespace prefix (default {@code "/"});
|
* Base router. Each router has a namespace prefix (default {@code "/"}).
|
||||||
* Error handlers are scoped to this router.
|
* Error handlers are scoped to this router.
|
||||||
* The namespace is set automatically by {@link GlobalRouter#mount}.
|
*
|
||||||
|
* <p>Router-level {@link Middleware middlewares} are passed at construction time and
|
||||||
|
* pre-fused into a single wrapper applied to every handler registered on this router.
|
||||||
|
* Handler-level middlewares are passed to the {@link #register} / {@link #get} family of
|
||||||
|
* methods and wrapped <em>inside</em> the router-level chain.
|
||||||
|
*
|
||||||
|
* <p>All middleware composition happens once at boot time; the hot-path sees only a plain
|
||||||
|
* {@link RequestHandler} call with zero allocation and zero lookup overhead.
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractRouter {
|
public abstract class AbstractRouter {
|
||||||
|
|
||||||
@@ -24,6 +31,12 @@ public abstract class AbstractRouter {
|
|||||||
@Getter(AccessLevel.PACKAGE)
|
@Getter(AccessLevel.PACKAGE)
|
||||||
protected byte[] namespaceBytes = new byte[]{ '/' };
|
protected byte[] namespaceBytes = new byte[]{ '/' };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-fused router-level middleware, or {@code null} when none were registered.
|
||||||
|
* A single null-check in {@link #compile} is the only cost when no router middleware exists.
|
||||||
|
*/
|
||||||
|
private final Middleware routerMiddleware;
|
||||||
|
|
||||||
protected SimpleHandler notFoundHandler = new SimpleHandler((req, res) -> {
|
protected SimpleHandler notFoundHandler = new SimpleHandler((req, res) -> {
|
||||||
res.setStatusCode(404);
|
res.setStatusCode(404);
|
||||||
res.setContentType(ContentType.TEXT_HTML);
|
res.setContentType(ContentType.TEXT_HTML);
|
||||||
@@ -36,6 +49,20 @@ public abstract class AbstractRouter {
|
|||||||
return ErrorPages.renderException(req, ex);
|
return ErrorPages.renderException(req, ex);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a router with optional router-level middlewares.
|
||||||
|
* Middlewares are pre-fused at construction time; the first element executes outermost.
|
||||||
|
*
|
||||||
|
* @param middlewares zero or more middlewares applied to every handler on this router
|
||||||
|
*/
|
||||||
|
protected AbstractRouter(Middleware... middlewares) {
|
||||||
|
this.routerMiddleware = middlewares.length == 0 ? null
|
||||||
|
: middlewares.length == 1 ? middlewares[0]
|
||||||
|
: Middleware.of(middlewares);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Internal wiring ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
SimpleHandler getNotFoundHandler() { return notFoundHandler; }
|
SimpleHandler getNotFoundHandler() { return notFoundHandler; }
|
||||||
ExceptionHandler getExceptionHandler() { return exceptionHandler; }
|
ExceptionHandler getExceptionHandler() { return exceptionHandler; }
|
||||||
void setNamespace(String namespace) {
|
void setNamespace(String namespace) {
|
||||||
@@ -43,6 +70,30 @@ public abstract class AbstractRouter {
|
|||||||
this.namespaceBytes = namespace.getBytes(StandardCharsets.UTF_8);
|
this.namespaceBytes = namespace.getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles a handler with its middleware chain. Called once per handler at registration.
|
||||||
|
*
|
||||||
|
* <p>Application order (innermost → outermost):
|
||||||
|
* <ol>
|
||||||
|
* <li>handler-level middlewares (passed at the call site)</li>
|
||||||
|
* <li>router-level middleware (set in the constructor)</li>
|
||||||
|
* </ol>
|
||||||
|
*/
|
||||||
|
private RequestHandler compile(RequestHandler handler, Middleware[] handlerMiddlewares) {
|
||||||
|
RequestHandler compiled = handler;
|
||||||
|
// handler-level: wrap from innermost outward; box FunctionalHandler → SimpleHandler once per layer
|
||||||
|
for (int i = handlerMiddlewares.length - 1; i >= 0; i--)
|
||||||
|
compiled = new SimpleHandler(handlerMiddlewares[i].wrap(compiled));
|
||||||
|
// router-level: outermost wrapper, executes first
|
||||||
|
if (routerMiddleware != null)
|
||||||
|
compiled = new SimpleHandler(routerMiddleware.wrap(compiled));
|
||||||
|
return compiled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Middleware[] NO_MIDDLEWARES = new Middleware[0];
|
||||||
|
|
||||||
|
// ── Error handler configuration ───────────────────────────────────────────
|
||||||
|
|
||||||
public AbstractRouter onNotFound(SimpleHandler.FunctionalHandler handler) {
|
public AbstractRouter onNotFound(SimpleHandler.FunctionalHandler handler) {
|
||||||
this.notFoundHandler = new SimpleHandler(handler);
|
this.notFoundHandler = new SimpleHandler(handler);
|
||||||
return this;
|
return this;
|
||||||
@@ -53,36 +104,54 @@ public abstract class AbstractRouter {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Lambda handler registration ───────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a route relative to this router's namespace. Return a {@link Response} to replace
|
* Registers a lambda handler with optional handler-level middlewares.
|
||||||
* it entirely, any other non-null value to set it as the body, or {@code null} to leave it unchanged.
|
* Router-level middlewares are applied automatically on top.
|
||||||
*/
|
*/
|
||||||
public AbstractRouter register(HttpMethod method, String path, SimpleHandler.FunctionalHandler handler) {
|
public AbstractRouter register(HttpMethod method, String path,
|
||||||
return addRoute(method, PathUtils.sanitize(path), new SimpleHandler(handler));
|
SimpleHandler.FunctionalHandler handler,
|
||||||
|
Middleware... middlewares) {
|
||||||
|
return addRoute(method, PathUtils.sanitize(path),
|
||||||
|
compile(new SimpleHandler(handler), middlewares));
|
||||||
}
|
}
|
||||||
|
|
||||||
public AbstractRouter get(String path, SimpleHandler.FunctionalHandler h) { return register(HttpMethod.GET, path, h); }
|
public AbstractRouter get (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { return register(HttpMethod.GET, path, h, m); }
|
||||||
public AbstractRouter post(String path, SimpleHandler.FunctionalHandler h) { return register(HttpMethod.POST, path, h); }
|
public AbstractRouter post (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { return register(HttpMethod.POST, path, h, m); }
|
||||||
public AbstractRouter put(String path, SimpleHandler.FunctionalHandler h) { return register(HttpMethod.PUT, path, h); }
|
public AbstractRouter put (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { return register(HttpMethod.PUT, path, h, m); }
|
||||||
public AbstractRouter delete(String path, SimpleHandler.FunctionalHandler h) { return register(HttpMethod.DELETE, path, h); }
|
public AbstractRouter delete (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { return register(HttpMethod.DELETE, path, h, m); }
|
||||||
public AbstractRouter patch(String path, SimpleHandler.FunctionalHandler h) { return register(HttpMethod.PATCH, path, h); }
|
public AbstractRouter patch (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { return register(HttpMethod.PATCH, path, h, m); }
|
||||||
public AbstractRouter options(String path, SimpleHandler.FunctionalHandler h) { return register(HttpMethod.OPTIONS, path, h); }
|
public AbstractRouter options(String path, SimpleHandler.FunctionalHandler h, Middleware... m) { return register(HttpMethod.OPTIONS, path, h, m); }
|
||||||
public AbstractRouter head(String path, SimpleHandler.FunctionalHandler h) { return register(HttpMethod.HEAD, path, h); }
|
public AbstractRouter head (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { return register(HttpMethod.HEAD, path, h, m); }
|
||||||
public AbstractRouter trace(String path, SimpleHandler.FunctionalHandler h) { return register(HttpMethod.TRACE, path, h); }
|
public AbstractRouter trace (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { return register(HttpMethod.TRACE, path, h, m); }
|
||||||
public AbstractRouter connect(String path, SimpleHandler.FunctionalHandler h) { return register(HttpMethod.CONNECT, path, h); }
|
public AbstractRouter connect(String path, SimpleHandler.FunctionalHandler h, Middleware... m) { return register(HttpMethod.CONNECT, path, h, m); }
|
||||||
public AbstractRouter purge(String path, SimpleHandler.FunctionalHandler h) { return register(HttpMethod.PURGE, path, h); }
|
public AbstractRouter purge (String path, SimpleHandler.FunctionalHandler h, Middleware... m) { return register(HttpMethod.PURGE, path, h, m); }
|
||||||
|
|
||||||
/** Registers a class-based handler; the class must be annotated with {@link Route @Route}. */
|
// ── Class-based handler registration ─────────────────────────────────────
|
||||||
public AbstractRouter register(RequestHandler handler) {
|
|
||||||
|
/**
|
||||||
|
* Registers a class-based handler with optional handler-level middlewares.
|
||||||
|
* The class must carry a {@link Route @Route} annotation declaring method and path.
|
||||||
|
* Router-level middlewares are applied automatically on top.
|
||||||
|
*/
|
||||||
|
public AbstractRouter register(RequestHandler handler, Middleware... middlewares) {
|
||||||
Route annotation = handler.getClass().getAnnotation(Route.class);
|
Route annotation = handler.getClass().getAnnotation(Route.class);
|
||||||
if (annotation != null) {
|
if (annotation != null)
|
||||||
addRoute(HttpMethod.valueOf(annotation.method()), annotation.path(), handler);
|
addRoute(annotation.method(), annotation.path(),
|
||||||
}
|
compile(handler, middlewares));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Routing ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
public abstract RequestHandler route(Request request);
|
public abstract RequestHandler route(Request request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a pre-compiled handler in the underlying routing structure.
|
||||||
|
* The handler passed here is already fully wrapped — implementations must not
|
||||||
|
* apply any additional middleware logic.
|
||||||
|
*/
|
||||||
protected abstract AbstractRouter addRoute(HttpMethod method, String path, RequestHandler handler);
|
protected abstract AbstractRouter addRoute(HttpMethod method, String path, RequestHandler handler);
|
||||||
|
|
||||||
protected static void setPathParams(Request request, String[] names, ByteView source, int[] starts, int[] lens) {
|
protected static void setPathParams(Request request, String[] names, ByteView source, int[] starts, int[] lens) {
|
||||||
|
|||||||
@@ -20,7 +20,14 @@ import java.util.Map;
|
|||||||
public class GlobalRouter extends AbstractRouter {
|
public class GlobalRouter extends AbstractRouter {
|
||||||
private final Map<String, AbstractRouter> subRoutersMap = new HashMap<>();
|
private final Map<String, AbstractRouter> subRoutersMap = new HashMap<>();
|
||||||
private final List<AbstractRouter> sortedSubRouters = new ArrayList<>();
|
private final List<AbstractRouter> sortedSubRouters = new ArrayList<>();
|
||||||
private final AbstractRouter internalRouter = new FastPathRouterImpl();
|
private final AbstractRouter internalRouter;
|
||||||
|
|
||||||
|
public GlobalRouter(Middleware... middlewares) {
|
||||||
|
super(middlewares);
|
||||||
|
this.internalRouter = new FastPathRouterImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlobalRouter() { this(new Middleware[0]); }
|
||||||
|
|
||||||
public void mount(String namespace, AbstractRouter router) {
|
public void mount(String namespace, AbstractRouter router) {
|
||||||
String sanitized = PathUtils.sanitize(namespace);
|
String sanitized = PathUtils.sanitize(namespace);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package dev.relism.routing;
|
package dev.relism.routing;
|
||||||
|
|
||||||
|
import dev.relism.http.HttpMethod;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
@@ -17,6 +19,6 @@ import java.lang.annotation.Target;
|
|||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public @interface Route {
|
public @interface Route {
|
||||||
String method() default "GET";
|
HttpMethod method() default HttpMethod.GET;
|
||||||
String path();
|
String path();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import dev.relism.http.HttpMethod;
|
|||||||
import dev.relism.models.Request;
|
import dev.relism.models.Request;
|
||||||
import dev.relism.models.RequestHandler;
|
import dev.relism.models.RequestHandler;
|
||||||
import dev.relism.routing.AbstractRouter;
|
import dev.relism.routing.AbstractRouter;
|
||||||
|
import dev.relism.routing.Middleware;
|
||||||
import dev.relism.routing.PathUtils;
|
import dev.relism.routing.PathUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,6 +23,9 @@ public class FastPathRouterImpl extends AbstractRouter {
|
|||||||
private volatile FastPathRouter<ByteView, RequestHandler> router;
|
private volatile FastPathRouter<ByteView, RequestHandler> router;
|
||||||
private String[] cachedParamNames;
|
private String[] cachedParamNames;
|
||||||
|
|
||||||
|
public FastPathRouterImpl(Middleware... middlewares) { super(middlewares); }
|
||||||
|
public FastPathRouterImpl() { super(); }
|
||||||
|
|
||||||
private static final class FastPathRouterContext {
|
private static final class FastPathRouterContext {
|
||||||
private static final ThreadLocal<MatchResult<RequestHandler>> RESULT_HOLDER =
|
private static final ThreadLocal<MatchResult<RequestHandler>> RESULT_HOLDER =
|
||||||
ThreadLocal.withInitial(() -> new MatchResult<>(32, 128));
|
ThreadLocal.withInitial(() -> new MatchResult<>(32, 128));
|
||||||
|
|||||||
Reference in New Issue
Block a user