From fa0a2d79b456c1c4a89061e22c7eff94b9d17f21 Mon Sep 17 00:00:00 2001 From: Zakaria El Orche Date: Mon, 10 Aug 2026 13:09:26 +0000 Subject: [PATCH] feat(core): add HTTP QUERY method support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the QUERY method (RFC 10008) — safe and idempotent like GET, but carries a request body like POST, useful for complex filters that don't fit in a URL query string. Co-Authored-By: Claude Sonnet 5 --- .../relism/flash/extension/FlashRegistrar.java | 1 + .../java/dev/relism/flash/http/HttpMethod.java | 3 ++- .../java/dev/relism/flash/routing/QUERY.java | 16 ++++++++++++++++ .../java/dev/relism/flash/RequestParserTest.java | 10 ++++++++++ .../dev/relism/flash/http/HttpMethodTest.java | 1 + 5 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 flash/src/main/java/dev/relism/flash/routing/QUERY.java diff --git a/flash/src/main/java/dev/relism/flash/extension/FlashRegistrar.java b/flash/src/main/java/dev/relism/flash/extension/FlashRegistrar.java index e663f17..0818bb3 100644 --- a/flash/src/main/java/dev/relism/flash/extension/FlashRegistrar.java +++ b/flash/src/main/java/dev/relism/flash/extension/FlashRegistrar.java @@ -45,6 +45,7 @@ public abstract class FlashRegistrar> { public final SELF trace (String path, SimpleHandler.FunctionalHandler h, Middleware... mw) { return route(HttpMethod.TRACE, path, h, mw); } public final SELF connect(String path, SimpleHandler.FunctionalHandler h, Middleware... mw) { return route(HttpMethod.CONNECT, path, h, mw); } public final SELF purge (String path, SimpleHandler.FunctionalHandler h, Middleware... mw) { return route(HttpMethod.PURGE, path, h, mw); } + public final SELF query (String path, SimpleHandler.FunctionalHandler h, Middleware... mw) { return route(HttpMethod.QUERY, path, h, mw); } // ── Middleware ──────────────────────────────────────────────────────────── diff --git a/flash/src/main/java/dev/relism/flash/http/HttpMethod.java b/flash/src/main/java/dev/relism/flash/http/HttpMethod.java index 39abfe5..d2c9e43 100644 --- a/flash/src/main/java/dev/relism/flash/http/HttpMethod.java +++ b/flash/src/main/java/dev/relism/flash/http/HttpMethod.java @@ -5,7 +5,7 @@ import java.nio.charset.StandardCharsets; @Getter public enum HttpMethod { - GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD, TRACE, CONNECT, PURGE; + GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD, TRACE, CONNECT, PURGE, QUERY; private static final HttpMethod[] VALUES = values(); private final byte[] bytes; @@ -35,6 +35,7 @@ public enum HttpMethod { case 'H' -> (len == 4 && buf[off + 1] == 'E' && buf[off + 2] == 'A' && buf[off + 3] == 'D') ? HEAD : null; case 'T' -> (len == 5 && buf[off + 1] == 'R' && buf[off + 2] == 'A' && buf[off + 3] == 'C' && buf[off + 4] == 'E') ? TRACE : null; case 'C' -> (len == 7 && buf[off + 1] == 'O' && buf[off + 2] == 'N' && buf[off + 3] == 'N' && buf[off + 4] == 'E' && buf[off + 5] == 'C' && buf[off + 6] == 'T') ? CONNECT : null; + case 'Q' -> (len == 5 && buf[off + 1] == 'U' && buf[off + 2] == 'E' && buf[off + 3] == 'R' && buf[off + 4] == 'Y') ? QUERY : null; default -> null; }; } diff --git a/flash/src/main/java/dev/relism/flash/routing/QUERY.java b/flash/src/main/java/dev/relism/flash/routing/QUERY.java new file mode 100644 index 0000000..77be283 --- /dev/null +++ b/flash/src/main/java/dev/relism/flash/routing/QUERY.java @@ -0,0 +1,16 @@ +package dev.relism.flash.routing; + +import dev.relism.flash.http.HttpMethod; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Shorthand for {@code @Route(method = HttpMethod.QUERY, path = "…")}. */ +@Route(method = HttpMethod.QUERY, path = "") +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface QUERY { + String value(); +} diff --git a/flash/src/test/java/dev/relism/flash/RequestParserTest.java b/flash/src/test/java/dev/relism/flash/RequestParserTest.java index d2ebb9a..2c8dd79 100644 --- a/flash/src/test/java/dev/relism/flash/RequestParserTest.java +++ b/flash/src/test/java/dev/relism/flash/RequestParserTest.java @@ -75,6 +75,16 @@ class RequestParserTest { assertEquals(body, new String(r.body().bytes(), StandardCharsets.UTF_8)); } + @Test + void body_parsed_forQueryMethod() throws IOException { + String body = "{\"filter\":\"active\"}"; + String raw = "QUERY / HTTP/1.1\r\nContent-Type: application/json\r\nContent-Length: " + body.length() + "\r\n\r\n" + body; + Request r = new RequestParser().parse(new ByteArrayInputStream(raw.getBytes(StandardCharsets.UTF_8))); + assertNotNull(r); + assertEquals(dev.relism.flash.http.HttpMethod.QUERY, r.method()); + assertEquals(body, new String(r.body().bytes(), StandardCharsets.UTF_8)); + } + @Test void body_emptyWhenNoContentLength() throws IOException { Request r = parse(req("GET / HTTP/1.1", "Host: localhost")); diff --git a/flash/src/test/java/dev/relism/flash/http/HttpMethodTest.java b/flash/src/test/java/dev/relism/flash/http/HttpMethodTest.java index 70dc6f6..ea6e316 100644 --- a/flash/src/test/java/dev/relism/flash/http/HttpMethodTest.java +++ b/flash/src/test/java/dev/relism/flash/http/HttpMethodTest.java @@ -34,6 +34,7 @@ class HttpMethodTest { assertEquals(HttpMethod.TRACE, parse("TRACE")); assertEquals(HttpMethod.CONNECT, parse("CONNECT")); assertEquals(HttpMethod.PURGE, parse("PURGE")); + assertEquals(HttpMethod.QUERY, parse("QUERY")); } @Test