From 4953c5173f4929f488050c22d11f04c2ab4997d5 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 9 Jan 2023 09:48:04 +1300 Subject: [PATCH 001/250] [maven-release-plugin] prepare for next development iteration --- avaje-jex-freemarker/pom.xml | 8 ++++---- avaje-jex-grizzly/pom.xml | 4 ++-- avaje-jex-jdk/pom.xml | 4 ++-- avaje-jex-jetty/pom.xml | 4 ++-- avaje-jex-mustache/pom.xml | 8 ++++---- avaje-jex-test/pom.xml | 4 ++-- avaje-jex/pom.xml | 2 +- pom.xml | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 9255274f..50c60dab 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 2.5 + 2.6-SNAPSHOT avaje-jex-freemarker @@ -18,7 +18,7 @@ io.avaje avaje-jex - 2.5 + 2.6-SNAPSHOT provided @@ -32,7 +32,7 @@ io.avaje avaje-jex-jetty - 2.5 + 2.6-SNAPSHOT test @@ -46,7 +46,7 @@ io.avaje avaje-jex-test - 2.5 + 2.6-SNAPSHOT test diff --git a/avaje-jex-grizzly/pom.xml b/avaje-jex-grizzly/pom.xml index a32a1510..3dd17bc1 100644 --- a/avaje-jex-grizzly/pom.xml +++ b/avaje-jex-grizzly/pom.xml @@ -3,7 +3,7 @@ avaje-jex-parent io.avaje - 2.5 + 2.6-SNAPSHOT 4.0.0 @@ -18,7 +18,7 @@ io.avaje avaje-jex - 2.5 + 2.6-SNAPSHOT diff --git a/avaje-jex-jdk/pom.xml b/avaje-jex-jdk/pom.xml index 04a8798b..8c08f563 100644 --- a/avaje-jex-jdk/pom.xml +++ b/avaje-jex-jdk/pom.xml @@ -3,7 +3,7 @@ avaje-jex-parent io.avaje - 2.5 + 2.6-SNAPSHOT 4.0.0 @@ -20,7 +20,7 @@ io.avaje avaje-jex - 2.5 + 2.6-SNAPSHOT diff --git a/avaje-jex-jetty/pom.xml b/avaje-jex-jetty/pom.xml index bd9cd88f..a6adc90e 100644 --- a/avaje-jex-jetty/pom.xml +++ b/avaje-jex-jetty/pom.xml @@ -3,7 +3,7 @@ avaje-jex-parent io.avaje - 2.5 + 2.6-SNAPSHOT 4.0.0 @@ -20,7 +20,7 @@ io.avaje avaje-jex - 2.5 + 2.6-SNAPSHOT diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 5f241248..e15b24fc 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 2.5 + 2.6-SNAPSHOT avaje-jex-mustache @@ -18,7 +18,7 @@ io.avaje avaje-jex - 2.5 + 2.6-SNAPSHOT provided @@ -33,7 +33,7 @@ io.avaje avaje-jex-jetty - 2.5 + 2.6-SNAPSHOT test @@ -47,7 +47,7 @@ io.avaje avaje-jex-test - 2.5 + 2.6-SNAPSHOT test diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index d811fd2d..eb680f5b 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 2.5 + 2.6-SNAPSHOT avaje-jex-test @@ -14,7 +14,7 @@ io.avaje avaje-jex - 2.5 + 2.6-SNAPSHOT provided diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index d0b7aa75..453e389d 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 2.5 + 2.6-SNAPSHOT avaje-jex diff --git a/pom.xml b/pom.xml index 69378e9d..754b0527 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.avaje avaje-jex-parent - 2.5 + 2.6-SNAPSHOT pom From 2d2f9b0b87c60e3d2fb475037a2de1deb3b82c6e Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 9 Jan 2023 09:53:12 +1300 Subject: [PATCH 002/250] Bump to 2.6-SNAPSHOT after release --- examples/example-grizzly/pom.xml | 2 +- examples/example-jdk-jsonb/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-jetty/pom.xml | 2 +- examples/example-katie/pom.xml | 4 ++-- examples/pom.xml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/example-grizzly/pom.xml b/examples/example-grizzly/pom.xml index bce25c0d..65767c1a 100644 --- a/examples/example-grizzly/pom.xml +++ b/examples/example-grizzly/pom.xml @@ -23,7 +23,7 @@ io.avaje avaje-jex-grizzly - 2.5-SNAPSHOT + 2.6-SNAPSHOT diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index ecee2dac..6bf97b66 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -27,7 +27,7 @@ io.avaje avaje-jex-jdk - 2.5-SNAPSHOT + 2.6-SNAPSHOT diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 72e52097..73287faa 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex-jdk - 2.5-SNAPSHOT + 2.6-SNAPSHOT diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index d34ed17a..17250302 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -30,7 +30,7 @@ io.avaje avaje-jex-jetty - 2.5-SNAPSHOT + 2.6-SNAPSHOT diff --git a/examples/example-katie/pom.xml b/examples/example-katie/pom.xml index b5879db4..127a837d 100644 --- a/examples/example-katie/pom.xml +++ b/examples/example-katie/pom.xml @@ -33,7 +33,7 @@ io.avaje avaje-jex-jetty - 2.5-SNAPSHOT + 2.6-SNAPSHOT @@ -52,7 +52,7 @@ io.avaje avaje-jex-test - 2.5-SNAPSHOT + 2.6-SNAPSHOT test diff --git a/examples/pom.xml b/examples/pom.xml index b8996a94..45a19df6 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 2.5-SNAPSHOT + 2.6-SNAPSHOT examples From bdbafa1e8faccbc5c7bf849b34065dfb63a09911 Mon Sep 17 00:00:00 2001 From: "robin.bygrave" Date: Mon, 17 Apr 2023 18:29:53 +1200 Subject: [PATCH 003/250] Bump avaje dependencies --- avaje-jex-test/pom.xml | 4 ++-- avaje-jex/pom.xml | 6 +++--- examples/example-jdk-jsonb/pom.xml | 4 ++-- examples/example-jetty/pom.xml | 8 ++++---- pom.xml | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index eb680f5b..bce041b5 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -28,14 +28,14 @@ io.avaje avaje-inject-test - 8.11 + 9.0 true io.avaje avaje-jsonb - 1.1 + 1.4 true diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 453e389d..c9e88777 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -24,14 +24,14 @@ io.avaje avaje-config - 2.4 + 3.1 true io.avaje avaje-inject - 8.11 + 9.0 true @@ -45,7 +45,7 @@ io.avaje avaje-jsonb - 1.1 + 1.4 true diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index 6bf97b66..c344e05d 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -33,7 +33,7 @@ io.avaje avaje-jsonb - 1.1 + 1.4 @@ -87,7 +87,7 @@ io.avaje avaje-jsonb-generator - 1.1 + 1.4 diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index 17250302..ce4a3fb7 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -48,26 +48,26 @@ io.avaje avaje-jsonb - 1.1 + 1.4 io.avaje avaje-jsonb-generator - 1.1 + 1.4 provided io.avaje avaje-inject - 8.11 + 9.0 io.avaje avaje-inject-generator - 8.11 + 9.0 provided diff --git a/pom.xml b/pom.xml index 754b0527..566f0eb9 100644 --- a/pom.xml +++ b/pom.xml @@ -45,7 +45,7 @@ io.avaje avaje-http-client - 1.21 + 1.35 test From c19794fa683c92c95ab2d0b166ef538c4045ad80 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 18 Apr 2023 23:25:27 +1200 Subject: [PATCH 004/250] Bump http client dependency --- .../src/test/java/io/avaje/jex/base/TestPair.java | 8 ++++---- avaje-jex-test/pom.xml | 2 +- examples/example-grizzly/pom.xml | 2 +- examples/example-jetty/pom.xml | 4 ++-- examples/example-katie/pom.xml | 2 +- pom.xml | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/TestPair.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/TestPair.java index f6974d20..b629383f 100644 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/TestPair.java +++ b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/TestPair.java @@ -1,6 +1,6 @@ package io.avaje.jex.base; -import io.avaje.http.client.HttpClientContext; +import io.avaje.http.client.HttpClient; import io.avaje.http.client.HttpClientRequest; import io.avaje.http.client.JacksonBodyAdapter; import io.avaje.jex.Jex; @@ -16,9 +16,9 @@ public class TestPair { private final Jex.Server server; - private final HttpClientContext client; + private final HttpClient client; - public TestPair(int port, Jex.Server server, HttpClientContext client) { + public TestPair(int port, Jex.Server server, HttpClient client) { this.port = port; this.server = server; this.client = client; @@ -52,7 +52,7 @@ public static TestPair create(Jex app, int port) { var jexServer = app.port(port).start(); var url = "http://localhost:" + port; - var client = HttpClientContext.builder() + var client = HttpClient.builder() .baseUrl(url) .bodyAdapter(new JacksonBodyAdapter()) .build(); diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index bce041b5..65a05769 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-http-client - 1.21 + 1.36 diff --git a/examples/example-grizzly/pom.xml b/examples/example-grizzly/pom.xml index 65767c1a..56fa3f77 100644 --- a/examples/example-grizzly/pom.xml +++ b/examples/example-grizzly/pom.xml @@ -35,7 +35,7 @@ io.avaje avaje-http-client - 1.21 + 1.36 diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index ce4a3fb7..c4053c2d 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -74,13 +74,13 @@ io.avaje avaje-http-api - 1.21 + 1.36 io.avaje avaje-http-jex-generator - 1.21 + 1.36 provided diff --git a/examples/example-katie/pom.xml b/examples/example-katie/pom.xml index 127a837d..949a89e3 100644 --- a/examples/example-katie/pom.xml +++ b/examples/example-katie/pom.xml @@ -46,7 +46,7 @@ io.avaje avaje-http-client - 1.21 + 1.36 diff --git a/pom.xml b/pom.xml index 566f0eb9..901d68e4 100644 --- a/pom.xml +++ b/pom.xml @@ -45,7 +45,7 @@ io.avaje avaje-http-client - 1.35 + 1.36 test From b7c9af01d7aa3fcb1b0f5464791119e55d4a6bc2 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 18 Apr 2023 23:51:02 +1200 Subject: [PATCH 005/250] Bump jetty dependency to 11.0.15 --- avaje-jex-jetty/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avaje-jex-jetty/pom.xml b/avaje-jex-jetty/pom.xml index a6adc90e..c815a24a 100644 --- a/avaje-jex-jetty/pom.xml +++ b/avaje-jex-jetty/pom.xml @@ -12,7 +12,7 @@ 11 11 - 11.0.13 + 11.0.15 false From 47e2f610edbd321a72160d321193d7ffed7c4728 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 7 Nov 2023 23:43:11 +1300 Subject: [PATCH 006/250] Update dependencies --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-grizzly/pom.xml | 2 +- avaje-jex-jdk/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-test/pom.xml | 8 ++++---- avaje-jex/pom.xml | 8 ++++---- examples/example-grizzly/pom.xml | 5 +++-- examples/example-katie/pom.xml | 2 +- pom.xml | 7 ++++--- 9 files changed, 20 insertions(+), 18 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 50c60dab..1ca43997 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -39,7 +39,7 @@ com.fasterxml.jackson.core jackson-databind - 2.13.4.2 + ${jackson.version} test diff --git a/avaje-jex-grizzly/pom.xml b/avaje-jex-grizzly/pom.xml index 3dd17bc1..1a6c31ff 100644 --- a/avaje-jex-grizzly/pom.xml +++ b/avaje-jex-grizzly/pom.xml @@ -37,7 +37,7 @@ com.fasterxml.jackson.core jackson-databind - 2.14.0 + ${jackson.version} test diff --git a/avaje-jex-jdk/pom.xml b/avaje-jex-jdk/pom.xml index 8c08f563..6bea9546 100644 --- a/avaje-jex-jdk/pom.xml +++ b/avaje-jex-jdk/pom.xml @@ -26,7 +26,7 @@ com.fasterxml.jackson.core jackson-databind - 2.14.0 + ${jackson.version} test diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index e15b24fc..79640dd3 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -40,7 +40,7 @@ com.fasterxml.jackson.core jackson-databind - 2.14.0 + ${jackson.version} test diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 65a05769..a5a079c8 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -21,28 +21,28 @@ io.avaje avaje-http-client - 1.36 + 2.0 io.avaje avaje-inject-test - 9.0 + 9.9 true io.avaje avaje-jsonb - 1.4 + 1.9 true com.fasterxml.jackson.core jackson-databind - 2.14.0 + ${jackson.version} true diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index c9e88777..8b4f51a6 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -24,28 +24,28 @@ io.avaje avaje-config - 3.1 + 3.9 true io.avaje avaje-inject - 9.0 + 9.9 true com.fasterxml.jackson.core jackson-databind - 2.14.0 + ${jackson.version} true io.avaje avaje-jsonb - 1.4 + 1.9 true diff --git a/examples/example-grizzly/pom.xml b/examples/example-grizzly/pom.xml index 56fa3f77..6fd7a8c7 100644 --- a/examples/example-grizzly/pom.xml +++ b/examples/example-grizzly/pom.xml @@ -16,6 +16,7 @@ 11 + 2.15.0 @@ -29,13 +30,13 @@ com.fasterxml.jackson.core jackson-databind - 2.14.0 + ${jackson.version} io.avaje avaje-http-client - 1.36 + 2.0 diff --git a/examples/example-katie/pom.xml b/examples/example-katie/pom.xml index 949a89e3..49689368 100644 --- a/examples/example-katie/pom.xml +++ b/examples/example-katie/pom.xml @@ -46,7 +46,7 @@ io.avaje avaje-http-client - 1.36 + 2.0 diff --git a/pom.xml b/pom.xml index 901d68e4..3e22beca 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.avaje java11-oss - 3.9 + 3.12 io.avaje @@ -19,6 +19,7 @@ true + 2.15.0 @@ -38,14 +39,14 @@ io.avaje junit - 1.1 + 1.3 test io.avaje avaje-http-client - 1.36 + 2.0 test From f381d912175c19b3a5878efbdf82c6703e36ef8d Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 7 Nov 2023 23:54:28 +1300 Subject: [PATCH 007/250] Update dependencies and HttpClient use in tests --- .../java/io/avaje/jex/grizzly/TestPair.java | 13 ++++---- .../java/io/avaje/jex/jdk/HeadersTest.java | 6 ++-- .../io/avaje/jex/jdk/HealthPluginTest.java | 7 ++--- .../io/avaje/jex/jdk/JdkJexServerTest.java | 4 +-- .../test/java/io/avaje/jex/jdk/TestPair.java | 8 ++--- .../io/avaje/jex/test/JexInjectPlugin.java | 12 ++++---- .../main/java/io/avaje/jex/test/TestPair.java | 10 +++---- avaje-jex/pom.xml | 30 +++++++++---------- .../src/test/java/org/example/ClientMain.java | 7 ++--- pom.xml | 1 + 10 files changed, 48 insertions(+), 50 deletions(-) diff --git a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/TestPair.java b/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/TestPair.java index 4fbdf6c4..41bc5a01 100644 --- a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/TestPair.java +++ b/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/TestPair.java @@ -1,13 +1,14 @@ package io.avaje.jex.grizzly; -import io.avaje.http.client.HttpClientContext; +import io.avaje.http.client.HttpClient; import io.avaje.http.client.HttpClientRequest; import io.avaje.http.client.JacksonBodyAdapter; import io.avaje.jex.Jex; -import java.net.http.HttpClient; import java.util.Random; +import static java.net.http.HttpClient.Version.HTTP_1_1; + /** * Server and Client pair for a test. */ @@ -17,9 +18,9 @@ public class TestPair { private final Jex.Server server; - private final HttpClientContext client; + private final HttpClient client; - public TestPair(int port, Jex.Server server, HttpClientContext client) { + public TestPair(int port, Jex.Server server, HttpClient client) { this.port = port; this.server = server; this.client = client; @@ -53,10 +54,10 @@ public static TestPair create(Jex app, int port) { var jexServer = app.port(port).start(); var url = "http://localhost:" + port; - var client = HttpClientContext.builder() + var client = HttpClient.builder() .baseUrl(url) .bodyAdapter(new JacksonBodyAdapter()) - .version(HttpClient.Version.HTTP_1_1) + .version(HTTP_1_1) .build(); return new TestPair(port, jexServer, client); diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HeadersTest.java b/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HeadersTest.java index 83e9738c..263452e9 100644 --- a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HeadersTest.java +++ b/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HeadersTest.java @@ -1,6 +1,6 @@ package io.avaje.jex.jdk; -import io.avaje.http.client.HttpClientContext; +import io.avaje.http.client.HttpClient; import io.avaje.http.client.JacksonBodyAdapter; import io.avaje.jex.Jex; import org.junit.jupiter.api.BeforeAll; @@ -17,7 +17,7 @@ class HeadersTest { static final int port = new Random().nextInt(1000) + 10_000; static Jex.Server server; - static HttpClientContext client; + static HttpClient client; @BeforeAll static void setup() { @@ -33,7 +33,7 @@ static void setup() { .port(port) .start(); - client = HttpClientContext.builder() + client = HttpClient.builder() .baseUrl("http://localhost:"+port) .bodyAdapter(new JacksonBodyAdapter()) .build(); diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HealthPluginTest.java b/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HealthPluginTest.java index ad47a08f..4dcbb4d5 100644 --- a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HealthPluginTest.java +++ b/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HealthPluginTest.java @@ -1,10 +1,9 @@ package io.avaje.jex.jdk; -import io.avaje.http.client.HttpClientContext; +import io.avaje.http.client.HttpClient; import io.avaje.http.client.JacksonBodyAdapter; import io.avaje.jex.AppLifecycle; import io.avaje.jex.Jex; -import io.avaje.jex.core.HealthPlugin; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -21,7 +20,7 @@ class HealthPluginTest { static final int port = new Random().nextInt(1000) + 10_000; static Jex jex; static Jex.Server server; - static HttpClientContext client; + static HttpClient client; @BeforeAll static void setup() { @@ -37,7 +36,7 @@ static void setup() { .port(port); server = jex.start(); - client = HttpClientContext.builder() + client = HttpClient.builder() .baseUrl("http://localhost:"+port) .bodyAdapter(new JacksonBodyAdapter()) .build(); diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java b/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java index 085e5f69..f284afcf 100644 --- a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java +++ b/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java @@ -1,6 +1,6 @@ package io.avaje.jex.jdk; -import io.avaje.http.client.HttpClientContext; +import io.avaje.http.client.HttpClient; import io.avaje.http.client.JacksonBodyAdapter; import io.avaje.jex.Jex; import org.junit.jupiter.api.Test; @@ -28,7 +28,7 @@ void init() { .port(8093) .start(); - final HttpClientContext client = HttpClientContext.builder() + final HttpClient client = HttpClient.builder() .baseUrl("http://localhost:8093") .bodyAdapter(new JacksonBodyAdapter()) .build(); diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/TestPair.java b/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/TestPair.java index 9bac68c2..3b9171f4 100644 --- a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/TestPair.java +++ b/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/TestPair.java @@ -1,6 +1,6 @@ package io.avaje.jex.jdk; -import io.avaje.http.client.HttpClientContext; +import io.avaje.http.client.HttpClient; import io.avaje.http.client.HttpClientRequest; import io.avaje.http.client.JacksonBodyAdapter; import io.avaje.jex.Jex; @@ -17,9 +17,9 @@ public class TestPair { private final Jex.Server server; - private final HttpClientContext client; + private final HttpClient client; - public TestPair(int port, Jex.Server server, HttpClientContext client) { + public TestPair(int port, Jex.Server server, HttpClient client) { this.port = port; this.server = server; this.client = client; @@ -54,7 +54,7 @@ public static TestPair create(Jex app, int port) { var jexServer = app.port(port).start(); var url = "http://localhost:" + port; - var client = HttpClientContext.builder() + var client = HttpClient.builder() .baseUrl(url) .bodyAdapter(new JacksonBodyAdapter()) .requestTimeout(Duration.ofMinutes(2)) diff --git a/avaje-jex-test/src/main/java/io/avaje/jex/test/JexInjectPlugin.java b/avaje-jex-test/src/main/java/io/avaje/jex/test/JexInjectPlugin.java index 22b80984..9bf7e0d4 100644 --- a/avaje-jex-test/src/main/java/io/avaje/jex/test/JexInjectPlugin.java +++ b/avaje-jex-test/src/main/java/io/avaje/jex/test/JexInjectPlugin.java @@ -1,6 +1,6 @@ package io.avaje.jex.test; -import io.avaje.http.client.HttpClientContext; +import io.avaje.http.client.HttpClient; import io.avaje.inject.BeanScope; import io.avaje.inject.test.Plugin; import io.avaje.jex.Jex; @@ -25,7 +25,7 @@ public final class JexInjectPlugin implements Plugin { */ @Override public boolean forType(Class type) { - return HttpClientContext.class.equals(type) || isHttpClientApi(type); + return HttpClient.class.equals(type) || isHttpClientApi(type); } private boolean isHttpClientApi(Class type) { @@ -57,7 +57,7 @@ public Scope createScope(BeanScope beanScope) { private static class LocalScope implements Plugin.Scope { private final Jex.Server server; - private final HttpClientContext httpClient; + private final HttpClient httpClient; LocalScope(BeanScope beanScope) { Jex jex = beanScope.getOptional(Jex.class) @@ -68,8 +68,8 @@ private static class LocalScope implements Plugin.Scope { // get a HttpClientContext.Builder provided by dependency injection test scope or new one up this.server = jex.start(); int port = server.port(); - this.httpClient = beanScope.getOptional(HttpClientContext.Builder.class) - .orElse(HttpClientContext.builder()) + this.httpClient = beanScope.getOptional(HttpClient.Builder.class) + .orElse(HttpClient.builder()) .configureWith(beanScope) .baseUrl("http://localhost:" + port) .build(); @@ -77,7 +77,7 @@ private static class LocalScope implements Plugin.Scope { @Override public Object create(Class type) { - if (HttpClientContext.class.equals(type)) { + if (HttpClient.class.equals(type)) { return httpClient; } return apiClient(type); diff --git a/avaje-jex-test/src/main/java/io/avaje/jex/test/TestPair.java b/avaje-jex-test/src/main/java/io/avaje/jex/test/TestPair.java index 09c25612..f40f63cc 100644 --- a/avaje-jex-test/src/main/java/io/avaje/jex/test/TestPair.java +++ b/avaje-jex-test/src/main/java/io/avaje/jex/test/TestPair.java @@ -1,10 +1,8 @@ package io.avaje.jex.test; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.avaje.http.client.HttpClientContext; +import io.avaje.http.client.HttpClient; import io.avaje.http.client.HttpClientRequest; import io.avaje.http.client.JacksonBodyAdapter; -import io.avaje.http.client.RequestLogger; import io.avaje.jex.Jex; import java.util.Random; @@ -18,9 +16,9 @@ public class TestPair { private final Jex.Server server; - private final HttpClientContext client; + private final HttpClient client; - public TestPair(int port, Jex.Server server, HttpClientContext client) { + public TestPair(int port, Jex.Server server, HttpClient client) { this.port = port; this.server = server; this.client = client; @@ -51,7 +49,7 @@ public static TestPair create(Jex app) { var jexServer = app.port(port).start(); var url = "http://localhost:" + port; - var client = HttpClientContext.builder() + var client = HttpClient.builder() .baseUrl(url) .bodyAdapter(new JacksonBodyAdapter()) .build(); diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 8b4f51a6..d90aea85 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -53,21 +53,21 @@ - - org.apache.maven.plugins - maven-surefire-plugin - - - --add-opens io.avaje.jex/io.avaje.jex.base=com.fasterxml.jackson.databind - --add-modules com.fasterxml.jackson.databind - --add-opens io.avaje.jex/io.avaje.jex=ALL-UNNAMED - --add-opens io.avaje.jex/io.avaje.jex.base=ALL-UNNAMED - --add-opens io.avaje.jex/io.avaje.jex.core=ALL-UNNAMED - --add-opens io.avaje.jex/io.avaje.jex.routes=ALL-UNNAMED - --add-opens io.avaje.jex/io.avaje.jex.jetty=ALL-UNNAMED - - - + + + + + + + + + + + + + + + diff --git a/examples/example-grizzly/src/test/java/org/example/ClientMain.java b/examples/example-grizzly/src/test/java/org/example/ClientMain.java index 28ad4ca4..e821f116 100644 --- a/examples/example-grizzly/src/test/java/org/example/ClientMain.java +++ b/examples/example-grizzly/src/test/java/org/example/ClientMain.java @@ -1,9 +1,8 @@ package org.example; -import io.avaje.http.client.HttpClientContext; +import io.avaje.http.client.HttpClient; import io.avaje.http.client.JacksonBodyAdapter; -import java.net.http.HttpClient; import java.net.http.HttpHeaders; import java.net.http.HttpResponse; @@ -11,10 +10,10 @@ public class ClientMain { public static void main(String[] args) { - final HttpClientContext ctx = HttpClientContext.builder() + final HttpClient ctx = HttpClient.builder() .baseUrl("http://localhost:7003") .bodyAdapter(new JacksonBodyAdapter()) - .version(HttpClient.Version.HTTP_1_1) + .version(java.net.http.HttpClient.Version.HTTP_1_1) .build(); final HttpResponse res = ctx.request() diff --git a/pom.xml b/pom.xml index 3e22beca..9b17193c 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ true 2.15.0 + false From e484881a54c565f3a4f108478020023202595764 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 21 Nov 2024 21:13:07 -0500 Subject: [PATCH 008/250] write more than a single byte at once --- .gitignore | 5 +++++ .../java/io/avaje/jex/jdk/BufferedOutStream.java | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/.gitignore b/.gitignore index 0c9d542d..8a8c950d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,8 @@ build/ .idea/ *.iml .gradle +.project +*.prefs +avaje-jex-jdk/.classpath +avaje-jex-jetty/.classpath +avaje-jex/.classpath diff --git a/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java b/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java index cc7065f6..8f1411b5 100644 --- a/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java +++ b/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java @@ -32,6 +32,19 @@ public void write(int b) throws IOException { } } + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (stream != null) { + stream.write(b, off, len); + } else { + count += len; + buffer.write(b, off, len); + if (count > max) { + initialiseChunked(); + } + } + } + /** * Use responseLength 0 and chunked response. */ From 0d2af6df17adb2a8549d3957fc449e8250eacbcc Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:40:07 -0500 Subject: [PATCH 009/250] Delete avaje-jex-grizzly directory --- avaje-jex-grizzly/pom.xml | 53 -- .../io/avaje/jex/grizzly/ContextUtil.java | 65 --- .../io/avaje/jex/grizzly/GrizzlyContext.java | 480 ------------------ .../avaje/jex/grizzly/GrizzlyJexServer.java | 57 --- .../avaje/jex/grizzly/GrizzlyServerStart.java | 42 -- .../avaje/jex/grizzly/HttpServerBuilder.java | 98 ---- .../io/avaje/jex/grizzly/RouteHandler.java | 82 --- .../io/avaje/jex/grizzly/ServiceManager.java | 22 - .../services/io.avaje.jex.spi.SpiStartServer | 1 - .../avaje/jex/grizzly/AutoCloseIterator.java | 32 -- .../jex/grizzly/ContextFormParamTest.java | 135 ----- .../avaje/jex/grizzly/ContextLengthTest.java | 106 ---- .../io/avaje/jex/grizzly/ContextTest.java | 194 ------- .../java/io/avaje/jex/grizzly/HelloDto.java | 30 -- .../io/avaje/jex/grizzly/HelloWorldTest.java | 48 -- .../java/io/avaje/jex/grizzly/JsonTest.java | 121 ----- .../io/avaje/jex/grizzly/QueryParamTest.java | 148 ------ .../java/io/avaje/jex/grizzly/TestPair.java | 65 --- .../io/avaje/jex/grizzly/VanillaMain.java | 42 -- .../src/test/resources/logback-test.xml | 19 - .../src/test/resources/myres/hello.txt | 1 - 21 files changed, 1841 deletions(-) delete mode 100644 avaje-jex-grizzly/pom.xml delete mode 100644 avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/ContextUtil.java delete mode 100644 avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/GrizzlyContext.java delete mode 100644 avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/GrizzlyJexServer.java delete mode 100644 avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/GrizzlyServerStart.java delete mode 100644 avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/HttpServerBuilder.java delete mode 100644 avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/RouteHandler.java delete mode 100644 avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/ServiceManager.java delete mode 100644 avaje-jex-grizzly/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiStartServer delete mode 100644 avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/AutoCloseIterator.java delete mode 100644 avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/ContextFormParamTest.java delete mode 100644 avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/ContextLengthTest.java delete mode 100644 avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/ContextTest.java delete mode 100644 avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/HelloDto.java delete mode 100644 avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/HelloWorldTest.java delete mode 100644 avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/JsonTest.java delete mode 100644 avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/QueryParamTest.java delete mode 100644 avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/TestPair.java delete mode 100644 avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/VanillaMain.java delete mode 100644 avaje-jex-grizzly/src/test/resources/logback-test.xml delete mode 100644 avaje-jex-grizzly/src/test/resources/myres/hello.txt diff --git a/avaje-jex-grizzly/pom.xml b/avaje-jex-grizzly/pom.xml deleted file mode 100644 index 1a6c31ff..00000000 --- a/avaje-jex-grizzly/pom.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - avaje-jex-parent - io.avaje - 2.6-SNAPSHOT - - 4.0.0 - - avaje-jex-grizzly - - - - - - - - - io.avaje - avaje-jex - 2.6-SNAPSHOT - - - - org.glassfish.grizzly - grizzly-http-server - 3.0.0 - - - - org.glassfish.grizzly - grizzly-http2 - 3.0.0 - true - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - test - - - - org.slf4j - jul-to-slf4j - 1.7.36 - test - - - - - diff --git a/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/ContextUtil.java b/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/ContextUtil.java deleted file mode 100644 index 82705c7d..00000000 --- a/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/ContextUtil.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.avaje.jex.grizzly; - -import org.glassfish.grizzly.http.server.Request; - -import java.io.*; - -class ContextUtil { - - private static final int DEFAULT_BUFFER_SIZE = 8 * 1024; - - private static final int BUFFER_MAX = 65536; - - static byte[] requestBodyAsBytes(Request req) { - final int len = req.getContentLength(); - try (final InputStream inputStream = req.getInputStream()) { - - int bufferSize = len > -1 ? len : DEFAULT_BUFFER_SIZE; - if (bufferSize > BUFFER_MAX) { - bufferSize = BUFFER_MAX; - } - ByteArrayOutputStream os = new ByteArrayOutputStream(bufferSize); - copy(inputStream, os, bufferSize); - return os.toByteArray(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - static void copy(InputStream in, OutputStream out, int bufferSize) throws IOException { - byte[] buffer = new byte[bufferSize]; - int len; - while ((len = in.read(buffer, 0, bufferSize)) > 0) { - out.write(buffer, 0, len); - } - } - - static String requestBodyAsString(Request request) { - final long requestLength = request.getContentLengthLong(); - if (requestLength == 0) { - return ""; - } - if (requestLength < 0) { - throw new IllegalStateException("No content-length set?"); - } - final int bufferSize = requestLength > 512 ? 512 : (int)requestLength; - - StringWriter writer = new StringWriter((int)requestLength); - final Reader reader = request.getReader(); - try { - long transferred = 0; - char[] buffer = new char[bufferSize]; - int nRead; - while ((nRead = reader.read(buffer, 0, bufferSize)) >= 0) { - writer.write(buffer, 0, nRead); - transferred += nRead; - if (transferred == requestLength) { - break; - } - } - return writer.toString(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } -} diff --git a/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/GrizzlyContext.java b/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/GrizzlyContext.java deleted file mode 100644 index 13f159f2..00000000 --- a/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/GrizzlyContext.java +++ /dev/null @@ -1,480 +0,0 @@ -package io.avaje.jex.grizzly; - -import io.avaje.jex.Context; -import io.avaje.jex.Routing; -import io.avaje.jex.UploadedFile; -import io.avaje.jex.http.RedirectResponse; -import io.avaje.jex.spi.HeaderKeys; -import io.avaje.jex.spi.SpiContext; -import org.glassfish.grizzly.http.server.Request; -import org.glassfish.grizzly.http.server.Response; -import org.glassfish.grizzly.http.server.Session; -import org.glassfish.grizzly.http.util.ContentType; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UncheckedIOException; -import java.util.*; -import java.util.stream.Stream; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; - -class GrizzlyContext implements Context, SpiContext { - - private static final ContentType JSON = ContentType.newContentType(APPLICATION_JSON); - private static final ContentType JSON_STREAM = ContentType.newContentType(APPLICATION_X_JSON_STREAM); - private static final ContentType HTML_UTF8 = ContentType.newContentType("text/html", "utf-8"); - private static final ContentType PLAIN_UTF8 = ContentType.newContentType("text/plain", "utf-8"); - - private static final String UTF8 = "UTF8"; - private static final int SC_MOVED_TEMPORARILY = 302; - private final ServiceManager mgr; - private final String path; - private final Map pathParams; - private final Request request; - private final Response response; - private Routing.Type mode; - private Map> formParams; - private Map> queryParams; - private Map cookieMap; - - GrizzlyContext(ServiceManager mgr, Request request, Response response, String path, Map pathParams) { - this.mgr = mgr; - this.request = request; - this.response = response; - this.path = path; - this.pathParams = pathParams; - } - - /** - * Create when no route matched. - */ - GrizzlyContext(ServiceManager mgr, Request request, Response response, String path) { - this.mgr = mgr; - this.request = request; - this.response = response; - this.path = path; - this.pathParams = null; - } - - @Override - public String matchedPath() { - return path; - } - - @Override - public Context attribute(String key, Object value) { - request.setAttribute(key, value); - return this; - } - - @Override - @SuppressWarnings("unchecked") - public T attribute(String key) { - return (T) request.getAttribute(key); - } - - @Override - public Map cookieMap() { - if (cookieMap == null) { - cookieMap = new LinkedHashMap<>(); - final org.glassfish.grizzly.http.Cookie[] cookies = request.getCookies(); - for (org.glassfish.grizzly.http.Cookie cookie : cookies) { - cookieMap.put(cookie.getName(), cookie.getValue()); - } - } - return cookieMap; - } - - @Override - public String cookie(String name) { - return cookieMap().get(name); - } - - @Override - public Context cookie(Cookie cookie) { - throw new UnsupportedOperationException(); - } - - @Override - public Context cookie(String name, String value) { - throw new UnsupportedOperationException(); - } - - @Override - public Context cookie(String name, String value, int maxAge) { - throw new UnsupportedOperationException(); - } - - @Override - public Context removeCookie(String name) { - throw new UnsupportedOperationException(); - } - - @Override - public Context removeCookie(String name, String path) { - throw new UnsupportedOperationException(); - } - - @Override - public void redirect(String location) { - redirect(location, SC_MOVED_TEMPORARILY); - } - - @Override - public void redirect(String location, int statusCode) { - status(statusCode); - if (mode == Routing.Type.BEFORE) { - header(HeaderKeys.LOCATION, location); - throw new RedirectResponse(statusCode); - } else { - try { - response.sendRedirect(location); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - } - - @Override - public void performRedirect() { - // TODO check this - } - - @Override - public T bodyAsClass(Class beanType) { - return mgr.jsonRead(beanType, this); - } - - @Override - public byte[] bodyAsBytes() { - return ContextUtil.requestBodyAsBytes(request); - } - - private String characterEncoding() { - String encoding = request.getCharacterEncoding(); - return encoding != null ? encoding : UTF8; - } - - @Override - public String body() { - return ContextUtil.requestBodyAsString(request); - } - - @Override - public long contentLength() { - return request.getContentLengthLong(); - } - - @Override - public String contentType() { - return request.getContentType(); - } - - @Override - public String responseHeader(String key) { - return response.getHeader(key); - } - - @Override - public Context contentType(String contentType) { - response.setContentType(contentType); - return this; - } - - @Override - public Map pathParamMap() { - return pathParams; - } - - @Override - public String pathParam(String name) { - return pathParams.get(name); - } - - @Override - public String queryParam(String name) { - final List values = queryParams(name); - return values == null || values.isEmpty() ? null : values.get(0); - } - - private Map> queryParams() { - if (queryParams == null) { - queryParams = mgr.parseParamMap(queryString(), characterEncoding()); - } - return queryParams; - } - - @Override - public List queryParams(String name) { - final List values = queryParams().get(name); - return values == null ? emptyList() : values; - } - - @Override - public Map queryParamMap() { - final Map> map = queryParams(); - if (map.isEmpty()) { - return emptyMap(); - } - final Map single = new LinkedHashMap<>(); - for (Map.Entry> entry : map.entrySet()) { - final List value = entry.getValue(); - if (value != null && !value.isEmpty()) { - single.put(entry.getKey(), value.get(0)); - } - } - return single; - } - - @Override - public String queryString() { - return request.getQueryString(); - } - - /** - * Return the first form param value for the specified key or null. - */ - @Override - public String formParam(String key) { - return request.getParameter(key); - } - - /** - * Return the first form param value for the specified key or the default value. - */ - @Override - public String formParam(String key, String defaultValue) { - String value = request.getParameter(key); - return value == null ? defaultValue : value; - } - - /** - * Return the form params for the specified key, or empty list. - */ - @Override - public List formParams(String key) { - final String[] values = request.getParameterValues(key); - return values == null ? emptyList() : asList(values); - } - - @Override - public Map> formParamMap() { - if (formParams == null) { - formParams = initFormParamMap(); - } - return formParams; - } - - private Map> initFormParamMap() { - final Map parameterMap = request.getParameterMap(); - if (parameterMap.isEmpty()) { - return emptyMap(); - } - final Set> entries = parameterMap.entrySet(); - Map> map = new LinkedHashMap<>(entries.size()); - for (Map.Entry entry : entries) { - map.put(entry.getKey(), asList(entry.getValue())); - } - return map; - } - - @Override - public String scheme() { - return request.getScheme(); - } - - @Override - public Context sessionAttribute(String key, Object value) { - request.getSession().setAttribute(key, value); - return this; - } - - @Override - @SuppressWarnings("unchecked") - public T sessionAttribute(String key) { - Session session = request.getSession(false); - return session == null ? null : (T) session.getAttribute(key); - } - - @Override - public Map sessionAttributeMap() { - Session session = request.getSession(false); - return session == null ? emptyMap() : session.attributes(); - } - - @Override - public String url() { - return scheme() + "://" + host() + ":" + port() + path; - } - - @Override - public String contextPath() { - return mgr.contextPath(); - } - - @Override - public Context status(int statusCode) { - response.setStatus(statusCode); - return this; - } - - @Override - public int status() { - return response.getStatus(); - } - - @Override - public boolean isCommitted() { - return response.isCommitted(); - } - - public void reset() { - response.reset(); - } - - @Override - public Context json(Object bean) { - response.setContentType(JSON); - mgr.jsonWrite(bean, this); - return this; - } - - @Override - public Context jsonStream(Stream stream) { - response.setContentType(JSON_STREAM); - mgr.jsonWriteStream(stream, this); - return this; - } - - @Override - public Context jsonStream(Iterator iterator) { - response.setContentType(JSON_STREAM); - mgr.jsonWriteStream(iterator, this); - return this; - } - - @Override - public Context text(String content) { - response.setContentType(PLAIN_UTF8); - return write(content); - } - - @Override - public Context html(String content) { - response.setContentType(HTML_UTF8); - return write(content); - } - - @Override - public Context write(String content) { - try { - response.getOutputBuffer().write(content); - return this; - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public Context render(String name, Map model) { - mgr.render(this, name, model); - return this; - } - - @Override - public Map headerMap() { - Map map = new LinkedHashMap<>(); - for (String headerName : request.getHeaderNames()) { - map.put(headerName, request.getHeader(headerName)); - } - return map; - } - - @Override - public String header(String key) { - return request.getHeader(key); - } - - @Override - public Context header(String key, String value) { - response.setHeader(key, value); - return this; - } - - @Override - public String host() { - return request.getRemoteHost(); - } - - @Override - public String ip() { - return request.getRemoteAddr(); - } - - @Override - public boolean isMultipart() { - // TODO - return false; - } - - @Override - public boolean isMultipartFormData() { - // TODO - return false; - } - - @Override - public String method() { - return request.getMethod().getMethodString(); - } - - @Override - public String path() { - return path; - } - - @Override - public int port() { - return request.getServerPort(); - } - - @Override - public String protocol() { - return request.getProtocol().getProtocolString(); - } - - @Override - public UploadedFile uploadedFile(String name) { - throw new UnsupportedOperationException(); - } - - @Override - public List uploadedFiles(String name) { - throw new UnsupportedOperationException(); - } - - @Override - public List uploadedFiles() { - throw new UnsupportedOperationException(); - } - - @Override - public OutputStream outputStream() { - return response.getOutputStream(); - } - - @Override - public InputStream inputStream() { - return request.getInputStream(); - } - - @Override - public void setMode(Routing.Type type) { - this.mode = type; - } - -} diff --git a/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/GrizzlyJexServer.java b/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/GrizzlyJexServer.java deleted file mode 100644 index d6ce9e46..00000000 --- a/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/GrizzlyJexServer.java +++ /dev/null @@ -1,57 +0,0 @@ -package io.avaje.jex.grizzly; - -import io.avaje.applog.AppLog; -import io.avaje.jex.AppLifecycle; -import io.avaje.jex.Jex; -import org.glassfish.grizzly.http.server.HttpServer; - -import java.lang.System.Logger.Level; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; - -class GrizzlyJexServer implements Jex.Server { - - private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); - - private final HttpServer server; - private final AppLifecycle lifecycle; - private final ReentrantLock lock = new ReentrantLock(); - private final int maxWaitSeconds = 30; - private boolean shutdown; - - GrizzlyJexServer(HttpServer server, AppLifecycle lifecycle) { - this.server = server; - this.lifecycle = lifecycle; - lifecycle.registerShutdownHook(this::shutdown); - lifecycle.status(AppLifecycle.Status.STARTED); - } - - @Override - public void onShutdown(Runnable onShutdown) { - lifecycle.onShutdown(onShutdown, Integer.MAX_VALUE); - } - - @Override - public void shutdown() { - lock.lock(); - try { - if (shutdown) { - log.log(Level.DEBUG, "shutdown in progress"); - } else { - shutdown = true; - lifecycle.status(AppLifecycle.Status.STOPPING); - log.log(Level.DEBUG, "initiate shutdown with maxWaitSeconds {0}", maxWaitSeconds); - try { - server.shutdown(maxWaitSeconds, TimeUnit.SECONDS).get(); - } catch (InterruptedException |ExecutionException e) { - log.log(Level.ERROR, "Error during server shutdown", e); - } - log.log(Level.TRACE, "server http listeners stopped"); - lifecycle.status(AppLifecycle.Status.STOPPED); - } - } finally { - lock.unlock(); - } - } -} diff --git a/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/GrizzlyServerStart.java b/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/GrizzlyServerStart.java deleted file mode 100644 index a3de6fb2..00000000 --- a/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/GrizzlyServerStart.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.avaje.jex.grizzly; - -import io.avaje.applog.AppLog; -import io.avaje.jex.Jex; -import io.avaje.jex.spi.SpiRoutes; -import io.avaje.jex.spi.SpiServiceManager; -import io.avaje.jex.spi.SpiStartServer; -import org.glassfish.grizzly.http.server.HttpServer; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.lang.System.Logger.Level; - -public class GrizzlyServerStart implements SpiStartServer { - - private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); - - @Override - public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceManager) { - - final ServiceManager manager = new ServiceManager(serviceManager, "http", ""); - RouteHandler handler = new RouteHandler(routes, manager); - - final int port = jex.config().port(); - final HttpServer httpServer = new HttpServerBuilder() - //.addHandler(clStaticHttpHandler, "cl") - //.addHandler(staticHttpHandler, "static") - .handler(handler) - .setPort(port) - .build(); - - try { - log.log(Level.DEBUG, "starting server on port {0,number,#}", port); - httpServer.start(); - log.log(Level.INFO, "server started on port {0,number,#}", port); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - - return new GrizzlyJexServer(httpServer, jex.lifecycle()); - } -} diff --git a/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/HttpServerBuilder.java b/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/HttpServerBuilder.java deleted file mode 100644 index 3e224a39..00000000 --- a/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/HttpServerBuilder.java +++ /dev/null @@ -1,98 +0,0 @@ -package io.avaje.jex.grizzly; - -import io.avaje.applog.AppLog; -import org.glassfish.grizzly.http.server.*; -import org.glassfish.grizzly.ssl.SSLEngineConfigurator; -import org.glassfish.grizzly.utils.Charsets; - -import java.lang.System.Logger.Level; - -public class HttpServerBuilder { - - private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); - - private int port = -1; - private String host = "0.0.0.0"; - private boolean secure; - private SSLEngineConfigurator sslEngineConfigurator; - - private final HttpServer server = new HttpServer(); - - public HttpServerBuilder setPort(int port) { - this.port = port; - return this; - } - - public HttpServerBuilder host(String host) { - this.host = host; - return this; - } - - public HttpServerBuilder sslEngineConfigurator(SSLEngineConfigurator sslEngineConfigurator) { - this.sslEngineConfigurator = sslEngineConfigurator; - return this; - } - - public HttpServerBuilder secure(boolean secure) { - this.secure = secure; - return this; - } - - /** - * Add a handler using root context. - */ - public HttpServerBuilder handler(HttpHandler handler) { - return handler(handler, ""); - } - - /** - * Add a handler with the given context. - */ - public HttpServerBuilder handler(HttpHandler handler, String context) { - handler(handler, HttpHandlerRegistration.fromString("/" + context + "/*")); - return this; - } - - /** - * Add a handler given the paths. - */ - public HttpServerBuilder handler(HttpHandler handler, HttpHandlerRegistration... paths) { - server.getServerConfiguration().addHttpHandler(handler, paths); - return this; - } - - /** - * Build and return the grizzly http server. - */ - public HttpServer build() { - - int serverPort = serverPort(); - NetworkListener listener = new NetworkListener("grizzly", host, serverPort); - - // TODO: Configure to use loom thread factory - // listener.getTransport().getWorkerThreadPoolConfig().setThreadFactory() - listener.setSecure(secure); - if (sslEngineConfigurator != null) { - listener.setSSLEngineConfig(sslEngineConfigurator); - } - addHttp2Support(listener); - server.addListener(listener); - ServerConfiguration config = server.getServerConfiguration(); - config.setPassTraceRequest(true); - config.setDefaultQueryEncoding(Charsets.UTF8_CHARSET); - return server; - } - - protected void addHttp2Support(NetworkListener listener) { - try { - Class.forName("org.glassfish.grizzly.http2.Http2AddOn"); -// listener.registerAddOn(new org.glassfish.grizzly.http2.Http2AddOn()); - } catch (Throwable e) { - log.log(Level.TRACE, "Http2AddOn was not registered"); - } - } - - protected int serverPort() { - return port != -1 ? port : (secure ? 8443 : 7001); - } -} diff --git a/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/RouteHandler.java b/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/RouteHandler.java deleted file mode 100644 index bbd02875..00000000 --- a/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/RouteHandler.java +++ /dev/null @@ -1,82 +0,0 @@ -package io.avaje.jex.grizzly; - -import io.avaje.jex.Context; -import io.avaje.jex.Routing; -import io.avaje.jex.http.NotFoundResponse; -import io.avaje.jex.spi.SpiContext; -import io.avaje.jex.spi.SpiRoutes; -import org.glassfish.grizzly.http.server.HttpHandler; -import org.glassfish.grizzly.http.server.Request; -import org.glassfish.grizzly.http.server.Response; - -import java.util.Map; - -class RouteHandler extends HttpHandler { - - private final SpiRoutes routes; - private final ServiceManager mgr; - - RouteHandler(SpiRoutes routes, ServiceManager mgr) { - this.mgr = mgr; - this.routes = routes; - } - - @Override - public void service(Request request, Response response) { - - final String uri = request.getRequestURI(); - final Routing.Type routeType = mgr.lookupRoutingType(request.getMethod().getMethodString()); - final SpiRoutes.Entry route = routes.match(routeType, uri); - - if (route == null) { - var ctx = new GrizzlyContext(mgr, request, response, uri); - try { - processNoRoute(ctx, uri, routeType); - routes.after(uri, ctx); - } catch (Exception e) { - handleException(ctx, e); - } - } else { - final Map params = route.pathParams(uri); - var ctx = new GrizzlyContext(mgr, request, response, route.matchPath(), params); - try { - processRoute(ctx, uri, route); - routes.after(uri, ctx); - } catch (Exception e) { - handleException(ctx, e); - } - } - } - - private void handleException(SpiContext ctx, Exception e) { - mgr.handleException(ctx, e); - } - - private void processRoute(GrizzlyContext ctx, String uri, SpiRoutes.Entry route) { - routes.before(uri, ctx); - ctx.setMode(null); - route.handle(ctx); - } - - private void processNoRoute(GrizzlyContext ctx, String uri, Routing.Type routeType) { - routes.before(uri, ctx); - if (routeType == Routing.Type.HEAD && hasGetHandler(uri)) { - processHead(ctx); - return; - } -// if (routeType == Routing.Type.GET || routeType == Routing.Type.HEAD) { -// // check if handled by static resource -// // check if handled by singlePageHandler -// } - throw new NotFoundResponse("uri: " + uri); - } - - private void processHead(Context ctx) { - ctx.status(200); - } - - private boolean hasGetHandler(String uri) { - return routes.match(Routing.Type.GET, uri) != null; - } - -} diff --git a/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/ServiceManager.java b/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/ServiceManager.java deleted file mode 100644 index b7be3145..00000000 --- a/avaje-jex-grizzly/src/main/java/io/avaje/jex/grizzly/ServiceManager.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.avaje.jex.grizzly; - -import io.avaje.jex.spi.ProxyServiceManager; -import io.avaje.jex.spi.SpiServiceManager; - -import java.io.OutputStream; - -class ServiceManager extends ProxyServiceManager { - - private final String scheme; - private final String contextPath; - - ServiceManager(SpiServiceManager delegate, String scheme, String contextPath) { - super(delegate); - this.scheme = scheme; - this.contextPath = contextPath; - } - - String contextPath() { - return contextPath; - } -} diff --git a/avaje-jex-grizzly/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiStartServer b/avaje-jex-grizzly/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiStartServer deleted file mode 100644 index 855a5fe8..00000000 --- a/avaje-jex-grizzly/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiStartServer +++ /dev/null @@ -1 +0,0 @@ -io.avaje.jex.grizzly.GrizzlyServerStart diff --git a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/AutoCloseIterator.java b/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/AutoCloseIterator.java deleted file mode 100644 index 819941a7..00000000 --- a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/AutoCloseIterator.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.avaje.jex.grizzly; - -import java.util.Iterator; - -public class AutoCloseIterator implements Iterator, AutoCloseable { - - private final Iterator it; - private boolean closed; - - public AutoCloseIterator(Iterator it) { - this.it = it; - } - - @Override - public boolean hasNext() { - return it.hasNext(); - } - - @Override - public E next() { - return it.next(); - } - - @Override - public void close() { - closed = true; - } - - public boolean isClosed() { - return closed; - } -} diff --git a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/ContextFormParamTest.java b/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/ContextFormParamTest.java deleted file mode 100644 index 11ed44c4..00000000 --- a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/ContextFormParamTest.java +++ /dev/null @@ -1,135 +0,0 @@ -package io.avaje.jex.grizzly; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class ContextFormParamTest { - - static TestPair pair = init(); - - static TestPair init() { - var app = Jex.create() - .routing(routing -> routing - .post("/", ctx -> ctx.text("map:" +ctx.formParamMap())) - .post("/formParams/{key}", ctx -> ctx.text("formParams:" + ctx.formParams(ctx.pathParam("key")))) - .post("/formParam/{key}", ctx -> ctx.text("formParam:" + ctx.formParam(ctx.pathParam("key")))) - .post("/formParamWithDefault/{key}", ctx -> ctx.text("formParam:" + ctx.formParam(ctx.pathParam("key"), "foo"))) - ); - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void formParamMap() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("map:{one=[ao, bo], two=[z]}"); - } - - - @Test - void formParams_one() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .path("formParams").path("one") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("formParams:[ao, bo]"); - } - - @Test - void formParams_two() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .path("formParams").path("two") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("formParams:[z]"); - } - - - @Test - void formParam_null() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .path("formParam").path("doesNotExist") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("formParam:null"); - } - - @Test - void formParam_first() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .path("formParam").path("one") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("formParam:ao"); - } - - @Test - void formParam_default() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .path("formParamWithDefault").path("doesNotExist") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("formParam:foo"); - } - - @Test - void formParam_default_first() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .path("formParamWithDefault").path("one") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("formParam:ao"); - } - - @Test - void formParam_default_only() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .path("formParamWithDefault").path("two") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("formParam:z"); - } -} diff --git a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/ContextLengthTest.java b/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/ContextLengthTest.java deleted file mode 100644 index 19097ddf..00000000 --- a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/ContextLengthTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package io.avaje.jex.grizzly; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class ContextLengthTest { - - static TestPair pair = init(); - - static TestPair init() { - var app = Jex.create() - .routing(routing -> routing - .post("/", ctx -> ctx.text("contentLength:" + ctx.contentLength() + " type:" + ctx.contentType())) - .get("/url", ctx -> ctx.text("url:" + ctx.url())) - .get("/fullUrl", ctx -> ctx.text("fullUrl:" + ctx.fullUrl())) - .get("/contextPath", ctx -> ctx.text("contextPath:" + ctx.contextPath())) - .get("/userAgent", ctx -> ctx.text("userAgent:" + ctx.userAgent())) - ); - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void when_noReqContentType() { - HttpResponse res = pair.request().body("MyBodyContent") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("contentLength:13 type:null"); - } - - @Test - void requestContentLengthAndType_notReqContentType() { - HttpResponse res = pair.request() - .formParam("a", "my-a-val") - .formParam("b", "my-b-val") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("contentLength:21 type:application/x-www-form-urlencoded"); - } - - @Test - void url() { - HttpResponse res = pair.request() - .path("url") - .queryParam("a", "av") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("url:http://localhost:" + pair.port() + "/url"); - } - - @Test - void fullUrl_no_queryString() { - HttpResponse res = pair.request() - .path("fullUrl") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("fullUrl:http://localhost:" + pair.port() + "/fullUrl"); - } - - @Test - void fullUrl_queryString() { - HttpResponse res = pair.request() - .path("fullUrl") - .queryParam("a", "av") - .queryParam("b", "bv") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("fullUrl:http://localhost:" + pair.port() + "/fullUrl?a=av&b=bv"); - } - - @Test - void contextPath() { - HttpResponse res = pair.request() - .path("contextPath") - .queryParam("a", "av") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("contextPath:"); - } - - @Test - void userAgent() { - HttpResponse res = pair.request() - .path("userAgent") - .queryParam("a", "av") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).contains("userAgent:Java-http-client"); - } -} diff --git a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/ContextTest.java b/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/ContextTest.java deleted file mode 100644 index 9e5f6403..00000000 --- a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/ContextTest.java +++ /dev/null @@ -1,194 +0,0 @@ -package io.avaje.jex.grizzly; - -import io.avaje.jex.Context; -import io.avaje.jex.Jex; -//import io.avaje.jex.core.JsonbJsonService; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; -import java.util.Optional; - -import static java.util.Objects.requireNonNull; -import static org.assertj.core.api.Assertions.assertThat; - -class ContextTest { - - static TestPair pair = init(); - - static TestPair init() { - - var me = new ContextTest(); - - final Jex app = Jex.create() - //.configure(jex -> jex.jsonService(new JsonbJsonService())) - .routing(routing -> routing - .get("/", ctx -> ctx.text("ze-get")) - .post("/", ctx -> ctx.text("ze-post")) - .get("/header", me::doHeader) - .get("/headerMap", ctx -> ctx.text("req-header-map[" + ctx.headerMap() + "]")) - .get("/host", me::doHost) - .get("/ip", me::doIp) - .post("/multipart", ctx -> ctx.text("isMultipart:" + ctx.isMultipart() + " isMultipartFormData:" + ctx.isMultipartFormData())) - .get("/method", ctx -> ctx.text("method:" + ctx.method() + " path:" + ctx.path() + " protocol:" + ctx.protocol() + " port:" + ctx.port())) - .post("/echo", ctx -> ctx.text("req-body[" + ctx.body() + "]")) - .get("/{a}/{b}", ctx -> ctx.text("ze-get-" + ctx.pathParamMap())) - .post("/{a}/{b}", ctx -> ctx.text("ze-post-" + ctx.pathParamMap())) - .get("/status", me::doStatus)); - - return TestPair.create(app); - } - - private void doStatus(Context ctx) { - ctx.status(201); - ctx.text("status:" + ctx.status()); - } - - private void doIp(Context ctx) { - final String ip = ctx.ip(); - requireNonNull(ip); - ctx.text("ip:" + ip); - } - - private void doHost(Context ctx) { - final String host = ctx.host(); - requireNonNull(host); - ctx.text("host:" + host); - } - - private void doHeader(Context ctx) { - ctx.header("From-My-Server", "Set-By-Server"); - ctx.text("req-header[" + ctx.header("From-My-Client") + "]"); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void get() { - HttpResponse res = pair.request().GET().asString(); - assertThat(res.body()).isEqualTo("ze-get"); - } - - @Test - void post() { - HttpResponse res = pair.request().body("simple").POST().asString(); - assertThat(res.body()).isEqualTo("ze-post"); - } - - @Test - void ctx_header_getSet() { - HttpResponse res = pair.request().path("header") - .header("From-My-Client", "client-value") - .GET().asString(); - - final Optional serverSetHeader = res.headers().firstValue("From-My-Server"); - assertThat(serverSetHeader.get()).isEqualTo("Set-By-Server"); - assertThat(res.body()).isEqualTo("req-header[client-value]"); - } - - @Test - void ctx_headerMap() { - HttpResponse res = pair.request().path("headerMap") - .header("X-Foo", "a") - .header("X-Bar", "b") - .GET().asString(); - - assertThat(res.body()).contains("x-foo=a"); // not maintaining case? - assertThat(res.body()).contains("x-bar=b"); - } - - @Test - void ctx_status() { - HttpResponse res = pair.request().path("status") - .GET().asString(); - - assertThat(res.body()).isEqualTo("status:201"); - } - - @Test - void ctx_host() { - HttpResponse res = pair.request().path("host") - .GET().asString(); - - assertThat(res.body()).contains("host:localhost"); - } - - @Test - void ctx_ip() { - HttpResponse res = pair.request().path("ip") - .GET().asString(); - - assertThat(res.body()).isEqualTo("ip:127.0.0.1"); - } - - @Test - void ctx_isMultiPart_when_not() { - HttpResponse res = pair.request().path("multipart") - .formParam("a", "aval") - .POST().asString(); - - assertThat(res.body()).isEqualTo("isMultipart:false isMultipartFormData:false"); - } - - - @Test - void ctx_isMultiPart_when_nothing() { - HttpResponse res = pair.request().path("multipart") - .body("junk") - .POST().asString(); - - assertThat(res.body()).isEqualTo("isMultipart:false isMultipartFormData:false"); - } - -// @Test -// void ctx_isMultiPart_when_isMultipart() { -// HttpResponse res = pair.request().path("multipart") -// .header("Content-Type", "multipart/foo") -// .body("junk") -// .POST().asString(); -// -// assertThat(res.body()).isEqualTo("isMultipart:true isMultipartFormData:false"); -// } -// -// @Test -// void ctx_isMultiPart_when_isMultipartFormData() { -// HttpResponse res = pair.request().path("multipart") -// .header("Content-Type", "multipart/form-data") -// .body("junk") -// .POST().asString(); -// -// assertThat(res.body()).isEqualTo("isMultipart:true isMultipartFormData:true"); -// } - - @Test - void ctx_methodPathPortProtocol() { - HttpResponse res = pair.request().path("method") - .GET().asString(); - - assertThat(res.body()).isEqualTo("method:GET path:/method protocol:HTTP/1.1 port:" + pair.port()); - } - - @Test - void post_body() { - HttpResponse res = pair.request().path("echo").body("simple").POST().asString(); - assertThat(res.body()).isEqualTo("req-body[simple]"); - } - - @Test - void get_path_path() { - var res = pair.request() - .path("A").path("B").GET().asString(); - - assertThat(res.body()).isEqualTo("ze-get-{a=A, b=B}"); - - res = pair.request() - .path("one").path("bar").body("simple").POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("ze-post-{a=one, b=bar}"); - } - -} diff --git a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/HelloDto.java b/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/HelloDto.java deleted file mode 100644 index d04072c1..00000000 --- a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/HelloDto.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.avaje.jex.grizzly; - -//import io.avaje.jsonb.Json; - -//@Json -public class HelloDto { - - public long id; - public String name; - - @Override - public String toString() { - return "id:" + id + " name:" + name; - } - - public static HelloDto rob() { - return create(42, "rob"); - } - - public static HelloDto fi() { - return create(45, "fi"); - } - - public static HelloDto create(long id, String name) { - HelloDto me = new HelloDto(); - me.id = id; - me.name = name; - return me; - } -} diff --git a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/HelloWorldTest.java b/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/HelloWorldTest.java deleted file mode 100644 index 6136b4ae..00000000 --- a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/HelloWorldTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.avaje.jex.grizzly; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; -import org.slf4j.bridge.SLF4JBridgeHandler; - -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class HelloWorldTest { - - static { - SLF4JBridgeHandler.removeHandlersForRootLogger(); - SLF4JBridgeHandler.install(); - } - - static TestPair pair = init(); - - static TestPair init() { - var app = Jex.create() - .routing(routing -> routing - .get("/", ctx -> ctx.text("hello")) - ); - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void get() { - HttpResponse res = pair.request().GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("hello"); - } - - @Test - void getAgain() { - HttpResponse res = pair.request().GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("hello"); - } - -} diff --git a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/JsonTest.java b/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/JsonTest.java deleted file mode 100644 index 1c5e0438..00000000 --- a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/JsonTest.java +++ /dev/null @@ -1,121 +0,0 @@ -package io.avaje.jex.grizzly; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpHeaders; -import java.net.http.HttpResponse; -import java.util.List; -import java.util.stream.Stream; - -import static java.util.Arrays.asList; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; - -class JsonTest { - - static List HELLO_BEANS = asList(HelloDto.rob(), HelloDto.fi()); - - static AutoCloseIterator ITERATOR = createBeanIterator(); - - private static AutoCloseIterator createBeanIterator() { - return new AutoCloseIterator<>(HELLO_BEANS.iterator()); - } - - static TestPair pair = init(); - - static TestPair init() { - Jex app = Jex.create() - .routing(routing -> routing - .get("/", ctx -> ctx.json(HelloDto.rob())) //.header("x2-foo","asd") - .get("/iterate", ctx -> ctx.jsonStream(ITERATOR)) - .get("/stream", ctx -> ctx.jsonStream(HELLO_BEANS.stream())) - .post("/", ctx -> ctx.text("bean[" + ctx.bodyAsClass(HelloDto.class) + "]"))); - - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void get() { - - var bean = pair.request() - .GET() - .bean(HelloDto.class); - - assertThat(bean.id).isEqualTo(42); - assertThat(bean.name).isEqualTo("rob"); - - final HttpResponse hres = pair.request() - .GET().asString(); - - final HttpHeaders headers = hres.headers(); - assertThat(headers.firstValue("content-type").get()).isEqualTo("application/json"); - } - - @Test - void stream_viaIterator() { - final Stream beanStream = pair.request() - .path("iterate") - .GET() - .stream(HelloDto.class); - - // expect client gets the expected stream of beans - assertCollectedStream(beanStream); - // assert AutoCloseable iterator on the server-side was closed - assertThat(ITERATOR.isClosed()).isTrue(); - } - - @Test - void stream() { - final Stream beanStream = pair.request() - .path("stream") - .GET() - .stream(HelloDto.class); - - assertCollectedStream(beanStream); - } - - private void assertCollectedStream(Stream beanStream) { - final List collectedBeans = beanStream.collect(toList()); - assertThat(collectedBeans).hasSize(2); - - final HelloDto first = collectedBeans.get(0); - assertThat(first.id).isEqualTo(42); - assertThat(first.name).isEqualTo("rob"); - - final HelloDto second = collectedBeans.get(1); - assertThat(second.id).isEqualTo(45); - assertThat(second.name).isEqualTo("fi"); - } - - @Test - void post() { - HelloDto dto = new HelloDto(); - dto.id = 42; - dto.name = "rob was here"; - - var res = pair.request() - .body(dto) - .POST().asString(); - - assertThat(res.body()).isEqualTo("bean[id:42 name:rob was here]"); - assertThat(res.statusCode()).isEqualTo(200); - - dto.id = 99; - dto.name = "fi"; - - res = pair.request() - .body(dto) - .POST().asString(); - - assertThat(res.body()).isEqualTo("bean[id:99 name:fi]"); - assertThat(res.statusCode()).isEqualTo(200); - } - -} diff --git a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/QueryParamTest.java b/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/QueryParamTest.java deleted file mode 100644 index 3ebd8b96..00000000 --- a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/QueryParamTest.java +++ /dev/null @@ -1,148 +0,0 @@ -package io.avaje.jex.grizzly; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class QueryParamTest { - - static TestPair pair = init(); - - static TestPair init() { - var app = Jex.create() - .routing(routing -> routing - .get("/", ctx -> ctx.text("hello")) - .get("/one/{id}", ctx -> ctx.text("one-" + ctx.pathParam("id") + "|match:" + ctx.matchedPath())) - .get("/one/{id}/{b}", ctx -> ctx.text("path:" + ctx.pathParamMap() + "|query:" + ctx.queryParam("z") + "|match:" + ctx.matchedPath())) - .get("/queryParamMap", ctx -> ctx.text("qpm: "+ctx.queryParamMap())) - .get("/queryParams", ctx -> ctx.text("qps: "+ctx.queryParams("a"))) - .get("/queryString", ctx -> ctx.text("qs: "+ctx.queryString())) - .get("/scheme", ctx -> ctx.text("scheme: "+ctx.scheme())) - ); - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void get() { - HttpResponse res = pair.request().GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("hello"); - } - - @Test - void getOne_path() { - var res = pair.request() - .path("one").path("foo").GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("one-foo|match:/one/{id}"); - - res = pair.request() - .path("one").path("bar").GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("one-bar|match:/one/{id}"); - } - - @Test - void getOne_path_path() { - var res = pair.request() - .path("one").path("foo").path("bar") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("path:{id=foo, b=bar}|query:null|match:/one/{id}/{b}"); - - res = pair.request() - .path("one").path("fo").path("ba").queryParam("z", "42") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("path:{id=fo, b=ba}|query:42|match:/one/{id}/{b}"); - } - - @Test - void queryParamMap_when_empty() { - HttpResponse res = pair.request().path("queryParamMap").GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("qpm: {}"); - } - - @Test - void queryParamMap_keyWithMultiValues_expect_firstValueInMap() { - HttpResponse res = pair.request().path("queryParamMap") - .queryParam("a","AVal0") - .queryParam("a","AVal1") - .queryParam("b", "BVal") - .GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("qpm: {a=AVal0, b=BVal}"); - } - - @Test - void queryParamMap_basic() { - HttpResponse res = pair.request().path("queryParamMap") - .queryParam("a","AVal") - .queryParam("b", "BVal") - .GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("qpm: {a=AVal, b=BVal}"); - } - - @Test - void queryParams_basic() { - HttpResponse res = pair.request().path("queryParams") - .queryParam("a","one") - .queryParam("a", "two") - .GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("qps: [one, two]"); - } - - @Test - void queryParams_when_null_expect_emptyList() { - HttpResponse res = pair.request().path("queryParams") - .queryParam("b","one") - .GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("qps: []"); - } - - @Test - void queryString_when_null() { - HttpResponse res = pair.request().path("queryString") - .GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("qs: null"); - } - - @Test - void queryString_when_set() { - HttpResponse res = pair.request().path("queryString") - .queryParam("foo","f1") - .queryParam("bar","b1") - .queryParam("bar","b2") - .GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("qs: foo=f1&bar=b1&bar=b2"); - } - - @Test - void scheme() { - HttpResponse res = pair.request().path("scheme") - .queryParam("foo","f1") - .GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("scheme: http"); - } - -} diff --git a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/TestPair.java b/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/TestPair.java deleted file mode 100644 index 41bc5a01..00000000 --- a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/TestPair.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.avaje.jex.grizzly; - -import io.avaje.http.client.HttpClient; -import io.avaje.http.client.HttpClientRequest; -import io.avaje.http.client.JacksonBodyAdapter; -import io.avaje.jex.Jex; - -import java.util.Random; - -import static java.net.http.HttpClient.Version.HTTP_1_1; - -/** - * Server and Client pair for a test. - */ -public class TestPair { - - private final int port; - - private final Jex.Server server; - - private final HttpClient client; - - public TestPair(int port, Jex.Server server, HttpClient client) { - this.port = port; - this.server = server; - this.client = client; - } - - public void shutdown() { - server.shutdown(); - } - - public HttpClientRequest request() { - return client.request(); - } - - public int port() { - return port; - } - - public String url() { - return client.url().build(); - } - - public static TestPair create(Jex app) { - int port = 10000 + new Random().nextInt(1000); - return create(app, port); - } - - /** - * Create a Server and Client pair for a given set of tests. - */ - public static TestPair create(Jex app, int port) { - var jexServer = app.port(port).start(); - - var url = "http://localhost:" + port; - var client = HttpClient.builder() - .baseUrl(url) - .bodyAdapter(new JacksonBodyAdapter()) - .version(HTTP_1_1) - .build(); - - return new TestPair(port, jexServer, client); - } -} diff --git a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/VanillaMain.java b/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/VanillaMain.java deleted file mode 100644 index 004d9091..00000000 --- a/avaje-jex-grizzly/src/test/java/io/avaje/jex/grizzly/VanillaMain.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.avaje.jex.grizzly; - -import org.glassfish.grizzly.http.server.*; - -import java.io.File; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -class VanillaMain { - - public static void main(String[] args) throws IOException, InterruptedException { - - File dir = new File("."); - System.out.println("workingDirectory" + dir.getAbsolutePath()); - - CLStaticHttpHandler clStaticHttpHandler = new CLStaticHttpHandler(VanillaMain.class.getClassLoader(), "/myres/"); - StaticHttpHandler staticHttpHandler = new StaticHttpHandler(); - - final HttpServer httpServer = new HttpServerBuilder() - .handler(clStaticHttpHandler, "cl") - .handler(staticHttpHandler, "static") - .handler(new MyHandler()) - .build(); - - httpServer.start(); - Thread.currentThread().join(); - } - - static class MyHandler extends HttpHandler { - - @Override - public void service(Request request, Response response) throws Exception { - final SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); - final String date = format.format(new Date(System.currentTimeMillis())); - response.setContentType("text/plain"); - response.setContentLength(date.length()); - response.getWriter().write(date); - } - } -} diff --git a/avaje-jex-grizzly/src/test/resources/logback-test.xml b/avaje-jex-grizzly/src/test/resources/logback-test.xml deleted file mode 100644 index ddb21350..00000000 --- a/avaje-jex-grizzly/src/test/resources/logback-test.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - TRACE - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - - diff --git a/avaje-jex-grizzly/src/test/resources/myres/hello.txt b/avaje-jex-grizzly/src/test/resources/myres/hello.txt deleted file mode 100644 index d6613f5f..00000000 --- a/avaje-jex-grizzly/src/test/resources/myres/hello.txt +++ /dev/null @@ -1 +0,0 @@ -Hello there From a811bdc015e71312e9f5aef696bf8601f72a2196 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:40:43 -0500 Subject: [PATCH 010/250] Delete avaje-jex-jetty directory --- avaje-jex-jetty/pom.xml | 49 -- .../java/io/avaje/jex/jetty/ContextUtil.java | 41 -- .../java/io/avaje/jex/jetty/JettyBuilder.java | 59 -- .../io/avaje/jex/jetty/JettyJexServer.java | 169 ------ .../io/avaje/jex/jetty/JettyServerConfig.java | 109 ---- .../io/avaje/jex/jetty/JettyStartServer.java | 17 - .../java/io/avaje/jex/jetty/JexHandler.java | 110 ---- .../io/avaje/jex/jetty/JexHttpContext.java | 515 ------------------ .../io/avaje/jex/jetty/MultipartUtil.java | 103 ---- .../io/avaje/jex/jetty/PartUploadedFile.java | 63 --- .../io/avaje/jex/jetty/ServiceManager.java | 34 -- .../io/avaje/jex/jetty/StaticHandler.java | 167 ------ .../avaje/jex/jetty/StaticHandlerFactory.java | 18 - .../src/main/java/module-info.java | 20 - .../services/io.avaje.jex.spi.SpiStartServer | 1 - .../test/java/io/avaje/jex/base/AppRoles.java | 11 - .../io/avaje/jex/base/AutoCloseIterator.java | 32 -- .../avaje/jex/base/CharacterEncodingTest.java | 50 -- .../avaje/jex/base/ContextAttributeTest.java | 52 -- .../io/avaje/jex/base/ContextCookieTest.java | 82 --- .../avaje/jex/base/ContextFormParamTest.java | 135 ----- .../io/avaje/jex/base/ContextLengthTest.java | 106 ---- .../java/io/avaje/jex/base/ContextTest.java | 194 ------- .../avaje/jex/base/ExceptionManagerTest.java | 80 --- .../java/io/avaje/jex/base/FilterTest.java | 74 --- .../test/java/io/avaje/jex/base/HelloDto.java | 27 - .../test/java/io/avaje/jex/base/JsonTest.java | 121 ---- .../avaje/jex/base/MultipartFormPostTest.java | 132 ----- .../io/avaje/jex/base/NestedRoutesTest.java | 73 --- .../java/io/avaje/jex/base/RedirectTest.java | 46 -- .../test/java/io/avaje/jex/base/Roles.java | 22 - .../java/io/avaje/jex/base/RolesTest.java | 102 ---- .../io/avaje/jex/base/RouteRegexTest.java | 53 -- .../io/avaje/jex/base/RouteSplatTest.java | 73 --- .../java/io/avaje/jex/base/SimpleTest.java | 204 ------- .../io/avaje/jex/base/StaticContentTest.java | 73 --- .../test/java/io/avaje/jex/base/TestPair.java | 62 --- .../test/java/io/avaje/jex/base/VerbTest.java | 139 ----- .../src/test/resources/logback-test.xml | 19 - .../src/test/resources/static-a/goodbye.html | 1 - .../src/test/resources/static-a/hello.txt | 1 - .../src/test/resources/static-a/hello2.txt | 1 - avaje-jex-jetty/test-static-files/basic.html | 1 - avaje-jex-jetty/test-static-files/index.html | 1 - .../test-static-files/plain-file.txt | 1 - 45 files changed, 3443 deletions(-) delete mode 100644 avaje-jex-jetty/pom.xml delete mode 100644 avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/ContextUtil.java delete mode 100644 avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyBuilder.java delete mode 100644 avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyJexServer.java delete mode 100644 avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyServerConfig.java delete mode 100644 avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyStartServer.java delete mode 100644 avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JexHandler.java delete mode 100644 avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JexHttpContext.java delete mode 100644 avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/MultipartUtil.java delete mode 100644 avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/PartUploadedFile.java delete mode 100644 avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/ServiceManager.java delete mode 100644 avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/StaticHandler.java delete mode 100644 avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/StaticHandlerFactory.java delete mode 100644 avaje-jex-jetty/src/main/java/module-info.java delete mode 100644 avaje-jex-jetty/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiStartServer delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/AppRoles.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/AutoCloseIterator.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/CharacterEncodingTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextAttributeTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextCookieTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextFormParamTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextLengthTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/ExceptionManagerTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/FilterTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/HelloDto.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/JsonTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/MultipartFormPostTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/NestedRoutesTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/RedirectTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/Roles.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/RolesTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/RouteRegexTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/RouteSplatTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/SimpleTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/StaticContentTest.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/TestPair.java delete mode 100644 avaje-jex-jetty/src/test/java/io/avaje/jex/base/VerbTest.java delete mode 100644 avaje-jex-jetty/src/test/resources/logback-test.xml delete mode 100644 avaje-jex-jetty/src/test/resources/static-a/goodbye.html delete mode 100644 avaje-jex-jetty/src/test/resources/static-a/hello.txt delete mode 100644 avaje-jex-jetty/src/test/resources/static-a/hello2.txt delete mode 100644 avaje-jex-jetty/test-static-files/basic.html delete mode 100644 avaje-jex-jetty/test-static-files/index.html delete mode 100644 avaje-jex-jetty/test-static-files/plain-file.txt diff --git a/avaje-jex-jetty/pom.xml b/avaje-jex-jetty/pom.xml deleted file mode 100644 index c815a24a..00000000 --- a/avaje-jex-jetty/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - avaje-jex-parent - io.avaje - 2.6-SNAPSHOT - - 4.0.0 - - avaje-jex-jetty - - - 11 - 11 - 11.0.15 - false - - - - - io.avaje - avaje-jex - 2.6-SNAPSHOT - - - - org.eclipse.jetty - jetty-server - ${jetty.version} - - - - com.mashape.unirest - unirest-java - 1.4.9 - test - - - - com.fasterxml.jackson.core - jackson-databind - 2.14.0 - test - - - - - - diff --git a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/ContextUtil.java b/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/ContextUtil.java deleted file mode 100644 index c986ffbe..00000000 --- a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/ContextUtil.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.avaje.jex.jetty; - -import jakarta.servlet.ServletInputStream; -import jakarta.servlet.http.HttpServletRequest; - -import java.io.*; - -class ContextUtil { - - private static final int DEFAULT_BUFFER_SIZE = 8 * 1024; - - private static final int BUFFER_MAX = 65536; - - static byte[] readBody(HttpServletRequest req) { - try { - final ServletInputStream inputStream = req.getInputStream(); - - int bufferSize = inputStream.available(); - if (bufferSize < DEFAULT_BUFFER_SIZE) { - bufferSize = DEFAULT_BUFFER_SIZE; - } else if (bufferSize > BUFFER_MAX) { - bufferSize = BUFFER_MAX; - } - - ByteArrayOutputStream os = new ByteArrayOutputStream(bufferSize); - copy(inputStream, os, bufferSize); - return os.toByteArray(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - static void copy(InputStream in, OutputStream out, int bufferSize) throws IOException { - byte[] buffer = new byte[bufferSize]; - int len; - while ((len = in.read(buffer, 0, bufferSize)) > 0) { - out.write(buffer, 0, len); - } - } - -} diff --git a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyBuilder.java b/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyBuilder.java deleted file mode 100644 index 99d59652..00000000 --- a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyBuilder.java +++ /dev/null @@ -1,59 +0,0 @@ -package io.avaje.jex.jetty; - -import io.avaje.jex.JexConfig; -import io.avaje.jex.Jex; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.eclipse.jetty.util.thread.ThreadPool; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.Constructor; - -/** - * Build the Jetty Server. - */ -class JettyBuilder { - - private static final Logger log = LoggerFactory.getLogger(JettyBuilder.class); - - private final JexConfig jexConfig; - private final JettyServerConfig jettyConfig; - - JettyBuilder(Jex jex, JettyServerConfig jettyConfig) { - this.jexConfig = jex.config(); - this.jettyConfig = jettyConfig; - } - - Server build() { - Server jetty = new Server(pool()); - ServerConnector connector = new ServerConnector(jetty); - connector.setPort(jexConfig.port()); - if (jexConfig.host() != null ) { - connector.setHost(jexConfig.host()); - } - jetty.setConnectors(new Connector[]{connector}); - return jetty; - } - - private ThreadPool pool() { - if (jexConfig.virtualThreads()) { - return virtualThreadBasePool(); - } else { - return jettyConfig.maxThreads() == 0 ? new QueuedThreadPool() : new QueuedThreadPool(jettyConfig.maxThreads()); - } - } - - private ThreadPool virtualThreadBasePool() { - try { - final Class aClass = Class.forName("io.avaje.jex.jetty.threadpool.VirtualThreadPool"); - final Constructor constructor = aClass.getConstructor(); - return (ThreadPool) constructor.newInstance(); - } catch (Exception e) { - throw new IllegalStateException("Failed to start Loom threadPool", e); - } - } - -} diff --git a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyJexServer.java b/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyJexServer.java deleted file mode 100644 index 7253c3a7..00000000 --- a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyJexServer.java +++ /dev/null @@ -1,169 +0,0 @@ -package io.avaje.jex.jetty; - -import io.avaje.jex.*; -import io.avaje.jex.spi.SpiRoutes; -import io.avaje.jex.spi.SpiServiceManager; -import jakarta.servlet.MultipartConfigElement; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.session.SessionHandler; -import org.eclipse.jetty.util.Uptime; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; - -class JettyJexServer implements Jex.Server { - - private static final Logger log = LoggerFactory.getLogger(Jex.class); - - private final Jex jex; - private final SpiRoutes routes; - private final ServiceManager serviceManager; - private final JettyServerConfig config; - private final AppLifecycle lifecycle; - private final long startTime; - private final JexConfig jexConfig; - private Server server; - - JettyJexServer(Jex jex, SpiRoutes routes, SpiServiceManager serviceManager) { - this.startTime = System.currentTimeMillis(); - this.jex = jex; - this.jexConfig = jex.config(); - this.lifecycle = jex.lifecycle(); - this.routes = routes; - this.serviceManager = new ServiceManager(serviceManager, initMultiPart()); - this.config = initConfig(jex.serverConfig()); - } - - private JettyServerConfig initConfig(ServerConfig config) { - return config == null ? new JettyServerConfig() : (JettyServerConfig) config; - } - - MultipartUtil initMultiPart() { - return new MultipartUtil(initMultipartConfigElement(jexConfig.multipartConfig())); - } - - MultipartConfigElement initMultipartConfigElement(UploadConfig uploadConfig) { - if (uploadConfig == null) { - final int fileThreshold = jexConfig.multipartFileThreshold(); - return new MultipartConfigElement(System.getProperty("java.io.tmpdir"), -1, -1, fileThreshold); - } - return new MultipartConfigElement(uploadConfig.location(), uploadConfig.maxFileSize(), uploadConfig.maxRequestSize(), uploadConfig.fileSizeThreshold()); - } - - @Override - public void onShutdown(Runnable onShutdown) { - lifecycle.onShutdown(onShutdown, Integer.MAX_VALUE); - } - - @Override - public void restart() { - try { - server.start(); - logOnStart(server); - lifecycle.status(AppLifecycle.Status.STARTED); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void shutdown() { - try { - log.trace("starting shutdown"); - lifecycle.status(AppLifecycle.Status.STOPPING); - routes.waitForIdle(30); - server.stop(); - log.trace("server http listeners stopped"); - lifecycle.status(AppLifecycle.Status.STOPPED); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public int port() { - return server.getURI().getPort(); - } - - protected Jex.Server start() { - try { - createServer(); - server.start(); - logOnStart(server); - lifecycle.registerShutdownHook(this::shutdown); - lifecycle.status(AppLifecycle.Status.STARTED); - return this; - } catch (Exception e) { - throw new IllegalStateException("Error starting server", e); - } - } - - protected void createServer() { - server = initServer(); - server.setHandler(initJettyHandler()); - if (server.getStopAtShutdown()) { - // do not use Jetty ShutdownHook, use the AppLifecycle one instead - server.setStopAtShutdown(false); - } - config.server(server); - config.postConfigure(); - } - - protected Server initServer() { - Server server = config.server(); - if (server != null) { - return server; - } - return new JettyBuilder(jex, config).build(); - } - - protected Handler initJettyHandler() { - var baseHandler = new JexHandler(jex, routes, serviceManager, initStaticHandler()); - if (!config.sessions()) { - return baseHandler; - } - var sessionHandler = initSessionHandler(); - sessionHandler.setHandler(baseHandler); - return sessionHandler; - } - - protected SessionHandler initSessionHandler() { - SessionHandler sh = config.sessionHandler(); - return sh == null ? defaultSessionHandler() : sh; - } - - protected SessionHandler defaultSessionHandler() { - SessionHandler sh = new SessionHandler(); - sh.setHttpOnly(true); - return sh; - } - - protected StaticHandler initStaticHandler() { - final List fileSources = jex.staticFiles().getSources(); - if (fileSources == null || fileSources.isEmpty()) { - return null; - } - StaticHandlerFactory factory = new StaticHandlerFactory(); - return factory.build(server, jex, fileSources); - } - - private void logOnStart(org.eclipse.jetty.server.Server server) { - long startup = System.currentTimeMillis() - startTime; - for (Connector c : server.getConnectors()) { - String virtualThreads = jexConfig.virtualThreads() ? "with virtualThreads" : ""; - if (c instanceof ServerConnector) { - ServerConnector sc = (ServerConnector) c; - String host = (sc.getHost() == null) ? "0.0.0.0" : sc.getHost(); - log.info("Listening with {} {}:{} in {}ms @{}ms {}", sc.getProtocols(), host, sc.getLocalPort(), startup, Uptime.getUptime(), virtualThreads); - } else { - log.info("bind to {} in {}ms @{}ms {}", c, startup, Uptime.getUptime(), virtualThreads); - } - } - } - - -} diff --git a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyServerConfig.java b/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyServerConfig.java deleted file mode 100644 index f8c41a17..00000000 --- a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyServerConfig.java +++ /dev/null @@ -1,109 +0,0 @@ -package io.avaje.jex.jetty; - -import io.avaje.jex.ServerConfig; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.session.SessionHandler; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -public class JettyServerConfig implements ServerConfig { - - private boolean sessions = true; - private boolean security = true; - - /** - * Set maxThreads when using default QueuedThreadPool. Defaults to 200. - */ - private int maxThreads; - private SessionHandler sessionHandler; - private Handler contextHandler; - private Server server; - private final List> configureCallback = new ArrayList<>(); - - public boolean sessions() { - return sessions; - } - - public JettyServerConfig sessions(boolean sessions) { - this.sessions = sessions; - return this; - } - - public boolean security() { - return security; - } - - public JettyServerConfig security(boolean security) { - this.security = security; - return this; - } - - public int maxThreads() { - return maxThreads; - } - - public JettyServerConfig maxThreads(int maxThreads) { - this.maxThreads = maxThreads; - return this; - } - - public SessionHandler sessionHandler() { - return sessionHandler; - } - - /** - * Set the SessionHandler to use. When not set one is created automatically. - */ - public JettyServerConfig sessionHandler(SessionHandler sessionHandler) { - this.sessionHandler = sessionHandler; - return this; - } - - public Handler contextHandler() { - return contextHandler; - } - - /** - * Set the Jetty Handler to use. When not set one is created automatically. - */ - public JettyServerConfig contextHandler(Handler contextHandler) { - this.contextHandler = contextHandler; - return this; - } - - public Server server() { - return server; - } - - /** - * Set the Jetty Server to use. When not set one is created automatically. - */ - public JettyServerConfig server(Server server) { - this.server = server; - return this; - } - - /** - * Register a callback that is executed after the server and contextHandler have been - * created but before the server has started. - *

- * When we use this to register filters to the ServletContextHandler or perform other - * changes prior to the server starting. - */ - public JettyServerConfig register(Consumer callback) { - configureCallback.add(callback); - return this; - } - - /** - * Run configuration callbacks prior to starting the server. - */ - void postConfigure() { - for (Consumer callback : configureCallback) { - callback.accept(this); - } - } -} diff --git a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyStartServer.java b/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyStartServer.java deleted file mode 100644 index 4d572895..00000000 --- a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JettyStartServer.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.avaje.jex.jetty; - -import io.avaje.jex.Jex; -import io.avaje.jex.spi.SpiRoutes; -import io.avaje.jex.spi.SpiServiceManager; -import io.avaje.jex.spi.SpiStartServer; - -/** - * Configure and starts the underlying Jetty server. - */ -public class JettyStartServer implements SpiStartServer { - - @Override - public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceManager) { - return new JettyJexServer(jex, routes, serviceManager).start(); - } -} diff --git a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JexHandler.java b/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JexHandler.java deleted file mode 100644 index 33bd44f3..00000000 --- a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JexHandler.java +++ /dev/null @@ -1,110 +0,0 @@ -package io.avaje.jex.jetty; - -import io.avaje.jex.Context; -import io.avaje.jex.Jex; -import io.avaje.jex.Routing; -import io.avaje.jex.http.NotFoundResponse; -import io.avaje.jex.spi.SpiContext; -import io.avaje.jex.spi.SpiRoutes; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; - -import java.util.Map; - -class JexHandler extends AbstractHandler { - - //private static final String X_HTTP_METHOD_OVERRIDE = "X-HTTP-Method-Override"; - private final SpiRoutes routes; - private final ServiceManager manager; - private final StaticHandler staticHandler; - - JexHandler(Jex jex, SpiRoutes routes, ServiceManager manager, StaticHandler staticHandler) { - this.routes = routes; - this.manager = manager; - this.staticHandler = staticHandler; - } - - @Override - public void handle(String target, Request baseRequest, HttpServletRequest req, HttpServletResponse res) { - try { - final Routing.Type routeType = method(req); - final String uri = req.getRequestURI(); - SpiRoutes.Entry route = routes.match(routeType, uri); - if (route == null) { - var ctx = new JexHttpContext(manager, req, res, uri); - routes.inc(); - try { - processNoRoute(target, baseRequest, ctx, uri, routeType); - routes.after(uri, ctx); - } catch (Exception e) { - handleException(ctx, e); - } finally { - routes.dec(); - } - } else { - final Map params = route.pathParams(uri); - var ctx = new JexHttpContext(manager, req, res, route.matchPath(), params); - route.inc(); - try { - processRoute(ctx, uri, route); - routes.after(uri, ctx); - } catch (Exception e) { - handleException(ctx, e); - } finally { - route.dec(); - } - } - } finally { - baseRequest.setHandled(true); - } - } - - private void handleException(SpiContext ctx, Exception e) { - manager.handleException(ctx, e); - } - - private void processRoute(JexHttpContext ctx, String uri, SpiRoutes.Entry route) { - routes.before(uri, ctx); - ctx.setMode(null); - route.handle(ctx); - } - - private void processNoRoute(String target, Request baseRequest, JexHttpContext ctx, String uri, Routing.Type routeType) { - routes.before(uri, ctx); - if (routeType == Routing.Type.HEAD && hasGetHandler(uri)) { - processHead(ctx); - return; - } - if (routeType == Routing.Type.GET || routeType == Routing.Type.HEAD) { - // check if handled by static resource - if (staticHandler != null && staticHandler.handle(target, baseRequest, ctx.req(), ctx.res())) { - return; - } - // todo: check if handled by singlePageHandler - //if (config.inner.singlePageHandler.handle(ctx)) return@tryWithExceptionMapper - } -// if (routeType == Routing.Type.OPTIONS && isCorsEnabled(config)) { // CORS is enabled, so we return 200 for OPTIONS -// return@tryWithExceptionMapper -// } -// if (prefer405) { -// //&& availableHandlerTypes.isNotEmpty() -// //val availableHandlerTypes = MethodNotAllowedUtil.findAvailableHttpHandlerTypes(matcher, requestUri) -// //throw MethodNotAllowedResponse(details = MethodNotAllowedUtil.getAvailableHandlerTypes(ctx, availableHandlerTypes)) -// } - throw new NotFoundResponse("uri: " + uri); - } - - private void processHead(Context ctx) { - ctx.status(200); - } - - private boolean hasGetHandler(String uri) { - return routes.match(Routing.Type.GET, uri) != null; - } - - private Routing.Type method(HttpServletRequest req) { - return manager.lookupRoutingType(req.getMethod()); - } -} diff --git a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JexHttpContext.java b/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JexHttpContext.java deleted file mode 100644 index 3c1935af..00000000 --- a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/JexHttpContext.java +++ /dev/null @@ -1,515 +0,0 @@ -package io.avaje.jex.jetty; - -import io.avaje.jex.Context; -import io.avaje.jex.Routing; -import io.avaje.jex.UploadedFile; -import io.avaje.jex.http.RedirectResponse; -import io.avaje.jex.spi.HeaderKeys; -import io.avaje.jex.spi.SpiContext; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UncheckedIOException; -import java.nio.charset.Charset; -import java.time.Duration; -import java.util.*; -import java.util.stream.Stream; - -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; - -class JexHttpContext implements SpiContext { - - private final ServiceManager mgr; - protected final HttpServletRequest req; - private final HttpServletResponse res; - private final Map pathParams; - private final String matchedPath; - private String characterEncoding; - private Routing.Type mode; - private Map> formParamMap; - - JexHttpContext(ServiceManager mgr, HttpServletRequest req, HttpServletResponse res, String matchedPath) { - this.mgr = mgr; - this.req = req; - this.res = res; - this.matchedPath = matchedPath; - this.pathParams = emptyMap(); - } - - JexHttpContext(ServiceManager mgr, HttpServletRequest req, HttpServletResponse res, String matchedPath, Map pathParams) { - this.mgr = mgr; - this.req = req; - this.res = res; - this.matchedPath = matchedPath; - this.pathParams = pathParams; - } - - @Override - public void setMode(Routing.Type mode) { - this.mode = mode; - } - - private String characterEncoding() { - if (characterEncoding == null) { - characterEncoding = mgr.requestCharset(this); - } - return characterEncoding; - } - - public HttpServletRequest req() { - return req; - } - - public HttpServletResponse res() { - return res; - } - - @Override - public boolean isCommitted() { - return res.isCommitted(); - } - - @Override - public void reset() { - res.reset(); - } - - @Override - public Context attribute(String key, Object value) { - req.setAttribute(key, value); - return this; - } - - @SuppressWarnings("unchecked") - @Override - public T attribute(String key) { - return (T) req.getAttribute(key); - } - - @Override - public String cookie(String name) { - final jakarta.servlet.http.Cookie[] cookies = req.getCookies(); - if (cookies != null) { - for (jakarta.servlet.http.Cookie cookie : cookies) { - if (cookie.getName().equals(name)) { - return cookie.getValue(); - } - } - } - return null; - } - - @Override - public Map cookieMap() { - final jakarta.servlet.http.Cookie[] cookies = req.getCookies(); - if (cookies == null) { - return emptyMap(); - } - final Map map = new LinkedHashMap<>(); - for (jakarta.servlet.http.Cookie cookie : cookies) { - map.put(cookie.getName(), cookie.getValue()); - } - return map; - } - - @Override - public Context cookie(Cookie cookie) { - final jakarta.servlet.http.Cookie newCookie = new jakarta.servlet.http.Cookie(cookie.name(), cookie.value()); - newCookie.setPath(cookie.path()); - if (newCookie.getPath() == null) { - newCookie.setPath("/"); - } - final String domain = cookie.domain(); - if (domain != null) { - newCookie.setDomain(domain); - } - final Duration duration = cookie.maxAge(); - if (duration != null) { - newCookie.setMaxAge((int)duration.toSeconds()); - } - newCookie.setHttpOnly(cookie.httpOnly()); - newCookie.setSecure(cookie.secure()); - res.addCookie(newCookie); - return this; - } - - @Override - public Context cookie(String name, String value, int maxAge) { - final jakarta.servlet.http.Cookie cookie = new jakarta.servlet.http.Cookie(name, value); - cookie.setPath("/"); - cookie.setMaxAge(maxAge); - res.addCookie(cookie); - return this; - } - - @Override - public Context cookie(String name, String value) { - return cookie(name, value, -1); - } - - @Override - public Context removeCookie(String name) { - return removeCookie(name, null); - } - - @Override - public Context removeCookie(String name, String path) { - if (path == null) { - path = "/"; - } - final jakarta.servlet.http.Cookie cookie = new jakarta.servlet.http.Cookie(name, ""); - cookie.setPath(path); - cookie.setMaxAge(0); - res.addCookie(cookie); - return this; - } - - @Override - public void redirect(String location) { - redirect(location, HttpServletResponse.SC_MOVED_TEMPORARILY); - } - - @Override - public void redirect(String location, int statusCode) { - res.setHeader(HeaderKeys.LOCATION, location); - status(statusCode); - if (mode == Routing.Type.BEFORE) { - throw new RedirectResponse(statusCode); - } - } - - @Override - public void performRedirect() { - // do nothing - } - - @Override - public String matchedPath() { - return matchedPath; - } - - @Override - public InputStream inputStream() { - try { - return req.getInputStream(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public T bodyAsClass(Class clazz) { - return mgr.jsonRead(clazz, this); - } - - @Override - public byte[] bodyAsBytes() { - return ContextUtil.readBody(req); - } - - @Override - public String body() { - return new String(bodyAsBytes(), Charset.forName(characterEncoding())); - } - - @Override - public long contentLength() { - return req.getContentLengthLong(); - } - - @Override - public Map pathParamMap() { - return pathParams; - } - - @Override - public String pathParam(String name) { - return pathParams.get(name); - } - - @Override - public String queryParam(String name) { - final String[] vals = req.getParameterValues(name); - if (vals == null || vals.length == 0) { - return null; - } else { - return vals[0]; - } - } - - @Override - public List queryParams(String name) { - final String[] vals = req.getParameterValues(name); - if (vals == null) { - return emptyList(); - } else { - return Arrays.asList(vals); - } - } - - @Override - public Map queryParamMap() { - final Map map = new LinkedHashMap<>(); - final Enumeration names = req.getParameterNames(); - while (names.hasMoreElements()) { - final String key = names.nextElement(); - map.put(key, queryParam(key)); - } - return map; - } - - @Override - public String queryString() { - return req.getQueryString(); - } - - @Override - public Map> formParamMap() { - if (formParamMap == null) { - formParamMap = initFormParamMap(); - } - return formParamMap; - } - - private Map> initFormParamMap() { - if (isMultipartFormData()) { - return mgr.multiPartForm(req); - } else { - return mgr.formParamMap(this, characterEncoding()); - } - } - - @Override - public String scheme() { - return req.getScheme(); - } - - @Override - public Context sessionAttribute(String key, Object value) { - req.getSession().setAttribute(key, value); - return this; - } - - @SuppressWarnings("unchecked") - @Override - public T sessionAttribute(String key) { - HttpSession session = req.getSession(false); - return session == null ? null : (T) session.getAttribute(key); - } - - @Override - public Map sessionAttributeMap() { - final Map map = new LinkedHashMap<>(); - final HttpSession session = req.getSession(false); - if (session == null) { - return emptyMap(); - } - final Enumeration names = session.getAttributeNames(); - while (names.hasMoreElements()) { - final String name = names.nextElement(); - map.put(name, session.getAttribute(name)); - } - return map; - } - - @Override - public String url() { - return req.getRequestURL().toString(); - } - - @Override - public String fullUrl() { - final String url = url(); - final String qs = queryString(); - return qs == null ? url : url + "?" + qs; - } - - @Override - public String contextPath() { - String path = req.getContextPath(); - return path == null ? "" : path; - } - - @Override - public Context status(int statusCode) { - res.setStatus(statusCode); - return this; - } - - @Override - public int status() { - return res.getStatus(); - } - - @Override - public String contentType() { - return req.getContentType(); - } - - @Override - public Context contentType(String contentType) { - res.setContentType(contentType); - return this; - } - - public Map headerMap() { - Map map = new LinkedHashMap<>(); - final Enumeration names = req.getHeaderNames(); - while (names.hasMoreElements()) { - final String name = names.nextElement(); - map.put(name, req.getHeader(name)); - } - return map; - } - - @Override - public String responseHeader(String key) { - return req.getHeader(key); - } - - @Override - public String header(String key) { - return req.getHeader(key); - } - - @Override - public Context header(String key, String value) { - res.setHeader(key, value); - return this; - } - - @Override - public String host() { - return req.getHeader(HeaderKeys.HOST); - } - - @Override - public String ip() { - return req.getRemoteAddr(); - } - - @Override - public boolean isMultipart() { - final String type = header(HeaderKeys.CONTENT_TYPE); - return type != null && type.toLowerCase().contains("multipart/"); - } - - @Override - public boolean isMultipartFormData() { - final String type = header(HeaderKeys.CONTENT_TYPE); - return type != null && type.toLowerCase().contains("multipart/form-data"); - } - - @Override - public String method() { - return req.getMethod(); - } - - @Override - public String path() { - return req.getRequestURI(); - } - - @Override - public int port() { - return req.getServerPort(); - } - - @Override - public String protocol() { - return req.getProtocol(); - } - - @Override - public Context json(Object bean) { - contentType(APPLICATION_JSON); - mgr.jsonWrite(bean, this); - return this; - } - - @Override - public Context jsonStream(Stream stream) { - contentType(APPLICATION_X_JSON_STREAM); - mgr.jsonWriteStream(stream, this); - return this; - } - - @Override - public Context jsonStream(Iterator iterator) { - contentType(APPLICATION_X_JSON_STREAM); - mgr.jsonWriteStream(iterator, this); - return this; - } - - /** - * Write plain text content to the response. - */ - @Override - public Context text(String content) { - contentType(TEXT_PLAIN); - return write(content); - } - - /** - * Write html content to the response. - */ - @Override - public Context html(String content) { - contentType(TEXT_HTML); - return write(content); - } - - @Override - public Context write(String content) { - try { - res.getWriter().write(content); - return this; - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public Context render(String name, Map model) { - mgr.render(this, name, model); - return this; - } - - @Override - public OutputStream outputStream() { - try { - return res.getOutputStream(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public UploadedFile uploadedFile(String name) { - final List files = uploadedFiles(name); - return files.isEmpty() ? null : files.get(0); - } - - @Override - public List uploadedFiles(String name) { - if (!isMultipartFormData()) { - return emptyList(); - } else { - return mgr.uploadedFiles(req, name); - } - } - - @Override - public List uploadedFiles() { - if (!isMultipartFormData()) { - return emptyList(); - } else { - return mgr.uploadedFiles(req); - } - } -} diff --git a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/MultipartUtil.java b/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/MultipartUtil.java deleted file mode 100644 index dea863bb..00000000 --- a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/MultipartUtil.java +++ /dev/null @@ -1,103 +0,0 @@ -package io.avaje.jex.jetty; - -import io.avaje.jex.UploadedFile; -import jakarta.servlet.MultipartConfigElement; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.Part; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; - -class MultipartUtil { - - private final MultipartConfigElement config; - - MultipartUtil(MultipartConfigElement config) { - this.config = config; - } - - private void setConfig(HttpServletRequest req) { - req.setAttribute("org.eclipse.jetty.multipartConfig", config); - } - - List uploadedFiles(HttpServletRequest req) { - try { - setConfig(req); - return req.getParts().stream() - .filter(part -> isFile(part)) - .map(this::toUploaded) - .collect(toList()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (ServletException e) { - throw new RuntimeException(e); - } - } - - List uploadedFiles(HttpServletRequest req, String partName) { - try { - setConfig(req); - return req.getParts().stream() - .filter(part -> part.getName().equals(partName) && isFile(part)) - .map(this::toUploaded) - .collect(toList()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (ServletException e) { - throw new RuntimeException(e); - } - } - - UploadedFile toUploaded(Part part) { - return new PartUploadedFile(part); - } - - Map> fieldMap(HttpServletRequest req) { - setConfig(req); - try { - Map> map = new LinkedHashMap<>(); - for (Part part : req.getParts()) { - if (isField(part)) { - final String name = part.getName(); - final String value = readAsString(part); - map.computeIfAbsent(name, s -> new ArrayList<>()).add(value); - } - } - return map; - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (ServletException e) { - throw new RuntimeException(e); - } - } - - private String readAsString(Part part) { - try { - return new BufferedReader(new InputStreamReader(part.getInputStream(), StandardCharsets.UTF_8)) - .lines() - .collect(joining("\n")); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private static boolean isFile(Part filePart) { - return !isField(filePart); - } - - private static boolean isField(Part filePart) { - return filePart.getSubmittedFileName() == null; // this is what Apache FileUpload does ... - } - -} diff --git a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/PartUploadedFile.java b/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/PartUploadedFile.java deleted file mode 100644 index 712ed3b6..00000000 --- a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/PartUploadedFile.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.avaje.jex.jetty; - -import io.avaje.jex.UploadedFile; -import jakarta.servlet.http.Part; - -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; - -/** - * UploadedFile using servlet Part. - */ -class PartUploadedFile implements UploadedFile { - - private final Part part; - - PartUploadedFile(Part part) { - this.part = part; - } - - @Override - public String name() { - return part.getName(); - } - - @Override - public String fileName() { - return part.getSubmittedFileName(); - } - - @Override - public InputStream content() { - try { - return part.getInputStream(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public String contentType() { - return part.getContentType(); - } - - @Override - public long size() { - return part.getSize(); - } - - @Override - public void delete() { - try { - part.delete(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public String toString() { - return "name:" + name() + " fileName:" + fileName() + " size:" + size(); - } -} diff --git a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/ServiceManager.java b/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/ServiceManager.java deleted file mode 100644 index 5eb0ddbc..00000000 --- a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/ServiceManager.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.avaje.jex.jetty; - -import io.avaje.jex.UploadedFile; -import io.avaje.jex.spi.ProxyServiceManager; -import io.avaje.jex.spi.SpiServiceManager; -import jakarta.servlet.http.HttpServletRequest; - -import java.util.List; -import java.util.Map; - -/** - * Jetty specific service manager. - */ -class ServiceManager extends ProxyServiceManager { - - private final MultipartUtil multipartUtil; - - ServiceManager(SpiServiceManager delegate, MultipartUtil multipartUtil) { - super(delegate); - this.multipartUtil = multipartUtil; - } - - List uploadedFiles(HttpServletRequest req) { - return multipartUtil.uploadedFiles(req); - } - - List uploadedFiles(HttpServletRequest req, String name) { - return multipartUtil.uploadedFiles(req, name); - } - - Map> multiPartForm(HttpServletRequest req) { - return multipartUtil.fieldMap(req); - } -} diff --git a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/StaticHandler.java b/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/StaticHandler.java deleted file mode 100644 index 3694c115..00000000 --- a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/StaticHandler.java +++ /dev/null @@ -1,167 +0,0 @@ -package io.avaje.jex.jetty; - -import io.avaje.jex.StaticFileSource; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.ResourceHandler; -import org.eclipse.jetty.util.resource.EmptyResource; -import org.eclipse.jetty.util.resource.Resource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -class StaticHandler { - - private static final Logger log = LoggerFactory.getLogger(StaticHandler.class); - - private final List handlers = new ArrayList<>(); - private final Server server; - private final boolean preCompress; - - StaticHandler(boolean preCompress, Server server) { - this.preCompress = preCompress; - this.server = server; - } - - void addStaticFileConfig(StaticFileSource config) { - ResourceHandler handler; - if ("/webjars".equals(config.getPath())) { - handler = new WebjarHandler(); - } else { - PrefixableHandler h = new PrefixableHandler(config.getUrlPathPrefix()); - h.setResourceBase(getResourcePath(config)); - h.setDirAllowed(false); - h.setEtags(true); - handler = h; - } - log.info("Static file handler added {}", config); - - try { - handler.setServer(server); - handler.start(); - } catch (Exception e) { - throw new RuntimeException("Error starting Jetty static resource handler", e); - } - handlers.add(handler); - } - - private String getResourcePath(StaticFileSource config) { - if (config.getLocation() == StaticFileSource.Location.CLASSPATH) { - var resource = Resource.newClassPathResource(config.getPath()); - if (resource == null) { - throw new RuntimeException(noSuchDir(config) + " Depending on your setup, empty folders might not get copied to classpath."); - } - return resource.toString(); - } - final File path = new File(config.getPath()); - if (!path.exists()) { - throw new RuntimeException(noSuchDir(config) + " path: " + path.getAbsolutePath()); - } - return config.getPath(); - } - - private String noSuchDir(StaticFileSource config) { - return "Static resource directory with path: '" + config.getPath() + "' does not exist."; - } - - boolean handle(String target, Request baseRequest, HttpServletRequest req, HttpServletResponse res) { - for (ResourceHandler handler : handlers) { - try { - var resource = handler.getResource(target); - if (isFile(resource) || isDirectoryWithWelcomeFile(resource, handler, target)) { -// val maxAge = if (target.startsWith("/immutable/") || handler is WebjarHandler) 31622400 else 0 -// httpResponse.setHeader(HeaderKeys.CACHE_CONTROL, "max-age=$maxAge"); - - // Remove the default content type because Jetty will not set the correct one - // if the HTTP response already has a content type set -// if (precompressStaticFiles && PrecompressingResourceHandler.handle(resource, httpRequest, httpResponse)) { -// return true -// } - res.setContentType(null); - handler.handle(target, baseRequest, req, res); - req.setAttribute("handled-as-static-file", true); -// (httpResponse as JavalinResponseWrapper).outputStream.finalize() - return true; - } - } catch (Exception e) { // it's fine -// if (!Util.isClientAbortException(e)) { -// Javalin.log?.error("Exception occurred while handling static resource", e) -// } - log.error("Exception occurred while handling static resource", e); - } - } - return false; - } - - private boolean isFile(Resource resource) { - return resource != null && resource.exists() && !resource.isDirectory(); - } - - private boolean isDirectoryWithWelcomeFile(Resource resource, ResourceHandler handler, String target) { - //String path = target.removeSuffix("/")+"/index.html"; - if (target.endsWith("/")) { - target = target.substring(0, target.length() - 1); - } - String path = target + "/index.html"; - if (resource == null || !resource.isDirectory()) { - return false; - } - try { - final Resource indexHtml = handler.getResource(path); - return indexHtml != null && indexHtml.exists(); - } catch (IOException e) { - log.warn("Error checking for welcome file", e); - return false; - } - } - - private static class WebjarHandler extends ResourceHandler { - @Override - public Resource getResource(String path) throws IOException { - final Resource resource = Resource.newClassPathResource("META-INF/resources" + path); - return (resource != null) ? resource : super.getResource(path); - } - } - - private static class PrefixableHandler extends ResourceHandler { - - private final String urlPathPrefix; - - PrefixableHandler(String urlPathPrefix) { - this.urlPathPrefix = urlPathPrefix; - } - - @Override - public Resource getResource(String path) throws IOException { - if (urlPathPrefix.equals("/")) { - return super.getResource(path); // same as regular ResourceHandler - } - String targetPath = target(path); - if ("".equals(targetPath)) { - return super.getResource("/"); // directory without trailing '/' - } - if (!path.startsWith(urlPathPrefix)) { - return EmptyResource.INSTANCE; - } - if (!targetPath.startsWith("/")) { - return EmptyResource.INSTANCE; - } else { - return super.getResource(targetPath); - } - } - - private String target(String path) { - if (path.startsWith(urlPathPrefix)) { - return path.substring(urlPathPrefix.length()); - } else { - return path; - } - } - } -} diff --git a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/StaticHandlerFactory.java b/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/StaticHandlerFactory.java deleted file mode 100644 index 5d9da4af..00000000 --- a/avaje-jex-jetty/src/main/java/io/avaje/jex/jetty/StaticHandlerFactory.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.avaje.jex.jetty; - -import io.avaje.jex.Jex; -import io.avaje.jex.StaticFileSource; -import org.eclipse.jetty.server.Server; - -import java.util.List; - -class StaticHandlerFactory { - - StaticHandler build(Server server, Jex jex, List sourceList) { - StaticHandler handler = new StaticHandler(jex.config().preCompressStaticFiles(), server); - for (StaticFileSource source : sourceList) { - handler.addStaticFileConfig(source); - } - return handler; - } -} diff --git a/avaje-jex-jetty/src/main/java/module-info.java b/avaje-jex-jetty/src/main/java/module-info.java deleted file mode 100644 index 8dbb3de8..00000000 --- a/avaje-jex-jetty/src/main/java/module-info.java +++ /dev/null @@ -1,20 +0,0 @@ -import io.avaje.jex.jetty.JettyStartServer; -import io.avaje.jex.spi.SpiStartServer; - -module io.avaje.jex.jetty { - - exports io.avaje.jex.jetty; - - requires transitive io.avaje.jex; - //requires io.avaje.jex.jettyx; - requires java.net.http; - requires transitive jetty.servlet.api; - requires transitive org.slf4j; - requires transitive org.eclipse.jetty.http; - requires transitive org.eclipse.jetty.server; - requires transitive org.eclipse.jetty.io; - requires transitive org.eclipse.jetty.util; - - - provides SpiStartServer with JettyStartServer; -} diff --git a/avaje-jex-jetty/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiStartServer b/avaje-jex-jetty/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiStartServer deleted file mode 100644 index 8adaab64..00000000 --- a/avaje-jex-jetty/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiStartServer +++ /dev/null @@ -1 +0,0 @@ -io.avaje.jex.jetty.JettyStartServer diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/AppRoles.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/AppRoles.java deleted file mode 100644 index 1ab3f369..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/AppRoles.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Role; - -/** - * Create an App specific enum that implements Role. - */ -public enum AppRoles implements Role { - ADMIN, - USER -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/AutoCloseIterator.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/AutoCloseIterator.java deleted file mode 100644 index c613db09..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/AutoCloseIterator.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.avaje.jex.base; - -import java.util.Iterator; - -public class AutoCloseIterator implements Iterator, AutoCloseable { - - private final Iterator it; - private boolean closed; - - public AutoCloseIterator(Iterator it) { - this.it = it; - } - - @Override - public boolean hasNext() { - return it.hasNext(); - } - - @Override - public E next() { - return it.next(); - } - - @Override - public void close() { - closed = true; - } - - public boolean isClosed() { - return closed; - } -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/CharacterEncodingTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/CharacterEncodingTest.java deleted file mode 100644 index 9fd11298..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/CharacterEncodingTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class CharacterEncodingTest { - - static TestPair pair = init(); - - static TestPair init() { - Jex app = Jex.create() - .routing(routing -> routing - .get("/text", ctx -> ctx.contentType("text/plain;charset=utf-8").write("суп из капусты")) - .get("/json", ctx -> ctx.json("白菜湯")) - .get("/html", ctx -> ctx.html("kålsuppe"))); - - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void get() { - - var textRes = pair.request().path("text").GET().asString(); - var jsonRes = pair.request().path("json").GET().asString(); - var htmlRes = pair.request().path("html").GET().asString(); - - assertThat(contentType(jsonRes)).isEqualTo("application/json"); - assertThat(contentType(htmlRes)).isEqualTo("text/html;charset=utf-8"); - assertThat(jsonRes.body()).isEqualTo("\"白菜湯\""); - assertThat(htmlRes.body()).isEqualTo("kålsuppe"); - - assertThat(contentType(textRes)).isEqualTo("text/plain;charset=utf-8"); - assertThat(textRes.body()).isEqualTo("суп из капусты"); - } - - private String contentType(HttpResponse res) { - return res.headers().firstValue("Content-Type").get(); - } - -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextAttributeTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextAttributeTest.java deleted file mode 100644 index 4035832b..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextAttributeTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; -import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThat; - -class ContextAttributeTest { - - static final UUID uuid = UUID.randomUUID(); - - static TestPair pair = init(); - - static TestPair attrPair; - static UUID attrUuid; - - static TestPair init() { - var app = Jex.create() - .routing(routing -> routing - .before( ctx -> ctx.attribute("oneUuid", uuid).attribute(TestPair.class.getName(), pair)) - .get("/", ctx -> { - attrUuid = ctx.attribute("oneUuid"); - attrPair = ctx.attribute(TestPair.class.getName()); - - assert attrUuid == uuid; - assert attrPair == pair; - ctx.text("all-good"); - }) - ); - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void get() { - HttpResponse res = pair.request().GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("all-good"); - - assertThat(attrPair).isSameAs(pair); - assertThat(attrUuid).isSameAs(uuid); - } - -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextCookieTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextCookieTest.java deleted file mode 100644 index a01e72f1..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextCookieTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Context; -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; -import java.time.Duration; -import java.time.temporal.ChronoUnit; - -import static org.assertj.core.api.Assertions.assertThat; - -class ContextCookieTest { - - static TestPair pair = init(); - - static TestPair init() { - var app = Jex.create() - .routing(routing -> routing - .get("/setCookie", ctx -> ctx.cookie("ck", "val").cookie("ck2", "val2")) - .get("/readCookie/{name}", ctx -> ctx.text("readCookie:" + ctx.cookie(ctx.pathParam("name")))) - .get("/readCookieMap", ctx -> ctx.text("cookieMap:" + ctx.cookieMap())) - .get("/removeCookie/{name}", ctx -> ctx.removeCookie(ctx.pathParam("name")).text("ok")) - .get("/setCookieAll", ctx -> { - final Context.Cookie cookie = Context.Cookie.of("ac", "v_all") - .path("/").httpOnly(true).maxAge(Duration.of(10, ChronoUnit.DAYS)); - ctx.cookie(cookie); - }) - ); - return TestPair.create(app, 9001); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void set_read_readMap_remove_readMap_remove_readMap() { - HttpResponse res = pair.request().path("removeCookie").path("ac").GET().asString(); - assertThat(res.body()).isEqualTo("ok"); - - res = pair.request().path("setCookie").GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - - res = pair.request().path("readCookie").path("ck").GET().asString(); - assertThat(res.body()).isEqualTo("readCookie:val"); - - res = pair.request().path("readCookie").path("ck2").GET().asString(); - assertThat(res.body()).isEqualTo("readCookie:val2"); - - res = pair.request().path("readCookieMap").GET().asString(); - assertThat(res.body()).isEqualTo("cookieMap:{ck=val, ck2=val2}"); - - res = pair.request().path("removeCookie").path("ck").GET().asString(); - assertThat(res.body()).isEqualTo("ok"); - - res = pair.request().path("readCookieMap").GET().asString(); - assertThat(res.body()).isEqualTo("cookieMap:{ck2=val2}"); - - res = pair.request().path("removeCookie").path("ck2").GET().asString(); - assertThat(res.body()).isEqualTo("ok"); - - res = pair.request().path("readCookieMap").GET().asString(); - assertThat(res.body()).isEqualTo("cookieMap:{}"); - } - - @Test - void setAll() { - HttpResponse res = pair.request().path("setCookieAll").GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - - res = pair.request().path("readCookieMap").GET().asString(); - assertThat(res.body()).isEqualTo("cookieMap:{ac=v_all}"); - - res = pair.request().path("readCookie").path("ac").GET().asString(); - assertThat(res.body()).isEqualTo("readCookie:v_all"); - } - -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextFormParamTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextFormParamTest.java deleted file mode 100644 index ca0677b8..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextFormParamTest.java +++ /dev/null @@ -1,135 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class ContextFormParamTest { - - static TestPair pair = init(); - - static TestPair init() { - var app = Jex.create() - .routing(routing -> routing - .post("/", ctx -> ctx.text("map:" +ctx.formParamMap())) - .post("/formParams/{key}", ctx -> ctx.text("formParams:" + ctx.formParams(ctx.pathParam("key")))) - .post("/formParam/{key}", ctx -> ctx.text("formParam:" + ctx.formParam(ctx.pathParam("key")))) - .post("/formParamWithDefault/{key}", ctx -> ctx.text("formParam:" + ctx.formParam(ctx.pathParam("key"), "foo"))) - ); - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void formParamMap() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("map:{one=[ao, bo], two=[z]}"); - } - - - @Test - void formParams_one() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .path("formParams").path("one") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("formParams:[ao, bo]"); - } - - @Test - void formParams_two() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .path("formParams").path("two") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("formParams:[z]"); - } - - - @Test - void formParam_null() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .path("formParam").path("doesNotExist") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("formParam:null"); - } - - @Test - void formParam_first() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .path("formParam").path("one") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("formParam:ao"); - } - - @Test - void formParam_default() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .path("formParamWithDefault").path("doesNotExist") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("formParam:foo"); - } - - @Test - void formParam_default_first() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .path("formParamWithDefault").path("one") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("formParam:ao"); - } - - @Test - void formParam_default_only() { - HttpResponse res = pair.request() - .formParam("one", "ao") - .formParam("one", "bo") - .formParam("two", "z") - .path("formParamWithDefault").path("two") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("formParam:z"); - } -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextLengthTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextLengthTest.java deleted file mode 100644 index a7b7c0cb..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextLengthTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class ContextLengthTest { - - static TestPair pair = init(); - - static TestPair init() { - var app = Jex.create() - .routing(routing -> routing - .post("/", ctx -> ctx.text("contentLength:" + ctx.contentLength() + " type:" + ctx.contentType())) - .get("/url", ctx -> ctx.text("url:" + ctx.url())) - .get("/fullUrl", ctx -> ctx.text("fullUrl:" + ctx.fullUrl())) - .get("/contextPath", ctx -> ctx.text("contextPath:" + ctx.contextPath())) - .get("/userAgent", ctx -> ctx.text("userAgent:" + ctx.userAgent())) - ); - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void when_noReqContentType() { - HttpResponse res = pair.request().body("MyBodyContent") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("contentLength:13 type:null"); - } - - @Test - void requestContentLengthAndType_notReqContentType() { - HttpResponse res = pair.request() - .formParam("a", "my-a-val") - .formParam("b", "my-b-val") - .POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("contentLength:21 type:application/x-www-form-urlencoded"); - } - - @Test - void url() { - HttpResponse res = pair.request() - .path("url") - .queryParam("a", "av") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("url:http://localhost:" + pair.port() + "/url"); - } - - @Test - void fullUrl_no_queryString() { - HttpResponse res = pair.request() - .path("fullUrl") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("fullUrl:http://localhost:" + pair.port() + "/fullUrl"); - } - - @Test - void fullUrl_queryString() { - HttpResponse res = pair.request() - .path("fullUrl") - .queryParam("a", "av") - .queryParam("b", "bv") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("fullUrl:http://localhost:" + pair.port() + "/fullUrl?a=av&b=bv"); - } - - @Test - void contextPath() { - HttpResponse res = pair.request() - .path("contextPath") - .queryParam("a", "av") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("contextPath:"); - } - - @Test - void userAgent() { - HttpResponse res = pair.request() - .path("userAgent") - .queryParam("a", "av") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).contains("userAgent:Java-http-client"); - } -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextTest.java deleted file mode 100644 index faa77f74..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ContextTest.java +++ /dev/null @@ -1,194 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; -import java.util.Optional; - -import static java.util.Objects.requireNonNull; -import static org.assertj.core.api.Assertions.assertThat; - -class ContextTest { - - static TestPair pair = init(); - - static TestPair init() { - final Jex app = Jex.create() - .routing(routing -> routing - .get("/", ctx -> ctx.text("ze-get")) - .post("/", ctx -> ctx.text("ze-post")) - .get("/header", ctx -> { - ctx.header("From-My-Server", "Set-By-Server"); - ctx.text("req-header[" + ctx.header("From-My-Client") + "]"); - }) - .get("/headerMap", ctx -> ctx.text("req-header-map[" + ctx.headerMap() + "]")) - .get("/host", ctx -> { - final String host = ctx.host(); - requireNonNull(host); - ctx.text("host:" + host); - }) - .get("/ip", ctx -> { - final String ip = ctx.ip(); - requireNonNull(ip); - ctx.text("ip:" + ip); - }) - .post("/multipart", ctx -> ctx.text("isMultipart:" + ctx.isMultipart() + " isMultipartFormData:" + ctx.isMultipartFormData())) - .get("/method", ctx -> ctx.text("method:" + ctx.method() + " path:" + ctx.path() + " protocol:" + ctx.protocol() + " port:" + ctx.port())) - .post("/echo", ctx -> ctx.text("req-body[" + ctx.body() + "]")) - .get("/{a}/{b}", ctx -> ctx.text("ze-get-" + ctx.pathParamMap())) - .post("/{a}/{b}", ctx -> ctx.text("ze-post-" + ctx.pathParamMap())) - .get("/status", ctx -> { - ctx.status(201); - ctx.text("status:" + ctx.status()); - })); - - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void health_liveness() { - HttpResponse res = pair.request().path("health/liveness").GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("ok"); - } - - @Test - void health_readiness() { - HttpResponse res = pair.request().path("health/readiness").GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("ok"); - } - - @Test - void get() { - HttpResponse res = pair.request().GET().asString(); - assertThat(res.body()).isEqualTo("ze-get"); - } - - @Test - void post() { - HttpResponse res = pair.request().body("simple").POST().asString(); - assertThat(res.body()).isEqualTo("ze-post"); - } - - @Test - void ctx_header_getSet() { - HttpResponse res = pair.request().path("header") - .header("From-My-Client", "client-value") - .GET().asString(); - - final Optional serverSetHeader = res.headers().firstValue("From-My-Server"); - assertThat(serverSetHeader.get()).isEqualTo("Set-By-Server"); - assertThat(res.body()).isEqualTo("req-header[client-value]"); - } - - @Test - void ctx_headerMap() { - HttpResponse res = pair.request().path("headerMap") - .header("X-Foo", "a") - .header("X-Bar", "b") - .GET().asString(); - - assertThat(res.body()).contains("X-Foo=a"); - assertThat(res.body()).contains("X-Bar=b"); - } - - @Test - void ctx_status() { - HttpResponse res = pair.request().path("status") - .GET().asString(); - - assertThat(res.body()).isEqualTo("status:201"); - } - - @Test - void ctx_host() { - HttpResponse res = pair.request().path("host") - .GET().asString(); - - assertThat(res.body()).contains("host:localhost"); - } - - @Test - void ctx_ip() { - HttpResponse res = pair.request().path("ip") - .GET().asString(); - - assertThat(res.body()).isEqualTo("ip:127.0.0.1"); - } - - @Test - void ctx_isMultiPart_when_not() { - HttpResponse res = pair.request().path("multipart") - .formParam("a", "aval") - .POST().asString(); - - assertThat(res.body()).isEqualTo("isMultipart:false isMultipartFormData:false"); - } - - - @Test - void ctx_isMultiPart_when_nothing() { - HttpResponse res = pair.request().path("multipart") - .body("junk") - .POST().asString(); - - assertThat(res.body()).isEqualTo("isMultipart:false isMultipartFormData:false"); - } - - @Test - void ctx_isMultiPart_when_isMultipart() { - HttpResponse res = pair.request().path("multipart") - .header("Content-Type", "multipart/foo") - .body("junk") - .POST().asString(); - - assertThat(res.body()).isEqualTo("isMultipart:true isMultipartFormData:false"); - } - - @Test - void ctx_isMultiPart_when_isMultipartFormData() { - HttpResponse res = pair.request().path("multipart") - .header("Content-Type", "multipart/form-data") - .body("junk") - .POST().asString(); - - assertThat(res.body()).isEqualTo("isMultipart:true isMultipartFormData:true"); - } - - @Test - void ctx_methodPathPortProtocol() { - HttpResponse res = pair.request().path("method") - .GET().asString(); - - assertThat(res.body()).isEqualTo("method:GET path:/method protocol:HTTP/1.1 port:" + pair.port()); - } - - @Test - void post_body() { - HttpResponse res = pair.request().path("echo").body("simple").POST().asString(); - assertThat(res.body()).isEqualTo("req-body[simple]"); - } - - @Test - void get_path_path() { - var res = pair.request() - .path("A").path("B").GET().asString(); - - assertThat(res.body()).isEqualTo("ze-get-{a=A, b=B}"); - - res = pair.request() - .path("one").path("bar").body("simple").POST().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("ze-post-{a=one, b=bar}"); - } - -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ExceptionManagerTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ExceptionManagerTest.java deleted file mode 100644 index 29358ec6..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/ExceptionManagerTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import io.avaje.jex.http.ConflictResponse; -import io.avaje.jex.http.ForbiddenResponse; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class ExceptionManagerTest { - - static TestPair pair = init(); - - static TestPair init() { - final Jex app = Jex.create() - .routing(routing -> routing - .get("/", ctx -> { - throw new ForbiddenResponse(); - }) - .post("/", ctx -> { - throw new IllegalStateException("foo"); - }) - .get("/conflict", ctx -> { - throw new ConflictResponse("Baz"); - }) - .get("/fiveHundred", ctx -> { - throw new IllegalArgumentException("Bar"); - })) - .exception(NullPointerException.class, (exception, ctx) -> ctx.text("npe")) - .exception(IllegalStateException.class, (exception, ctx) -> ctx.status(222).text("Handled IllegalStateException|" + exception.getMessage())) - .exception(ForbiddenResponse.class, (exception, ctx) -> ctx.status(223).text("Handled ForbiddenResponse|" + exception.getMessage())); - - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void get() { - HttpResponse res = pair.request().GET().asString(); - assertThat(res.statusCode()).isEqualTo(223); - assertThat(res.body()).isEqualTo("Handled ForbiddenResponse|Forbidden"); - } - - @Test - void post() { - HttpResponse res = pair.request().body("simple").POST().asString(); - assertThat(res.statusCode()).isEqualTo(222); - assertThat(res.body()).isEqualTo("Handled IllegalStateException|foo"); - } - - @Test - void expect_fallback_to_default_asPlainText() { - HttpResponse res = pair.request().path("conflict").GET().asString(); - assertThat(res.statusCode()).isEqualTo(409); - assertThat(res.body()).isEqualTo("Baz"); - assertThat(res.headers().firstValue("Content-Type").get()).contains("text/plain"); - } - - @Test - void expect_fallback_to_default_asJson() { - HttpResponse res = pair.request().path("conflict").header("Accept", "application/json").GET().asString(); - assertThat(res.statusCode()).isEqualTo(409); - assertThat(res.body()).isEqualTo("{\"title\": Baz, \"status\": 409}"); - assertThat(res.headers().firstValue("Content-Type").get()).contains("application/json"); - } - - @Test - void expect_fallback_to_internalServerError() { - HttpResponse res = pair.request().path("fiveHundred").GET().asString(); - assertThat(res.statusCode()).isEqualTo(500); - assertThat(res.body()).isEqualTo("Internal server error"); - } -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/FilterTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/FilterTest.java deleted file mode 100644 index 550eded8..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/FilterTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpHeaders; -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class FilterTest { - - static TestPair pair = init(); - - static TestPair init() { - final Jex app = Jex.create() - .routing(routing -> routing - .get("/", ctx -> ctx.text("roo")) - .get("/one", ctx -> ctx.text("one")) - .get("/two", ctx -> ctx.text("two")) - .get("/two/{id}", ctx -> ctx.text("two-id")) - .before(ctx -> ctx.header("before-all", "set")) - .before("/two/*", ctx -> ctx.header("before-two", "set")) - .after(ctx -> ctx.header("after-all", "set")) - .after("/two/*", ctx -> ctx.header("after-two", "set")) - .get("/dummy", ctx -> ctx.text("dummy")) - ); - - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void get() { - HttpResponse res = pair.request().GET().asString(); - assertHasBeforeAfterAll(res); - assertNoBeforeAfterTwo(res); - - res = pair.request().path("one").GET().asString(); - assertHasBeforeAfterAll(res); - assertNoBeforeAfterTwo(res); - - res = pair.request().path("two").GET().asString(); - assertHasBeforeAfterAll(res); - assertNoBeforeAfterTwo(res); - } - - - @Test - void get_two_expect_extraFilters() { - HttpResponse res = pair.request() - .path("two/42").GET().asString(); - - final HttpHeaders headers = res.headers(); - assertHasBeforeAfterAll(res); - assertThat(headers.firstValue("before-two")).get().isEqualTo("set"); - assertThat(headers.firstValue("after-two")).get().isEqualTo("set"); - } - - private void assertNoBeforeAfterTwo(HttpResponse res) { - assertThat(res.headers().firstValue("before-two")).isEmpty(); - assertThat(res.headers().firstValue("after-two")).isEmpty(); - } - - private void assertHasBeforeAfterAll(HttpResponse res) { - assertThat(res.headers().firstValue("before-all")).get().isEqualTo("set"); - assertThat(res.headers().firstValue("after-all")).get().isEqualTo("set"); - } -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/HelloDto.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/HelloDto.java deleted file mode 100644 index 6e892c1c..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/HelloDto.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.avaje.jex.base; - -public class HelloDto { - - public long id; - public String name; - - @Override - public String toString() { - return "id:" + id + " name:" + name; - } - - public static HelloDto rob() { - return create(42, "rob"); - } - - public static HelloDto fi() { - return create(45, "fi"); - } - - public static HelloDto create(long id, String name) { - HelloDto me = new HelloDto(); - me.id = id; - me.name = name; - return me; - } -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/JsonTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/JsonTest.java deleted file mode 100644 index e7d3085a..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/JsonTest.java +++ /dev/null @@ -1,121 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpHeaders; -import java.net.http.HttpResponse; -import java.util.List; -import java.util.stream.Stream; - -import static java.util.Arrays.asList; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; - -class JsonTest { - - static List HELLO_BEANS = asList(HelloDto.rob(), HelloDto.fi()); - - static AutoCloseIterator ITERATOR = createBeanIterator(); - - private static AutoCloseIterator createBeanIterator() { - return new AutoCloseIterator<>(HELLO_BEANS.iterator()); - } - - static TestPair pair = init(); - - static TestPair init() { - Jex app = Jex.create() - .routing(routing -> routing - .get("/", ctx -> ctx.json(HelloDto.rob()).status(200)) - .get("/iterate", ctx -> ctx.jsonStream(ITERATOR)) - .get("/stream", ctx -> ctx.jsonStream(HELLO_BEANS.stream())) - .post("/", ctx -> ctx.text("bean[" + ctx.bodyAsClass(HelloDto.class) + "]"))); - - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void get() { - - var bean = pair.request() - .GET() - .bean(HelloDto.class); - - assertThat(bean.id).isEqualTo(42); - assertThat(bean.name).isEqualTo("rob"); - - final HttpResponse hres = pair.request() - .GET().asString(); - - final HttpHeaders headers = hres.headers(); - assertThat(headers.firstValue("Content-Type").get()).isEqualTo("application/json"); - } - - @Test - void stream_viaIterator() { - final Stream beanStream = pair.request() - .path("iterate") - .GET() - .stream(HelloDto.class); - - // expect client gets the expected stream of beans - assertCollectedStream(beanStream); - // assert AutoCloseable iterator on the server-side was closed - assertThat(ITERATOR.isClosed()).isTrue(); - } - - @Test - void stream() { - final Stream beanStream = pair.request() - .path("stream") - .GET() - .stream(HelloDto.class); - - assertCollectedStream(beanStream); - } - - private void assertCollectedStream(Stream beanStream) { - final List collectedBeans = beanStream.collect(toList()); - assertThat(collectedBeans).hasSize(2); - - final HelloDto first = collectedBeans.get(0); - assertThat(first.id).isEqualTo(42); - assertThat(first.name).isEqualTo("rob"); - - final HelloDto second = collectedBeans.get(1); - assertThat(second.id).isEqualTo(45); - assertThat(second.name).isEqualTo("fi"); - } - - @Test - void post() { - HelloDto dto = new HelloDto(); - dto.id = 42; - dto.name = "rob was here"; - - var res = pair.request() - .body(dto) - .POST().asString(); - - assertThat(res.body()).isEqualTo("bean[id:42 name:rob was here]"); - assertThat(res.statusCode()).isEqualTo(200); - - dto.id = 99; - dto.name = "fi"; - - res = pair.request() - .body(dto) - .POST().asString(); - - assertThat(res.body()).isEqualTo("bean[id:99 name:fi]"); - assertThat(res.statusCode()).isEqualTo(200); - } - -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/MultipartFormPostTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/MultipartFormPostTest.java deleted file mode 100644 index 69b202f0..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/MultipartFormPostTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package io.avaje.jex.base; - -import com.mashape.unirest.http.Unirest; -import com.mashape.unirest.http.exceptions.UnirestException; -import io.avaje.jex.Jex; -import io.avaje.jex.UploadedFile; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class MultipartFormPostTest { - - static TestPair pair = init(); - - static final File helloFile = new File("src/test/resources/static-a/hello.txt"); - static final File hello2File = new File("src/test/resources/static-a/hello2.txt"); - - static TestPair init() { - final Jex app = Jex.create() - .routing(routing -> routing - .post("/simple", ctx -> { - final UploadedFile file = ctx.uploadedFile("one"); - ctx.text("nm:" + file.name() + " fn:" + file.fileName() + " size:" + file.size()); - }) - .post("/both", ctx -> { - final UploadedFile file = ctx.uploadedFile("one"); - ctx.text("nm:" + file.name() + " fn:" + file.fileName() + " size:" + file.size() + " paramMap:" + ctx.formParamMap()); - }) - .post("/multi", ctx -> { - String out = ""; - final List files = ctx.uploadedFiles("one"); - for (UploadedFile file : files) { - out += "file[nm:" + file.name() + " fn:" + file.fileName() + " size:" + file.size() + "]"; - } - ctx.text(out + " paramMap:" + ctx.formParamMap()); - }) - .post("/multiAll", ctx -> { - String out = ""; - final List files = ctx.uploadedFiles(); - for (UploadedFile file : files) { - out += "file[nm:" + file.name() + " fn:" + file.fileName() + " size:" + file.size() + "]"; - } - ctx.text(out); - }) - .post("/delete", ctx -> { - final UploadedFile file = ctx.uploadedFile("one"); - file.delete(); - ctx.text("withDelete nm:" + file.name() + " fn:" + file.fileName() + " size:" + file.size()); - }) - ); - - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void simple() throws UnirestException { - final String baseUrl = pair.url(); - - final com.mashape.unirest.http.HttpResponse res = - Unirest.post(baseUrl + "/simple") - .field("one", helloFile) - .asString(); - - assertThat(res.getBody()).isEqualTo("nm:one fn:hello.txt size:" + helloFile.length()); - } - - @Test - void delete() throws UnirestException { - final String baseUrl = pair.url(); - - final com.mashape.unirest.http.HttpResponse res = - Unirest.post(baseUrl + "/delete") - .field("one", helloFile) - .asString(); - - assertThat(res.getBody()).isEqualTo("withDelete nm:one fn:hello.txt size:" + helloFile.length()); - } - - @Test - void both() throws UnirestException { - final String baseUrl = pair.url(); - - final com.mashape.unirest.http.HttpResponse res = - Unirest.post(baseUrl + "/both") - .field("a", "aval") - .field("b", "bval") - .field("one", helloFile) - .asString(); - - assertThat(res.getBody()).isEqualTo("nm:one fn:hello.txt size:" + helloFile.length() + " paramMap:{a=[aval], b=[bval]}"); - } - - @Test - void multipleFiles() throws UnirestException { - final String baseUrl = pair.url(); - - final com.mashape.unirest.http.HttpResponse res = - Unirest.post(baseUrl + "/multi") - .field("a", "a1") - .field("a", "a2") - .field("b", "b1") - .field("b", "b2") - .field("c", "c1") - .field("one", helloFile) - .field("one", hello2File) - .asString(); - - assertThat(res.getBody()).isEqualTo("file[nm:one fn:hello.txt size:" + helloFile.length()+ "]file[nm:one fn:hello2.txt size:" + hello2File.length() + "] paramMap:{a=[a1, a2], b=[b1, b2], c=[c1]}"); - } - - @Test - void multipleFilesAll() throws UnirestException { - final String baseUrl = pair.url(); - - final com.mashape.unirest.http.HttpResponse res = - Unirest.post(baseUrl + "/multiAll") - .field("one", helloFile) - .field("two", hello2File) - .asString(); - - assertThat(res.getBody()).isEqualTo("file[nm:one fn:hello.txt size:" + helloFile.length()+ "]file[nm:two fn:hello2.txt size:" + hello2File.length() + "]"); - } -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/NestedRoutesTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/NestedRoutesTest.java deleted file mode 100644 index d7858d97..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/NestedRoutesTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class NestedRoutesTest { - - static TestPair pair = init(); - - static TestPair init() { - Jex app = Jex.create() - .routing(routing -> routing - .get("/", ctx -> ctx.text("hello")) - .path("api", () -> { - routing.get(ctx -> ctx.text("apiRoot")); - routing.get("{id}", ctx -> ctx.text("api-" + ctx.pathParam("id"))); - }) - .path("extra", () -> { - routing.get(ctx -> ctx.text("extraRoot")); - routing.get("{id}", ctx -> ctx.text("extra-id-" + ctx.pathParam("id"))); - routing.get("more/{id}", ctx -> ctx.text("extraMore-" + ctx.pathParam("id"))); - })); - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void get() { - HttpResponse res = pair.request().GET().asString(); - assertThat(res.body()).isEqualTo("hello"); - } - - @Test - void get_api_paths() { - var res = pair.request() - .path("api").GET().asString(); - - assertThat(res.body()).isEqualTo("apiRoot"); - - res = pair.request() - .path("api").path("99").GET().asString(); - - assertThat(res.body()).isEqualTo("api-99"); - } - - @Test - void get_extra_paths() { - var res = pair.request() - .path("extra").GET().asString(); - - assertThat(res.body()).isEqualTo("extraRoot"); - - res = pair.request() - .path("extra").path("99").GET().asString(); - - assertThat(res.body()).isEqualTo("extra-id-99"); - - res = pair.request() - .path("extra").path("more").path("42").GET().asString(); - - assertThat(res.body()).isEqualTo("extraMore-42"); - } - -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/RedirectTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/RedirectTest.java deleted file mode 100644 index 96383246..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/RedirectTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class RedirectTest { - - - static TestPair pair = init(); - - static TestPair init() { - var app = Jex.create() - .routing(routing -> routing - .before("/other/*", ctx -> ctx.redirect("/two?from=filter")) - .get("/one", ctx -> ctx.text("one")) - .get("/two", ctx -> ctx.text("two")) - .get("/redirect/me", ctx -> ctx.redirect("/one?from=handler")) - .get("/other/me", ctx -> ctx.text("never hit")) - ); - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void redirect_via_handler() { - HttpResponse res = pair.request().path("redirect/me").GET().asString(); - assertThat(res.body()).isEqualTo("one"); - assertThat(res.statusCode()).isEqualTo(200); - } - - @Test - void redirect_via_beforeHandler() { - HttpResponse res = pair.request().path("other/me").GET().asString(); - assertThat(res.body()).isEqualTo("two"); - assertThat(res.statusCode()).isEqualTo(200); - } -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/Roles.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/Roles.java deleted file mode 100644 index 87dd02e0..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/Roles.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.avaje.jex.base; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -/** - * Create an app specific Role annotation that uses the - * app specific role enum. - */ -@Target(value={METHOD, TYPE}) -@Retention(value=RUNTIME) -public @interface Roles { - - /** - * Specify the permitted roles (using app specific enum). - */ - AppRoles[] value() default {}; -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/RolesTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/RolesTest.java deleted file mode 100644 index 111092bc..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/RolesTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import io.avaje.jex.Role; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class RolesTest { - - enum AppRoles implements Role { - ADMIN, - USER, - } - - static TestPair pair = init(); - - static TestPair init() { - var app = Jex.create() - .accessManager((handler, ctx, permittedRoles) -> { - final String role = ctx.queryParam("role"); - if (role == null || !permittedRoles.contains(AppRoles.valueOf(role))) { - ctx.status(401).text("Unauthorized"); - } else { - ctx.attribute("authBy", role); - handler.handle(ctx); - } - }) - .routing(routing -> routing - .get(ctx -> ctx.text("get")) - .get("/multi", ctx -> ctx.text("multi-" + ctx.attribute("authBy"))).withRoles(AppRoles.ADMIN, AppRoles.USER) - .get("/user", ctx -> ctx.text("user")).withRoles(AppRoles.USER) - ); - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void noRoles() { - HttpResponse res = pair.request().GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("get"); - } - - @Test - void singleRole_withRole() { - HttpResponse res = pair.request() - .path("user").queryParam("role", "USER") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("user"); - } - - @Test - void singleRole_withoutRole() { - HttpResponse res = pair.request() - .path("user") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(401); - assertThat(res.body()).isEqualTo("Unauthorized"); - } - - @Test - void multiRole_withRole() { - HttpResponse res = pair.request() - .path("multi").queryParam("role", "USER") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("multi-USER"); - } - - @Test - void multiRole_withRole2() { - HttpResponse res = pair.request() - .path("multi").queryParam("role", "ADMIN") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("multi-ADMIN"); - } - - @Test - void multiRole_withoutRole() { - HttpResponse res = pair.request() - .path("multi") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(401); - assertThat(res.body()).isEqualTo("Unauthorized"); - } - -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/RouteRegexTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/RouteRegexTest.java deleted file mode 100644 index bd00befa..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/RouteRegexTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class RouteRegexTest { - - static TestPair pair = init(); - - static TestPair init() { - var app = Jex.create() - .routing(routing -> routing - .get("/foo/{id:[0-9]+}", ctx -> ctx.text("digit:" + ctx.pathParam("id"))) - .get("/foo/count", ctx -> ctx.text("count")) - ); - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void when_digitMatch() { - HttpResponse res = pair.request().path("foo/7").GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("digit:7"); - } - - @Test - void when_notDigitMatch() { - HttpResponse res = pair.request().path("foo/count").GET().asString(); - - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("count"); - } - - @Test - void when_noMatch() { - HttpResponse res = pair.request().path("foo/a").GET().asString(); - - assertThat(res.statusCode()).isEqualTo(404); - } - -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/RouteSplatTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/RouteSplatTest.java deleted file mode 100644 index 41aae324..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/RouteSplatTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class RouteSplatTest { - - static TestPair pair = init(); - - static TestPair init() { - var app = Jex.create() - .routing(routing -> routing - .get("/{id}/one", ctx -> ctx.text("id:" + ctx.pathParam("id"))) - .get("/{id}/one2", ctx -> ctx.text("id:" + ctx.pathParam("id"))) - .get("//one", ctx -> ctx.text("s1:" + ctx.pathParam("a"))) - .get("//two/", ctx -> ctx.text("s2:" + ctx.pathParam("a") + "|" + ctx.pathParam("b"))) - ); - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void when_utf8Encoded() { - // This fails in Jetty 11.0.2 due to: https://github.com/eclipse/jetty.project/issues/6001 - // String path = URLEncoder.encode("java/kotlin", StandardCharsets.UTF_8) - // + "/two/" + URLEncoder.encode("x/y", StandardCharsets.UTF_8); - // HttpResponse res = pair.request().path(path).get().asString(); - - HttpResponse res = pair.request().path("java/kotlin/two/x/y").GET().asString(); - assertThat(res.body()).isEqualTo("s2:java/kotlin|x/y"); - assertThat(res.statusCode()).isEqualTo(200); - } - - @Test - void when_pathParamMatch() { - HttpResponse res = pair.request().path("42/one").GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("id:42"); - } - - @Test - void when_splatMatch() { - HttpResponse res = pair.request().path("42/foo/one").GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("s1:42/foo"); - } - - @Test - void when_splats() { - HttpResponse res = pair.request().path("a/b/c/two/x/y").GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("s2:a/b/c|x/y"); - } - - @Test - void when_noSplats() { - HttpResponse res = pair.request().path("42/one2").GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("id:42"); - } -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/SimpleTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/SimpleTest.java deleted file mode 100644 index b018ebda..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/SimpleTest.java +++ /dev/null @@ -1,204 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; -import java.util.Map; -import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThat; - -class SimpleTest { - - static UUID uuid = UUID.randomUUID(); - - static UUID sessAttrUuid; - static Map sessAttrMap; - - static TestPair pair = init(); - - static TestPair init() { - var app = Jex.create() - .routing(routing -> routing - .get("/", ctx -> ctx.text("hello")) - .get("/one/{id}", ctx -> ctx.text("one-" + ctx.pathParam("id") + "|match:" + ctx.matchedPath())) - .get("/one/{id}/{b}", ctx -> ctx.text("path:" + ctx.pathParamMap() + "|query:" + ctx.queryParam("z") + "|match:" + ctx.matchedPath())) - .get("/two", ctx -> ctx.text("query:" + ctx.queryParam("z", "defVal"))) - .get("/queryParamMap", ctx -> ctx.text("qpm: "+ctx.queryParamMap())) - .get("/queryParams", ctx -> ctx.text("qps: "+ctx.queryParams("a"))) - .get("/queryString", ctx -> ctx.text("qs: "+ctx.queryString())) - .get("/scheme", ctx -> ctx.text("scheme: "+ctx.scheme())) - .get("/sessionSet", ctx -> { - ctx.sessionAttribute("myAttr", uuid).text("ok"); - }) - .get("/sessionGet", ctx -> { - sessAttrUuid = ctx.sessionAttribute("myAttr"); - ctx.text("ok"); - }) - .get("/sessionMap", ctx -> { - sessAttrMap = ctx.sessionAttributeMap(); - ctx.text("ok"); - }) - - ); - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void get() { - HttpResponse res = pair.request().GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("hello"); - } - - @Test - void getOne_path() { - var res = pair.request() - .path("one").path("foo").GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("one-foo|match:/one/{id}"); - - res = pair.request() - .path("one").path("bar").GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("one-bar|match:/one/{id}"); - } - - @Test - void getOne_path_path() { - var res = pair.request() - .path("one").path("foo").path("bar") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("path:{id=foo, b=bar}|query:null|match:/one/{id}/{b}"); - - res = pair.request() - .path("one").path("fo").path("ba").queryParam("z", "42") - .GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("path:{id=fo, b=ba}|query:42|match:/one/{id}/{b}"); - } - - @Test - void getTwo_withParam() { - var res = pair.request() - .path("two").queryParam("z", "hello").GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("query:hello"); - - res = pair.request() - .path("two").GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("query:defVal"); - - res = pair.request() - .path("two").queryParam("notZ", "hello").GET().asString(); - - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("query:defVal"); - } - - @Test - void queryParamMap_when_empty() { - HttpResponse res = pair.request().path("queryParamMap").GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("qpm: {}"); - } - - @Test - void queryParamMap_keyWithMultiValues_expect_firstValueInMap() { - HttpResponse res = pair.request().path("queryParamMap") - .queryParam("a","AVal0") - .queryParam("a","AVal1") - .queryParam("b", "BVal") - .GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("qpm: {a=AVal0, b=BVal}"); - } - - @Test - void queryParamMap_basic() { - HttpResponse res = pair.request().path("queryParamMap") - .queryParam("a","AVal") - .queryParam("b", "BVal") - .GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("qpm: {a=AVal, b=BVal}"); - } - - @Test - void queryParams_basic() { - HttpResponse res = pair.request().path("queryParams") - .queryParam("a","one") - .queryParam("a", "two") - .GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("qps: [one, two]"); - } - - @Test - void queryParams_when_null_expect_emptyList() { - HttpResponse res = pair.request().path("queryParams") - .queryParam("b","one") - .GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("qps: []"); - } - - @Test - void queryString_when_null() { - HttpResponse res = pair.request().path("queryString") - .GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("qs: null"); - } - - @Test - void queryString_when_set() { - HttpResponse res = pair.request().path("queryString") - .queryParam("foo","f1") - .queryParam("bar","b1") - .queryParam("bar","b2") - .GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("qs: foo=f1&bar=b1&bar=b2"); - } - - @Test - void scheme() { - HttpResponse res = pair.request().path("scheme") - .queryParam("foo","f1") - .GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("scheme: http"); - } - - @Test - void sessionSetGetMap() { - HttpResponse res = pair.request().path("sessionSet").GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - - res = pair.request().path("sessionGet").GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(sessAttrUuid).isSameAs(uuid); - - res = pair.request().path("sessionMap").GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(sessAttrMap).hasSize(1); - assertThat(sessAttrMap.get("myAttr")).isSameAs(uuid); - } - -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/StaticContentTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/StaticContentTest.java deleted file mode 100644 index 5795cd28..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/StaticContentTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpHeaders; -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class StaticContentTest { - - static TestPair pair = init(); - - static TestPair init() { - final Jex app = Jex.create() - .routing(routing -> routing - .get("/", ctx -> ctx.text("ze-get")) - .get("/foo", ctx -> ctx.text("ze-post")) - ) - .staticFiles().addClasspath("/static", "static-a") - .staticFiles().addExternal("/other", "test-static-files"); - ; - - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void get_fromClassPath() { - HttpResponse res = pair.request().path("static/hello.txt").GET().asString(); - assertThat(res.body().trim()).isEqualTo("hello-from-static"); - assertThat(contentType(res.headers())).isEqualTo("text/plain"); - } - - @Test - void get_fromClassPath_another() { - HttpResponse res = pair.request().path("static/goodbye.html").GET().asString(); - assertThat(res.body().trim()).isEqualTo("goodbye"); - assertThat(contentType(res.headers())).isEqualTo("text/html"); - } - - @Test - void get_fromExternalFile() { - HttpResponse res = pair.request().path("other/plain-file.txt").GET().asString(); - assertThat(res.body().trim()).isEqualTo("plain-file"); - assertThat(contentType(res.headers())).isEqualTo("text/plain"); - } - - @Test - void get_fromExternalFile2() { - HttpResponse res = pair.request().path("other/basic.html").GET().asString(); - assertThat(res.body().trim()).isEqualTo("basic"); - assertThat(contentType(res.headers())).isEqualTo("text/html"); - } - -// @Test -// void get_fromExternal_index() { -// HttpResponse res = pair.request().path("other/").GET().asString(); -// assertThat(res.body().trim()).isEqualTo("hello-from-static"); -// assertThat(contentType(res.headers())).isEqualTo("text/plain"); -// } - - private String contentType(HttpHeaders headers) { - return headers.firstValue("Content-Type").get(); - } - -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/TestPair.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/TestPair.java deleted file mode 100644 index b629383f..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/TestPair.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.http.client.HttpClient; -import io.avaje.http.client.HttpClientRequest; -import io.avaje.http.client.JacksonBodyAdapter; -import io.avaje.jex.Jex; - -import java.util.Random; - -/** - * Server and Client pair for a test. - */ -public class TestPair { - - private final int port; - - private final Jex.Server server; - - private final HttpClient client; - - public TestPair(int port, Jex.Server server, HttpClient client) { - this.port = port; - this.server = server; - this.client = client; - } - - public void shutdown() { - server.shutdown(); - } - - public HttpClientRequest request() { - return client.request(); - } - - public int port() { - return port; - } - - public String url() { - return client.url().build(); - } - - public static TestPair create(Jex app) { - int port = 10000 + new Random().nextInt(1000); - return create(app, port); - } - - /** - * Create a Server and Client pair for a given set of tests. - */ - public static TestPair create(Jex app, int port) { - var jexServer = app.port(port).start(); - - var url = "http://localhost:" + port; - var client = HttpClient.builder() - .baseUrl(url) - .bodyAdapter(new JacksonBodyAdapter()) - .build(); - - return new TestPair(port, jexServer, client); - } -} diff --git a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/VerbTest.java b/avaje-jex-jetty/src/test/java/io/avaje/jex/base/VerbTest.java deleted file mode 100644 index 3e34e69f..00000000 --- a/avaje-jex-jetty/src/test/java/io/avaje/jex/base/VerbTest.java +++ /dev/null @@ -1,139 +0,0 @@ -package io.avaje.jex.base; - -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import java.net.http.HttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -class VerbTest { - - static TestPair pair = init(); - - static TestPair init() { - var app = Jex.create() - .routing(routing -> routing - .head(ctx -> ctx.text("head")) - .head("/head", ctx -> ctx.text("headWithPath")) - .get(ctx -> ctx.text("get")) - .get("/get", ctx -> ctx.text("getWithPath")) - .put(ctx -> ctx.text("put")) - .put("/put", ctx -> ctx.text("putWithPath")) - .post(ctx -> ctx.text("post")) - .post("/post", ctx -> ctx.text("postWithPath")) - .patch(ctx -> ctx.text("patch")) - .patch("/patch", ctx -> ctx.text("patchWithPath")) - .delete(ctx -> ctx.text("delete")) - .delete("/delete", ctx -> ctx.text("deleteWithPath")) - .trace(ctx -> ctx.text("trace")) - .trace("/trace", ctx -> ctx.text("traceWithPath")) - .get("/dummy", ctx -> ctx.text("dummy")) - ); - return TestPair.create(app); - } - - @AfterAll - static void end() { - pair.shutdown(); - } - - @Test - void get() { - HttpResponse res = pair.request().GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("get"); - } - - @Test - void get_with_path() { - HttpResponse res = pair.request().path("get").GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("getWithPath"); - } - - @Test - void post() { - HttpResponse res = pair.request().body("dummy").POST().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("post"); - } - - @Test - void post_with_path() { - HttpResponse res = pair.request().path("post").body("dummy").POST().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("postWithPath"); - } - - @Test - void put() { - HttpResponse res = pair.request().body("dummy").PUT().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("put"); - } - - @Test - void put_with_path() { - HttpResponse res = pair.request().path("put").body("dummy").PUT().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("putWithPath"); - } - - @Test - void delete() { - HttpResponse res = pair.request().body("dummy").DELETE().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("delete"); - } - - @Test - void delete_with_path() { - HttpResponse res = pair.request().path("delete").body("dummy").DELETE().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("deleteWithPath"); - } - - @Test - void head() { - HttpResponse res = pair.request().HEAD().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo(""); - } - - @Test - void head_with_path() { - HttpResponse res = pair.request().path("head").HEAD().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo(""); - } - - @Test - void patch() { - HttpResponse res = pair.request().body("dummy").PATCH().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("patch"); - } - - @Test - void patch_with_path() { - HttpResponse res = pair.request().path("patch").body("dummy").PATCH().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("patchWithPath"); - } - - @Test - void trace() { - HttpResponse res = pair.request().body("dummy").TRACE().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("trace"); - } - - @Test - void trace_with_path() { - HttpResponse res = pair.request().path("trace").body("dummy").TRACE().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("traceWithPath"); - } -} diff --git a/avaje-jex-jetty/src/test/resources/logback-test.xml b/avaje-jex-jetty/src/test/resources/logback-test.xml deleted file mode 100644 index ddb21350..00000000 --- a/avaje-jex-jetty/src/test/resources/logback-test.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - TRACE - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - - diff --git a/avaje-jex-jetty/src/test/resources/static-a/goodbye.html b/avaje-jex-jetty/src/test/resources/static-a/goodbye.html deleted file mode 100644 index ea1f574f..00000000 --- a/avaje-jex-jetty/src/test/resources/static-a/goodbye.html +++ /dev/null @@ -1 +0,0 @@ -goodbye diff --git a/avaje-jex-jetty/src/test/resources/static-a/hello.txt b/avaje-jex-jetty/src/test/resources/static-a/hello.txt deleted file mode 100644 index dbabe200..00000000 --- a/avaje-jex-jetty/src/test/resources/static-a/hello.txt +++ /dev/null @@ -1 +0,0 @@ -hello-from-static diff --git a/avaje-jex-jetty/src/test/resources/static-a/hello2.txt b/avaje-jex-jetty/src/test/resources/static-a/hello2.txt deleted file mode 100644 index 56aaa1e3..00000000 --- a/avaje-jex-jetty/src/test/resources/static-a/hello2.txt +++ /dev/null @@ -1 +0,0 @@ -hello2 other static content diff --git a/avaje-jex-jetty/test-static-files/basic.html b/avaje-jex-jetty/test-static-files/basic.html deleted file mode 100644 index 8b4e34d7..00000000 --- a/avaje-jex-jetty/test-static-files/basic.html +++ /dev/null @@ -1 +0,0 @@ -basic diff --git a/avaje-jex-jetty/test-static-files/index.html b/avaje-jex-jetty/test-static-files/index.html deleted file mode 100644 index 0ce384c3..00000000 --- a/avaje-jex-jetty/test-static-files/index.html +++ /dev/null @@ -1 +0,0 @@ -index diff --git a/avaje-jex-jetty/test-static-files/plain-file.txt b/avaje-jex-jetty/test-static-files/plain-file.txt deleted file mode 100644 index 6be11da0..00000000 --- a/avaje-jex-jetty/test-static-files/plain-file.txt +++ /dev/null @@ -1 +0,0 @@ -plain-file From 36277314b3155cfacc45c46cb248796947340622 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:41:42 -0500 Subject: [PATCH 011/250] Update pom.xml --- pom.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pom.xml b/pom.xml index 9b17193c..9a1e56b3 100644 --- a/pom.xml +++ b/pom.xml @@ -28,10 +28,7 @@ avaje-jex-test avaje-jex-freemarker avaje-jex-mustache - avaje-jex-jetty avaje-jex-jdk - avaje-jex-grizzly - From 1035abb11b54e70e9cf52ae8e138bb5448c5a20c Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:01:35 -0500 Subject: [PATCH 012/250] remove from test --- .gitignore | 6 + avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- examples/example-grizzly/pom.xml | 49 ------ .../src/main/java/org/example/GMain.java | 55 ------ .../src/main/java/org/example/HelloDto.java | 14 -- .../src/test/java/org/example/ClientMain.java | 32 ---- .../src/test/resources/logback-test.xml | 19 -- .../graalvm-meta/reflection.json | 6 - examples/example-jetty/pom.xml | 166 ------------------ .../src/main/java/org/example/HelloDto.java | 10 -- .../src/main/java/org/example/JMain.java | 59 ------- .../java/org/example/WiredController.java | 35 ---- .../src/main/resources/content/basic.html | 1 - .../src/main/resources/content/index.html | 1 - .../src/main/resources/content/plain-file.txt | 1 - .../src/main/resources/logback.xml | 19 -- .../src/test/resources/logback-test.xml | 19 -- examples/pom.xml | 3 - 19 files changed, 8 insertions(+), 491 deletions(-) delete mode 100644 examples/example-grizzly/pom.xml delete mode 100644 examples/example-grizzly/src/main/java/org/example/GMain.java delete mode 100644 examples/example-grizzly/src/main/java/org/example/HelloDto.java delete mode 100644 examples/example-grizzly/src/test/java/org/example/ClientMain.java delete mode 100644 examples/example-grizzly/src/test/resources/logback-test.xml delete mode 100644 examples/example-jetty/graalvm-meta/reflection.json delete mode 100644 examples/example-jetty/pom.xml delete mode 100644 examples/example-jetty/src/main/java/org/example/HelloDto.java delete mode 100644 examples/example-jetty/src/main/java/org/example/JMain.java delete mode 100644 examples/example-jetty/src/main/java/org/example/WiredController.java delete mode 100644 examples/example-jetty/src/main/resources/content/basic.html delete mode 100644 examples/example-jetty/src/main/resources/content/index.html delete mode 100644 examples/example-jetty/src/main/resources/content/plain-file.txt delete mode 100644 examples/example-jetty/src/main/resources/logback.xml delete mode 100644 examples/example-jetty/src/test/resources/logback-test.xml diff --git a/.gitignore b/.gitignore index 0c9d542d..755d9ccc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,9 @@ build/ .idea/ *.iml .gradle +.project +*.prefs +avaje-jex-freemarker/.classpath +avaje-jex-jdk/.classpath +avaje-jex-jetty/.classpath +avaje-jex/.classpath diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 1ca43997..04544058 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -31,7 +31,7 @@ io.avaje - avaje-jex-jetty + avaje-jex-jdk 2.6-SNAPSHOT test diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 79640dd3..938e201e 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -32,7 +32,7 @@ io.avaje - avaje-jex-jetty + avaje-jex-jdk 2.6-SNAPSHOT test diff --git a/examples/example-grizzly/pom.xml b/examples/example-grizzly/pom.xml deleted file mode 100644 index 6fd7a8c7..00000000 --- a/examples/example-grizzly/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - 4.0.0 - - org.avaje - java11-oss - 3.9 - - - - org.example - example-grizzly - 1 - - - 11 - 2.15.0 - - - - - - io.avaje - avaje-jex-grizzly - 2.6-SNAPSHOT - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - - io.avaje - avaje-http-client - 2.0 - - - - org.avaje - logback - 1.0 - - - - diff --git a/examples/example-grizzly/src/main/java/org/example/GMain.java b/examples/example-grizzly/src/main/java/org/example/GMain.java deleted file mode 100644 index 5cedc0f5..00000000 --- a/examples/example-grizzly/src/main/java/org/example/GMain.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.example; - -import io.avaje.jex.Jex; -import io.avaje.jex.core.HealthPlugin; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -public class GMain { - - private static final Logger log = LoggerFactory.getLogger(GMain.class); - - public static void main(String[] args) throws InterruptedException { - - Jex.create() - //.attribute(Executor.class, Executors.newVirtualThreadExecutor()) - .routing(routing -> routing - //.get("/", ctx -> ctx.text("hello world")) - .get("/", ctx -> ctx.json(HelloDto.rob())) //.header("x2-foo","asd") - .get("/foo/{id}", ctx -> { - HelloDto bean = new HelloDto(); - bean.id = Integer.parseInt(ctx.pathParam("id")); - bean.name = "Rob"; - ctx.json(bean); - }) - .get("/delay", ctx -> { - log.info("delay start"); - try { - Thread.sleep(5_000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - e.printStackTrace(); - } - ctx.text("delay done"); - log.info("delay done"); - }) - .get("/dump", ctx -> dumpThreadCount()) - ) - .port(7003) - .start(); - - Thread.currentThread().join(); - } - - private static void dumpThreadCount() { - Map allStackTraces = Thread.getAllStackTraces(); - System.out.println("Thread count: " + allStackTraces.size()); - Set threads = allStackTraces.keySet(); - System.out.println("Threads: " + threads); - } -} diff --git a/examples/example-grizzly/src/main/java/org/example/HelloDto.java b/examples/example-grizzly/src/main/java/org/example/HelloDto.java deleted file mode 100644 index de0c8524..00000000 --- a/examples/example-grizzly/src/main/java/org/example/HelloDto.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.example; - -public class HelloDto { - - public long id; - public String name; - - public static HelloDto rob() { - HelloDto bean = new HelloDto(); - bean.id = 42; - bean.name = "rob"; - return bean; - } -} diff --git a/examples/example-grizzly/src/test/java/org/example/ClientMain.java b/examples/example-grizzly/src/test/java/org/example/ClientMain.java deleted file mode 100644 index e821f116..00000000 --- a/examples/example-grizzly/src/test/java/org/example/ClientMain.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.example; - -import io.avaje.http.client.HttpClient; -import io.avaje.http.client.JacksonBodyAdapter; - -import java.net.http.HttpHeaders; -import java.net.http.HttpResponse; - -public class ClientMain { - - public static void main(String[] args) { - - final HttpClient ctx = HttpClient.builder() - .baseUrl("http://localhost:7003") - .bodyAdapter(new JacksonBodyAdapter()) - .version(java.net.http.HttpClient.Version.HTTP_1_1) - .build(); - - final HttpResponse res = ctx.request() - .path("foo/99") - .GET() - .asPlainString(); - final HttpHeaders headers = res.headers(); - System.out.println("got " + res.body()); - - HelloDto bean = ctx.request() - .GET() - .bean(HelloDto.class); - - System.out.println("bean " + bean); - } -} diff --git a/examples/example-grizzly/src/test/resources/logback-test.xml b/examples/example-grizzly/src/test/resources/logback-test.xml deleted file mode 100644 index 2c7f5454..00000000 --- a/examples/example-grizzly/src/test/resources/logback-test.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - TRACE - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - - diff --git a/examples/example-jetty/graalvm-meta/reflection.json b/examples/example-jetty/graalvm-meta/reflection.json deleted file mode 100644 index d96ec561..00000000 --- a/examples/example-jetty/graalvm-meta/reflection.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "name": "io.avaje.jex.jetty.JettyStartServer", - "methods": [ { "name": "", "parameterTypes": [ ] } ] - } -] diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml deleted file mode 100644 index c4053c2d..00000000 --- a/examples/example-jetty/pom.xml +++ /dev/null @@ -1,166 +0,0 @@ - - - 4.0.0 - - org.avaje - java11-oss - 3.9 - - - - org.example - example-jetty - 1 - - - 17 - 0.9.16 - - - - - - ch.qos.logback - logback-classic - 1.2.11 - - - - io.avaje - avaje-jex-jetty - 2.6-SNAPSHOT - - - - - - - - - - - - - - - - io.avaje - avaje-jsonb - 1.4 - - - - io.avaje - avaje-jsonb-generator - 1.4 - provided - - - - io.avaje - avaje-inject - 9.0 - - - - io.avaje - avaje-inject-generator - 9.0 - provided - - - - io.avaje - avaje-http-api - 1.36 - - - - io.avaje - avaje-http-jex-generator - 1.36 - provided - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.10.1 - - 11 - - - - io.repaint.maven - tiles-maven-plugin - 2.22 - true - - - org.avaje.tile:lib-classpath:1.1 - - - - - - - - - - native - - - - org.graalvm.buildtools - native-maven-plugin - ${native.maven.plugin.version} - true - - - build-native - - build - - package - - - test-native - - test - - test - - - - - true - - mytest - org.example.JMain - - - -J--add-modules - -JALL-SYSTEM - --no-fallback - --allow-incomplete-classpath - -H:IncludeResources=".*/logback\\.xml" - - -H:ReflectionConfigurationFiles=graalvm-meta/reflection.json - - - - - - - - - - - - diff --git a/examples/example-jetty/src/main/java/org/example/HelloDto.java b/examples/example-jetty/src/main/java/org/example/HelloDto.java deleted file mode 100644 index 31e106c4..00000000 --- a/examples/example-jetty/src/main/java/org/example/HelloDto.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.example; - -import io.avaje.jsonb.Json; - -@Json -public class HelloDto { - - public long id; - public String name; -} diff --git a/examples/example-jetty/src/main/java/org/example/JMain.java b/examples/example-jetty/src/main/java/org/example/JMain.java deleted file mode 100644 index e98202a7..00000000 --- a/examples/example-jetty/src/main/java/org/example/JMain.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.example; - -import io.avaje.inject.BeanScope; -import io.avaje.jex.Context; -import io.avaje.jex.Jex; -import io.avaje.jex.jetty.JettyServerConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.locks.LockSupport; - -public class JMain { - - private static final Logger log = LoggerFactory.getLogger(JMain.class); - - public static void main(String[] args) { - new JMain().start(BeanScope.builder().build()); - } - - void start(BeanScope beanScope) { - - Jex.create() - //.configure(config -> config.virtualThreads(true)) - .configureWith(beanScope) - .routing(routing -> routing - .get("/", JMain::hello) - .get("/foo/{id}", JMain::helloBean) - .get("/delay", JMain::delay) - ) -// .staticFiles().addClasspath("/static", "content") -// .staticFiles().addExternal("/", "/tmp/junk") - .port(7004) - .start(); - } - - private static void hello(Context context) { - context.text("hello"); - } - - private static void helloBean(Context ctx) { - HelloDto bean = new HelloDto(); - bean.id = Integer.parseInt(ctx.pathParam("id")); - bean.name = "Rob"; - ctx.json(bean); - } - - private static void delay(Context ctx) { - log.info("delay start"); - try { - Thread.sleep(15_000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - e.printStackTrace(); - } - ctx.text("delay done"); - log.info("delay done"); - } - -} diff --git a/examples/example-jetty/src/main/java/org/example/WiredController.java b/examples/example-jetty/src/main/java/org/example/WiredController.java deleted file mode 100644 index 99f1f9e5..00000000 --- a/examples/example-jetty/src/main/java/org/example/WiredController.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.example; - -import io.avaje.http.api.Controller; -import io.avaje.http.api.Get; -import io.avaje.http.api.Path; -import io.avaje.http.api.Produces; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Path("/wired") -@Controller -public class WiredController implements AutoCloseable { - - private static final Logger log = LoggerFactory.getLogger(WiredController.class); - - @Produces("text/plain") - @Get - String hello() { - return "Hello from Controller"; - } - - @Override - public void close() { - log.info("close starting ... "); - try { - Thread.sleep(500); - log.info("close done"); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - e.printStackTrace(); - } - } - - -} diff --git a/examples/example-jetty/src/main/resources/content/basic.html b/examples/example-jetty/src/main/resources/content/basic.html deleted file mode 100644 index 8b4e34d7..00000000 --- a/examples/example-jetty/src/main/resources/content/basic.html +++ /dev/null @@ -1 +0,0 @@ -basic diff --git a/examples/example-jetty/src/main/resources/content/index.html b/examples/example-jetty/src/main/resources/content/index.html deleted file mode 100644 index 0ce384c3..00000000 --- a/examples/example-jetty/src/main/resources/content/index.html +++ /dev/null @@ -1 +0,0 @@ -index diff --git a/examples/example-jetty/src/main/resources/content/plain-file.txt b/examples/example-jetty/src/main/resources/content/plain-file.txt deleted file mode 100644 index 6be11da0..00000000 --- a/examples/example-jetty/src/main/resources/content/plain-file.txt +++ /dev/null @@ -1 +0,0 @@ -plain-file diff --git a/examples/example-jetty/src/main/resources/logback.xml b/examples/example-jetty/src/main/resources/logback.xml deleted file mode 100644 index 2c7f5454..00000000 --- a/examples/example-jetty/src/main/resources/logback.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - TRACE - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - - diff --git a/examples/example-jetty/src/test/resources/logback-test.xml b/examples/example-jetty/src/test/resources/logback-test.xml deleted file mode 100644 index 71d2fe94..00000000 --- a/examples/example-jetty/src/test/resources/logback-test.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - TRACE - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - - diff --git a/examples/pom.xml b/examples/pom.xml index 45a19df6..588bb45a 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -14,9 +14,6 @@ example-jdk - example-jetty - example-grizzly - From 66e97047f181f7e4352299c98b3dbfdcef41c21a Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:02:50 -0500 Subject: [PATCH 013/250] delete jetty loom --- avaje-jetty-loom/pom.xml | 92 ------------------- .../jetty/threadpool/VirtualThreadPool.java | 40 -------- .../src/main/java9/module-info.java | 6 -- 3 files changed, 138 deletions(-) delete mode 100644 avaje-jetty-loom/pom.xml delete mode 100644 avaje-jetty-loom/src/main/java/io/avaje/jex/jetty/threadpool/VirtualThreadPool.java delete mode 100644 avaje-jetty-loom/src/main/java9/module-info.java diff --git a/avaje-jetty-loom/pom.xml b/avaje-jetty-loom/pom.xml deleted file mode 100644 index 613c1736..00000000 --- a/avaje-jetty-loom/pom.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - 4.0.0 - - java11-oss - org.avaje - 3.3 - - - avaje-jex-loomjetty - io.avaje - 1.1 - - - 18 - 18 - 18 - 18 - 11.0.13 - - - - - - org.eclipse.jetty - jetty-servlet - ${jetty.version} - provided - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - 18 - - --enable-preview - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - ossrh - https://oss.sonatype.org/ - ${nexus.staging.autoReleaseAfterClose} - - - - com.thoughtworks.xstream - xstream - 1.4.15 - - - - - - org.moditect - moditect-maven-plugin - 1.0.0.RC1 - - - add-module-infos - package - - add-module-info - - - 9 - - src/main/java9/module-info.java - - - - - - - - - diff --git a/avaje-jetty-loom/src/main/java/io/avaje/jex/jetty/threadpool/VirtualThreadPool.java b/avaje-jetty-loom/src/main/java/io/avaje/jex/jetty/threadpool/VirtualThreadPool.java deleted file mode 100644 index c59bd85f..00000000 --- a/avaje-jetty-loom/src/main/java/io/avaje/jex/jetty/threadpool/VirtualThreadPool.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.avaje.jex.jetty.threadpool; - -import org.eclipse.jetty.util.thread.ThreadPool; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * Loom Virtual threads based Jetty ThreadPool. - */ -public class VirtualThreadPool implements ThreadPool { - - private final ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor(); - - @Override - public void execute(Runnable command) { - executorService.submit(command); - } - - @Override - public void join() { - // do nothing - } - - @Override - public int getThreads() { - return 1; - } - - @Override - public int getIdleThreads() { - return 1; - } - - @Override - public boolean isLowOnThreads() { - return false; - } - -} diff --git a/avaje-jetty-loom/src/main/java9/module-info.java b/avaje-jetty-loom/src/main/java9/module-info.java deleted file mode 100644 index aa763208..00000000 --- a/avaje-jetty-loom/src/main/java9/module-info.java +++ /dev/null @@ -1,6 +0,0 @@ -module io.avaje.jex.jettyx { - - exports io.avaje.jex.jetty.threadpool; - - requires transitive org.eclipse.jetty.util; -} From 490762e9167798756e57eee86a73ac9e973b7659 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:16:24 -0500 Subject: [PATCH 014/250] start --- .project | 17 ++ .settings/org.eclipse.core.resources.prefs | 2 + .settings/org.eclipse.m2e.core.prefs | 4 + avaje-jex-freemarker/.classpath | 57 +++++ avaje-jex-freemarker/.project | 23 ++ .../org.eclipse.core.resources.prefs | 6 + .../.settings/org.eclipse.jdt.apt.core.prefs | 2 + .../.settings/org.eclipse.jdt.core.prefs | 9 + .../.settings/org.eclipse.m2e.core.prefs | 4 + avaje-jex-jdk/pom.xml | 36 ---- avaje-jex-jdk/src/main/java/module-info.java | 11 - .../services/io.avaje.jex.spi.SpiStartServer | 1 - .../src/test/resources/logback-test.xml | 19 -- avaje-jex-jetty/.classpath | 57 +++++ avaje-jex-jetty/.project | 23 ++ .../org.eclipse.core.resources.prefs | 6 + .../.settings/org.eclipse.jdt.apt.core.prefs | 2 + .../.settings/org.eclipse.jdt.core.prefs | 9 + .../.settings/org.eclipse.m2e.core.prefs | 4 + avaje-jex/.classpath | 53 +++++ avaje-jex/.project | 23 ++ .../org.eclipse.core.resources.prefs | 6 + .../.settings/org.eclipse.jdt.apt.core.prefs | 2 + .../.settings/org.eclipse.jdt.core.prefs | 9 + .../.settings/org.eclipse.m2e.core.prefs | 4 + avaje-jex/pom.xml | 20 +- .../src/main/java/io/avaje/jex/BootJex.java | 11 - .../main/java/io/avaje/jex/BootJexState.java | 12 +- .../src/main/java/io/avaje/jex/Context.java | 35 ---- .../src/main/java/io/avaje/jex/DJex.java | 16 +- .../main/java/io/avaje/jex/DJexConfig.java | 20 +- .../java/io/avaje/jex/DefaultRouting.java | 101 +-------- .../src/main/java/io/avaje/jex/JexConfig.java | 14 +- .../java/io/avaje/jex/TemplateRender.java | 4 +- .../jex/core/BootstapServiceManager.java | 2 + .../io/avaje/jex/core/CoreServiceLoader.java | 197 ++++++++++++++++++ .../io/avaje/jex/core/CoreServiceManager.java | 2 +- .../io/avaje/jex/core/ExceptionManager.java | 71 ++----- .../io/avaje/jex/http/BadGatewayResponse.java | 13 -- .../io/avaje/jex/http/BadRequestResponse.java | 13 -- .../io/avaje/jex/http/ConflictResponse.java | 13 -- .../java/io/avaje/jex/http/ErrorCode.java | 44 ++++ .../io/avaje/jex/http/ForbiddenResponse.java | 13 -- .../jex/http/GatewayTimeoutResponse.java | 13 -- .../java/io/avaje/jex/http/GoneResponse.java | 13 -- .../avaje/jex/http/HttpResponseException.java | 12 +- .../jex/http/InternalServerErrorResponse.java | 13 -- .../jex/http/MethodNotAllowedResponse.java | 15 -- .../io/avaje/jex/http/NotFoundResponse.java | 13 -- .../io/avaje/jex/http/RedirectResponse.java | 26 --- .../jex/http/ServiceUnavailableResponse.java | 13 -- .../avaje/jex/http/UnauthorizedResponse.java | 13 -- .../java/io/avaje/jex/jdk/BaseHandler.java | 15 +- .../io/avaje/jex/jdk/BufferedOutStream.java | 14 ++ .../java/io/avaje/jex/jdk/CookieParser.java | 0 .../java/io/avaje/jex/jdk/JdkContext.java | 43 +--- .../java/io/avaje/jex/jdk/JdkJexServer.java | 0 .../java/io/avaje/jex/jdk/JdkServerStart.java | 27 ++- .../java/io/avaje/jex/jdk/ServiceManager.java | 9 +- .../io/avaje/jex/routes/BootstrapRoutes.java | 5 +- .../java/io/avaje/jex/spi/JexExtension.java | 6 + .../java/io/avaje/jex/spi/JsonService.java | 2 +- .../io/avaje/jex/spi/SpiRoutesProvider.java | 2 +- .../jex/spi/SpiServiceManagerProvider.java | 2 +- .../java/io/avaje/jex/spi/SpiStartServer.java | 2 +- avaje-jex/src/main/java/module-info.java | 7 +- .../io/avaje/jex/jdk/AutoCloseIterator.java | 0 .../avaje/jex/jdk/CharacterEncodingTest.java | 0 .../avaje/jex/jdk/ContextAttributeTest.java | 0 .../avaje/jex/jdk/ContextFormParamTest.java | 0 .../io/avaje/jex/jdk/ContextLengthTest.java | 0 .../java/io/avaje/jex/jdk/ContextTest.java | 39 ---- .../io/avaje/jex/jdk/CookieParserTest.java | 0 .../io/avaje/jex/jdk/CookieServerTest.java | 0 .../avaje/jex/jdk/ExceptionManagerTest.java | 12 +- .../java/io/avaje/jex/jdk/FilterTest.java | 0 .../java/io/avaje/jex/jdk/HeadersTest.java | 0 .../io/avaje/jex/jdk/HealthPluginOffTest.java | 0 .../io/avaje/jex/jdk/HealthPluginTest.java | 0 .../test/java/io/avaje/jex/jdk/HelloBean.java | 0 .../test/java/io/avaje/jex/jdk/HelloDto.java | 0 .../io/avaje/jex/jdk/JdkJexServerTest.java | 0 .../test/java/io/avaje/jex/jdk/JsonTest.java | 0 .../src/test/java/io/avaje/jex/jdk/Main.java | 0 .../io/avaje/jex/jdk/NestedRoutesTest.java | 0 .../java/io/avaje/jex/jdk/QueryParamTest.java | 0 .../java/io/avaje/jex/jdk/RedirectTest.java | 0 .../test/java/io/avaje/jex/jdk/TestPair.java | 0 examples/example-jdk/pom.xml | 2 +- pom.xml | 12 +- 90 files changed, 708 insertions(+), 612 deletions(-) create mode 100644 .project create mode 100644 .settings/org.eclipse.core.resources.prefs create mode 100644 .settings/org.eclipse.m2e.core.prefs create mode 100644 avaje-jex-freemarker/.classpath create mode 100644 avaje-jex-freemarker/.project create mode 100644 avaje-jex-freemarker/.settings/org.eclipse.core.resources.prefs create mode 100644 avaje-jex-freemarker/.settings/org.eclipse.jdt.apt.core.prefs create mode 100644 avaje-jex-freemarker/.settings/org.eclipse.jdt.core.prefs create mode 100644 avaje-jex-freemarker/.settings/org.eclipse.m2e.core.prefs delete mode 100644 avaje-jex-jdk/pom.xml delete mode 100644 avaje-jex-jdk/src/main/java/module-info.java delete mode 100644 avaje-jex-jdk/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiStartServer delete mode 100644 avaje-jex-jdk/src/test/resources/logback-test.xml create mode 100644 avaje-jex-jetty/.classpath create mode 100644 avaje-jex-jetty/.project create mode 100644 avaje-jex-jetty/.settings/org.eclipse.core.resources.prefs create mode 100644 avaje-jex-jetty/.settings/org.eclipse.jdt.apt.core.prefs create mode 100644 avaje-jex-jetty/.settings/org.eclipse.jdt.core.prefs create mode 100644 avaje-jex-jetty/.settings/org.eclipse.m2e.core.prefs create mode 100644 avaje-jex/.classpath create mode 100644 avaje-jex/.project create mode 100644 avaje-jex/.settings/org.eclipse.core.resources.prefs create mode 100644 avaje-jex/.settings/org.eclipse.jdt.apt.core.prefs create mode 100644 avaje-jex/.settings/org.eclipse.jdt.core.prefs create mode 100644 avaje-jex/.settings/org.eclipse.m2e.core.prefs create mode 100644 avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/BadGatewayResponse.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/BadRequestResponse.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/ConflictResponse.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/ForbiddenResponse.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/GatewayTimeoutResponse.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/GoneResponse.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorResponse.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/MethodNotAllowedResponse.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/NotFoundResponse.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/RedirectResponse.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/ServiceUnavailableResponse.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/UnauthorizedResponse.java rename {avaje-jex-jdk => avaje-jex}/src/main/java/io/avaje/jex/jdk/BaseHandler.java (84%) rename {avaje-jex-jdk => avaje-jex}/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java (81%) rename {avaje-jex-jdk => avaje-jex}/src/main/java/io/avaje/jex/jdk/CookieParser.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/main/java/io/avaje/jex/jdk/JdkContext.java (93%) rename {avaje-jex-jdk => avaje-jex}/src/main/java/io/avaje/jex/jdk/JdkJexServer.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/main/java/io/avaje/jex/jdk/JdkServerStart.java (79%) rename {avaje-jex-jdk => avaje-jex}/src/main/java/io/avaje/jex/jdk/ServiceManager.java (73%) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/CharacterEncodingTest.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/ContextAttributeTest.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/ContextFormParamTest.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/ContextLengthTest.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/ContextTest.java (75%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/CookieParserTest.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/CookieServerTest.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java (87%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/FilterTest.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/HeadersTest.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/HealthPluginOffTest.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/HealthPluginTest.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/HelloBean.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/HelloDto.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/JsonTest.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/Main.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/NestedRoutesTest.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/QueryParamTest.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/RedirectTest.java (100%) rename {avaje-jex-jdk => avaje-jex}/src/test/java/io/avaje/jex/jdk/TestPair.java (100%) diff --git a/.project b/.project new file mode 100644 index 00000000..c2f3b10d --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + avaje-jex-parent + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..99f26c02 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 00000000..f897a7f1 --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/avaje-jex-freemarker/.classpath b/avaje-jex-freemarker/.classpath new file mode 100644 index 00000000..8c458320 --- /dev/null +++ b/avaje-jex-freemarker/.classpath @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/avaje-jex-freemarker/.project b/avaje-jex-freemarker/.project new file mode 100644 index 00000000..7146c529 --- /dev/null +++ b/avaje-jex-freemarker/.project @@ -0,0 +1,23 @@ + + + avaje-jex-freemarker + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/avaje-jex-freemarker/.settings/org.eclipse.core.resources.prefs b/avaje-jex-freemarker/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..29abf999 --- /dev/null +++ b/avaje-jex-freemarker/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/avaje-jex-freemarker/.settings/org.eclipse.jdt.apt.core.prefs b/avaje-jex-freemarker/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 00000000..d4313d4b --- /dev/null +++ b/avaje-jex-freemarker/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=false diff --git a/avaje-jex-freemarker/.settings/org.eclipse.jdt.core.prefs b/avaje-jex-freemarker/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..99a63d54 --- /dev/null +++ b/avaje-jex-freemarker/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,9 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.processAnnotations=disabled +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=11 diff --git a/avaje-jex-freemarker/.settings/org.eclipse.m2e.core.prefs b/avaje-jex-freemarker/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 00000000..f897a7f1 --- /dev/null +++ b/avaje-jex-freemarker/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/avaje-jex-jdk/pom.xml b/avaje-jex-jdk/pom.xml deleted file mode 100644 index 6bea9546..00000000 --- a/avaje-jex-jdk/pom.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - avaje-jex-parent - io.avaje - 2.6-SNAPSHOT - - 4.0.0 - - avaje-jex-jdk - - - 11 - 11 - false - - - - - - io.avaje - avaje-jex - 2.6-SNAPSHOT - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - test - - - - - - diff --git a/avaje-jex-jdk/src/main/java/module-info.java b/avaje-jex-jdk/src/main/java/module-info.java deleted file mode 100644 index a30fd880..00000000 --- a/avaje-jex-jdk/src/main/java/module-info.java +++ /dev/null @@ -1,11 +0,0 @@ -import io.avaje.jex.jdk.JdkServerStart; -import io.avaje.jex.spi.SpiStartServer; - -module io.avaje.jex.jdk { - - requires transitive io.avaje.jex; - requires transitive java.net.http; - requires transitive jdk.httpserver; - - provides SpiStartServer with JdkServerStart; -} diff --git a/avaje-jex-jdk/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiStartServer b/avaje-jex-jdk/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiStartServer deleted file mode 100644 index 79199ebe..00000000 --- a/avaje-jex-jdk/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiStartServer +++ /dev/null @@ -1 +0,0 @@ -io.avaje.jex.jdk.JdkServerStart diff --git a/avaje-jex-jdk/src/test/resources/logback-test.xml b/avaje-jex-jdk/src/test/resources/logback-test.xml deleted file mode 100644 index 5e5a132a..00000000 --- a/avaje-jex-jdk/src/test/resources/logback-test.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - TRACE - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - - diff --git a/avaje-jex-jetty/.classpath b/avaje-jex-jetty/.classpath new file mode 100644 index 00000000..8c458320 --- /dev/null +++ b/avaje-jex-jetty/.classpath @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/avaje-jex-jetty/.project b/avaje-jex-jetty/.project new file mode 100644 index 00000000..0ce2b5cf --- /dev/null +++ b/avaje-jex-jetty/.project @@ -0,0 +1,23 @@ + + + avaje-jex-jetty + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/avaje-jex-jetty/.settings/org.eclipse.core.resources.prefs b/avaje-jex-jetty/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..29abf999 --- /dev/null +++ b/avaje-jex-jetty/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/avaje-jex-jetty/.settings/org.eclipse.jdt.apt.core.prefs b/avaje-jex-jetty/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 00000000..d4313d4b --- /dev/null +++ b/avaje-jex-jetty/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=false diff --git a/avaje-jex-jetty/.settings/org.eclipse.jdt.core.prefs b/avaje-jex-jetty/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..99a63d54 --- /dev/null +++ b/avaje-jex-jetty/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,9 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.processAnnotations=disabled +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=11 diff --git a/avaje-jex-jetty/.settings/org.eclipse.m2e.core.prefs b/avaje-jex-jetty/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 00000000..f897a7f1 --- /dev/null +++ b/avaje-jex-jetty/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/avaje-jex/.classpath b/avaje-jex/.classpath new file mode 100644 index 00000000..319356eb --- /dev/null +++ b/avaje-jex/.classpath @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/avaje-jex/.project b/avaje-jex/.project new file mode 100644 index 00000000..8a83aa90 --- /dev/null +++ b/avaje-jex/.project @@ -0,0 +1,23 @@ + + + avaje-jex + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/avaje-jex/.settings/org.eclipse.core.resources.prefs b/avaje-jex/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..29abf999 --- /dev/null +++ b/avaje-jex/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/avaje-jex/.settings/org.eclipse.jdt.apt.core.prefs b/avaje-jex/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 00000000..d4313d4b --- /dev/null +++ b/avaje-jex/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=false diff --git a/avaje-jex/.settings/org.eclipse.jdt.core.prefs b/avaje-jex/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..a75d6ef8 --- /dev/null +++ b/avaje-jex/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,9 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=21 +org.eclipse.jdt.core.compiler.compliance=21 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.processAnnotations=disabled +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=21 diff --git a/avaje-jex/.settings/org.eclipse.m2e.core.prefs b/avaje-jex/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 00000000..f897a7f1 --- /dev/null +++ b/avaje-jex/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index d90aea85..03082691 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -10,7 +10,6 @@ avaje-jex - 11.0.13 @@ -31,7 +30,7 @@ io.avaje avaje-inject - 9.9 + 10.6 true @@ -45,7 +44,7 @@ io.avaje avaje-jsonb - 1.9 + 2.3 true @@ -53,21 +52,6 @@ - - - - - - - - - - - - - - - diff --git a/avaje-jex/src/main/java/io/avaje/jex/BootJex.java b/avaje-jex/src/main/java/io/avaje/jex/BootJex.java index 321032e2..f12d8241 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/BootJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/BootJex.java @@ -22,15 +22,4 @@ public interface BootJex { static void start() { BootJexState.start(); } - -// /** -// * Stop the Jex server (for CRaC). -// */ -// static void stop() { -// BootJexState.stop(); -// } -// -// static void restart() { -// BootJexState.restart(); -// } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java b/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java index f95b8a00..c94ac006 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java +++ b/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java @@ -27,22 +27,18 @@ State create() { JexConfig config = jex.config(); int port = config.port(); - if (port == 7001) { - config.port(Config.getInt("jex.port", port)); - } + config.port(Config.getInt("jex.port", port)); jex.lifecycle().onShutdown(beanScope::close); - return new State(jex.start(), beanScope); + return new State(jex.start()); } private static class State { private final Jex.Server server; - private final BeanScope beanScope; - State(Jex.Server server, BeanScope beanScope) { + State(Jex.Server server) { this.server = server; - this.beanScope = beanScope; } void stop() { @@ -50,8 +46,6 @@ void stop() { } public void restart() { - // CRaC based startup ... - //beanScope.restart(); server.restart(); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index 305b7636..8eb06b67 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -193,21 +193,6 @@ default List formParams(String key) { */ String scheme(); - /** - * Sets an attribute for the user session. - */ - Context sessionAttribute(String key, Object value); - - /** - * Gets specified attribute from the user session, or null. - */ - T sessionAttribute(String key); - - /** - * Return a map of all the attributes in the user session. - */ - Map sessionAttributeMap(); - /** * Return the request url. */ @@ -297,16 +282,6 @@ default Context render(String name) { */ Context render(String name, Map model); - /** - * Return true if content has already been written to the underlying server outputStream. - */ - boolean isCommitted(); - - /** - * If not committed reset the underlying response status, headers and buffer. - */ - void reset(); - /** * Return all the request headers as a map. */ @@ -342,16 +317,6 @@ default Context render(String name) { */ String ip(); - /** - * Returns true if request is multipart. - */ - boolean isMultipart(); - - /** - * Returns true if request is multipart/form-data. - */ - boolean isMultipartFormData(); - /** * Returns the request method. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index 5c741c5e..7c711c9c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -158,17 +158,19 @@ public Server start() { if (config.health()) { plugin(new HealthPlugin()); } - final SpiRoutes routes = ServiceLoader.load(SpiRoutesProvider.class) - .findFirst().get() - .create(this.routing, this.config.accessManager(), this.config.ignoreTrailingSlashes()); + final SpiRoutes routes = + ServiceLoader.load(SpiRoutesProvider.class) + .findFirst() + .orElseThrow() + .create(this.routing, this.config.accessManager(), this.config.ignoreTrailingSlashes()); - final SpiServiceManager serviceManager = ServiceLoader.load(SpiServiceManagerProvider.class) - .findFirst().get() - .create(this); + final SpiServiceManager serviceManager = + ServiceLoader.load(SpiServiceManagerProvider.class).findFirst().orElseThrow().create(this); final Optional start = ServiceLoader.load(SpiStartServer.class).findFirst(); if (start.isEmpty()) { - throw new IllegalStateException("There is no SpiStartServer? Missing dependency on jex-jetty?"); + throw new IllegalStateException( + "There is no SpiStartServer? Missing dependency on jex-jetty?"); } return start.get().start(this, routes, serviceManager); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 5f9894ec..ea1a8c3b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -4,15 +4,17 @@ import java.util.HashMap; import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; class DJexConfig implements JexConfig { - private int port = 7001; + private int port = 8080; private String host; private String contextPath = "/"; private boolean health = true; private boolean ignoreTrailingSlashes = true; - private boolean virtualThreads; + private Executor executor; private boolean preCompressStaticFiles; private JsonService jsonService; @@ -88,13 +90,19 @@ public JexConfig renderer(String extension, TemplateRender renderer) { } @Override - public boolean virtualThreads() { - return virtualThreads; + public Executor executor() { + + if (executor == null) { + executor = + Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("jex-http-", 0).factory()); + } + + return executor; } @Override - public JexConfig virtualThreads(boolean virtualThreads) { - this.virtualThreads = virtualThreads; + public JexConfig executor(Executor executor) { + this.executor = executor; return this; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index 25445250..6317b204 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -2,13 +2,11 @@ import java.util.*; -/** - * - */ class DefaultRouting implements Routing { private final List handlers = new ArrayList<>(); private final Deque pathDeque = new ArrayDeque<>(); + /** * Last entry that we can add permitted roles to. */ @@ -168,7 +166,6 @@ public Routing trace(Handler handler) { // ******************************************************************************************** // Before/after handlers (filters) // ******************************************************************************************** - @Override public Routing before(String path, Handler handler) { addBefore(path, handler); @@ -193,102 +190,6 @@ public Routing after(Handler handler) { return this; } - // ******************************************************************************************** - // WebSocket - // ******************************************************************************************** -// -// /** -// * Adds a WebSocket handler on the specified path. -// * The method can only be called inside a {@link Javalin#routes(EndpointGroup)}. -// * -// * @see WebSockets in docs -// */ -// public static void ws(String path, Consumer ws) { -// me().ws(prefixPath(path), ws); -// } -// -// /** -// * Adds a WebSocket handler with the given roles for the specified path. -// * The method can only be called inside a {@link Javalin#routes(EndpointGroup)}. -// * -// * @see WebSockets in docs -// */ -// public static void ws(String path, Consumer ws, Set permittedRoles) { -// me().ws(prefixPath(path), ws, permittedRoles); -// } -// -// /** -// * Adds a WebSocket handler on the current path. -// * The method can only be called inside a {@link Javalin#routes(EndpointGroup)}. -// * -// * @see WebSockets in docs -// */ -// public static void ws(Consumer ws) { -// me().ws(prefixPath(""), ws); -// } -// -// /** -// * Adds a WebSocket handler with the given roles for the current path. -// * The method can only be called inside a {@link Javalin#routes(EndpointGroup)}. -// * -// * @see WebSockets in docs -// */ -// public static void ws(Consumer ws, Set permittedRoles) { -// me().ws(prefixPath(""), ws, permittedRoles); -// } -// -// /** -// * Adds a WebSocket before handler for the specified path to the {@link Javalin} instance. -// * The method can only be called inside a {@link Javalin#routes(EndpointGroup)}. -// */ -// public Javalin wsBefore(String path, Consumer wsHandler) { -// return me().wsBefore(prefixPath(path), wsHandler); -// } -// -// /** -// * Adds a WebSocket before handler for the current path to the {@link Javalin} instance. -// * The method can only be called inside a {@link Javalin#routes(EndpointGroup)}. -// */ -// public Javalin wsBefore(Consumer wsHandler) { -// return me().wsBefore(prefixPath("/*"), wsHandler); -// } -// -// /** -// * Adds a WebSocket after handler for the specified path to the {@link Javalin} instance. -// * The method can only be called inside a {@link Javalin#routes(EndpointGroup)}. -// */ -// public Javalin wsAfter(String path, Consumer wsHandler) { -// return me().wsAfter(prefixPath(path), wsHandler); -// } -// -// /** -// * Adds a WebSocket after handler for the current path to the {@link Javalin} instance. -// * The method can only be called inside a {@link Javalin#routes(EndpointGroup)}. -// */ -// public Javalin wsAfter(Consumer wsHandler) { -// return me().wsAfter(prefixPath("/*"), wsHandler); -// } -// -// // ******************************************************************************************** -// // Server-sent events -// // ******************************************************************************************** -// -// public static void sse(String path, Consumer client) { -// me().sse(prefixPath(path), client); -// } -// -// public static void sse(String path, Consumer client, Set permittedRoles) { -// me().sse(prefixPath(path), client, permittedRoles); -// } -// -// public static void sse(Consumer client) { -// me().sse(prefixPath(""), client); -// } -// -// public static void sse(Consumer client, Set permittedRoles) { -// me().sse(prefixPath(""), client, permittedRoles); -// } - private static class Entry implements Routing.Entry { private final Type type; diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index fe6b6914..62307b81 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -1,8 +1,10 @@ package io.avaje.jex; -import io.avaje.jex.spi.JsonService; - import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +import io.avaje.jex.spi.JsonService; /** * Jex configuration. @@ -68,14 +70,14 @@ public interface JexConfig { JexConfig renderer(String extension, TemplateRender renderer); /** - * Set to true to use virtual threads if supported. Defaults to false. + * Executor for serving requests. Defaults to {@link Executors#newVirtualThreadPerTaskExecutor()} */ - JexConfig virtualThreads(boolean virtualThreads); + JexConfig executor(Executor executor); /** - * Return true if virtual threads should be used. + * Executor for serving requests. Defaults to {@link Executors#newVirtualThreadPerTaskExecutor()} */ - boolean virtualThreads(); + Executor executor(); /** * Return the port to use. diff --git a/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java b/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java index c8219042..85d75088 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java +++ b/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java @@ -2,10 +2,12 @@ import java.util.Map; +import io.avaje.jex.spi.JexExtension; + /** * Template rendering typically of html. */ -public interface TemplateRender { +public interface TemplateRender extends JexExtension { /** * Return the extensions this template renders for by default. diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstapServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstapServiceManager.java index 1895a6e5..b8b2986b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstapServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstapServiceManager.java @@ -3,7 +3,9 @@ import io.avaje.jex.Jex; import io.avaje.jex.spi.SpiServiceManager; import io.avaje.jex.spi.SpiServiceManagerProvider; +import io.avaje.spi.ServiceProvider; +@ServiceProvider public class BootstapServiceManager implements SpiServiceManagerProvider { @Override public SpiServiceManager create(Jex jex) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java new file mode 100644 index 00000000..b8d959b1 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java @@ -0,0 +1,197 @@ +package io.avaje.jex.core; + +import io.avaje.applog.AppLog; +import io.avaje.jex.*; +import io.avaje.jex.spi.HeaderKeys; +import io.avaje.jex.spi.JsonService; +import io.avaje.jex.spi.SpiContext; +import io.avaje.jex.spi.SpiServiceManager; + +import java.io.UncheckedIOException; +import java.io.UnsupportedEncodingException; +import java.lang.System.Logger.Level; +import java.net.URLDecoder; +import java.util.*; +import java.util.stream.Stream; + +/** + * Core implementation of SpiServiceManager provided to specific implementations like jetty etc. + */ +class CoreServiceLoader implements SpiServiceManager { + + private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); + public static final String UTF_8 = "UTF-8"; + + private final HttpMethodMap methodMap = new HttpMethodMap(); + private final JsonService jsonService; + private final ExceptionManager exceptionHandler; + private final TemplateManager templateManager; + + static SpiServiceManager create(Jex jex) { + return new Builder(jex).build(); + } + + CoreServiceLoader(JsonService jsonService, ErrorHandling errorHandling, TemplateManager templateManager) { + this.jsonService = jsonService; + this.exceptionHandler = new ExceptionManager(errorHandling); + this.templateManager = templateManager; + } + + @Override + public T jsonRead(Class clazz, SpiContext ctx) { + return jsonService.jsonRead(clazz, ctx); + } + + @Override + public void jsonWrite(Object bean, SpiContext ctx) { + jsonService.jsonWrite(bean, ctx); + } + + @Override + public void jsonWriteStream(Stream stream, SpiContext ctx) { + try (stream) { + jsonService.jsonWriteStream(stream.iterator(), ctx); + } + } + + @Override + public void jsonWriteStream(Iterator iterator, SpiContext ctx) { + try { + jsonService.jsonWriteStream(iterator, ctx); + } finally { + maybeClose(iterator); + } + } + + @Override + public void maybeClose(Object iterator) { + if (iterator instanceof AutoCloseable) { + try { + ((AutoCloseable) iterator).close(); + } catch (Exception e) { + throw new RuntimeException("Error closing iterator " + iterator, e); + } + } + } + + @Override + public Routing.Type lookupRoutingType(String method) { + return methodMap.get(method); + } + + @Override + public void handleException(SpiContext ctx, Exception e) { + exceptionHandler.handle(ctx, e); + } + + @Override + public void render(Context ctx, String name, Map model) { + templateManager.render(ctx, name, model); + } + + + @Override + public String requestCharset(Context ctx) { + return parseCharset(ctx.header(HeaderKeys.CONTENT_TYPE)); + } + + static String parseCharset(String header) { + if (header != null) { + for (String val : header.split(";")) { + val = val.trim(); + if (val.regionMatches(true, 0, "charset", 0, "charset".length())) { + return val.split("=")[1].trim(); + } + } + } + return UTF_8; + } + + @Override + public Map> formParamMap(Context ctx, String charset) { + return parseParamMap(ctx.body(), charset); + } + + @Override + public Map> parseParamMap(String body, String charset) { + if (body == null || body.isEmpty()) { + return Collections.emptyMap(); + } + try { + Map> map = new LinkedHashMap<>(); + for (String pair : body.split("&")) { + final String[] split1 = pair.split("=", 2); + String key = URLDecoder.decode(split1[0], charset); + String val = split1.length > 1 ? URLDecoder.decode(split1[1], charset) : ""; + map.computeIfAbsent(key, s -> new ArrayList<>()).add(val); + } + return map; + } catch (UnsupportedEncodingException e) { + throw new UncheckedIOException(e); + } + } + + private static class Builder { + private final Jex jex; + + Builder(Jex jex) { + this.jex = jex; + } + + SpiServiceManager build() { + return new CoreServiceLoader(initJsonService(), jex.errorHandling(), initTemplateMgr()); + } + + JsonService initJsonService() { + final JsonService jsonService = jex.config().jsonService(); + if (jsonService != null) { + return jsonService; + } + return ServiceLoader.load(JsonService.class) + .findFirst() + .orElseGet(this::defaultJsonService); + } + + /** + * Create a reasonable default JsonService if Jackson or avaje-jsonb are present. + */ + JsonService defaultJsonService() { + if (detectJackson()) { + try { + return new JacksonJsonService(); + } catch (IllegalAccessError errorNotInModulePath) { + // not in module path + log.log(Level.DEBUG, "Not using Jackson due to module path {0}", errorNotInModulePath.getMessage()); + } + } + return detectJsonb() ? new JsonbJsonService() : null; + } + + boolean detectJackson() { + return detectTypeExists("com.fasterxml.jackson.databind.ObjectMapper"); + } + + boolean detectJsonb() { + return detectTypeExists("io.avaje.jsonb.Jsonb"); + } + + private boolean detectTypeExists(String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + TemplateManager initTemplateMgr() { + TemplateManager mgr = new TemplateManager(); + mgr.register(jex.config().renderers()); + for (TemplateRender render : ServiceLoader.load(TemplateRender.class)) { + mgr.registerDefault(render); + } + return mgr; + } + + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java index bf73da9f..18b7131f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java @@ -65,7 +65,7 @@ public void jsonWriteStream(Iterator iterator, SpiContext ctx) { @Override public void maybeClose(Object iterator) { - if (AutoCloseable.class.isAssignableFrom(iterator.getClass())) { + if (iterator instanceof AutoCloseable) { try { ((AutoCloseable) iterator).close(); } catch (Exception e) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index 960585f1..8278ad87 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -3,9 +3,8 @@ import io.avaje.applog.AppLog; import io.avaje.jex.ErrorHandling; import io.avaje.jex.ExceptionHandler; +import io.avaje.jex.http.ErrorCode; import io.avaje.jex.http.HttpResponseException; -import io.avaje.jex.http.InternalServerErrorResponse; -import io.avaje.jex.http.RedirectResponse; import io.avaje.jex.spi.HeaderKeys; import io.avaje.jex.spi.SpiContext; @@ -13,6 +12,8 @@ class ExceptionManager { + private static final String APPLICATION_JSON = "application/json"; + private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); private final ErrorHandling errorHandling; @@ -22,81 +23,49 @@ class ExceptionManager { } void handle(SpiContext ctx, Exception e) { - if (!isRedirect(e)) { - if (ctx.isCommitted()) { - log.log(WARNING, "Response is already committed when handling exception", e); - throw new InternalServerErrorResponse("Response already committed on error " + e); - } else { - // reset the status, headers and buffers in order to write the error content - ctx.reset(); - } - } final ExceptionHandler handler = errorHandling.find(e.getClass()); if (handler != null) { handler.handle(e, ctx); + } else if (e instanceof HttpResponseException ex) { + defaultHandling(ctx, ex); } else { - if (canHandle(e)) { - defaultHandling(ctx, e); - } else { - unhandledException(ctx, e); - } + unhandledException(ctx, e); } } private void unhandledException(SpiContext ctx, Exception e) { log.log(WARNING, "Uncaught exception", e); - defaultHandling(ctx, new InternalServerErrorResponse()); - } - - private boolean canHandle(Exception e) { - return HttpResponseException.class.isAssignableFrom(e.getClass()); + defaultHandling(ctx, new HttpResponseException(ErrorCode.INTERNAL_SERVER_ERROR)); } - private boolean isRedirect(Exception e) { - return RedirectResponse.class.isAssignableFrom(e.getClass()); - } + private void defaultHandling(SpiContext ctx, HttpResponseException exception) { - private void defaultHandling(SpiContext ctx, Exception exception) { - final HttpResponseException e = unwrap(exception); - ctx.status(e.getStatus()); - if (isRedirect(e)) { + ctx.status(exception.getStatus()); + if (exception.getStatus() == ErrorCode.REDIRECT.status()) { ctx.performRedirect(); } else if (useJson(ctx)) { - ctx.contentType("application/json").write(asJsonContent(e)); + ctx.contentType(APPLICATION_JSON).write(asJsonContent(exception)); } else { - ctx.text(asTextContent(e)); + ctx.text(exception.getMessage()); } } - private String asTextContent(HttpResponseException e) { - return e.getMessage(); - // + "\n" details - } - private String asJsonContent(HttpResponseException e) { - return "{\"title\": " + jsonEscape(e.getMessage()) + ", " + - "\"status\": " + e.getStatus() + - //+ ", " "\"type\": " + ", " + - jsonDetails(e) + "}"; + return "{\"title\": " + + jsonEscape(e.getMessage()) + + ", " + + "\"status\": " + + e.getStatus() + + "}"; } private String jsonEscape(String message) { return message; } - private String jsonDetails(HttpResponseException e) { - return ""; - } - - private HttpResponseException unwrap(Exception e) { - return (HttpResponseException) e; - //(if (e is CompletionException) e.cause else e) as HttpResponseException - } - private boolean useJson(SpiContext ctx) { final String acceptHeader = ctx.header(HeaderKeys.ACCEPT); - return (acceptHeader != null && acceptHeader.contains("application/json") - || "application/json".equals(ctx.responseHeader(HeaderKeys.CONTENT_TYPE))); + return (acceptHeader != null && acceptHeader.contains(APPLICATION_JSON) + || APPLICATION_JSON.equals(ctx.responseHeader(HeaderKeys.CONTENT_TYPE))); } - } diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/BadGatewayResponse.java b/avaje-jex/src/main/java/io/avaje/jex/http/BadGatewayResponse.java deleted file mode 100644 index 96ab8597..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/http/BadGatewayResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.avaje.jex.http; - -public class BadGatewayResponse extends HttpResponseException { - - public BadGatewayResponse(String message) { - super(502, message); - } - - public BadGatewayResponse() { - super(502, "Bad gateway"); - } - -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/BadRequestResponse.java b/avaje-jex/src/main/java/io/avaje/jex/http/BadRequestResponse.java deleted file mode 100644 index 4f3096f1..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/http/BadRequestResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.avaje.jex.http; - -public class BadRequestResponse extends HttpResponseException { - - public BadRequestResponse(String message) { - super(400, message); - } - - public BadRequestResponse() { - super(400, "Bad request"); - } - -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/ConflictResponse.java b/avaje-jex/src/main/java/io/avaje/jex/http/ConflictResponse.java deleted file mode 100644 index 00286ae1..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/http/ConflictResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.avaje.jex.http; - -public class ConflictResponse extends HttpResponseException { - - public ConflictResponse(String message) { - super(409, message); - } - - public ConflictResponse() { - super(409, "Conflict"); - } - -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java b/avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java new file mode 100644 index 00000000..f1123dd4 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java @@ -0,0 +1,44 @@ +package io.avaje.jex.http; + +public enum ErrorCode { + REDIRECT(302, "Redirect"), + + // 4xx + BAD_REQUEST(400, "Bad Request"), + UNAUTHORIZED(401, "Unauthorized"), + FORBIDDEN(403, "Forbidden"), + NOT_FOUND(404, "Not Found"), + METHOD_NOT_ALLOWED(405, "Method Not Allowed"), + REQUEST_TIMEOUT(408, "Request Timeout"), + CONFLICT(409, "Conflict"), + GONE(410, "Gone"), + + // 5xx Server Error + INTERNAL_SERVER_ERROR(500, "Internal Server Error"), + BAD_GATEWAY(502, "Bad Gateway"), + SERVICE_UNAVAILABLE(503, "Service Unavailable"), + GATEWAY_TIMEOUT(504, "Gateway Timeout"), + HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version Not Supported"), + VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates"), + INSUFFICIENT_STORAGE(507, "Insufficient Storage"), + LOOP_DETECTED(508, "Loop Detected"), + BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded"), + NOT_EXTENDED(510, "Not Extended"), + NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required"); + + private final int status; + private final String message; + + ErrorCode(int status, String message) { + this.status = status; + this.message = message; + } + + public int status() { + return status; + } + + public String message() { + return message; + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/ForbiddenResponse.java b/avaje-jex/src/main/java/io/avaje/jex/http/ForbiddenResponse.java deleted file mode 100644 index 737fe8fc..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/http/ForbiddenResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.avaje.jex.http; - -public class ForbiddenResponse extends HttpResponseException { - - public ForbiddenResponse(String message) { - super(403, message); - } - - public ForbiddenResponse() { - super(403, "Forbidden"); - } - -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/GatewayTimeoutResponse.java b/avaje-jex/src/main/java/io/avaje/jex/http/GatewayTimeoutResponse.java deleted file mode 100644 index efacb6da..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/http/GatewayTimeoutResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.avaje.jex.http; - -public class GatewayTimeoutResponse extends HttpResponseException { - - public GatewayTimeoutResponse(String message) { - super(504, message); - } - - public GatewayTimeoutResponse() { - super(504, "Gateway timeout"); - } - -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/GoneResponse.java b/avaje-jex/src/main/java/io/avaje/jex/http/GoneResponse.java deleted file mode 100644 index 279ee227..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/http/GoneResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.avaje.jex.http; - -public class GoneResponse extends HttpResponseException { - - public GoneResponse(String message) { - super(410, message); - } - - public GoneResponse() { - super(410, "Gone"); - } - -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java b/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java index a5ce0d64..1aef4859 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java @@ -5,8 +5,8 @@ public class HttpResponseException extends RuntimeException { - private int status; - private Map details; + private final int status; + private final Map details; public HttpResponseException(int status, String message, Map details) { super(message); @@ -18,6 +18,14 @@ public HttpResponseException(int status, String message) { this(status, message, Collections.emptyMap()); } + public HttpResponseException(ErrorCode code) { + this(code.status(), code.message()); + } + + public HttpResponseException(ErrorCode code, Map details) { + this(code.status(), code.message(), details); + } + public int getStatus() { return status; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorResponse.java b/avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorResponse.java deleted file mode 100644 index 04f44c1c..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.avaje.jex.http; - -public class InternalServerErrorResponse extends HttpResponseException { - - public InternalServerErrorResponse(String message) { - super(500, message); - } - - public InternalServerErrorResponse() { - super(500, "Internal server error"); - } - -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/MethodNotAllowedResponse.java b/avaje-jex/src/main/java/io/avaje/jex/http/MethodNotAllowedResponse.java deleted file mode 100644 index dabff066..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/http/MethodNotAllowedResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.avaje.jex.http; - -import java.util.Map; - -public class MethodNotAllowedResponse extends HttpResponseException { - - public MethodNotAllowedResponse(String message, Map details) { - super(403, message, details); - } - - public MethodNotAllowedResponse(String message) { - super(403, message); - } - -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/NotFoundResponse.java b/avaje-jex/src/main/java/io/avaje/jex/http/NotFoundResponse.java deleted file mode 100644 index cead2669..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/http/NotFoundResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.avaje.jex.http; - -public class NotFoundResponse extends HttpResponseException { - - public NotFoundResponse(String message) { - super(404, message); - } - - public NotFoundResponse() { - super(404, "Not found"); - } - -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/RedirectResponse.java b/avaje-jex/src/main/java/io/avaje/jex/http/RedirectResponse.java deleted file mode 100644 index 0295c0c2..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/http/RedirectResponse.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.avaje.jex.http; - -public class RedirectResponse extends HttpResponseException { - - /** - * Redirect with the given message. - */ - public RedirectResponse(String message) { - super(302, message); - } - - /** - * Redirect with the given http status code. - */ - public RedirectResponse(int statusCode) { - super(statusCode, null); - } - - /** - * Redirect with 302 http status code. - */ - public RedirectResponse() { - super(302, "Redirect"); - } - -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/ServiceUnavailableResponse.java b/avaje-jex/src/main/java/io/avaje/jex/http/ServiceUnavailableResponse.java deleted file mode 100644 index 84625911..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/http/ServiceUnavailableResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.avaje.jex.http; - -public class ServiceUnavailableResponse extends HttpResponseException { - - public ServiceUnavailableResponse(String message) { - super(503, message); - } - - public ServiceUnavailableResponse() { - super(503, "Service unavailable"); - } - -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/UnauthorizedResponse.java b/avaje-jex/src/main/java/io/avaje/jex/http/UnauthorizedResponse.java deleted file mode 100644 index b47c27cf..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/http/UnauthorizedResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.avaje.jex.http; - -public class UnauthorizedResponse extends HttpResponseException { - - public UnauthorizedResponse(String message) { - super(401, message); - } - - public UnauthorizedResponse() { - super(401, "Unauthorized"); - } - -} diff --git a/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/BaseHandler.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java similarity index 84% rename from avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/BaseHandler.java rename to avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java index f9beef54..43d0a61f 100644 --- a/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/BaseHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java @@ -2,9 +2,8 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; -import io.avaje.jex.Context; import io.avaje.jex.Routing; -import io.avaje.jex.http.NotFoundResponse; +import io.avaje.jex.http.HttpResponseException; import io.avaje.jex.spi.SpiContext; import io.avaje.jex.spi.SpiRoutes; @@ -72,18 +71,10 @@ private void processRoute(JdkContext ctx, String uri, SpiRoutes.Entry route) { private void processNoRoute(JdkContext ctx, String uri, Routing.Type routeType) { routes.before(uri, ctx); if (routeType == Routing.Type.HEAD && hasGetHandler(uri)) { - processHead(ctx); + ctx.status(200); return; } -// if (routeType == Routing.Type.GET || routeType == Routing.Type.HEAD) { -// // check if handled by static resource -// // check if handled by singlePageHandler -// } - throw new NotFoundResponse("uri: " + uri); - } - - private void processHead(Context ctx) { - ctx.status(200); + throw new HttpResponseException(404, "uri: " + uri); } private boolean hasGetHandler(String uri) { diff --git a/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java similarity index 81% rename from avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java rename to avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java index cc7065f6..9cf86328 100644 --- a/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java @@ -5,6 +5,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.Objects; class BufferedOutStream extends OutputStream { @@ -32,6 +33,19 @@ public void write(int b) throws IOException { } } + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (stream != null) { + stream.write(b, off, len); + } else { + count += len; + buffer.write(b, off, len); + if (count > max) { + initialiseChunked(); + } + } + } + /** * Use responseLength 0 and chunked response. */ diff --git a/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/CookieParser.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/CookieParser.java similarity index 100% rename from avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/CookieParser.java rename to avaje-jex/src/main/java/io/avaje/jex/jdk/CookieParser.java diff --git a/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java similarity index 93% rename from avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/JdkContext.java rename to avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index 450fa9de..ec67dfc0 100644 --- a/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -5,7 +5,8 @@ import io.avaje.jex.Context; import io.avaje.jex.Routing; import io.avaje.jex.UploadedFile; -import io.avaje.jex.http.RedirectResponse; +import io.avaje.jex.http.ErrorCode; +import io.avaje.jex.http.HttpResponseException; import io.avaje.jex.spi.HeaderKeys; import io.avaje.jex.spi.SpiContext; @@ -139,7 +140,7 @@ public void redirect(String location, int statusCode) { header(HeaderKeys.LOCATION, location); status(statusCode); if (mode == Routing.Type.BEFORE) { - throw new RedirectResponse(statusCode); + throw new HttpResponseException(ErrorCode.REDIRECT); } else { performRedirect(); } @@ -275,21 +276,6 @@ public String scheme() { return mgr.scheme(); } - @Override - public Context sessionAttribute(String key, Object value) { - throw new UnsupportedOperationException(); - } - - @Override - public T sessionAttribute(String key) { - throw new UnsupportedOperationException(); - } - - @Override - public Map sessionAttributeMap() { - throw new UnsupportedOperationException(); - } - @Override public String url() { return scheme() + "://" + host() + path; @@ -362,17 +348,6 @@ void writeBytes(byte[] bytes) throws IOException { os.close(); } - @Override - public boolean isCommitted() { - // no support for this - return false; - } - - @Override - public void reset() { - // do nothing - } - int statusCode() { return statusCode == 0 ? 200 : statusCode; } @@ -421,18 +396,6 @@ public String ip() { return address == null ? remote.getHostString() : address.getHostAddress(); } - @Override - public boolean isMultipart() { - // not really supported - return false; - } - - @Override - public boolean isMultipartFormData() { - // not really supported - return false; - } - @Override public String method() { return exchange.getRequestMethod(); diff --git a/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/JdkJexServer.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java similarity index 100% rename from avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/JdkJexServer.java rename to avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java diff --git a/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java similarity index 79% rename from avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/JdkServerStart.java rename to avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index 47e7d8a5..7ca5ccc9 100644 --- a/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -1,6 +1,12 @@ package io.avaje.jex.jdk; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.System.Logger.Level; +import java.net.InetSocketAddress; + import com.sun.net.httpserver.HttpServer; + import io.avaje.applog.AppLog; import io.avaje.jex.AppLifecycle; import io.avaje.jex.Jex; @@ -8,11 +14,6 @@ import io.avaje.jex.spi.SpiServiceManager; import io.avaje.jex.spi.SpiStartServer; -import java.io.IOException; -import java.lang.System.Logger.Level; -import java.net.InetSocketAddress; -import java.util.concurrent.Executor; - public class JdkServerStart implements SpiStartServer { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); @@ -22,22 +23,20 @@ public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceMana final ServiceManager manager = new ServiceManager(serviceManager, "http", ""); BaseHandler handler = new BaseHandler(routes, manager); try { - final HttpServer server = HttpServer.create(); - server.createContext("/", handler); - final Executor executor = jex.attribute(Executor.class); - if (executor != null) { - server.setExecutor(executor); - } + int port = jex.config().port(); - server.bind(new InetSocketAddress(port), 0); + final HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); + + server.createContext("/", handler); + server.setExecutor(jex.config().executor()); + server.start(); jex.lifecycle().status(AppLifecycle.Status.STARTED); String jexVersion = Jex.class.getPackage().getImplementationVersion(); log.log(Level.INFO, "started server on port {0,number,#} version {1}", port, jexVersion); return new JdkJexServer(server, jex.lifecycle(), handler); - } catch (IOException e) { - throw new RuntimeException(e); + throw new UncheckedIOException(e); } } } diff --git a/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/ServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/ServiceManager.java similarity index 73% rename from avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/ServiceManager.java rename to avaje-jex/src/main/java/io/avaje/jex/jdk/ServiceManager.java index 59df313c..14734a07 100644 --- a/avaje-jex-jdk/src/main/java/io/avaje/jex/jdk/ServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/ServiceManager.java @@ -9,8 +9,9 @@ class ServiceManager extends ProxyServiceManager { private final String scheme; private final String contextPath; - private final long outputBufferMax = 1024; - private final int outputBufferInitial = 256; + private static final long outputBufferMax = Long.getLong("jex.outputBuffer.max", 1024); + private static final int outputBufferInitial = + Integer.getInteger("jex.outputBuffer.initial", 256); ServiceManager(SpiServiceManager delegate, String scheme, String contextPath) { super(delegate); @@ -26,8 +27,8 @@ String scheme() { return scheme; } - public String url(JdkContext jdkContext) { - return scheme+"://"; + public String url() { + return scheme + "://"; } public String contextPath() { diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/BootstrapRoutes.java b/avaje-jex/src/main/java/io/avaje/jex/routes/BootstrapRoutes.java index 49b6449b..dfe3a8c7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/BootstrapRoutes.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/BootstrapRoutes.java @@ -4,11 +4,14 @@ import io.avaje.jex.Routing; import io.avaje.jex.spi.SpiRoutes; import io.avaje.jex.spi.SpiRoutesProvider; +import io.avaje.spi.ServiceProvider; +@ServiceProvider public class BootstrapRoutes implements SpiRoutesProvider { @Override - public SpiRoutes create(Routing routing, AccessManager accessManager, boolean ignoreTrailingSlashes) { + public SpiRoutes create( + Routing routing, AccessManager accessManager, boolean ignoreTrailingSlashes) { return new RoutesBuilder(routing, accessManager, ignoreTrailingSlashes).build(); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java new file mode 100644 index 00000000..f4064617 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java @@ -0,0 +1,6 @@ +package io.avaje.jex.spi; + +import io.avaje.spi.Service; + +@Service +public sealed interface JexExtension permits JsonService{} diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java index 8a85151b..821c407c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java @@ -5,7 +5,7 @@ /** * Service used to convert request/response bodies to beans. */ -public interface JsonService { +public non-sealed interface JsonService extends JexExtension { /** * Read the request body as a bean and return the bean. diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiRoutesProvider.java b/avaje-jex/src/main/java/io/avaje/jex/spi/SpiRoutesProvider.java index d6a4e118..a5fa1ea5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiRoutesProvider.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/SpiRoutesProvider.java @@ -3,7 +3,7 @@ import io.avaje.jex.AccessManager; import io.avaje.jex.Routing; -public interface SpiRoutesProvider { +public interface SpiRoutesProvider extends JexExtension { /** * Build and return the Routing. diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiServiceManagerProvider.java b/avaje-jex/src/main/java/io/avaje/jex/spi/SpiServiceManagerProvider.java index 90449ce5..ad78af0e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiServiceManagerProvider.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/SpiServiceManagerProvider.java @@ -2,7 +2,7 @@ import io.avaje.jex.Jex; -public interface SpiServiceManagerProvider { +public interface SpiServiceManagerProvider extends JexExtension { SpiServiceManager create(Jex jex); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiStartServer.java b/avaje-jex/src/main/java/io/avaje/jex/spi/SpiStartServer.java index fa068a15..6a469447 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiStartServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/SpiStartServer.java @@ -5,7 +5,7 @@ /** * Start the server. */ -public interface SpiStartServer { +public interface SpiStartServer extends JexExtension { /** * Return the started server. diff --git a/avaje-jex/src/main/java/module-info.java b/avaje-jex/src/main/java/module-info.java index a4b1eaf5..ccc914eb 100644 --- a/avaje-jex/src/main/java/module-info.java +++ b/avaje-jex/src/main/java/module-info.java @@ -1,6 +1,7 @@ import io.avaje.jex.TemplateRender; import io.avaje.jex.core.BootstapServiceManager; import io.avaje.jex.routes.BootstrapRoutes; +import io.avaje.jex.spi.JexExtension; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.SpiRoutesProvider; import io.avaje.jex.spi.SpiServiceManagerProvider; @@ -14,19 +15,21 @@ exports io.avaje.jex.core; requires transitive java.net.http; + requires transitive jdk.httpserver; requires transitive io.avaje.applog; requires static com.fasterxml.jackson.core; requires static com.fasterxml.jackson.databind; requires static io.avaje.jsonb; requires static io.avaje.inject; requires static io.avaje.config; + requires static io.avaje.spi; + uses JexExtension; uses TemplateRender; uses SpiRoutesProvider; uses SpiServiceManagerProvider; uses SpiStartServer; uses JsonService; - provides SpiRoutesProvider with BootstrapRoutes; - provides SpiServiceManagerProvider with BootstapServiceManager; + provides JexExtension with BootstrapRoutes, BootstapServiceManager; } diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/CharacterEncodingTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/CharacterEncodingTest.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/CharacterEncodingTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/CharacterEncodingTest.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/ContextAttributeTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextAttributeTest.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/ContextAttributeTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/ContextAttributeTest.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/ContextFormParamTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextFormParamTest.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/ContextFormParamTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/ContextFormParamTest.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/ContextLengthTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextLengthTest.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/ContextLengthTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/ContextLengthTest.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/ContextTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextTest.java similarity index 75% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/ContextTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/ContextTest.java index 478c347b..a1619978 100644 --- a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/ContextTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextTest.java @@ -34,7 +34,6 @@ static TestPair init() { requireNonNull(ip); ctx.text("ip:" + ip); }) - .post("/multipart", ctx -> ctx.text("isMultipart:" + ctx.isMultipart() + " isMultipartFormData:" + ctx.isMultipartFormData())) .get("/method", ctx -> ctx.text("method:" + ctx.method() + " path:" + ctx.path() + " protocol:" + ctx.protocol() + " port:" + ctx.port())) .post("/echo", ctx -> ctx.text("req-body[" + ctx.body() + "]")) .get("/{a}/{b}", ctx -> ctx.text("ze-get-" + ctx.pathParamMap())) @@ -110,44 +109,6 @@ void ctx_ip() { assertThat(res.body()).isEqualTo("ip:127.0.0.1"); } - @Test - void ctx_isMultiPart_when_not() { - HttpResponse res = pair.request().path("multipart") - .formParam("a", "aval") - .POST().asString(); - - assertThat(res.body()).isEqualTo("isMultipart:false isMultipartFormData:false"); - } - - - @Test - void ctx_isMultiPart_when_nothing() { - HttpResponse res = pair.request().path("multipart") - .body("junk") - .POST().asString(); - - assertThat(res.body()).isEqualTo("isMultipart:false isMultipartFormData:false"); - } - -// @Test -// void ctx_isMultiPart_when_isMultipart() { -// HttpResponse res = pair.request().path("multipart") -// .header("Content-Type", "multipart/foo") -// .body("junk") -// .POST().asString(); -// -// assertThat(res.body()).isEqualTo("isMultipart:true isMultipartFormData:false"); -// } -// -// @Test -// void ctx_isMultiPart_when_isMultipartFormData() { -// HttpResponse res = pair.request().path("multipart") -// .header("Content-Type", "multipart/form-data") -// .body("junk") -// .POST().asString(); -// -// assertThat(res.body()).isEqualTo("isMultipart:true isMultipartFormData:true"); -// } @Test void ctx_methodPathPortProtocol() { diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/CookieParserTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/CookieParserTest.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/CookieParserTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/CookieParserTest.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/CookieServerTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/CookieServerTest.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/CookieServerTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/CookieServerTest.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java similarity index 87% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java index 2e6f60ca..8670f5de 100644 --- a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java @@ -1,8 +1,9 @@ package io.avaje.jex.jdk; import io.avaje.jex.Jex; -import io.avaje.jex.http.ConflictResponse; -import io.avaje.jex.http.ForbiddenResponse; +import io.avaje.jex.http.ErrorCode; +import io.avaje.jex.http.HttpResponseException; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; @@ -18,20 +19,19 @@ static TestPair init() { final Jex app = Jex.create() .routing(routing -> routing .get("/", ctx -> { - throw new ForbiddenResponse(); + throw new HttpResponseException(ErrorCode.FORBIDDEN); }) .post("/", ctx -> { throw new IllegalStateException("foo"); }) .get("/conflict", ctx -> { - throw new ConflictResponse("Baz"); + throw new HttpResponseException(409, "Baz"); }) .get("/fiveHundred", ctx -> { throw new IllegalArgumentException("Bar"); })) .exception(NullPointerException.class, (exception, ctx) -> ctx.text("npe")) - .exception(IllegalStateException.class, (exception, ctx) -> ctx.status(222).text("Handled IllegalStateException|" + exception.getMessage())) - .exception(ForbiddenResponse.class, (exception, ctx) -> ctx.status(223).text("Handled ForbiddenResponse|" + exception.getMessage())); + .exception(IllegalStateException.class, (exception, ctx) -> ctx.status(222).text("Handled IllegalStateException|" + exception.getMessage())); return TestPair.create(app); } diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/FilterTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/FilterTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HeadersTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/HeadersTest.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HeadersTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/HeadersTest.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HealthPluginOffTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/HealthPluginOffTest.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HealthPluginOffTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/HealthPluginOffTest.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HealthPluginTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/HealthPluginTest.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HealthPluginTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/HealthPluginTest.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HelloBean.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/HelloBean.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HelloBean.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/HelloBean.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HelloDto.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/HelloDto.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/HelloDto.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/HelloDto.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/JsonTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/JsonTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/Main.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/Main.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/Main.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/Main.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/NestedRoutesTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/NestedRoutesTest.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/NestedRoutesTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/NestedRoutesTest.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/QueryParamTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/QueryParamTest.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/QueryParamTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/QueryParamTest.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/RedirectTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/RedirectTest.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/RedirectTest.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/RedirectTest.java diff --git a/avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/TestPair.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/TestPair.java similarity index 100% rename from avaje-jex-jdk/src/test/java/io/avaje/jex/jdk/TestPair.java rename to avaje-jex/src/test/java/io/avaje/jex/jdk/TestPair.java diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 73287faa..b518c9d5 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -23,7 +23,7 @@ io.avaje - avaje-jex-jdk + avaje-jex 2.6-SNAPSHOT diff --git a/pom.xml b/pom.xml index 9b17193c..e6e929b1 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,7 @@ true 2.15.0 false + 21 @@ -28,10 +29,6 @@ avaje-jex-test avaje-jex-freemarker avaje-jex-mustache - avaje-jex-jetty - avaje-jex-jdk - avaje-jex-grizzly - @@ -50,6 +47,13 @@ 2.0 test + + + io.avaje + avaje-spi-service + 2.8 + provided + From dd72d6cf5a36270ae78d0e153f35c4a4fb6fe0ce Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:54:42 -0500 Subject: [PATCH 015/250] start --- .github/workflows/build.yml | 4 +- .github/workflows/jdk-ea.yml | 2 +- .../src/main/java/io/avaje/jex/DJex.java | 24 +-- .../java/io/avaje/jex/TemplateRender.java | 2 +- .../jex/core/BootstapServiceManager.java | 14 -- .../io/avaje/jex/core/CoreServiceLoader.java | 197 ------------------ .../jex/core/internal/CoreServiceLoader.java | 98 +++++++++ .../{ => internal}/CoreServiceManager.java | 17 +- .../core/{ => internal}/ExceptionManager.java | 2 +- .../core/{ => internal}/HttpMethodMap.java | 2 +- .../java/io/avaje/jex/jdk/BaseHandler.java | 2 +- .../java/io/avaje/jex/jdk/JdkServerStart.java | 6 +- .../io/avaje/jex/routes/BootstrapRoutes.java | 17 -- .../java/io/avaje/jex/routes/FilterEntry.java | 1 - .../java/io/avaje/jex/routes/RouteEntry.java | 1 - .../java/io/avaje/jex/routes/RouteIndex.java | 2 - .../main/java/io/avaje/jex/routes/Routes.java | 3 +- .../io/avaje/jex/routes/RoutesBuilder.java | 5 +- .../avaje/jex/{spi => routes}/SpiRoutes.java | 5 +- .../java/io/avaje/jex/spi/JexExtension.java | 3 +- .../io/avaje/jex/spi/ProxyServiceManager.java | 2 +- .../io/avaje/jex/spi/SpiRoutesProvider.java | 13 -- .../jex/spi/SpiServiceManagerProvider.java | 8 - .../java/io/avaje/jex/spi/SpiStartServer.java | 15 -- avaje-jex/src/main/java/module-info.java | 14 -- .../io.avaje.jex.spi.SpiRoutesProvider | 1 - ...io.avaje.jex.spi.SpiServiceManagerProvider | 1 - .../core/{ => internal}/ContextUtilTest.java | 4 +- .../io/avaje/jex/routes/RouteIndexTest.java | 2 +- 29 files changed, 139 insertions(+), 328 deletions(-) delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/core/BootstapServiceManager.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java rename avaje-jex/src/main/java/io/avaje/jex/core/{ => internal}/CoreServiceManager.java (91%) rename avaje-jex/src/main/java/io/avaje/jex/core/{ => internal}/ExceptionManager.java (98%) rename avaje-jex/src/main/java/io/avaje/jex/core/{ => internal}/HttpMethodMap.java (91%) delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/routes/BootstrapRoutes.java rename avaje-jex/src/main/java/io/avaje/jex/{spi => routes}/SpiRoutes.java (94%) delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/spi/SpiRoutesProvider.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/spi/SpiServiceManagerProvider.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/spi/SpiStartServer.java delete mode 100644 avaje-jex/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiRoutesProvider delete mode 100644 avaje-jex/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiServiceManagerProvider rename avaje-jex/src/test/java/io/avaje/jex/core/{ => internal}/ContextUtilTest.java (90%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e3c763b7..e9c216e3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,12 +13,12 @@ jobs: strategy: fail-fast: false matrix: - java_version: [11, 17] + java_version: [21] os: [ubuntu-latest] steps: - uses: actions/checkout@v2 - - name: Set up JDK 11 + - name: Set up JDK uses: actions/setup-java@v2 with: java-version: ${{ matrix.java_version }} diff --git a/.github/workflows/jdk-ea.yml b/.github/workflows/jdk-ea.yml index 49b05c71..e9a76d5a 100644 --- a/.github/workflows/jdk-ea.yml +++ b/.github/workflows/jdk-ea.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - java_version: [EA,GA] ## valhalla (fails javadoc) + java_version: [EA, GA] ## valhalla (fails javadoc) os: [ubuntu-latest] steps: diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index 7c711c9c..04e8fb2d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -2,6 +2,10 @@ import io.avaje.inject.BeanScope; import io.avaje.jex.core.HealthPlugin; +import io.avaje.jex.core.internal.CoreServiceManager; +import io.avaje.jex.jdk.JdkServerStart; +import io.avaje.jex.routes.RoutesBuilder; +import io.avaje.jex.routes.SpiRoutes; import io.avaje.jex.spi.*; import java.util.*; @@ -159,20 +163,10 @@ public Server start() { plugin(new HealthPlugin()); } final SpiRoutes routes = - ServiceLoader.load(SpiRoutesProvider.class) - .findFirst() - .orElseThrow() - .create(this.routing, this.config.accessManager(), this.config.ignoreTrailingSlashes()); - - final SpiServiceManager serviceManager = - ServiceLoader.load(SpiServiceManagerProvider.class).findFirst().orElseThrow().create(this); - - final Optional start = ServiceLoader.load(SpiStartServer.class).findFirst(); - if (start.isEmpty()) { - throw new IllegalStateException( - "There is no SpiStartServer? Missing dependency on jex-jetty?"); - } - return start.get().start(this, routes, serviceManager); - } + new RoutesBuilder( + this.routing, this.config.accessManager(), this.config.ignoreTrailingSlashes()) + .build(); + return new JdkServerStart().start(this, routes, CoreServiceManager.create(this)); + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java b/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java index 85d75088..1d58d8f4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java +++ b/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java @@ -7,7 +7,7 @@ /** * Template rendering typically of html. */ -public interface TemplateRender extends JexExtension { +public non-sealed interface TemplateRender extends JexExtension { /** * Return the extensions this template renders for by default. diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstapServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstapServiceManager.java deleted file mode 100644 index b8b2986b..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstapServiceManager.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.avaje.jex.core; - -import io.avaje.jex.Jex; -import io.avaje.jex.spi.SpiServiceManager; -import io.avaje.jex.spi.SpiServiceManagerProvider; -import io.avaje.spi.ServiceProvider; - -@ServiceProvider -public class BootstapServiceManager implements SpiServiceManagerProvider { - @Override - public SpiServiceManager create(Jex jex) { - return CoreServiceManager.create(jex); - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java deleted file mode 100644 index b8d959b1..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java +++ /dev/null @@ -1,197 +0,0 @@ -package io.avaje.jex.core; - -import io.avaje.applog.AppLog; -import io.avaje.jex.*; -import io.avaje.jex.spi.HeaderKeys; -import io.avaje.jex.spi.JsonService; -import io.avaje.jex.spi.SpiContext; -import io.avaje.jex.spi.SpiServiceManager; - -import java.io.UncheckedIOException; -import java.io.UnsupportedEncodingException; -import java.lang.System.Logger.Level; -import java.net.URLDecoder; -import java.util.*; -import java.util.stream.Stream; - -/** - * Core implementation of SpiServiceManager provided to specific implementations like jetty etc. - */ -class CoreServiceLoader implements SpiServiceManager { - - private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); - public static final String UTF_8 = "UTF-8"; - - private final HttpMethodMap methodMap = new HttpMethodMap(); - private final JsonService jsonService; - private final ExceptionManager exceptionHandler; - private final TemplateManager templateManager; - - static SpiServiceManager create(Jex jex) { - return new Builder(jex).build(); - } - - CoreServiceLoader(JsonService jsonService, ErrorHandling errorHandling, TemplateManager templateManager) { - this.jsonService = jsonService; - this.exceptionHandler = new ExceptionManager(errorHandling); - this.templateManager = templateManager; - } - - @Override - public T jsonRead(Class clazz, SpiContext ctx) { - return jsonService.jsonRead(clazz, ctx); - } - - @Override - public void jsonWrite(Object bean, SpiContext ctx) { - jsonService.jsonWrite(bean, ctx); - } - - @Override - public void jsonWriteStream(Stream stream, SpiContext ctx) { - try (stream) { - jsonService.jsonWriteStream(stream.iterator(), ctx); - } - } - - @Override - public void jsonWriteStream(Iterator iterator, SpiContext ctx) { - try { - jsonService.jsonWriteStream(iterator, ctx); - } finally { - maybeClose(iterator); - } - } - - @Override - public void maybeClose(Object iterator) { - if (iterator instanceof AutoCloseable) { - try { - ((AutoCloseable) iterator).close(); - } catch (Exception e) { - throw new RuntimeException("Error closing iterator " + iterator, e); - } - } - } - - @Override - public Routing.Type lookupRoutingType(String method) { - return methodMap.get(method); - } - - @Override - public void handleException(SpiContext ctx, Exception e) { - exceptionHandler.handle(ctx, e); - } - - @Override - public void render(Context ctx, String name, Map model) { - templateManager.render(ctx, name, model); - } - - - @Override - public String requestCharset(Context ctx) { - return parseCharset(ctx.header(HeaderKeys.CONTENT_TYPE)); - } - - static String parseCharset(String header) { - if (header != null) { - for (String val : header.split(";")) { - val = val.trim(); - if (val.regionMatches(true, 0, "charset", 0, "charset".length())) { - return val.split("=")[1].trim(); - } - } - } - return UTF_8; - } - - @Override - public Map> formParamMap(Context ctx, String charset) { - return parseParamMap(ctx.body(), charset); - } - - @Override - public Map> parseParamMap(String body, String charset) { - if (body == null || body.isEmpty()) { - return Collections.emptyMap(); - } - try { - Map> map = new LinkedHashMap<>(); - for (String pair : body.split("&")) { - final String[] split1 = pair.split("=", 2); - String key = URLDecoder.decode(split1[0], charset); - String val = split1.length > 1 ? URLDecoder.decode(split1[1], charset) : ""; - map.computeIfAbsent(key, s -> new ArrayList<>()).add(val); - } - return map; - } catch (UnsupportedEncodingException e) { - throw new UncheckedIOException(e); - } - } - - private static class Builder { - private final Jex jex; - - Builder(Jex jex) { - this.jex = jex; - } - - SpiServiceManager build() { - return new CoreServiceLoader(initJsonService(), jex.errorHandling(), initTemplateMgr()); - } - - JsonService initJsonService() { - final JsonService jsonService = jex.config().jsonService(); - if (jsonService != null) { - return jsonService; - } - return ServiceLoader.load(JsonService.class) - .findFirst() - .orElseGet(this::defaultJsonService); - } - - /** - * Create a reasonable default JsonService if Jackson or avaje-jsonb are present. - */ - JsonService defaultJsonService() { - if (detectJackson()) { - try { - return new JacksonJsonService(); - } catch (IllegalAccessError errorNotInModulePath) { - // not in module path - log.log(Level.DEBUG, "Not using Jackson due to module path {0}", errorNotInModulePath.getMessage()); - } - } - return detectJsonb() ? new JsonbJsonService() : null; - } - - boolean detectJackson() { - return detectTypeExists("com.fasterxml.jackson.databind.ObjectMapper"); - } - - boolean detectJsonb() { - return detectTypeExists("io.avaje.jsonb.Jsonb"); - } - - private boolean detectTypeExists(String className) { - try { - Class.forName(className); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - - TemplateManager initTemplateMgr() { - TemplateManager mgr = new TemplateManager(); - mgr.register(jex.config().renderers()); - for (TemplateRender render : ServiceLoader.load(TemplateRender.class)) { - mgr.registerDefault(render); - } - return mgr; - } - - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java new file mode 100644 index 00000000..7e062bba --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java @@ -0,0 +1,98 @@ +package io.avaje.jex.core.internal; + +import io.avaje.applog.AppLog; +import io.avaje.config.ConfigExtension; +import io.avaje.config.ConfigParser; +import io.avaje.config.ConfigServiceLoader; +import io.avaje.config.ConfigurationLog; +import io.avaje.config.ConfigurationPlugin; +import io.avaje.config.ConfigurationSource; +import io.avaje.config.CoreConfiguration; +import io.avaje.config.DefaultConfigurationLog; +import io.avaje.config.DefaultResourceLoader; +import io.avaje.config.ModificationEventRunner; +import io.avaje.config.Parsers; +import io.avaje.config.ResourceLoader; +import io.avaje.jex.*; +import io.avaje.jex.spi.HeaderKeys; +import io.avaje.jex.spi.JsonService; +import io.avaje.jex.spi.SpiContext; +import io.avaje.jex.spi.SpiServiceManager; + +import java.io.UncheckedIOException; +import java.io.UnsupportedEncodingException; +import java.lang.System.Logger.Level; +import java.net.URLDecoder; +import java.util.*; +import java.util.stream.Stream; + +/** + * Core implementation of SpiServiceManager provided to specific implementations like jetty etc. + */ +class CoreServiceLoader { + + private static final ConfigServiceLoader INSTANCE = new ConfigServiceLoader(); + + static ConfigServiceLoader get() { + return INSTANCE; + } + + private final ConfigurationLog log; + private final ResourceLoader resourceLoader; + private final ModificationEventRunner eventRunner; + private final List sources = new ArrayList<>(); + private final List plugins = new ArrayList<>(); + private final Parsers parsers; + + ConfigServiceLoader() { + ModificationEventRunner _eventRunner = null; + ConfigurationLog _log = null; + ResourceLoader _resourceLoader = null; + List otherParsers = new ArrayList<>(); + + for (var spi : ServiceLoader.load(ConfigExtension.class)) { + if (spi instanceof ConfigurationSource) { + sources.add((ConfigurationSource) spi); + } else if (spi instanceof ConfigurationPlugin) { + plugins.add((ConfigurationPlugin) spi); + } else if (spi instanceof ConfigParser) { + otherParsers.add((ConfigParser) spi); + } else if (spi instanceof ConfigurationLog) { + _log = (ConfigurationLog) spi; + } else if (spi instanceof ResourceLoader) { + _resourceLoader = (ResourceLoader) spi; + } else if (spi instanceof ModificationEventRunner) { + _eventRunner = (ModificationEventRunner) spi; + } + } + + this.log = _log == null ? new DefaultConfigurationLog() : _log; + this.resourceLoader = _resourceLoader == null ? new DefaultResourceLoader() : _resourceLoader; + this.eventRunner = _eventRunner == null ? new CoreConfiguration.ForegroundEventRunner() : _eventRunner; + this.parsers = new Parsers(otherParsers); + } + + Parsers parsers() { + return parsers; + } + + ConfigurationLog log() { + return log; + } + + ResourceLoader resourceLoader() { + return resourceLoader; + } + + ModificationEventRunner eventRunner() { + return eventRunner; + } + + List sources() { + return sources; + } + + List plugins() { + return plugins; + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java similarity index 91% rename from avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java rename to avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java index 18b7131f..63a430c4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java @@ -1,7 +1,10 @@ -package io.avaje.jex.core; +package io.avaje.jex.core.internal; import io.avaje.applog.AppLog; import io.avaje.jex.*; +import io.avaje.jex.core.JacksonJsonService; +import io.avaje.jex.core.JsonbJsonService; +import io.avaje.jex.core.TemplateManager; import io.avaje.jex.spi.HeaderKeys; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.SpiContext; @@ -17,7 +20,7 @@ /** * Core implementation of SpiServiceManager provided to specific implementations like jetty etc. */ -class CoreServiceManager implements SpiServiceManager { +public class CoreServiceManager implements SpiServiceManager { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); public static final String UTF_8 = "UTF-8"; @@ -27,11 +30,11 @@ class CoreServiceManager implements SpiServiceManager { private final ExceptionManager exceptionHandler; private final TemplateManager templateManager; - static SpiServiceManager create(Jex jex) { + public static SpiServiceManager create(Jex jex) { return new Builder(jex).build(); } - CoreServiceManager(JsonService jsonService, ErrorHandling errorHandling, TemplateManager templateManager) { + public CoreServiceManager(JsonService jsonService, ErrorHandling errorHandling, TemplateManager templateManager) { this.jsonService = jsonService; this.exceptionHandler = new ExceptionManager(errorHandling); this.templateManager = templateManager; @@ -65,9 +68,9 @@ public void jsonWriteStream(Iterator iterator, SpiContext ctx) { @Override public void maybeClose(Object iterator) { - if (iterator instanceof AutoCloseable) { - try { - ((AutoCloseable) iterator).close(); + if (iterator instanceof AutoCloseable closeable) { + try (closeable) { + // nothing } catch (Exception e) { throw new RuntimeException("Error closing iterator " + iterator, e); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/ExceptionManager.java similarity index 98% rename from avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java rename to avaje-jex/src/main/java/io/avaje/jex/core/internal/ExceptionManager.java index 8278ad87..767277fe 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/ExceptionManager.java @@ -1,4 +1,4 @@ -package io.avaje.jex.core; +package io.avaje.jex.core.internal; import io.avaje.applog.AppLog; import io.avaje.jex.ErrorHandling; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/HttpMethodMap.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/HttpMethodMap.java similarity index 91% rename from avaje-jex/src/main/java/io/avaje/jex/core/HttpMethodMap.java rename to avaje-jex/src/main/java/io/avaje/jex/core/internal/HttpMethodMap.java index d44dca71..f8d1c2c5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/HttpMethodMap.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/HttpMethodMap.java @@ -1,4 +1,4 @@ -package io.avaje.jex.core; +package io.avaje.jex.core.internal; import io.avaje.jex.Routing; diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java index 43d0a61f..fbf923d2 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java @@ -4,8 +4,8 @@ import com.sun.net.httpserver.HttpHandler; import io.avaje.jex.Routing; import io.avaje.jex.http.HttpResponseException; +import io.avaje.jex.routes.SpiRoutes; import io.avaje.jex.spi.SpiContext; -import io.avaje.jex.spi.SpiRoutes; import java.util.Map; diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index 7ca5ccc9..35e828e8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -10,15 +10,13 @@ import io.avaje.applog.AppLog; import io.avaje.jex.AppLifecycle; import io.avaje.jex.Jex; -import io.avaje.jex.spi.SpiRoutes; +import io.avaje.jex.routes.SpiRoutes; import io.avaje.jex.spi.SpiServiceManager; -import io.avaje.jex.spi.SpiStartServer; -public class JdkServerStart implements SpiStartServer { +public class JdkServerStart { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); - @Override public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceManager) { final ServiceManager manager = new ServiceManager(serviceManager, "http", ""); BaseHandler handler = new BaseHandler(routes, manager); diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/BootstrapRoutes.java b/avaje-jex/src/main/java/io/avaje/jex/routes/BootstrapRoutes.java deleted file mode 100644 index dfe3a8c7..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/BootstrapRoutes.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.avaje.jex.routes; - -import io.avaje.jex.AccessManager; -import io.avaje.jex.Routing; -import io.avaje.jex.spi.SpiRoutes; -import io.avaje.jex.spi.SpiRoutesProvider; -import io.avaje.spi.ServiceProvider; - -@ServiceProvider -public class BootstrapRoutes implements SpiRoutesProvider { - - @Override - public SpiRoutes create( - Routing routing, AccessManager accessManager, boolean ignoreTrailingSlashes) { - return new RoutesBuilder(routing, accessManager, ignoreTrailingSlashes).build(); - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/FilterEntry.java b/avaje-jex/src/main/java/io/avaje/jex/routes/FilterEntry.java index 340ca1a3..6f4f5411 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/FilterEntry.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/FilterEntry.java @@ -3,7 +3,6 @@ import io.avaje.jex.Context; import io.avaje.jex.Handler; import io.avaje.jex.Routing; -import io.avaje.jex.spi.SpiRoutes; import java.util.Map; diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java index 3c5d1a71..d39efc58 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java @@ -2,7 +2,6 @@ import io.avaje.jex.Context; import io.avaje.jex.Handler; -import io.avaje.jex.spi.SpiRoutes; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java index 62127c95..ba90afaf 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java @@ -1,7 +1,5 @@ package io.avaje.jex.routes; -import io.avaje.jex.spi.SpiRoutes; - import java.util.ArrayList; import java.util.List; diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java b/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java index 4fb07131..dfa4ffd7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java @@ -3,7 +3,6 @@ import io.avaje.applog.AppLog; import io.avaje.jex.Routing; import io.avaje.jex.spi.SpiContext; -import io.avaje.jex.spi.SpiRoutes; import java.lang.System.Logger.Level; import java.util.EnumMap; @@ -11,7 +10,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.LockSupport; -class Routes implements SpiRoutes { +final class Routes implements SpiRoutes { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java index 96fd816f..28a57d78 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java @@ -1,14 +1,13 @@ package io.avaje.jex.routes; import io.avaje.jex.*; -import io.avaje.jex.spi.SpiRoutes; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; import java.util.Set; -class RoutesBuilder { +public class RoutesBuilder { private final EnumMap typeMap = new EnumMap<>(Routing.Type.class); private final List before = new ArrayList<>(); @@ -16,7 +15,7 @@ class RoutesBuilder { private final boolean ignoreTrailingSlashes; private final AccessManager accessManager; - RoutesBuilder(Routing routing, AccessManager accessManager, boolean ignoreTrailingSlashes) { + public RoutesBuilder(Routing routing, AccessManager accessManager, boolean ignoreTrailingSlashes) { this.accessManager = accessManager; this.ignoreTrailingSlashes = ignoreTrailingSlashes; for (Routing.Entry handler : routing.all()) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiRoutes.java b/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java similarity index 94% rename from avaje-jex/src/main/java/io/avaje/jex/spi/SpiRoutes.java rename to avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java index 7e819044..6836aedf 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiRoutes.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java @@ -1,14 +1,15 @@ -package io.avaje.jex.spi; +package io.avaje.jex.routes; import io.avaje.jex.Context; import io.avaje.jex.Routing; +import io.avaje.jex.spi.SpiContext; import java.util.Map; /** * Route matching and filter handling. */ -public interface SpiRoutes { +public sealed interface SpiRoutes permits Routes { /** * Find the matching handler entry given the type and request URI. diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java index f4064617..31b9c132 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java @@ -1,6 +1,7 @@ package io.avaje.jex.spi; +import io.avaje.jex.TemplateRender; import io.avaje.spi.Service; @Service -public sealed interface JexExtension permits JsonService{} +public sealed interface JexExtension permits JsonService, TemplateRender {} diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/ProxyServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/spi/ProxyServiceManager.java index 994b850e..aeb80c02 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/ProxyServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/ProxyServiceManager.java @@ -18,7 +18,7 @@ public abstract class ProxyServiceManager implements SpiServiceManager { protected final SpiServiceManager delegate; - public ProxyServiceManager(SpiServiceManager delegate) { + protected ProxyServiceManager(SpiServiceManager delegate) { this.delegate = delegate; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiRoutesProvider.java b/avaje-jex/src/main/java/io/avaje/jex/spi/SpiRoutesProvider.java deleted file mode 100644 index a5fa1ea5..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiRoutesProvider.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.avaje.jex.spi; - -import io.avaje.jex.AccessManager; -import io.avaje.jex.Routing; - -public interface SpiRoutesProvider extends JexExtension { - - /** - * Build and return the Routing. - */ - SpiRoutes create(Routing routing, AccessManager accessManager, boolean ignoreTrailingSlashes); - -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiServiceManagerProvider.java b/avaje-jex/src/main/java/io/avaje/jex/spi/SpiServiceManagerProvider.java deleted file mode 100644 index ad78af0e..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiServiceManagerProvider.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.avaje.jex.spi; - -import io.avaje.jex.Jex; - -public interface SpiServiceManagerProvider extends JexExtension { - - SpiServiceManager create(Jex jex); -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiStartServer.java b/avaje-jex/src/main/java/io/avaje/jex/spi/SpiStartServer.java deleted file mode 100644 index 6a469447..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiStartServer.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.avaje.jex.spi; - -import io.avaje.jex.Jex; - -/** - * Start the server. - */ -public interface SpiStartServer extends JexExtension { - - /** - * Return the started server. - */ - Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceManager); - -} diff --git a/avaje-jex/src/main/java/module-info.java b/avaje-jex/src/main/java/module-info.java index ccc914eb..8b483c0d 100644 --- a/avaje-jex/src/main/java/module-info.java +++ b/avaje-jex/src/main/java/module-info.java @@ -1,11 +1,4 @@ -import io.avaje.jex.TemplateRender; -import io.avaje.jex.core.BootstapServiceManager; -import io.avaje.jex.routes.BootstrapRoutes; import io.avaje.jex.spi.JexExtension; -import io.avaje.jex.spi.JsonService; -import io.avaje.jex.spi.SpiRoutesProvider; -import io.avaje.jex.spi.SpiServiceManagerProvider; -import io.avaje.jex.spi.SpiStartServer; module io.avaje.jex { @@ -25,11 +18,4 @@ requires static io.avaje.spi; uses JexExtension; - uses TemplateRender; - uses SpiRoutesProvider; - uses SpiServiceManagerProvider; - uses SpiStartServer; - uses JsonService; - - provides JexExtension with BootstrapRoutes, BootstapServiceManager; } diff --git a/avaje-jex/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiRoutesProvider b/avaje-jex/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiRoutesProvider deleted file mode 100644 index 8d8e8739..00000000 --- a/avaje-jex/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiRoutesProvider +++ /dev/null @@ -1 +0,0 @@ -io.avaje.jex.routes.BootstrapRoutes diff --git a/avaje-jex/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiServiceManagerProvider b/avaje-jex/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiServiceManagerProvider deleted file mode 100644 index deef4fcd..00000000 --- a/avaje-jex/src/main/resources/META-INF/services/io.avaje.jex.spi.SpiServiceManagerProvider +++ /dev/null @@ -1 +0,0 @@ -io.avaje.jex.core.BootstapServiceManager diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/internal/ContextUtilTest.java similarity index 90% rename from avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/internal/ContextUtilTest.java index 944bcc8b..ada6e09f 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/internal/ContextUtilTest.java @@ -1,7 +1,9 @@ -package io.avaje.jex.core; +package io.avaje.jex.core.internal; import org.junit.jupiter.api.Test; +import io.avaje.jex.core.internal.CoreServiceManager; + import static org.assertj.core.api.Assertions.assertThat; class ContextUtilTest { diff --git a/avaje-jex/src/test/java/io/avaje/jex/routes/RouteIndexTest.java b/avaje-jex/src/test/java/io/avaje/jex/routes/RouteIndexTest.java index 5ef62346..0e7d2487 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/routes/RouteIndexTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/routes/RouteIndexTest.java @@ -1,7 +1,7 @@ package io.avaje.jex.routes; import io.avaje.jex.Routing; -import io.avaje.jex.spi.SpiRoutes; + import org.junit.jupiter.api.Test; import org.mockito.Mockito; From 527cc42d7b5094a5fd6832476ec5ef5dbe9e00d0 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:56:21 -0500 Subject: [PATCH 016/250] remove redundant --- avaje-jex-mustache/pom.xml | 7 ------ examples/example-jdk-jsonb/pom.xml | 36 +----------------------------- 2 files changed, 1 insertion(+), 42 deletions(-) diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 938e201e..f2fc4a38 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -30,13 +30,6 @@ - - io.avaje - avaje-jex-jdk - 2.6-SNAPSHOT - test - - com.fasterxml.jackson.core jackson-databind diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index c344e05d..c6167cd2 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -26,7 +26,7 @@ io.avaje - avaje-jex-jdk + avaje-jex 2.6-SNAPSHOT @@ -35,40 +35,6 @@ avaje-jsonb 1.4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From ea682eb4b5d1efaaa9bca5db7ebc461f68f1bf02 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:10:06 -0500 Subject: [PATCH 017/250] consolidate spi interfaces --- .gitignore | 6 +- avaje-jex-freemarker/pom.xml | 7 -- .../render/freemarker/FreeMarkerRender.java | 11 +- .../src/main/java/module-info.java | 6 +- .../services/io.avaje.jex.TemplateRender | 1 - .../src/main/java/module-info.java | 2 + .../src/main/java/io/avaje/jex/BootJex.java | 24 ++++- .../main/java/io/avaje/jex/BootJexState.java | 7 +- .../main/java/io/avaje/jex/DJexConfig.java | 23 ++-- .../io/avaje/jex/DefaultErrorHandling.java | 1 + .../src/main/java/io/avaje/jex/JexConfig.java | 8 +- .../java/io/avaje/jex/StaticFileSource.java | 8 +- .../jex/core/internal/CoreServiceLoader.java | 102 +++++------------- .../jex/core/internal/CoreServiceManager.java | 5 +- .../io/avaje/jex/jdk/BufferedOutStream.java | 23 ++-- .../java/io/avaje/jex/jdk/CookieParser.java | 29 +++-- .../java/io/avaje/jex/jdk/JdkServerStart.java | 3 +- .../java/io/avaje/jex/jdk/ServiceManager.java | 7 +- .../avaje/jex/routes/PathSegmentParser.java | 9 +- .../java/io/avaje/jex/spi/HeaderKeys.java | 2 + .../java/io/avaje/jex/spi/SpiContext.java | 2 +- .../src/main/java/module-info.java | 2 +- .../src/main/java/module-info.java | 2 +- pom.xml | 2 +- 24 files changed, 122 insertions(+), 170 deletions(-) delete mode 100644 avaje-jex-freemarker/src/main/resources/META-INF/services/io.avaje.jex.TemplateRender diff --git a/.gitignore b/.gitignore index 755d9ccc..c7ccf989 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,5 @@ build/ .gradle .project *.prefs -avaje-jex-freemarker/.classpath -avaje-jex-jdk/.classpath -avaje-jex-jetty/.classpath -avaje-jex/.classpath +*.classpath +*.prefs diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 04544058..230d675c 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -29,13 +29,6 @@ - - io.avaje - avaje-jex-jdk - 2.6-SNAPSHOT - test - - com.fasterxml.jackson.core jackson-databind diff --git a/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java b/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java index a5878059..b9648228 100644 --- a/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java +++ b/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java @@ -1,5 +1,9 @@ package io.avaje.jex.render.freemarker; +import java.io.IOException; +import java.io.StringWriter; +import java.io.UncheckedIOException; +import java.util.Map; import freemarker.template.Configuration; import freemarker.template.Template; @@ -7,12 +11,9 @@ import freemarker.template.Version; import io.avaje.jex.Context; import io.avaje.jex.TemplateRender; +import io.avaje.spi.ServiceProvider; -import java.io.IOException; -import java.io.StringWriter; -import java.io.UncheckedIOException; -import java.util.Map; - +@ServiceProvider public class FreeMarkerRender implements TemplateRender { private final Configuration configuration; diff --git a/avaje-jex-freemarker/src/main/java/module-info.java b/avaje-jex-freemarker/src/main/java/module-info.java index 8c422744..93741ac3 100644 --- a/avaje-jex-freemarker/src/main/java/module-info.java +++ b/avaje-jex-freemarker/src/main/java/module-info.java @@ -1,9 +1,13 @@ +import io.avaje.jex.render.freemarker.FreeMarkerRender; +import io.avaje.jex.spi.JexExtension; + open module io.avaje.jex.freemarker { requires transitive io.avaje.jex; requires transitive freemarker; requires java.net.http; + requires static io.avaje.spi; - provides io.avaje.jex.TemplateRender with io.avaje.jex.render.freemarker.FreeMarkerRender; + provides JexExtension with FreeMarkerRender; } diff --git a/avaje-jex-freemarker/src/main/resources/META-INF/services/io.avaje.jex.TemplateRender b/avaje-jex-freemarker/src/main/resources/META-INF/services/io.avaje.jex.TemplateRender deleted file mode 100644 index d9fbcc87..00000000 --- a/avaje-jex-freemarker/src/main/resources/META-INF/services/io.avaje.jex.TemplateRender +++ /dev/null @@ -1 +0,0 @@ -io.avaje.jex.render.freemarker.FreeMarkerRender diff --git a/avaje-jex-mustache/src/main/java/module-info.java b/avaje-jex-mustache/src/main/java/module-info.java index 77d4f013..4d87122c 100644 --- a/avaje-jex-mustache/src/main/java/module-info.java +++ b/avaje-jex-mustache/src/main/java/module-info.java @@ -4,5 +4,7 @@ requires transitive com.github.mustachejava; requires java.net.http; + requires static io.avaje.spi; + provides io.avaje.jex.TemplateRender with io.avaje.jex.render.mustache.MustacheRender; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/BootJex.java b/avaje-jex/src/main/java/io/avaje/jex/BootJex.java index f12d8241..2b78ab69 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/BootJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/BootJex.java @@ -1,5 +1,7 @@ package io.avaje.jex; +import io.avaje.inject.BeanScope; + /** * Start Jex using {@code @Controller} and avaje-inject, avaje-http, avaje-config. *

@@ -13,13 +15,27 @@ public interface BootJex { * Start Jex server using {@code @Controller} with avaje-inject, avaje-http, avaje-config. * *

{@code
-   *   public static void main(String[] args) {
+   * public static void main(String[] args) {
    *
-   *     BootJex.start();
-   *   }
+   *   BootJex.start();
+   * }
    * }
*/ static void start() { - BootJexState.start(); + BootJexState.start(BeanScope.builder().build()); + } + + /** + * Start Jex server using {@code @Controller} with avaje-inject, avaje-http, avaje-config. + * + *
{@code
+   * public static void main(String[] args) {
+   *
+   *   BootJex.start();
+   * }
+   * }
+ */ + static void start(BeanScope beanScope) { + BootJexState.start(beanScope); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java b/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java index c94ac006..34d9a0cb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java +++ b/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java @@ -7,8 +7,8 @@ class BootJexState { private static State state; - static void start() { - state = new BootJexState().create(); + static void start(BeanScope beanScope) { + state = new BootJexState().create(beanScope); } static void stop() { @@ -19,8 +19,7 @@ static void restart() { state.restart(); } - State create() { - BeanScope beanScope = BeanScope.builder().build(); + State create(BeanScope beanScope) { Jex jex = beanScope.getOptional(Jex.class).orElse(Jex.create()); jex.configureWith(beanScope); diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index ea1a8c3b..f3a0a2f8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -1,11 +1,10 @@ package io.avaje.jex; -import io.avaje.jex.spi.JsonService; - import java.util.HashMap; import java.util.Map; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +import io.avaje.jex.spi.JsonService; class DJexConfig implements JexConfig { @@ -14,7 +13,7 @@ class DJexConfig implements JexConfig { private String contextPath = "/"; private boolean health = true; private boolean ignoreTrailingSlashes = true; - private Executor executor; + private ThreadFactory factory; private boolean preCompressStaticFiles; private JsonService jsonService; @@ -90,19 +89,19 @@ public JexConfig renderer(String extension, TemplateRender renderer) { } @Override - public Executor executor() { + public ThreadFactory threadFactory() { - if (executor == null) { - executor = - Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("jex-http-", 0).factory()); + if (factory == null) { + factory = + Thread.ofVirtual().name("jex-http-", 0).factory(); } - return executor; + return factory; } @Override - public JexConfig executor(Executor executor) { - this.executor = executor; + public JexConfig threadFactory(ThreadFactory factory) { + this.factory = factory; return this; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultErrorHandling.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultErrorHandling.java index bbaeda6f..012eaf0e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultErrorHandling.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultErrorHandling.java @@ -23,6 +23,7 @@ public ErrorHandling error(int statusCode, String contentType, Handler handler) return null; } + @Override @SuppressWarnings("unchecked") public ExceptionHandler find(Class exceptionType) { Class type = exceptionType; diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index 62307b81..87e2f2e3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -1,8 +1,8 @@ package io.avaje.jex; import java.util.Map; -import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; import io.avaje.jex.spi.JsonService; @@ -70,14 +70,14 @@ public interface JexConfig { JexConfig renderer(String extension, TemplateRender renderer); /** - * Executor for serving requests. Defaults to {@link Executors#newVirtualThreadPerTaskExecutor()} + * ThreadFactory for serving requests. Defaults to a {@link Thread#ofVirtual()} factory */ - JexConfig executor(Executor executor); + JexConfig threadFactory(ThreadFactory executor); /** * Executor for serving requests. Defaults to {@link Executors#newVirtualThreadPerTaskExecutor()} */ - Executor executor(); + ThreadFactory threadFactory(); /** * Return the port to use. diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticFileSource.java b/avaje-jex/src/main/java/io/avaje/jex/StaticFileSource.java index 07ba465e..327264b2 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticFileSource.java +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticFileSource.java @@ -32,8 +32,12 @@ public Location getLocation() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } StaticFileSource that = (StaticFileSource) o; return urlPathPrefix.equals(that.urlPathPrefix) && path.equals(that.path) && diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java index 7e062bba..21b3de23 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java @@ -1,98 +1,44 @@ package io.avaje.jex.core.internal; -import io.avaje.applog.AppLog; -import io.avaje.config.ConfigExtension; -import io.avaje.config.ConfigParser; -import io.avaje.config.ConfigServiceLoader; -import io.avaje.config.ConfigurationLog; -import io.avaje.config.ConfigurationPlugin; -import io.avaje.config.ConfigurationSource; -import io.avaje.config.CoreConfiguration; -import io.avaje.config.DefaultConfigurationLog; -import io.avaje.config.DefaultResourceLoader; -import io.avaje.config.ModificationEventRunner; -import io.avaje.config.Parsers; -import io.avaje.config.ResourceLoader; -import io.avaje.jex.*; -import io.avaje.jex.spi.HeaderKeys; -import io.avaje.jex.spi.JsonService; -import io.avaje.jex.spi.SpiContext; -import io.avaje.jex.spi.SpiServiceManager; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.ServiceLoader; -import java.io.UncheckedIOException; -import java.io.UnsupportedEncodingException; -import java.lang.System.Logger.Level; -import java.net.URLDecoder; -import java.util.*; -import java.util.stream.Stream; +import io.avaje.jex.TemplateRender; +import io.avaje.jex.spi.JexExtension; +import io.avaje.jex.spi.JsonService; -/** - * Core implementation of SpiServiceManager provided to specific implementations like jetty etc. - */ +/** Core implementation of SpiServiceManager provided to specific implementations like jetty etc. */ class CoreServiceLoader { - private static final ConfigServiceLoader INSTANCE = new ConfigServiceLoader(); + private static final CoreServiceLoader INSTANCE = new CoreServiceLoader(); - static ConfigServiceLoader get() { + static CoreServiceLoader get() { return INSTANCE; } - private final ConfigurationLog log; - private final ResourceLoader resourceLoader; - private final ModificationEventRunner eventRunner; - private final List sources = new ArrayList<>(); - private final List plugins = new ArrayList<>(); - private final Parsers parsers; + private final JsonService jsonService; + private final List renders = new ArrayList<>(); + + CoreServiceLoader() { + JsonService spiJsonService = null; - ConfigServiceLoader() { - ModificationEventRunner _eventRunner = null; - ConfigurationLog _log = null; - ResourceLoader _resourceLoader = null; - List otherParsers = new ArrayList<>(); + for (var spi : ServiceLoader.load(JexExtension.class)) { - for (var spi : ServiceLoader.load(ConfigExtension.class)) { - if (spi instanceof ConfigurationSource) { - sources.add((ConfigurationSource) spi); - } else if (spi instanceof ConfigurationPlugin) { - plugins.add((ConfigurationPlugin) spi); - } else if (spi instanceof ConfigParser) { - otherParsers.add((ConfigParser) spi); - } else if (spi instanceof ConfigurationLog) { - _log = (ConfigurationLog) spi; - } else if (spi instanceof ResourceLoader) { - _resourceLoader = (ResourceLoader) spi; - } else if (spi instanceof ModificationEventRunner) { - _eventRunner = (ModificationEventRunner) spi; + switch (spi) { + case JsonService s -> spiJsonService = s; + case TemplateRender r -> renders.add(r); } } - - this.log = _log == null ? new DefaultConfigurationLog() : _log; - this.resourceLoader = _resourceLoader == null ? new DefaultResourceLoader() : _resourceLoader; - this.eventRunner = _eventRunner == null ? new CoreConfiguration.ForegroundEventRunner() : _eventRunner; - this.parsers = new Parsers(otherParsers); - } - - Parsers parsers() { - return parsers; - } - - ConfigurationLog log() { - return log; - } - - ResourceLoader resourceLoader() { - return resourceLoader; - } - - ModificationEventRunner eventRunner() { - return eventRunner; + jsonService = spiJsonService; } - List sources() { - return sources; + public Optional getJsonService() { + return Optional.ofNullable(jsonService); } - List plugins() { - return plugins; + public List getRenders() { + return renders; } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java index 63a430c4..9c4affea 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java @@ -150,8 +150,7 @@ JsonService initJsonService() { if (jsonService != null) { return jsonService; } - return ServiceLoader.load(JsonService.class) - .findFirst() + return CoreServiceLoader.get().getJsonService() .orElseGet(this::defaultJsonService); } @@ -190,7 +189,7 @@ private boolean detectTypeExists(String className) { TemplateManager initTemplateMgr() { TemplateManager mgr = new TemplateManager(); mgr.register(jex.config().renderers()); - for (TemplateRender render : ServiceLoader.load(TemplateRender.class)) { + for (TemplateRender render : CoreServiceLoader.get().getRenders()) { mgr.registerDefault(render); } return mgr; diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java index 9cf86328..5343dc58 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java @@ -1,24 +1,25 @@ package io.avaje.jex.jdk; -import com.sun.net.httpserver.HttpExchange; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.util.Objects; + +import com.sun.net.httpserver.HttpExchange; class BufferedOutStream extends OutputStream { + private static final long MAX = Long.getLong("jex.outputBuffer.max", 1024); + private static final int INITIAL = + Integer.getInteger("jex.outputBuffer.initial", 256); + private final JdkContext context; - private final long max; private ByteArrayOutputStream buffer; private OutputStream stream; private long count; - BufferedOutStream(JdkContext context, long max, int bufferSize) { + BufferedOutStream(JdkContext context) { this.context = context; - this.max = max; - this.buffer = new ByteArrayOutputStream(bufferSize); + this.buffer = new ByteArrayOutputStream(INITIAL); } @Override @@ -27,7 +28,7 @@ public void write(int b) throws IOException { stream.write(b); } else { buffer.write(b); - if (count++ > max) { + if (count++ > MAX) { initialiseChunked(); } } @@ -40,15 +41,13 @@ public void write(byte[] b, int off, int len) throws IOException { } else { count += len; buffer.write(b, off, len); - if (count > max) { + if (count > MAX) { initialiseChunked(); } } } - /** - * Use responseLength 0 and chunked response. - */ + /** Use responseLength 0 and chunked response. */ private void initialiseChunked() throws IOException { final HttpExchange exchange = context.exchange(); exchange.sendResponseHeaders(context.statusCode(), 0); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/CookieParser.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/CookieParser.java index 9948fb26..62115541 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/CookieParser.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/CookieParser.java @@ -57,10 +57,7 @@ public static Map parse(String rawHeader) { int eqInd = token.indexOf('='); if (eqInd > 0) { String name = token.substring(0, eqInd).trim(); - if (name.isEmpty()) { - continue; // Name MOST NOT be empty; - } - if (isRfc2965 && name.charAt(0) == '$' && ignore(name)) { + if (name.isEmpty() || (isRfc2965 && name.charAt(0) == '$' && ignore(name))) { continue; // Skip RFC2965 attributes } final String value = unwrap(token.substring(eqInd + 1).trim()); @@ -104,22 +101,20 @@ static List tokenize(char separator, String text) { quoted = false; } token.append(ch); + } else if (ch == separator) { + if (token.length() > 0) { + result.add(token.toString()); + } + token.setLength(0); } else { - if (ch == separator) { - if (token.length() > 0) { - result.add(token.toString()); + for (char quote : CookieParser.QUOTE_CHARS) { + if (ch == quote) { + quoted = true; + lastQuoteCharacter = ch; + break; } - token.setLength(0); - } else { - for (char quote : CookieParser.QUOTE_CHARS) { - if (ch == quote) { - quoted = true; - lastQuoteCharacter = ch; - break; - } - } - token.append(ch); } + token.append(ch); } } if (token.length() > 0) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index 35e828e8..222107aa 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -4,6 +4,7 @@ import java.io.UncheckedIOException; import java.lang.System.Logger.Level; import java.net.InetSocketAddress; +import java.util.concurrent.Executors; import com.sun.net.httpserver.HttpServer; @@ -26,7 +27,7 @@ public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceMana final HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); server.createContext("/", handler); - server.setExecutor(jex.config().executor()); + server.setExecutor(Executors.newThreadPerTaskExecutor(jex.config().threadFactory())); server.start(); jex.lifecycle().status(AppLifecycle.Status.STARTED); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/ServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/ServiceManager.java index 14734a07..b1ce71c3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/ServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/ServiceManager.java @@ -5,13 +5,10 @@ import java.io.OutputStream; -class ServiceManager extends ProxyServiceManager { +final class ServiceManager extends ProxyServiceManager { private final String scheme; private final String contextPath; - private static final long outputBufferMax = Long.getLong("jex.outputBuffer.max", 1024); - private static final int outputBufferInitial = - Integer.getInteger("jex.outputBuffer.initial", 256); ServiceManager(SpiServiceManager delegate, String scheme, String contextPath) { super(delegate); @@ -20,7 +17,7 @@ class ServiceManager extends ProxyServiceManager { } OutputStream createOutputStream(JdkContext jdkContext) { - return new BufferedOutStream(jdkContext, outputBufferMax, outputBufferInitial); + return new BufferedOutStream(jdkContext); } String scheme() { diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java b/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java index 0f89c0d5..b25a69dc 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java @@ -31,7 +31,7 @@ class PathSegmentParser { } static PathSegment parse(String seg, String rawPath) { - if (seg.equals("*")) { + if ("*".equals(seg)) { return WILDCARD; } return new PathSegmentParser(seg, rawPath).parse(); @@ -42,10 +42,7 @@ PathSegment parse() { if (matchOnlyStartEnd('<', '>')) { return new PathSegment.SlashAcceptingParameter(trim(segment)); } - if (matchOnlyStartEnd('{', '}')) { - return new PathSegment.SlashIgnoringParameter(trim(segment)); - } - if (matchParamWithRegex(segment)) { + if (matchOnlyStartEnd('{', '}') || matchParamWithRegex(segment)) { return new PathSegment.SlashIgnoringParameter(trim(segment)); } if (matchLiteral(segment)) { @@ -72,7 +69,7 @@ private PathSegment parseMultiSegment() { } private PathSegment tokenSegment(String token) { - if (token.equals("*")) { + if ("*".equals(token)) { return WILDCARD; } else if (token.startsWith("<")) { return slashAccepting(token); diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/HeaderKeys.java b/avaje-jex/src/main/java/io/avaje/jex/spi/HeaderKeys.java index e99047ab..c7eaaf71 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/HeaderKeys.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/HeaderKeys.java @@ -2,6 +2,8 @@ public class HeaderKeys { + private HeaderKeys() {} + public static final String ACCEPT = "Accept"; public static final String CONTENT_ENCODING = "Content-Encoding"; public static final String CONTENT_DISPOSITION = "Content-Disposition"; diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiContext.java b/avaje-jex/src/main/java/io/avaje/jex/spi/SpiContext.java index 0f8d46a6..c75c3360 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/SpiContext.java @@ -34,7 +34,7 @@ public interface SpiContext extends Context { void setMode(Routing.Type type); /** - * Preform the redirect as part of Exception handling typically due to before handler. + * Perform the redirect as part of Exception handling. Typically due to before handler. */ void performRedirect(); } diff --git a/examples/example-jdk-jsonb/src/main/java/module-info.java b/examples/example-jdk-jsonb/src/main/java/module-info.java index 02f09ecc..b3538a38 100644 --- a/examples/example-jdk-jsonb/src/main/java/module-info.java +++ b/examples/example-jdk-jsonb/src/main/java/module-info.java @@ -2,7 +2,7 @@ module example.jdkTwo { - requires io.avaje.jex.jdk; + requires io.avaje.jex; requires io.avaje.jsonb; provides Jsonb.GeneratedComponent with org.example.jsonb.GeneratedJsonComponent; diff --git a/examples/example-jdk/src/main/java/module-info.java b/examples/example-jdk/src/main/java/module-info.java index c4284407..3f7a240d 100644 --- a/examples/example-jdk/src/main/java/module-info.java +++ b/examples/example-jdk/src/main/java/module-info.java @@ -1,6 +1,6 @@ module example.jdk { - requires transitive io.avaje.jex.jdk; + requires transitive io.avaje.jex; requires transitive org.slf4j; exports org.example to com.fasterxml.jackson.databind; diff --git a/pom.xml b/pom.xml index 41b7cb32..913e04b1 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.avaje java11-oss - 3.12 + 4.5 io.avaje From 66808b9c707c45285e6dd7867da9e2c67eab498c Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:16:59 -0500 Subject: [PATCH 018/250] write --- avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java | 2 +- .../src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java index 5343dc58..0940b949 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java @@ -53,7 +53,7 @@ private void initialiseChunked() throws IOException { exchange.sendResponseHeaders(context.statusCode(), 0); stream = exchange.getResponseBody(); // empty the existing buffer - stream.write(buffer.toByteArray()); + buffer.writeTo(stream); buffer = null; } diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java index 8670f5de..8b01851e 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java @@ -75,6 +75,6 @@ void expect_fallback_to_default_asJson() { void expect_fallback_to_internalServerError() { HttpResponse res = pair.request().path("fiveHundred").GET().asString(); assertThat(res.statusCode()).isEqualTo(500); - assertThat(res.body()).isEqualTo("Internal server error"); + assertThat(res.body()).isEqualTo("Internal Server Error"); } } From 1b02dd4cf3681dc7327167740fcfde66b2fa66ec Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:18:00 -0500 Subject: [PATCH 019/250] ignore --- .project | 17 ------ .settings/org.eclipse.core.resources.prefs | 2 - .settings/org.eclipse.m2e.core.prefs | 4 -- avaje-jex-freemarker/.classpath | 57 ------------------- avaje-jex-freemarker/.project | 23 -------- .../org.eclipse.core.resources.prefs | 6 -- .../.settings/org.eclipse.jdt.apt.core.prefs | 2 - .../.settings/org.eclipse.jdt.core.prefs | 9 --- .../.settings/org.eclipse.m2e.core.prefs | 4 -- avaje-jex-jetty/.classpath | 57 ------------------- avaje-jex-jetty/.project | 23 -------- .../org.eclipse.core.resources.prefs | 6 -- .../.settings/org.eclipse.jdt.apt.core.prefs | 2 - .../.settings/org.eclipse.jdt.core.prefs | 9 --- .../.settings/org.eclipse.m2e.core.prefs | 4 -- avaje-jex/.classpath | 53 ----------------- avaje-jex/.project | 23 -------- .../org.eclipse.core.resources.prefs | 6 -- .../.settings/org.eclipse.jdt.apt.core.prefs | 2 - .../.settings/org.eclipse.jdt.core.prefs | 9 --- .../.settings/org.eclipse.m2e.core.prefs | 4 -- 21 files changed, 322 deletions(-) delete mode 100644 .project delete mode 100644 .settings/org.eclipse.core.resources.prefs delete mode 100644 .settings/org.eclipse.m2e.core.prefs delete mode 100644 avaje-jex-freemarker/.classpath delete mode 100644 avaje-jex-freemarker/.project delete mode 100644 avaje-jex-freemarker/.settings/org.eclipse.core.resources.prefs delete mode 100644 avaje-jex-freemarker/.settings/org.eclipse.jdt.apt.core.prefs delete mode 100644 avaje-jex-freemarker/.settings/org.eclipse.jdt.core.prefs delete mode 100644 avaje-jex-freemarker/.settings/org.eclipse.m2e.core.prefs delete mode 100644 avaje-jex-jetty/.classpath delete mode 100644 avaje-jex-jetty/.project delete mode 100644 avaje-jex-jetty/.settings/org.eclipse.core.resources.prefs delete mode 100644 avaje-jex-jetty/.settings/org.eclipse.jdt.apt.core.prefs delete mode 100644 avaje-jex-jetty/.settings/org.eclipse.jdt.core.prefs delete mode 100644 avaje-jex-jetty/.settings/org.eclipse.m2e.core.prefs delete mode 100644 avaje-jex/.classpath delete mode 100644 avaje-jex/.project delete mode 100644 avaje-jex/.settings/org.eclipse.core.resources.prefs delete mode 100644 avaje-jex/.settings/org.eclipse.jdt.apt.core.prefs delete mode 100644 avaje-jex/.settings/org.eclipse.jdt.core.prefs delete mode 100644 avaje-jex/.settings/org.eclipse.m2e.core.prefs diff --git a/.project b/.project deleted file mode 100644 index c2f3b10d..00000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - avaje-jex-parent - - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.m2e.core.maven2Nature - - diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index 99f26c02..00000000 --- a/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -encoding/=UTF-8 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f1..00000000 --- a/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/avaje-jex-freemarker/.classpath b/avaje-jex-freemarker/.classpath deleted file mode 100644 index 8c458320..00000000 --- a/avaje-jex-freemarker/.classpath +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/avaje-jex-freemarker/.project b/avaje-jex-freemarker/.project deleted file mode 100644 index 7146c529..00000000 --- a/avaje-jex-freemarker/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - avaje-jex-freemarker - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/avaje-jex-freemarker/.settings/org.eclipse.core.resources.prefs b/avaje-jex-freemarker/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index 29abf999..00000000 --- a/avaje-jex-freemarker/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,6 +0,0 @@ -eclipse.preferences.version=1 -encoding//src/main/java=UTF-8 -encoding//src/main/resources=UTF-8 -encoding//src/test/java=UTF-8 -encoding//src/test/resources=UTF-8 -encoding/=UTF-8 diff --git a/avaje-jex-freemarker/.settings/org.eclipse.jdt.apt.core.prefs b/avaje-jex-freemarker/.settings/org.eclipse.jdt.apt.core.prefs deleted file mode 100644 index d4313d4b..00000000 --- a/avaje-jex-freemarker/.settings/org.eclipse.jdt.apt.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.apt.aptEnabled=false diff --git a/avaje-jex-freemarker/.settings/org.eclipse.jdt.core.prefs b/avaje-jex-freemarker/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 99a63d54..00000000 --- a/avaje-jex-freemarker/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,9 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 -org.eclipse.jdt.core.compiler.compliance=11 -org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore -org.eclipse.jdt.core.compiler.processAnnotations=disabled -org.eclipse.jdt.core.compiler.release=enabled -org.eclipse.jdt.core.compiler.source=11 diff --git a/avaje-jex-freemarker/.settings/org.eclipse.m2e.core.prefs b/avaje-jex-freemarker/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f1..00000000 --- a/avaje-jex-freemarker/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/avaje-jex-jetty/.classpath b/avaje-jex-jetty/.classpath deleted file mode 100644 index 8c458320..00000000 --- a/avaje-jex-jetty/.classpath +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/avaje-jex-jetty/.project b/avaje-jex-jetty/.project deleted file mode 100644 index 0ce2b5cf..00000000 --- a/avaje-jex-jetty/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - avaje-jex-jetty - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/avaje-jex-jetty/.settings/org.eclipse.core.resources.prefs b/avaje-jex-jetty/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index 29abf999..00000000 --- a/avaje-jex-jetty/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,6 +0,0 @@ -eclipse.preferences.version=1 -encoding//src/main/java=UTF-8 -encoding//src/main/resources=UTF-8 -encoding//src/test/java=UTF-8 -encoding//src/test/resources=UTF-8 -encoding/=UTF-8 diff --git a/avaje-jex-jetty/.settings/org.eclipse.jdt.apt.core.prefs b/avaje-jex-jetty/.settings/org.eclipse.jdt.apt.core.prefs deleted file mode 100644 index d4313d4b..00000000 --- a/avaje-jex-jetty/.settings/org.eclipse.jdt.apt.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.apt.aptEnabled=false diff --git a/avaje-jex-jetty/.settings/org.eclipse.jdt.core.prefs b/avaje-jex-jetty/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 99a63d54..00000000 --- a/avaje-jex-jetty/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,9 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 -org.eclipse.jdt.core.compiler.compliance=11 -org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore -org.eclipse.jdt.core.compiler.processAnnotations=disabled -org.eclipse.jdt.core.compiler.release=enabled -org.eclipse.jdt.core.compiler.source=11 diff --git a/avaje-jex-jetty/.settings/org.eclipse.m2e.core.prefs b/avaje-jex-jetty/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f1..00000000 --- a/avaje-jex-jetty/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/avaje-jex/.classpath b/avaje-jex/.classpath deleted file mode 100644 index 319356eb..00000000 --- a/avaje-jex/.classpath +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/avaje-jex/.project b/avaje-jex/.project deleted file mode 100644 index 8a83aa90..00000000 --- a/avaje-jex/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - avaje-jex - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/avaje-jex/.settings/org.eclipse.core.resources.prefs b/avaje-jex/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index 29abf999..00000000 --- a/avaje-jex/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,6 +0,0 @@ -eclipse.preferences.version=1 -encoding//src/main/java=UTF-8 -encoding//src/main/resources=UTF-8 -encoding//src/test/java=UTF-8 -encoding//src/test/resources=UTF-8 -encoding/=UTF-8 diff --git a/avaje-jex/.settings/org.eclipse.jdt.apt.core.prefs b/avaje-jex/.settings/org.eclipse.jdt.apt.core.prefs deleted file mode 100644 index d4313d4b..00000000 --- a/avaje-jex/.settings/org.eclipse.jdt.apt.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.apt.aptEnabled=false diff --git a/avaje-jex/.settings/org.eclipse.jdt.core.prefs b/avaje-jex/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index a75d6ef8..00000000 --- a/avaje-jex/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,9 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=21 -org.eclipse.jdt.core.compiler.compliance=21 -org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning -org.eclipse.jdt.core.compiler.processAnnotations=disabled -org.eclipse.jdt.core.compiler.release=enabled -org.eclipse.jdt.core.compiler.source=21 diff --git a/avaje-jex/.settings/org.eclipse.m2e.core.prefs b/avaje-jex/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f1..00000000 --- a/avaje-jex/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 From cbbe70df965e09ac3c40cb89210694c894eba4c9 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:03:51 -0500 Subject: [PATCH 020/250] cleanup --- .gitignore | 1 + .../io/avaje/jex/render/mustache/MustacheRender.java | 2 ++ avaje-jex-mustache/src/main/java/module-info.java | 4 ++-- .../META-INF/services/io.avaje.jex.TemplateRender | 1 - .../main/java/io/avaje/jex/core/TemplateManager.java | 4 +--- .../avaje/jex/core/internal/CoreServiceLoader.java | 12 ++++-------- .../avaje/jex/core/internal/CoreServiceManager.java | 4 ++-- .../java/io/avaje/jex/jdk/ExceptionManagerTest.java | 4 ++-- 8 files changed, 14 insertions(+), 18 deletions(-) delete mode 100644 avaje-jex-mustache/src/main/resources/META-INF/services/io.avaje.jex.TemplateRender diff --git a/.gitignore b/.gitignore index c7ccf989..3097fb34 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ build/ *.prefs *.classpath *.prefs +bin/ diff --git a/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java b/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java index 805dcbbb..21d93700 100644 --- a/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java +++ b/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java @@ -4,12 +4,14 @@ import com.github.mustachejava.MustacheFactory; import io.avaje.jex.Context; import io.avaje.jex.TemplateRender; +import io.avaje.spi.ServiceProvider; import java.io.IOException; import java.io.StringWriter; import java.io.UncheckedIOException; import java.util.Map; +@ServiceProvider public class MustacheRender implements TemplateRender { private final MustacheFactory mustacheFactory; diff --git a/avaje-jex-mustache/src/main/java/module-info.java b/avaje-jex-mustache/src/main/java/module-info.java index 4d87122c..e553bae8 100644 --- a/avaje-jex-mustache/src/main/java/module-info.java +++ b/avaje-jex-mustache/src/main/java/module-info.java @@ -1,4 +1,4 @@ -open module io.avaje.jex.mustache { +module io.avaje.jex.mustache { requires transitive io.avaje.jex; requires transitive com.github.mustachejava; @@ -6,5 +6,5 @@ requires static io.avaje.spi; - provides io.avaje.jex.TemplateRender with io.avaje.jex.render.mustache.MustacheRender; + provides io.avaje.jex.spi.JexExtension with io.avaje.jex.render.mustache.MustacheRender; } diff --git a/avaje-jex-mustache/src/main/resources/META-INF/services/io.avaje.jex.TemplateRender b/avaje-jex-mustache/src/main/resources/META-INF/services/io.avaje.jex.TemplateRender deleted file mode 100644 index f92a16e3..00000000 --- a/avaje-jex-mustache/src/main/resources/META-INF/services/io.avaje.jex.TemplateRender +++ /dev/null @@ -1 +0,0 @@ -io.avaje.jex.render.mustache.MustacheRender diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java index 76fd4f76..11d7f51f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java @@ -31,9 +31,7 @@ public void register(Map source) { public void registerDefault(TemplateRender render) { if (!renderTypes.contains(render.getClass())) { for (String extension : render.defaultExtensions()) { - if (!map.containsKey(extension)) { - map.put(extension, render); - } + map.computeIfAbsent(extension, k->render); } } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java index 21b3de23..e3ce91cb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java @@ -14,10 +14,6 @@ class CoreServiceLoader { private static final CoreServiceLoader INSTANCE = new CoreServiceLoader(); - static CoreServiceLoader get() { - return INSTANCE; - } - private final JsonService jsonService; private final List renders = new ArrayList<>(); @@ -34,11 +30,11 @@ static CoreServiceLoader get() { jsonService = spiJsonService; } - public Optional getJsonService() { - return Optional.ofNullable(jsonService); + public static Optional getJsonService() { + return Optional.ofNullable(INSTANCE.jsonService); } - public List getRenders() { - return renders; + public static List getRenders() { + return INSTANCE.renders; } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java index 9c4affea..91f6a8e3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java @@ -150,7 +150,7 @@ JsonService initJsonService() { if (jsonService != null) { return jsonService; } - return CoreServiceLoader.get().getJsonService() + return CoreServiceLoader.getJsonService() .orElseGet(this::defaultJsonService); } @@ -189,7 +189,7 @@ private boolean detectTypeExists(String className) { TemplateManager initTemplateMgr() { TemplateManager mgr = new TemplateManager(); mgr.register(jex.config().renderers()); - for (TemplateRender render : CoreServiceLoader.get().getRenders()) { + for (TemplateRender render : CoreServiceLoader.getRenders()) { mgr.registerDefault(render); } return mgr; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java index 8b01851e..8b88365c 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java @@ -44,8 +44,8 @@ static void end() { @Test void get() { HttpResponse res = pair.request().GET().asString(); - assertThat(res.statusCode()).isEqualTo(223); - assertThat(res.body()).isEqualTo("Handled ForbiddenResponse|Forbidden"); + assertThat(res.statusCode()).isEqualTo(403); + assertThat(res.body()).isEqualTo("Forbidden"); } @Test From 32a93cf660a3f46688ad45c36e9e0933d1f82780 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:06:49 -0500 Subject: [PATCH 021/250] Update module-info.java --- avaje-jex-freemarker/src/main/java/module-info.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avaje-jex-freemarker/src/main/java/module-info.java b/avaje-jex-freemarker/src/main/java/module-info.java index 93741ac3..7e07d87a 100644 --- a/avaje-jex-freemarker/src/main/java/module-info.java +++ b/avaje-jex-freemarker/src/main/java/module-info.java @@ -1,7 +1,7 @@ import io.avaje.jex.render.freemarker.FreeMarkerRender; import io.avaje.jex.spi.JexExtension; -open module io.avaje.jex.freemarker { +module io.avaje.jex.freemarker { requires transitive io.avaje.jex; requires transitive freemarker; From e1126d434050f2b67dd4a6e6a73cc7afd59c3f31 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:02:33 -0500 Subject: [PATCH 022/250] support https server --- .../src/main/java/io/avaje/jex/DJexConfig.java | 14 ++++++++++++++ .../src/main/java/io/avaje/jex/JexConfig.java | 8 ++++++++ .../java/io/avaje/jex/jdk/JdkServerStart.java | 15 +++++++++++++-- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index f3a0a2f8..1ec81c40 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -4,6 +4,8 @@ import java.util.Map; import java.util.concurrent.ThreadFactory; +import javax.net.ssl.SSLContext; + import io.avaje.jex.spi.JsonService; class DJexConfig implements JexConfig { @@ -21,6 +23,7 @@ class DJexConfig implements JexConfig { private UploadConfig multipartConfig; private int multipartFileThreshold = 8 * 1024; private final Map renderers = new HashMap<>(); + private SSLContext sslContext; @Override public JexConfig port(int port) { @@ -160,4 +163,15 @@ public Map renderers() { return renderers; } + @Override + public SSLContext sslContext() { + + return this.sslContext; + } + + @Override + public JexConfig sslContext(SSLContext ssl) { + this.sslContext = ssl; + return this; + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index 87e2f2e3..c69ebd48 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -4,6 +4,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import javax.net.ssl.SSLContext; + import io.avaje.jex.spi.JsonService; /** @@ -119,6 +121,12 @@ public interface JexConfig { */ AccessManager accessManager(); + /** Return the ssl context if https is enabled. */ + SSLContext sslContext(); + + /** Enable https with the provided SSLContext. */ + JexConfig sslContext(SSLContext ssl); + /** * Return the multipartConfig. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index 222107aa..f80807e7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -7,6 +7,8 @@ import java.util.concurrent.Executors; import com.sun.net.httpserver.HttpServer; +import com.sun.net.httpserver.HttpsConfigurator; +import com.sun.net.httpserver.HttpsServer; import io.avaje.applog.AppLog; import io.avaje.jex.AppLifecycle; @@ -24,12 +26,21 @@ public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceMana try { int port = jex.config().port(); - final HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); + final HttpServer server; + final var sslContext = jex.config().sslContext(); + if (sslContext != null) { + + var httpsServer = HttpsServer.create(new InetSocketAddress(port), 0); + httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext)); + server = httpsServer; + } else { + server = HttpServer.create(new InetSocketAddress(port), 0); + } server.createContext("/", handler); server.setExecutor(Executors.newThreadPerTaskExecutor(jex.config().threadFactory())); - server.start(); + jex.lifecycle().status(AppLifecycle.Status.STARTED); String jexVersion = Jex.class.getPackage().getImplementationVersion(); log.log(Level.INFO, "started server on port {0,number,#} version {1}", port, jexVersion); From e0715052534360809cb3b009a2d45c9ec76a0e0e Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Sat, 23 Nov 2024 11:13:50 +1300 Subject: [PATCH 023/250] Bump version to 3.0-SNAPSHOT --- avaje-jex-freemarker/pom.xml | 6 +++--- avaje-jex-mustache/pom.xml | 6 +++--- avaje-jex-test/pom.xml | 4 ++-- avaje-jex/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 230d675c..28e5b00d 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 2.6-SNAPSHOT + 3.0-SNAPSHOT avaje-jex-freemarker @@ -18,7 +18,7 @@ io.avaje avaje-jex - 2.6-SNAPSHOT + 3.0-SNAPSHOT provided @@ -39,7 +39,7 @@ io.avaje avaje-jex-test - 2.6-SNAPSHOT + 3.0-SNAPSHOT test diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index f2fc4a38..ad27a950 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 2.6-SNAPSHOT + 3.0-SNAPSHOT avaje-jex-mustache @@ -18,7 +18,7 @@ io.avaje avaje-jex - 2.6-SNAPSHOT + 3.0-SNAPSHOT provided @@ -40,7 +40,7 @@ io.avaje avaje-jex-test - 2.6-SNAPSHOT + 3.0-SNAPSHOT test diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index a5a079c8..4afd4d6c 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 2.6-SNAPSHOT + 3.0-SNAPSHOT avaje-jex-test @@ -14,7 +14,7 @@ io.avaje avaje-jex - 2.6-SNAPSHOT + 3.0-SNAPSHOT provided diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 03082691..aa5ccc24 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 2.6-SNAPSHOT + 3.0-SNAPSHOT avaje-jex diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index b518c9d5..dd7789ff 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 2.6-SNAPSHOT + 3.0-SNAPSHOT diff --git a/examples/pom.xml b/examples/pom.xml index 588bb45a..4bf8bc13 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 2.6-SNAPSHOT + 3.0-SNAPSHOT examples diff --git a/pom.xml b/pom.xml index 913e04b1..ac3694c4 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.avaje avaje-jex-parent - 2.6-SNAPSHOT + 3.0-SNAPSHOT pom From b9104a0aec433994f4f84f2a203cbeac82218444 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 20:23:32 -0500 Subject: [PATCH 024/250] refactor filter system --- .../main/java/io/avaje/jex/AccessManager.java | 49 ---------- .../src/main/java/io/avaje/jex/Context.java | 13 ++- .../src/main/java/io/avaje/jex/DJex.java | 9 +- .../main/java/io/avaje/jex/DJexConfig.java | 12 --- .../java/io/avaje/jex/DefaultRouting.java | 48 ++++------ .../main/java/io/avaje/jex/FilterChain.java | 19 ++++ .../main/java/io/avaje/jex/HttpFilter.java | 41 +++++++++ avaje-jex/src/main/java/io/avaje/jex/Jex.java | 5 -- .../src/main/java/io/avaje/jex/JexConfig.java | 10 --- .../src/main/java/io/avaje/jex/Routing.java | 31 ++----- .../java/io/avaje/jex/jdk/BaseFilter.java | 89 +++++++++++++++++++ .../java/io/avaje/jex/jdk/BaseHandler.java | 72 +++------------ .../java/io/avaje/jex/jdk/JdkContext.java | 6 +- .../main/java/io/avaje/jex/jdk/JdkFilter.java | 30 +++++++ .../java/io/avaje/jex/jdk/JdkServerStart.java | 10 ++- .../java/io/avaje/jex/routes/FilterEntry.java | 76 ---------------- .../main/java/io/avaje/jex/routes/Routes.java | 42 +++------ .../io/avaje/jex/routes/RoutesBuilder.java | 61 +++---------- .../java/io/avaje/jex/routes/SpiRoutes.java | 18 ++-- .../avaje/jex/jdk/ContextAttributeTest.java | 54 ++++++----- .../java/io/avaje/jex/jdk/FilterTest.java | 52 +++++++---- .../java/io/avaje/jex/jdk/RedirectTest.java | 23 +++-- 22 files changed, 349 insertions(+), 421 deletions(-) delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/AccessManager.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/FilterChain.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilter.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/jdk/JdkFilter.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/routes/FilterEntry.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/AccessManager.java b/avaje-jex/src/main/java/io/avaje/jex/AccessManager.java deleted file mode 100644 index 0e54839b..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/AccessManager.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Javalin - https://javalin.io - * Copyright 2017 David Åse - * Licensed under Apache 2.0: https://github.com/tipsy/javalin/blob/master/LICENSE - */ -package io.avaje.jex; - - -import java.util.Set; - -/** - * Provide access check for routes that have permitted roles assigned to them. - * - *

- * An example implementation might look like the code below. - * - *

{@code
- *
- *   var app = Jex.create()
- *     .accessManager((handler, ctx, permittedRoles) -> {
- *
- *         // obtain current users role(s)
- *         final String userRole = ...
- *
- *         if (userRole == null || !permittedRoles.contains(AppRoles.valueOf(userRole))) {
- *           ctx.status(401).text("Unauthorized");
- *         } else {
- *           // allow
- *           handler.handle(ctx);
- *         }
- *       })
- *
- * }
- */ -@FunctionalInterface -public interface AccessManager { - - /** - * Check that the current user has one of the required roles. - *

- * Implementations should call the handler if the user has one of - * the permitted roles. - * - * @param handler The handler to call if the user has an appropriate role. - * @param ctx The context - * @param permittedRoles The permitted roles for the endpoint - */ - void manage(Handler handler, Context ctx, Set permittedRoles); -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index 8eb06b67..98944435 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -1,6 +1,7 @@ package io.avaje.jex; -import io.avaje.jex.spi.HeaderKeys; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; import java.time.Duration; import java.time.LocalDateTime; @@ -12,8 +13,9 @@ import java.util.Map; import java.util.stream.Stream; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; +import com.sun.net.httpserver.HttpExchange; + +import io.avaje.jex.spi.HeaderKeys; /** * Provides access to functions for handling the request and response. @@ -188,6 +190,11 @@ default List formParams(String key) { */ Map> formParamMap(); + /** + * Return the underlying JDK {@link HttpExchange} object backing the context + */ + HttpExchange jdkExchange(); + /** * Return the request scheme. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index 04e8fb2d..59fbfd20 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -82,12 +82,6 @@ public Routing routing() { return routing; } - @Override - public Jex accessManager(AccessManager accessManager) { - this.config.accessManager(accessManager); - return this; - } - @Override public Jex jsonService(JsonService jsonService) { this.config.jsonService(jsonService); @@ -111,7 +105,6 @@ public Jex configureWith(BeanScope beanScope) { } routing.addAll(beanScope.list(Routing.Service.class)); beanScope.getOptional(JsonService.class).ifPresent(this::jsonService); - beanScope.getOptional(AccessManager.class).ifPresent(this::accessManager); return this; } @@ -164,7 +157,7 @@ public Server start() { } final SpiRoutes routes = new RoutesBuilder( - this.routing, this.config.accessManager(), this.config.ignoreTrailingSlashes()) + this.routing, this.config.ignoreTrailingSlashes()) .build(); return new JdkServerStart().start(this, routes, CoreServiceManager.create(this)); diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 1ec81c40..0b4e5b51 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -19,7 +19,6 @@ class DJexConfig implements JexConfig { private boolean preCompressStaticFiles; private JsonService jsonService; - private AccessManager accessManager; private UploadConfig multipartConfig; private int multipartFileThreshold = 8 * 1024; private final Map renderers = new HashMap<>(); @@ -67,12 +66,6 @@ public JexConfig jsonService(JsonService jsonService) { return this; } - @Override - public JexConfig accessManager(AccessManager accessManager) { - this.accessManager = accessManager; - return this; - } - @Override public JexConfig multipartConfig(UploadConfig multipartConfig) { this.multipartConfig = multipartConfig; @@ -143,11 +136,6 @@ public JsonService jsonService() { return jsonService; } - @Override - public AccessManager accessManager() { - return accessManager; - } - @Override public UploadConfig multipartConfig() { return multipartConfig; diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index 6317b204..5520680d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -1,10 +1,19 @@ package io.avaje.jex; -import java.util.*; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.List; +import java.util.Set; + +import io.avaje.jex.jdk.JdkFilter; class DefaultRouting implements Routing { private final List handlers = new ArrayList<>(); + private final List filters = new ArrayList<>(); private final Deque pathDeque = new ArrayDeque<>(); /** @@ -13,10 +22,15 @@ class DefaultRouting implements Routing { private Entry lastEntry; @Override - public List all() { + public List handlers() { return handlers; } + @Override + public List filters() { + return filters; + } + private String path(String path) { return String.join("", pathDeque) + ((path.startsWith("/") || path.isEmpty()) ? path : "/" + path); } @@ -67,14 +81,6 @@ private void add(Type verb, String path, Handler handler) { handlers.add(lastEntry); } - private void addBefore(String path, Handler handler) { - add(Type.BEFORE, path(path), handler); - } - - private void addAfter(String path, Handler handler) { - add(Type.AFTER, path(path), handler); - } - // ******************************************************************************************** // HTTP verbs // ******************************************************************************************** @@ -164,31 +170,15 @@ public Routing trace(Handler handler) { } // ******************************************************************************************** - // Before/after handlers (filters) + // Filters // ******************************************************************************************** - @Override - public Routing before(String path, Handler handler) { - addBefore(path, handler); - return this; - } @Override - public Routing before(Handler handler) { - before("/*", handler); + public Routing filter(HttpFilter handler) { + filters.add(handler); return this; } - @Override - public Routing after(String path, Handler handler) { - addAfter(path, handler); - return this; - } - - @Override - public Routing after(Handler handler) { - after("/*", handler); - return this; - } private static class Entry implements Routing.Entry { diff --git a/avaje-jex/src/main/java/io/avaje/jex/FilterChain.java b/avaje-jex/src/main/java/io/avaje/jex/FilterChain.java new file mode 100644 index 00000000..b5966196 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/FilterChain.java @@ -0,0 +1,19 @@ +package io.avaje.jex; + +import java.io.IOException; + +import com.sun.net.httpserver.HttpExchange; + +@FunctionalInterface +public interface FilterChain { + + /** + * Calls the next filter in the chain, or else the users exchange handler, if this is the final + * filter in the chain. The {@link HttpFilter} may decide to terminate the chain, by not + * calling this method. In this case, the filter must send the response to the request, + * because the application's {@linkplain HttpExchange exchange} handler will not be invoked. + * + * @throws IOException if an I/O error occurs + */ + void proceed() throws IOException; +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java b/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java new file mode 100644 index 00000000..d52b6539 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java @@ -0,0 +1,41 @@ +package io.avaje.jex; + +import java.io.IOException; + +import com.sun.net.httpserver.Filter.Chain; + +/** + * A filter used to pre- and post-process incoming requests. Pre-processing occurs before the + * application's exchange handler is invoked, and post-processing occurs after the exchange handler + * returns. Filters are organized in chains, and are associated with {@link Context} instances. + * + *

Each {@code HttpFilter} in the chain, invokes the next filter within its own {@link + * #filter(Context, Chain)} implementation. The final {@code HttpFilter} in the chain invokes the + * applications exchange handler. + */ +@FunctionalInterface +public interface HttpFilter { + + /** + * Asks this filter to pre/post-process the given request. The filter can: + * + *

    + *
  • Examine or modify the request headers. + *
  • Set attribute objects in the context, which other filters or the handler can access. + *
  • Decide to either: + *
      + *
    1. Invoke the next filter in the chain, by calling {@link FilterChain#proceed}. + *
    2. Terminate the chain of invocation, by not calling {@link + * FilterChain#filter}. + *
    + *
  • If option 1. above is taken, then when filter() returns all subsequent filters in the + * Chain have been called, and the response headers can be examined or modified. + *
  • If option 2. above is taken, then this Filter must use the Context to send back an + * appropriate response. + *
+ * + * @param ctx the {@code Context} of the current request + * @param chain the {@code FilterChain} which allows the next filter to be invoked + */ + void filter(Context ctx, FilterChain chain) throws IOException; +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 0aef346b..ecb5fef6 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -89,11 +89,6 @@ static Jex create() { */ Routing routing(); - /** - * Set the AccessManager. - */ - Jex accessManager(AccessManager accessManager); - /** * Set the JsonService. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index c69ebd48..f8815696 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -48,11 +48,6 @@ public interface JexConfig { */ JexConfig jsonService(JsonService jsonService); - /** - * Set the AccessManager to use. - */ - JexConfig accessManager(AccessManager accessManager); - /** * Set the upload configuration. */ @@ -116,11 +111,6 @@ public interface JexConfig { */ JsonService jsonService(); - /** - * Return the access manager. - */ - AccessManager accessManager(); - /** Return the ssl context if https is enabled. */ SSLContext sslContext(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index f7daf58e..238ab74b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -122,29 +122,19 @@ public interface Routing { Routing trace(Handler handler); /** - * Add a before filter for the given path. + * Add a filter for all requests. */ - Routing before(String path, Handler handler); + Routing filter(HttpFilter handler); /** - * Add a before filter for all requests. - */ - Routing before(Handler handler); - - /** - * Add a after filter for the given path. - */ - Routing after(String path, Handler handler); - - /** - * Add an after filter for all requests. + * Return all the registered handlers. */ - Routing after(Handler handler); + List handlers(); /** - * Return all the registered handlers. + * Return all the registered filters. */ - List all(); + List filters(); /** * A group of routing entries prefixed by a common path. @@ -203,13 +193,9 @@ interface Entry { */ enum Type { /** - * Before filter. + * Http Filter. */ - BEFORE, - /** - * After filter. - */ - AFTER, + FILTER, /** * Http GET. */ @@ -239,4 +225,5 @@ enum Type { */ TRACE//, CONNECT, OPTIONS, INVALID; } + } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilter.java new file mode 100644 index 00000000..067b3187 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilter.java @@ -0,0 +1,89 @@ +package io.avaje.jex.jdk; + +import java.util.Map; +import java.util.function.Consumer; + +import com.sun.net.httpserver.Filter; +import com.sun.net.httpserver.HttpExchange; + +import io.avaje.jex.Context; +import io.avaje.jex.Routing; +import io.avaje.jex.Routing.Type; +import io.avaje.jex.http.HttpResponseException; +import io.avaje.jex.routes.SpiRoutes; +import io.avaje.jex.spi.SpiContext; + +class BaseFilter extends Filter { + + private final SpiRoutes routes; + private final ServiceManager mgr; + + BaseFilter(SpiRoutes routes, ServiceManager mgr) { + this.mgr = mgr; + this.routes = routes; + } + + void waitForIdle(long maxSeconds) { + routes.waitForIdle(maxSeconds); + } + + @Override + public void doFilter(HttpExchange exchange, Filter.Chain chain) { + + final String uri = exchange.getRequestURI().getPath(); + final Routing.Type routeType = mgr.lookupRoutingType(exchange.getRequestMethod()); + final SpiRoutes.Entry route = routes.match(routeType, uri); + + if (route == null) { + var ctx = new JdkContext(mgr, exchange, uri); + routes.inc(); + try { + processNoRoute(ctx, uri, routeType); + exchange.setAttribute("JdkContext", ctx); + chain.doFilter(exchange); + } catch (Exception e) { + handleException(ctx, e); + } finally { + routes.dec(); + } + } else { + route.inc(); + try { + final Map params = route.pathParams(uri); + JdkContext ctx = new JdkContext(mgr, exchange, route.matchPath(), params); + try { + ctx.setMode(Type.FILTER); + exchange.setAttribute("JdkContext", ctx); + Consumer handlerConsumer = route::handle; + exchange.setAttribute("SpiRoutes.Entry.Handler", handlerConsumer); + chain.doFilter(exchange); + } catch (Exception e) { + handleException(ctx, e); + } + } finally { + route.dec(); + } + } + } + + private void handleException(SpiContext ctx, Exception e) { + mgr.handleException(ctx, e); + } + + private void processNoRoute(JdkContext ctx, String uri, Routing.Type routeType) { + if (routeType == Routing.Type.HEAD && hasGetHandler(uri)) { + ctx.status(200); + return; + } + throw new HttpResponseException(404, "uri: " + uri); + } + + private boolean hasGetHandler(String uri) { + return routes.match(Routing.Type.GET, uri) != null; + } + + @Override + public String description() { + return "Routing Filter"; + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java index fbf923d2..04715a6b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java @@ -1,21 +1,19 @@ package io.avaje.jex.jdk; +import java.util.function.Consumer; + import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; -import io.avaje.jex.Routing; -import io.avaje.jex.http.HttpResponseException; -import io.avaje.jex.routes.SpiRoutes; -import io.avaje.jex.spi.SpiContext; -import java.util.Map; +import io.avaje.jex.Context; +import io.avaje.jex.Routing.Type; +import io.avaje.jex.routes.SpiRoutes; class BaseHandler implements HttpHandler { private final SpiRoutes routes; - private final ServiceManager mgr; - BaseHandler(SpiRoutes routes, ServiceManager mgr) { - this.mgr = mgr; + BaseHandler(SpiRoutes routes) { this.routes = routes; } @@ -26,59 +24,13 @@ void waitForIdle(long maxSeconds) { @Override public void handle(HttpExchange exchange) { - final String uri = exchange.getRequestURI().getPath(); - final Routing.Type routeType = mgr.lookupRoutingType(exchange.getRequestMethod()); - final SpiRoutes.Entry route = routes.match(routeType, uri); - - if (route == null) { - var ctx = new JdkContext(mgr, exchange, uri); - routes.inc(); - try { - processNoRoute(ctx, uri, routeType); - routes.after(uri, ctx); - } catch (Exception e) { - handleException(ctx, e); - } finally { - routes.dec(); - } - } else { - route.inc(); - try { - final Map params = route.pathParams(uri); - JdkContext ctx = new JdkContext(mgr, exchange, route.matchPath(), params); - try { - processRoute(ctx, uri, route); - routes.after(uri, ctx); - } catch (Exception e) { - handleException(ctx, e); - } - } finally { - route.dec(); - } - } - } - - private void handleException(SpiContext ctx, Exception e) { - mgr.handleException(ctx, e); - } + JdkContext ctx = (JdkContext) exchange.getAttribute("JdkContext"); + @SuppressWarnings("unchecked") + Consumer handlerConsumer = + (Consumer) exchange.getAttribute("SpiRoutes.Entry.Handler"); - private void processRoute(JdkContext ctx, String uri, SpiRoutes.Entry route) { - routes.before(uri, ctx); ctx.setMode(null); - route.handle(ctx); - } - - private void processNoRoute(JdkContext ctx, String uri, Routing.Type routeType) { - routes.before(uri, ctx); - if (routeType == Routing.Type.HEAD && hasGetHandler(uri)) { - ctx.status(200); - return; - } - throw new HttpResponseException(404, "uri: " + uri); + handlerConsumer.accept(ctx); + ctx.setMode(Type.FILTER); } - - private boolean hasGetHandler(String uri) { - return routes.match(Routing.Type.GET, uri) != null; - } - } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index ec67dfc0..0c06d835 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -139,7 +139,7 @@ public void redirect(String location) { public void redirect(String location, int statusCode) { header(HeaderKeys.LOCATION, location); status(statusCode); - if (mode == Routing.Type.BEFORE) { + if (mode == Routing.Type.FILTER) { throw new HttpResponseException(ErrorCode.REDIRECT); } else { performRedirect(); @@ -450,4 +450,8 @@ HttpExchange exchange() { return exchange; } + @Override + public HttpExchange jdkExchange() { + return exchange; + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkFilter.java new file mode 100644 index 00000000..8305f1b6 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkFilter.java @@ -0,0 +1,30 @@ +package io.avaje.jex.jdk; + +import java.io.IOException; + +import com.sun.net.httpserver.Filter; +import com.sun.net.httpserver.HttpExchange; + +import io.avaje.jex.HttpFilter; + +public class JdkFilter extends Filter { + + private final HttpFilter handler; + + public JdkFilter(HttpFilter handler) { + this.handler = handler; + } + + @Override + public void doFilter(HttpExchange exchange, Chain chain) throws IOException { + + var ctx = (JdkContext) exchange.getAttribute("JdkContext"); + + handler.filter(ctx, () -> chain.doFilter(exchange)); + } + + @Override + public String description() { + return "JexFilter"; + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index f80807e7..1ace94d4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -6,7 +6,7 @@ import java.net.InetSocketAddress; import java.util.concurrent.Executors; -import com.sun.net.httpserver.HttpServer; +import com.sun.net.httpserver.*; import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsServer; @@ -22,9 +22,8 @@ public class JdkServerStart { public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceManager) { final ServiceManager manager = new ServiceManager(serviceManager, "http", ""); - BaseHandler handler = new BaseHandler(routes, manager); - try { + try { int port = jex.config().port(); final HttpServer server; @@ -37,7 +36,10 @@ public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceMana } else { server = HttpServer.create(new InetSocketAddress(port), 0); } - server.createContext("/", handler); + var handler = new BaseHandler(routes); + var context = server.createContext("/", handler); + context.getFilters().add(new BaseFilter(routes, manager)); + context.getFilters().addAll(routes.filters()); server.setExecutor(Executors.newThreadPerTaskExecutor(jex.config().threadFactory())); server.start(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/FilterEntry.java b/avaje-jex/src/main/java/io/avaje/jex/routes/FilterEntry.java deleted file mode 100644 index 6f4f5411..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/FilterEntry.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.avaje.jex.routes; - -import io.avaje.jex.Context; -import io.avaje.jex.Handler; -import io.avaje.jex.Routing; - -import java.util.Map; - -/** - * Filter with special matchAll. - */ -class FilterEntry implements SpiRoutes.Entry { - - private final String path; - private final boolean matchAll; - private final PathParser pathParser; - private final Handler handler; - - FilterEntry(Routing.Entry entry, boolean ignoreTrailingSlashes) { - this.path = entry.getPath(); - this.matchAll = "/*".equals(path) || "*".equals(path); - this.pathParser = matchAll ? null : new PathParser(path, ignoreTrailingSlashes); - this.handler = entry.getHandler(); - } - - @Override - public void inc() { - // do nothing - } - - @Override - public void dec() { - // do nothing - } - - @Override - public long activeRequests() { - // always zero for filters - return 0; - } - - @Override - public String matchPath() { - return path; - } - - @Override - public boolean matches(String requestUri) { - return matchAll || pathParser.matches(requestUri); - } - - @Override - public void handle(Context ctx) { - handler.handle(ctx); - } - - @Override - public Map pathParams(String uri) { - throw new IllegalStateException("not allowed"); - } - - @Override - public int segmentCount() { - throw new IllegalStateException("not allowed"); - } - - @Override - public boolean multiSlash() { - return pathParser != null && pathParser.multiSlash(); - } - - @Override - public boolean literal() { - return pathParser != null && pathParser.literal(); - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java b/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java index dfa4ffd7..4d9b68b5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java @@ -1,15 +1,15 @@ package io.avaje.jex.routes; -import io.avaje.applog.AppLog; -import io.avaje.jex.Routing; -import io.avaje.jex.spi.SpiContext; - import java.lang.System.Logger.Level; import java.util.EnumMap; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.LockSupport; +import io.avaje.applog.AppLog; +import io.avaje.jex.Routing; +import io.avaje.jex.jdk.JdkFilter; + final class Routes implements SpiRoutes { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); @@ -20,21 +20,16 @@ final class Routes implements SpiRoutes { private final EnumMap typeMap; /** - * The before filters. + * The filters. */ - private final List before; - - /** - * The after filters. - */ - private final List after; + private final List filters; private final AtomicLong noRouteCounter = new AtomicLong(); - Routes(EnumMap typeMap, List before, List after) { + + Routes(EnumMap typeMap, List filters) { this.typeMap = typeMap; - this.before = before; - this.after = after; + this.filters = filters; } @Override @@ -84,22 +79,7 @@ public Entry match(Routing.Type type, String pathInfo) { } @Override - public void before(String pathInfo, SpiContext ctx) { - ctx.setMode(Routing.Type.BEFORE); - for (Entry beforeFilter : before) { - if (beforeFilter.matches(pathInfo)) { - beforeFilter.handle(ctx); - } - } - } - - @Override - public void after(String pathInfo, SpiContext ctx) { - ctx.setMode(Routing.Type.AFTER); - for (Entry afterFilter : after) { - if (afterFilter.matches(pathInfo)) { - afterFilter.handle(ctx); - } - } + public List filters() { + return filters; } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java index 28a57d78..4b339b1f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java @@ -1,75 +1,34 @@ package io.avaje.jex.routes; import io.avaje.jex.*; +import io.avaje.jex.jdk.JdkFilter; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; -import java.util.Set; public class RoutesBuilder { private final EnumMap typeMap = new EnumMap<>(Routing.Type.class); - private final List before = new ArrayList<>(); - private final List after = new ArrayList<>(); private final boolean ignoreTrailingSlashes; - private final AccessManager accessManager; + private final List filters = new ArrayList<>(); - public RoutesBuilder(Routing routing, AccessManager accessManager, boolean ignoreTrailingSlashes) { - this.accessManager = accessManager; + public RoutesBuilder(Routing routing, boolean ignoreTrailingSlashes) { this.ignoreTrailingSlashes = ignoreTrailingSlashes; - for (Routing.Entry handler : routing.all()) { - switch (handler.getType()) { - case BEFORE: - before.add(filter(handler)); - break; - case AFTER: - after.add(filter(handler)); - break; - default: - typeMap.computeIfAbsent(handler.getType(), h -> new RouteIndex()).add(convert(handler)); - } + for (var handler : routing.handlers()) { + typeMap.computeIfAbsent(handler.getType(), h -> new RouteIndex()).add(convert(handler)); + } + for (var handler : routing.filters()) { + filters.add(new JdkFilter(handler)); } - } - - private FilterEntry filter(Routing.Entry entry) { - return new FilterEntry(entry, ignoreTrailingSlashes); } private SpiRoutes.Entry convert(Routing.Entry handler) { final PathParser pathParser = new PathParser(handler.getPath(), ignoreTrailingSlashes); - return new RouteEntry(pathParser, extractHandler(handler)); - } - - private Handler extractHandler(Routing.Entry entry) { - if (entry.getRoles().isEmpty() || accessManager == null) { - return entry.getHandler(); - } - return new AccessHandler(accessManager, entry.getHandler(), entry.getRoles()); + return new RouteEntry(pathParser, handler.getHandler()); } public SpiRoutes build() { - return new Routes(typeMap, before, after); - } - - /** - * Wrap the handler with access check based on permitted roles. - */ - static class AccessHandler implements Handler { - - private final AccessManager manager; - private final Handler handler; - private final Set roles; - - AccessHandler(AccessManager manager, Handler handler, Set roles) { - this.manager = manager; - this.handler = handler; - this.roles = roles; - } - - @Override - public void handle(Context ctx) { - manager.manage(handler, ctx, roles); - } + return new Routes(typeMap, filters); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java b/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java index 6836aedf..0dd93c5c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java @@ -2,8 +2,10 @@ import io.avaje.jex.Context; import io.avaje.jex.Routing; +import io.avaje.jex.jdk.JdkFilter; import io.avaje.jex.spi.SpiContext; +import java.util.List; import java.util.Map; /** @@ -16,16 +18,6 @@ public sealed interface SpiRoutes permits Routes { */ Entry match(Routing.Type type, String pathInfo); - /** - * Execute all appropriate before filters for the given request URI. - */ - void before(String pathInfo, SpiContext ctx); - - /** - * Execute all appropriate after filters for the given request URI. - */ - void after(String pathInfo, SpiContext ctx); - /** * Increment active request count for no route match. */ @@ -46,6 +38,11 @@ public sealed interface SpiRoutes permits Routes { */ void waitForIdle(long maxSeconds); + /** + * Get filters + */ + List filters(); + /** * A route entry. */ @@ -101,4 +98,5 @@ interface Entry { */ long activeRequests(); } + } diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextAttributeTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextAttributeTest.java index 116ff93f..aad420d9 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextAttributeTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextAttributeTest.java @@ -19,28 +19,36 @@ class ContextAttributeTest { static UUID attrUuid; static TestPair init() { - var app = Jex.create() - .routing(routing -> routing - .before(ctx -> { - ctx.attribute("oneUuid", uuid).attribute(TestPair.class.getName(), pair); - }) - .get("/", ctx -> { - attrUuid = ctx.attribute("oneUuid"); - attrPair = ctx.attribute(TestPair.class.getName()); - - assert attrUuid == uuid; - assert attrPair == pair; - -// ctx.attributeMap() is not supported -// final Map attrMap = ctx.attributeMap(); -// final Object mapUuid = attrMap.get("oneUuid"); -// assert mapUuid == uuid; -// -// final Object mapPair = attrMap.get(TestPair.class.getName()); -// assert mapPair == pair; - ctx.text("all-good"); - }) - ); + var app = + Jex.create() + .routing( + routing -> + routing + .filter( + (ctx, chain) -> { + ctx.attribute("oneUuid", uuid) + .attribute(TestPair.class.getName(), pair); + chain.proceed(); + }) + .get( + "/", + ctx -> { + attrUuid = ctx.attribute("oneUuid"); + attrPair = ctx.attribute(TestPair.class.getName()); + + assert attrUuid == uuid; + assert attrPair == pair; + + // ctx.attributeMap() is not supported + // final Map attrMap = ctx.attributeMap(); + // final Object mapUuid = attrMap.get("oneUuid"); + // assert mapUuid == uuid; + // + // final Object mapPair = + // attrMap.get(TestPair.class.getName()); + // assert mapPair == pair; + ctx.text("all-good"); + })); return TestPair.create(app); } @@ -58,6 +66,4 @@ void get() { assertThat(attrPair).isSameAs(pair); assertThat(attrUuid).isSameAs(uuid); } - - } diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java index 9280e929..7d9e4b03 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java @@ -17,22 +17,41 @@ class FilterTest { static AtomicReference afterTwo = new AtomicReference<>(); static TestPair init() { - final Jex app = Jex.create() - .routing(routing -> routing - .get("/", ctx -> ctx.text("roo")) - .get("/one", ctx -> ctx.text("one")) - .get("/two", ctx -> ctx.text("two")) - .get("/two/{id}", ctx -> ctx.text("two-id")) - .before(ctx -> { - ctx.header("before-all", "set"); - }) - .before("/two/*", ctx -> ctx.header("before-two", "set")) - .after(ctx -> { - afterAll.set("set"); - }) - .after("/two/*", ctx -> afterTwo.set("set")) - .get("/dummy", ctx -> ctx.text("dummy")) - ); + final Jex app = + Jex.create() + .routing( + routing -> + routing + .get("/", ctx -> ctx.text("roo")) + .get("/one", ctx -> ctx.text("one")) + .get("/two", ctx -> ctx.text("two")) + .get("/two/{id}", ctx -> ctx.text("two-id")) + .filter( + (ctx, chain) -> { + ctx.header("before-all", "set"); + chain.proceed(); + }) + .filter( + (ctx, chain) -> { + if (ctx.url().contains("/two/")) { + ctx.header("before-two", "set"); + } + chain.proceed(); + }) + .filter( + (ctx, chain) -> { + chain.proceed(); + afterAll.set("set"); + }) + .filter( + (ctx, chain) -> { + chain.proceed(); + if (ctx.url().contains("/two/")) { + + afterTwo.set("set"); + } + }) + .get("/dummy", ctx -> ctx.text("dummy"))); return TestPair.create(app); } @@ -65,7 +84,6 @@ void get() { assertNoBeforeAfterTwo(res); } - @Test void get_two_expect_extraFilters() { clearAfter(); diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/RedirectTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/RedirectTest.java index 418b21be..0ad09b81 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/RedirectTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/RedirectTest.java @@ -10,18 +10,23 @@ class RedirectTest { - static TestPair pair = init(); static TestPair init() { - var app = Jex.create() - .routing(routing -> routing - .before("/other/*", ctx -> ctx.redirect("/two?from=filter")) - .get("/one", ctx -> ctx.text("one")) - .get("/two", ctx -> ctx.text("two")) - .get("/redirect/me", ctx -> ctx.redirect("/one?from=handler")) - .get("/other/me", ctx -> ctx.text("never hit")) - ); + var app = + Jex.create() + .routing( + routing -> + routing + .filter( + (ctx, chain) -> { + if (ctx.url().contains("/other/")) ctx.redirect("/two?from=filter"); + chain.proceed(); + }) + .get("/one", ctx -> ctx.text("one")) + .get("/two", ctx -> ctx.text("two")) + .get("/redirect/me", ctx -> ctx.redirect("/one?from=handler")) + .get("/other/me", ctx -> ctx.text("never hit"))); return TestPair.create(app); } From 77a10a906a538ee1a26e57088dbcf03c04a44c1d Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 21:00:48 -0500 Subject: [PATCH 025/250] Create dependabot.yml --- .github/workflows/dependabot.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/dependabot.yml diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml new file mode 100644 index 00000000..8178f991 --- /dev/null +++ b/.github/workflows/dependabot.yml @@ -0,0 +1,25 @@ +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 10 + groups: + dependencies: + patterns: + - "*" + labels: + - "dependencies" + target-branch: "master" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + commit-message: + prefix: "[workflow]" + labels: + - "dependencies" + target-branch: "master" From e0d2207941a01ce78babdb6dbce6684a3a03fc7e Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 21:03:23 -0500 Subject: [PATCH 026/250] fix build --- .github/{workflows => }/dependabot.yml | 0 .github/workflows/dependabot-merge.yml | 28 ++++++++++++++++++++++++++ .github/workflows/jdk-ea.yml | 2 ++ pom.xml | 1 + 4 files changed, 31 insertions(+) rename .github/{workflows => }/dependabot.yml (100%) create mode 100644 .github/workflows/dependabot-merge.yml diff --git a/.github/workflows/dependabot.yml b/.github/dependabot.yml similarity index 100% rename from .github/workflows/dependabot.yml rename to .github/dependabot.yml diff --git a/.github/workflows/dependabot-merge.yml b/.github/workflows/dependabot-merge.yml new file mode 100644 index 00000000..c444bfa4 --- /dev/null +++ b/.github/workflows/dependabot-merge.yml @@ -0,0 +1,28 @@ +name: Dependabot auto-merge +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Approve a PR + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + # Enable for automerge + - name: Enable auto-merge for Dependabot PRs + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/jdk-ea.yml b/.github/workflows/jdk-ea.yml index e9a76d5a..e8f51d5c 100644 --- a/.github/workflows/jdk-ea.yml +++ b/.github/workflows/jdk-ea.yml @@ -2,6 +2,8 @@ name: JDK EA on: + push: + pull_request: workflow_dispatch: schedule: - cron: '12 8 * * 1,3,5' diff --git a/pom.xml b/pom.xml index ac3694c4..6132bfee 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,7 @@ 2.15.0 false 21 + 2024-10-25T04:21:12Z From b19cdd6f2bd1e5668799541f11c3fb2322a36586 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 02:05:22 +0000 Subject: [PATCH 027/250] [workflow]: Bump actions/checkout from 2 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/jdk-ea.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e9c216e3..5e98a277 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: os: [ubuntu-latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v2 with: diff --git a/.github/workflows/jdk-ea.yml b/.github/workflows/jdk-ea.yml index e8f51d5c..263b2718 100644 --- a/.github/workflows/jdk-ea.yml +++ b/.github/workflows/jdk-ea.yml @@ -22,7 +22,7 @@ jobs: os: [ubuntu-latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Java uses: oracle-actions/setup-java@v1 with: From c819f579781855d243375f3d32d5df2de67e6b3f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 02:05:42 +0000 Subject: [PATCH 028/250] Bump the dependencies group with 15 updates Bumps the dependencies group with 15 updates: | Package | From | To | | --- | --- | --- | | [org.avaje:java11-oss](https://github.com/avaje-pom/java11-oss) | `3.9` | `4.5` | | io.avaje:junit | `1.3` | `1.5` | | [io.avaje:avaje-http-client](https://github.com/avaje/avaje-http-client) | `2.0` | `2.8` | | [io.avaje:avaje-config](https://github.com/avaje/avaje-config) | `3.9` | `4.0` | | [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson) | `2.14.0` | `2.18.1` | | [io.avaje:avaje-jsonb](https://github.com/avaje/avaje-jsonb) | `1.4` | `2.3` | | io.avaje:avaje-inject-test | `9.9` | `10.6` | | org.freemarker:freemarker | `2.3.31` | `2.3.33` | | [com.github.spullara.mustache.java:compiler](https://github.com/spullara/mustache.java) | `0.9.10` | `0.9.14` | | org.slf4j:slf4j-api | `1.7.36` | `2.0.16` | | [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) | `1.2.11` | `1.5.12` | | [org.apache.maven.plugins:maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) | `3.8.1` | `3.13.0` | | [io.repaint.maven:tiles-maven-plugin](https://github.com/repaint-io/maven-tiles) | `2.22` | `2.40` | | io.avaje:avaje-jsonb-generator | `1.4` | `2.3` | | [org.graalvm.buildtools:native-maven-plugin](https://github.com/graalvm/native-build-tools) | `0.9.16` | `0.10.3` | Updates `org.avaje:java11-oss` from 3.9 to 4.5 - [Release notes](https://github.com/avaje-pom/java11-oss/releases) - [Commits](https://github.com/avaje-pom/java11-oss/commits) Updates `io.avaje:junit` from 1.3 to 1.5 Updates `io.avaje:avaje-http-client` from 2.0 to 2.8 - [Release notes](https://github.com/avaje/avaje-http-client/releases) - [Commits](https://github.com/avaje/avaje-http-client/commits) Updates `io.avaje:avaje-config` from 3.9 to 4.0 - [Release notes](https://github.com/avaje/avaje-config/releases) - [Commits](https://github.com/avaje/avaje-config/compare/3.9...4.0) Updates `com.fasterxml.jackson.core:jackson-databind` from 2.14.0 to 2.18.1 - [Commits](https://github.com/FasterXML/jackson/commits) Updates `io.avaje:avaje-jsonb` from 1.4 to 2.3 - [Release notes](https://github.com/avaje/avaje-jsonb/releases) - [Commits](https://github.com/avaje/avaje-jsonb/compare/1.4...2.3) Updates `io.avaje:avaje-inject-test` from 9.9 to 10.6 Updates `org.freemarker:freemarker` from 2.3.31 to 2.3.33 Updates `com.github.spullara.mustache.java:compiler` from 0.9.10 to 0.9.14 - [Commits](https://github.com/spullara/mustache.java/compare/0.9.10...mustache.java-0.9.14) Updates `org.slf4j:slf4j-api` from 1.7.36 to 2.0.16 Updates `ch.qos.logback:logback-classic` from 1.2.11 to 1.5.12 - [Commits](https://github.com/qos-ch/logback/compare/v_1.2.11...v_1.5.12) Updates `org.apache.maven.plugins:maven-compiler-plugin` from 3.8.1 to 3.13.0 - [Release notes](https://github.com/apache/maven-compiler-plugin/releases) - [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.8.1...maven-compiler-plugin-3.13.0) Updates `io.repaint.maven:tiles-maven-plugin` from 2.22 to 2.40 - [Release notes](https://github.com/repaint-io/maven-tiles/releases) - [Changelog](https://github.com/repaint-io/maven-tiles/blob/master/CHANGELOG.adoc) - [Commits](https://github.com/repaint-io/maven-tiles/compare/tiles-maven-plugin-2.22...tiles-maven-plugin-2.40) Updates `io.avaje:avaje-jsonb-generator` from 1.4 to 2.3 Updates `org.graalvm.buildtools:native-maven-plugin` from 0.9.16 to 0.10.3 - [Release notes](https://github.com/graalvm/native-build-tools/releases) - [Commits](https://github.com/graalvm/native-build-tools/compare/0.9.16...0.10.3) --- updated-dependencies: - dependency-name: org.avaje:java11-oss dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies - dependency-name: io.avaje:junit dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-config dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-jsonb dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-test dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies - dependency-name: org.freemarker:freemarker dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: com.github.spullara.mustache.java:compiler dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: org.apache.maven.plugins:maven-compiler-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.repaint.maven:tiles-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-jsonb-generator dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies - dependency-name: org.graalvm.buildtools:native-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-test/pom.xml | 6 +++--- avaje-jex/pom.xml | 2 +- examples/example-jdk-jsonb/pom.xml | 10 +++++----- examples/example-jdk/pom.xml | 12 ++++++------ pom.xml | 6 +++--- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 28e5b00d..2006c846 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -10,7 +10,7 @@ avaje-jex-freemarker - 2.3.31 + 2.3.33 diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index ad27a950..07c6dc50 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -10,7 +10,7 @@ avaje-jex-mustache - 0.9.10 + 0.9.14 diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 4afd4d6c..8e7ebf7e 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -21,21 +21,21 @@ io.avaje avaje-http-client - 2.0 + 2.8 io.avaje avaje-inject-test - 9.9 + 10.6 true io.avaje avaje-jsonb - 1.9 + 2.3 true diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index aa5ccc24..5e1a4f64 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -23,7 +23,7 @@ io.avaje avaje-config - 3.9 + 4.0 true diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index c6167cd2..dc43bde6 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -6,7 +6,7 @@ org.avaje java11-oss - 3.9 + 4.5 @@ -19,7 +19,7 @@ 17 - 0.9.16 + 0.10.3 @@ -33,7 +33,7 @@ io.avaje avaje-jsonb - 1.4 + 2.3 @@ -43,7 +43,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.10.1 + 3.13.0 17 @@ -53,7 +53,7 @@ io.avaje avaje-jsonb-generator - 1.4 + 2.3 diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index dd7789ff..5eb24826 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -6,7 +6,7 @@ org.avaje java11-oss - 3.9 + 4.5 @@ -30,19 +30,19 @@ com.fasterxml.jackson.core jackson-databind - 2.14.0 + 2.18.1 org.slf4j slf4j-api - 1.7.36 + 2.0.16 ch.qos.logback logback-classic - 1.2.11 + 1.5.12 @@ -52,7 +52,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.13.0 ${java.release} @@ -60,7 +60,7 @@ io.repaint.maven tiles-maven-plugin - 2.22 + 2.40 true diff --git a/pom.xml b/pom.xml index 6132bfee..913c9c77 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ true - 2.15.0 + 2.18.1 false 21 2024-10-25T04:21:12Z @@ -38,14 +38,14 @@ io.avaje junit - 1.3 + 1.5 test io.avaje avaje-http-client - 2.0 + 2.8 test From 459566a4c96f0b2fa3bc9a5b590e1b3ccaac3016 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 02:07:47 +0000 Subject: [PATCH 029/250] [workflow]: Bump actions/cache from 2 to 4 Bumps [actions/cache](https://github.com/actions/cache) from 2 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/jdk-ea.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e98a277..e615ba25 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: java-version: ${{ matrix.java_version }} distribution: 'zulu' - name: Maven cache - uses: actions/cache@v2 + uses: actions/cache@v4 env: cache-name: maven-cache with: diff --git a/.github/workflows/jdk-ea.yml b/.github/workflows/jdk-ea.yml index 263b2718..23727c1d 100644 --- a/.github/workflows/jdk-ea.yml +++ b/.github/workflows/jdk-ea.yml @@ -29,7 +29,7 @@ jobs: website: jdk.java.net release: ${{ matrix.java_version }} - name: Maven cache - uses: actions/cache@v2 + uses: actions/cache@v4 env: cache-name: maven-cache with: From 7d859e65a9b4639b96cfe712d3967dfbfd48d7d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 02:09:26 +0000 Subject: [PATCH 030/250] [workflow]: Bump actions/setup-java from 2 to 4 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 2 to 4. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/setup-java dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e615ba25..4864ad95 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: ${{ matrix.java_version }} distribution: 'zulu' From 2ed1f4fbdf0977ceb594bc7b5e3a5e3c00ba3cae Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 21:21:03 -0500 Subject: [PATCH 031/250] Update JexInjectPlugin.java --- .../io/avaje/jex/test/JexInjectPlugin.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/avaje-jex-test/src/main/java/io/avaje/jex/test/JexInjectPlugin.java b/avaje-jex-test/src/main/java/io/avaje/jex/test/JexInjectPlugin.java index 9bf7e0d4..d12b975b 100644 --- a/avaje-jex-test/src/main/java/io/avaje/jex/test/JexInjectPlugin.java +++ b/avaje-jex-test/src/main/java/io/avaje/jex/test/JexInjectPlugin.java @@ -1,12 +1,13 @@ package io.avaje.jex.test; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + import io.avaje.http.client.HttpClient; import io.avaje.inject.BeanScope; import io.avaje.inject.test.Plugin; import io.avaje.jex.Jex; -import java.lang.annotation.Annotation; - /** * avaje-inject-test plugin that: * @@ -20,12 +21,13 @@ public final class JexInjectPlugin implements Plugin { private static final String AVAJE_HTTP_CLIENT = "io.avaje.http.api.Client"; private static final String AVAJE_HTTP_PATH = "io.avaje.http.api.Path"; - /** - * Return true if it's a http client this plugin supports. - */ + /** Return true if it's a http client this plugin supports. */ @Override - public boolean forType(Class type) { - return HttpClient.class.equals(type) || isHttpClientApi(type); + public boolean forType(Type type) { + + if (!(type instanceof Class clazz)) return false; + + return HttpClient.class.equals(clazz) || isHttpClientApi(clazz); } private boolean isHttpClientApi(Class type) { @@ -34,10 +36,7 @@ private boolean isHttpClientApi(Class type) { } for (Annotation annotation : type.getAnnotations()) { String name = annotation.annotationType().getName(); - if (AVAJE_HTTP_CLIENT.equals(name)) { - return true; - } - if (AVAJE_HTTP_PATH.equals(name)) { + if (AVAJE_HTTP_CLIENT.equals(name) || AVAJE_HTTP_PATH.equals(name)) { return true; } } @@ -76,11 +75,12 @@ private static class LocalScope implements Plugin.Scope { } @Override - public Object create(Class type) { + public Object create(Type type) { + if (HttpClient.class.equals(type)) { return httpClient; } - return apiClient(type); + return apiClient((Class) type); } private Object apiClient(Class clientInterface) { From 519d5f3bc9477a7a28e69b4cab10c104baa1a9d7 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 22 Nov 2024 23:46:43 -0500 Subject: [PATCH 032/250] get roles and basic auth --- .../src/main/java/io/avaje/jex/Context.java | 15 ++++ .../java/io/avaje/jex/DefaultRouting.java | 2 +- .../src/main/java/io/avaje/jex/Role.java | 4 -- .../src/main/java/io/avaje/jex/Routing.java | 2 + .../java/io/avaje/jex/jdk/BaseFilter.java | 5 +- .../java/io/avaje/jex/jdk/JdkContext.java | 70 ++++++++++++++----- .../java/io/avaje/jex/routes/RouteEntry.java | 17 +++-- .../io/avaje/jex/routes/RoutesBuilder.java | 2 +- .../java/io/avaje/jex/routes/SpiRoutes.java | 12 ++-- .../jex/security/BasicAuthCredentials.java | 3 + .../main/java/io/avaje/jex/security/Role.java | 6 ++ avaje-jex/src/main/java/module-info.java | 3 +- .../io/avaje/jex/routes/RouteIndexTest.java | 8 ++- 13 files changed, 113 insertions(+), 36 deletions(-) delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/Role.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/security/BasicAuthCredentials.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/security/Role.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index 98944435..3cebb73f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -11,10 +11,13 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Stream; import com.sun.net.httpserver.HttpExchange; +import io.avaje.jex.security.BasicAuthCredentials; +import io.avaje.jex.security.Role; import io.avaje.jex.spi.HeaderKeys; /** @@ -84,6 +87,9 @@ public interface Context { */ void redirect(String location, int httpStatusCode); + /** Roles attached to this route */ + Set routeRoles(); + /** * Return the request body as bytes. */ @@ -489,4 +495,13 @@ public String toString() { return result.toString(); } } + + /** + * Gets basic-auth credentials from the request, or throws. + * + *

Returns a wrapper object containing the Base64 decoded username + * and password from the Authorization header, or null if basic-auth is not properly configured + */ + BasicAuthCredentials basicAuthCredentials(); + } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index 5520680d..94bf2c9f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -8,7 +8,7 @@ import java.util.List; import java.util.Set; -import io.avaje.jex.jdk.JdkFilter; +import io.avaje.jex.security.Role; class DefaultRouting implements Routing { diff --git a/avaje-jex/src/main/java/io/avaje/jex/Role.java b/avaje-jex/src/main/java/io/avaje/jex/Role.java deleted file mode 100644 index 9906821d..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/Role.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.avaje.jex; - -public interface Role { -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index 238ab74b..c2096e8f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -4,6 +4,8 @@ import java.util.List; import java.util.Set; +import io.avaje.jex.security.Role; + public interface Routing { /** diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilter.java index 067b3187..3117f83a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilter.java @@ -1,6 +1,7 @@ package io.avaje.jex.jdk; import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import com.sun.net.httpserver.Filter; @@ -35,7 +36,7 @@ public void doFilter(HttpExchange exchange, Filter.Chain chain) { final SpiRoutes.Entry route = routes.match(routeType, uri); if (route == null) { - var ctx = new JdkContext(mgr, exchange, uri); + var ctx = new JdkContext(mgr, exchange, uri, Set.of()); routes.inc(); try { processNoRoute(ctx, uri, routeType); @@ -50,7 +51,7 @@ public void doFilter(HttpExchange exchange, Filter.Chain chain) { route.inc(); try { final Map params = route.pathParams(uri); - JdkContext ctx = new JdkContext(mgr, exchange, route.matchPath(), params); + JdkContext ctx = new JdkContext(mgr, exchange, route.matchPath(), params, route.roles()); try { ctx.setMode(Type.FILTER); exchange.setAttribute("JdkContext", ctx); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index 0c06d835..c7066bf3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -1,14 +1,7 @@ package io.avaje.jex.jdk; -import com.sun.net.httpserver.Headers; -import com.sun.net.httpserver.HttpExchange; -import io.avaje.jex.Context; -import io.avaje.jex.Routing; -import io.avaje.jex.UploadedFile; -import io.avaje.jex.http.ErrorCode; -import io.avaje.jex.http.HttpResponseException; -import io.avaje.jex.spi.HeaderKeys; -import io.avaje.jex.spi.SpiContext; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; import java.io.IOException; import java.io.InputStream; @@ -19,14 +12,26 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.Base64; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Stream; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; + +import io.avaje.jex.Context; +import io.avaje.jex.Routing; +import io.avaje.jex.UploadedFile; +import io.avaje.jex.http.ErrorCode; +import io.avaje.jex.http.HttpResponseException; +import io.avaje.jex.security.BasicAuthCredentials; +import io.avaje.jex.security.Role; +import io.avaje.jex.spi.HeaderKeys; +import io.avaje.jex.spi.SpiContext; class JdkContext implements Context, SpiContext { @@ -37,6 +42,7 @@ class JdkContext implements Context, SpiContext { private final ServiceManager mgr; private final String path; private final Map pathParams; + private final Set roles; private final HttpExchange exchange; private Routing.Type mode; private Map> formParams; @@ -45,18 +51,23 @@ class JdkContext implements Context, SpiContext { private int statusCode; private String characterEncoding; - JdkContext(ServiceManager mgr, HttpExchange exchange, String path, Map pathParams) { + JdkContext( + ServiceManager mgr, + HttpExchange exchange, + String path, + Map pathParams, + Set roles) { this.mgr = mgr; + this.roles = roles; this.exchange = exchange; this.path = path; this.pathParams = pathParams; } - /** - * Create when no route matched. - */ - JdkContext(ServiceManager mgr, HttpExchange exchange, String path) { + /** Create when no route matched. */ + JdkContext(ServiceManager mgr, HttpExchange exchange, String path, Set roles) { this.mgr = mgr; + this.roles = roles; this.exchange = exchange; this.path = path; this.pathParams = null; @@ -454,4 +465,31 @@ HttpExchange exchange() { public HttpExchange jdkExchange() { return exchange; } + + @Override + public Set routeRoles() { + return roles; + } + + @Override + public BasicAuthCredentials basicAuthCredentials() { + return getBasicAuthCredentials(header("Authorization")); + } + + private static BasicAuthCredentials getBasicAuthCredentials(String authorizationHeader) { + if (authorizationHeader == null || !authorizationHeader.startsWith("Basic ")) { + return null; + } + + String base64Credentials = authorizationHeader.substring("Basic ".length()); + byte[] decodedCredentials = Base64.getDecoder().decode(base64Credentials); + String credentialsString = new String(decodedCredentials); + + String[] credentials = credentialsString.split(":", 2); + if (credentials.length != 2) { + throw new IllegalStateException("Invalid Basic Auth header"); + } + + return new BasicAuthCredentials(credentials[0], credentials[1]); + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java index d39efc58..275989c8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java @@ -1,20 +1,24 @@ package io.avaje.jex.routes; -import io.avaje.jex.Context; -import io.avaje.jex.Handler; - import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicLong; +import io.avaje.jex.Context; +import io.avaje.jex.Handler; +import io.avaje.jex.security.Role; + class RouteEntry implements SpiRoutes.Entry { private final AtomicLong active = new AtomicLong(); private final PathParser path; private final Handler handler; + private final Set roles; - RouteEntry(PathParser path, Handler handler) { + RouteEntry(PathParser path, Handler handler, Set roles) { this.path = path; this.handler = handler; + this.roles = roles; } @Override @@ -66,4 +70,9 @@ public boolean multiSlash() { public boolean literal() { return path.literal(); } + + @Override + public Set roles() { + return roles; + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java index 4b339b1f..fd614327 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java @@ -25,7 +25,7 @@ public RoutesBuilder(Routing routing, boolean ignoreTrailingSlashes) { private SpiRoutes.Entry convert(Routing.Entry handler) { final PathParser pathParser = new PathParser(handler.getPath(), ignoreTrailingSlashes); - return new RouteEntry(pathParser, handler.getHandler()); + return new RouteEntry(pathParser, handler.getHandler(), handler.getRoles()); } public SpiRoutes build() { diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java b/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java index 0dd93c5c..be4f2bfc 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java @@ -1,12 +1,13 @@ package io.avaje.jex.routes; +import java.util.List; +import java.util.Map; +import java.util.Set; + import io.avaje.jex.Context; import io.avaje.jex.Routing; import io.avaje.jex.jdk.JdkFilter; -import io.avaje.jex.spi.SpiContext; - -import java.util.List; -import java.util.Map; +import io.avaje.jex.security.Role; /** * Route matching and filter handling. @@ -97,6 +98,9 @@ interface Entry { * Return the active request count for the route. */ long activeRequests(); + + /** Return the authentication roles for the route. */ + Set roles(); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/security/BasicAuthCredentials.java b/avaje-jex/src/main/java/io/avaje/jex/security/BasicAuthCredentials.java new file mode 100644 index 00000000..97d2a20b --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/security/BasicAuthCredentials.java @@ -0,0 +1,3 @@ +package io.avaje.jex.security; + +public record BasicAuthCredentials(String userName, String password) {} diff --git a/avaje-jex/src/main/java/io/avaje/jex/security/Role.java b/avaje-jex/src/main/java/io/avaje/jex/security/Role.java new file mode 100644 index 00000000..95c28594 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/security/Role.java @@ -0,0 +1,6 @@ +package io.avaje.jex.security; + +import io.avaje.jex.Context; + +/** Marker interface for roles used in route declarations. See {@link Context#routeRoles()}. */ +public interface Role {} diff --git a/avaje-jex/src/main/java/module-info.java b/avaje-jex/src/main/java/module-info.java index 8b483c0d..d01cc5e0 100644 --- a/avaje-jex/src/main/java/module-info.java +++ b/avaje-jex/src/main/java/module-info.java @@ -4,8 +4,9 @@ exports io.avaje.jex; exports io.avaje.jex.http; - exports io.avaje.jex.spi; exports io.avaje.jex.core; + exports io.avaje.jex.security; + exports io.avaje.jex.spi; requires transitive java.net.http; requires transitive jdk.httpserver; diff --git a/avaje-jex/src/test/java/io/avaje/jex/routes/RouteIndexTest.java b/avaje-jex/src/test/java/io/avaje/jex/routes/RouteIndexTest.java index 0e7d2487..5db0e53e 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/routes/RouteIndexTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/routes/RouteIndexTest.java @@ -1,11 +1,13 @@ package io.avaje.jex.routes; -import io.avaje.jex.Routing; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Set; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import static org.assertj.core.api.Assertions.assertThat; +import io.avaje.jex.Routing; class RouteIndexTest { @@ -66,6 +68,6 @@ void match_splat() { } private SpiRoutes.Entry entry(String path) { - return new RouteEntry(new PathParser(path, true), routingEntry.getHandler()); + return new RouteEntry(new PathParser(path, true), routingEntry.getHandler(), Set.of()); } } From c6f6fddf9060539e4a01d8f87da420791a82dc22 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 23 Nov 2024 01:47:25 -0500 Subject: [PATCH 033/250] support input streams --- .../src/main/java/io/avaje/jex/Context.java | 11 ++++++ .../main/java/io/avaje/jex/DJexConfig.java | 2 +- .../java/io/avaje/jex/TemplateRender.java | 7 ++-- .../io/avaje/jex/jdk/BufferedOutStream.java | 2 +- .../java/io/avaje/jex/jdk/JdkContext.java | 34 ++++++++++++++----- .../java/io/avaje/jex/jdk/JdkServerStart.java | 11 +++--- .../{BaseFilter.java => RoutingFilter.java} | 4 +-- 7 files changed, 49 insertions(+), 22 deletions(-) rename avaje-jex/src/main/java/io/avaje/jex/jdk/{BaseFilter.java => RoutingFilter.java} (96%) diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index 3cebb73f..1b94b007 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -3,6 +3,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; +import java.io.InputStream; import java.time.Duration; import java.time.LocalDateTime; import java.time.ZoneId; @@ -278,6 +279,16 @@ default String userAgent() { */ Context write(String content); + /** + * Write raw bytes to the response. + */ + Context write(byte[] bytes); + + /** + * Write raw inputStream to the response. + */ + Context write(InputStream is); + /** * Render a template typically as html. * diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 0b4e5b51..536380c9 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -89,7 +89,7 @@ public ThreadFactory threadFactory() { if (factory == null) { factory = - Thread.ofVirtual().name("jex-http-", 0).factory(); + Thread.ofVirtual().name("avaje-jex-http-", 0).factory(); } return factory; diff --git a/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java b/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java index 1d58d8f4..562d3aaa 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java +++ b/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java @@ -11,10 +11,9 @@ public non-sealed interface TemplateRender extends JexExtension { /** * Return the extensions this template renders for by default. - *

- * When the template render is not explicitly registered it can be - * automatically registered via ServiceLoader and these are the extensions - * it will register for by default. + * + *

When the template render is not explicitly registered, it can be automatically registered + * via ServiceLoader with the provided extensions by default. */ String[] defaultExtensions(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java index 0940b949..0387eddd 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java @@ -63,7 +63,7 @@ public void close() throws IOException { stream.flush(); stream.close(); } else { - context.writeBytes(buffer.toByteArray()); + context.write(buffer.toByteArray()); } } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index c7066bf3..7dd5ec6b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -343,20 +343,36 @@ public Context html(String content) { @Override public Context write(String content) { - try { - writeBytes(content.getBytes(StandardCharsets.UTF_8)); - return this; + + write(content.getBytes(StandardCharsets.UTF_8)); + return this; + } + + @Override + public Context write(byte[] bytes) { + + try (var os = exchange.getResponseBody()) { + exchange.sendResponseHeaders(statusCode(), bytes.length); + + os.write(bytes); + os.flush(); } catch (IOException e) { throw new UncheckedIOException(e); } + return this; } - void writeBytes(byte[] bytes) throws IOException { - exchange.sendResponseHeaders(statusCode(), bytes.length); - final OutputStream os = exchange.getResponseBody(); - os.write(bytes); - os.flush(); - os.close(); + @Override + public Context write(InputStream is) { + + try (is; var os = exchange.getResponseBody()) { + exchange.sendResponseHeaders(statusCode(), 0); + is.transferTo(os); + os.flush(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return this; } int statusCode() { diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index 1ace94d4..5676b749 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -24,21 +24,22 @@ public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceMana final ServiceManager manager = new ServiceManager(serviceManager, "http", ""); try { - int port = jex.config().port(); final HttpServer server; + var port = new InetSocketAddress(jex.config().port()); final var sslContext = jex.config().sslContext(); - if (sslContext != null) { - var httpsServer = HttpsServer.create(new InetSocketAddress(port), 0); + if (sslContext != null) { + var httpsServer = HttpsServer.create(port, 0); httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext)); server = httpsServer; } else { - server = HttpServer.create(new InetSocketAddress(port), 0); + server = HttpServer.create(port, 0); } + var handler = new BaseHandler(routes); var context = server.createContext("/", handler); - context.getFilters().add(new BaseFilter(routes, manager)); + context.getFilters().add(new RoutingFilter(routes, manager)); context.getFilters().addAll(routes.filters()); server.setExecutor(Executors.newThreadPerTaskExecutor(jex.config().threadFactory())); server.start(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java similarity index 96% rename from avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilter.java rename to avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java index 3117f83a..f78fdbdd 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java @@ -14,12 +14,12 @@ import io.avaje.jex.routes.SpiRoutes; import io.avaje.jex.spi.SpiContext; -class BaseFilter extends Filter { +final class RoutingFilter extends Filter { private final SpiRoutes routes; private final ServiceManager mgr; - BaseFilter(SpiRoutes routes, ServiceManager mgr) { + RoutingFilter(SpiRoutes routes, ServiceManager mgr) { this.mgr = mgr; this.routes = routes; } From 30195d8582d367f749befd12a6dfbc447fa40eae Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 23 Nov 2024 10:39:39 -0500 Subject: [PATCH 034/250] add before/after --- .../java/io/avaje/jex/DefaultRouting.java | 2 +- .../main/java/io/avaje/jex/FilterChain.java | 2 +- .../src/main/java/io/avaje/jex/Handler.java | 11 ++++++++++ .../src/main/java/io/avaje/jex/Routing.java | 21 +++++++++++++++++++ .../java/io/avaje/jex/jdk/RoutingFilter.java | 2 ++ .../java/io/avaje/jex/jdk/FilterTest.java | 12 ++--------- 6 files changed, 38 insertions(+), 12 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index 94bf2c9f..2cf07e2f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -7,6 +7,7 @@ import java.util.Deque; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import io.avaje.jex.security.Role; @@ -179,7 +180,6 @@ public Routing filter(HttpFilter handler) { return this; } - private static class Entry implements Routing.Entry { private final Type type; diff --git a/avaje-jex/src/main/java/io/avaje/jex/FilterChain.java b/avaje-jex/src/main/java/io/avaje/jex/FilterChain.java index b5966196..033f6aaf 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/FilterChain.java +++ b/avaje-jex/src/main/java/io/avaje/jex/FilterChain.java @@ -8,7 +8,7 @@ public interface FilterChain { /** - * Calls the next filter in the chain, or else the users exchange handler, if this is the final + * Calls the next filter in the chain, or else the user's exchange handler, if this is the final * filter in the chain. The {@link HttpFilter} may decide to terminate the chain, by not * calling this method. In this case, the filter must send the response to the request, * because the application's {@linkplain HttpExchange exchange} handler will not be invoked. diff --git a/avaje-jex/src/main/java/io/avaje/jex/Handler.java b/avaje-jex/src/main/java/io/avaje/jex/Handler.java index e23816d2..aacbf15f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Handler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Handler.java @@ -1,7 +1,18 @@ package io.avaje.jex; +/** + * A handler which is invoked to process HTTP exchanges. Each HTTP exchange is handled by one of + * these handlers. + */ @FunctionalInterface public interface Handler { + /** + * Handle the given request and generate an appropriate response. See {@link Context} for a + * description of the steps involved in handling an exchange. + * + * @param ctx the request context containing the request from the client and used to send the + * response + */ void handle(Context ctx); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index c2096e8f..c36022f6 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -3,6 +3,7 @@ import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import io.avaje.jex.security.Role; @@ -128,6 +129,26 @@ public interface Routing { */ Routing filter(HttpFilter handler); + /** Add a preprocessing filter for all requests. */ + default Routing before(Consumer handler) { + + return filter( + (ctx, chain) -> { + handler.accept(ctx); + chain.proceed(); + }); + } + + /** Add a post-processing filter for all requests. */ + default Routing after(Consumer handler) { + + return filter( + (ctx, chain) -> { + chain.proceed(); + handler.accept(ctx); + }); + } + /** * Return all the registered handlers. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java index f78fdbdd..32ecb31d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java @@ -46,6 +46,7 @@ public void doFilter(HttpExchange exchange, Filter.Chain chain) { handleException(ctx, e); } finally { routes.dec(); + exchange.close(); } } else { route.inc(); @@ -63,6 +64,7 @@ public void doFilter(HttpExchange exchange, Filter.Chain chain) { } } finally { route.dec(); + exchange.close(); } } } diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java index 7d9e4b03..a40afe17 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java @@ -26,11 +26,7 @@ static TestPair init() { .get("/one", ctx -> ctx.text("one")) .get("/two", ctx -> ctx.text("two")) .get("/two/{id}", ctx -> ctx.text("two-id")) - .filter( - (ctx, chain) -> { - ctx.header("before-all", "set"); - chain.proceed(); - }) + .before(ctx -> ctx.header("before-all", "set")) .filter( (ctx, chain) -> { if (ctx.url().contains("/two/")) { @@ -38,11 +34,7 @@ static TestPair init() { } chain.proceed(); }) - .filter( - (ctx, chain) -> { - chain.proceed(); - afterAll.set("set"); - }) + .after(ctx -> afterAll.set("set")) .filter( (ctx, chain) -> { chain.proceed(); From fa3183513989378b5e2f63c269597133b445a9b6 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 23 Nov 2024 12:30:05 -0500 Subject: [PATCH 035/250] refactor internals --- .gitignore | 1 + .../render/freemarker/FreeMarkerRender.java | 2 +- .../jex/render/mustache/MustacheRender.java | 2 +- .../src/main/java/io/avaje/jex/DJex.java | 4 +- .../main/java/io/avaje/jex/DJexConfig.java | 1 + avaje-jex/src/main/java/io/avaje/jex/Jex.java | 3 +- .../src/main/java/io/avaje/jex/JexConfig.java | 1 + .../avaje/jex/{Plugin.java => JexPlugin.java} | 2 +- .../java/io/avaje/jex/core/HealthPlugin.java | 4 +- .../io/avaje/jex/core/TemplateManager.java | 2 +- .../jex/core/internal/CoreServiceLoader.java | 2 +- .../jex/core/internal/CoreServiceManager.java | 4 +- .../internal}/SpiServiceManager.java | 6 ++- .../avaje/jex/http/HttpResponseException.java | 4 ++ .../CtxServiceManager.java} | 38 ++++++++++++++----- .../java/io/avaje/jex/jdk/JdkContext.java | 10 ++--- .../java/io/avaje/jex/jdk/JdkServerStart.java | 10 +++-- .../java/io/avaje/jex/jdk/RoutingFilter.java | 4 +- .../java/io/avaje/jex/jdk/ServiceManager.java | 34 ----------------- .../java/io/avaje/jex/spi/JexExtension.java | 1 - .../avaje/jex/{ => spi}/TemplateRender.java | 4 +- 21 files changed, 67 insertions(+), 72 deletions(-) rename avaje-jex/src/main/java/io/avaje/jex/{Plugin.java => JexPlugin.java} (86%) rename avaje-jex/src/main/java/io/avaje/jex/{spi => core/internal}/SpiServiceManager.java (88%) rename avaje-jex/src/main/java/io/avaje/jex/{spi/ProxyServiceManager.java => jdk/CtxServiceManager.java} (67%) delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/jdk/ServiceManager.java rename avaje-jex/src/main/java/io/avaje/jex/{ => spi}/TemplateRender.java (92%) diff --git a/.gitignore b/.gitignore index 3097fb34..f4aa06b9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ build/ *.prefs *.classpath *.prefs +*.factorypath bin/ diff --git a/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java b/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java index b9648228..67d4370a 100644 --- a/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java +++ b/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java @@ -10,7 +10,7 @@ import freemarker.template.TemplateException; import freemarker.template.Version; import io.avaje.jex.Context; -import io.avaje.jex.TemplateRender; +import io.avaje.jex.spi.TemplateRender; import io.avaje.spi.ServiceProvider; @ServiceProvider diff --git a/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java b/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java index 21d93700..f2aaf27c 100644 --- a/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java +++ b/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java @@ -3,7 +3,7 @@ import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.MustacheFactory; import io.avaje.jex.Context; -import io.avaje.jex.TemplateRender; +import io.avaje.jex.spi.TemplateRender; import io.avaje.spi.ServiceProvider; import java.io.IOException; diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index 59fbfd20..3cd3a211 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -89,7 +89,7 @@ public Jex jsonService(JsonService jsonService) { } @Override - public Jex plugin(Plugin plugin) { + public Jex plugin(JexPlugin plugin) { plugin.apply(this); return this; } @@ -97,7 +97,7 @@ public Jex plugin(Plugin plugin) { @Override public Jex configureWith(BeanScope beanScope) { lifecycle.onShutdown(beanScope::close); - for (Plugin plugin : beanScope.list(Plugin.class)) { + for (JexPlugin plugin : beanScope.list(JexPlugin.class)) { plugin.apply(this); } for (ErrorHandling.Service service : beanScope.list(ErrorHandling.Service.class)) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 536380c9..51fe8356 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -7,6 +7,7 @@ import javax.net.ssl.SSLContext; import io.avaje.jex.spi.JsonService; +import io.avaje.jex.spi.TemplateRender; class DJexConfig implements JexConfig { diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index ecb5fef6..a64824ba 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -2,6 +2,7 @@ import io.avaje.inject.BeanScope; import io.avaje.jex.spi.JsonService; +import io.avaje.jex.spi.TemplateRender; import java.util.Collection; import java.util.function.Consumer; @@ -97,7 +98,7 @@ static Jex create() { /** * Add Plugin functionality. */ - Jex plugin(Plugin plugin); + Jex plugin(JexPlugin plugin); /** * Configure given the dependency injection scope from avaje-inject. diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index f8815696..bbf3dbd1 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -7,6 +7,7 @@ import javax.net.ssl.SSLContext; import io.avaje.jex.spi.JsonService; +import io.avaje.jex.spi.TemplateRender; /** * Jex configuration. diff --git a/avaje-jex/src/main/java/io/avaje/jex/Plugin.java b/avaje-jex/src/main/java/io/avaje/jex/JexPlugin.java similarity index 86% rename from avaje-jex/src/main/java/io/avaje/jex/Plugin.java rename to avaje-jex/src/main/java/io/avaje/jex/JexPlugin.java index a9feb473..805402cb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Plugin.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexPlugin.java @@ -3,7 +3,7 @@ /** * A plugin that can register things like routes, exception handlers etc. */ -public interface Plugin { +public interface JexPlugin { /** * Register the plugin features with jex. diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java b/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java index 8ea2da11..6a84c555 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java @@ -3,13 +3,13 @@ import io.avaje.jex.AppLifecycle; import io.avaje.jex.Context; import io.avaje.jex.Jex; -import io.avaje.jex.Plugin; +import io.avaje.jex.JexPlugin; /** * Health plugin with liveness and readiness support based on * the application lifecycle support. */ -public class HealthPlugin implements Plugin { +public class HealthPlugin implements JexPlugin { private AppLifecycle lifecycle; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java index 11d7f51f..4de183ac 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java @@ -1,7 +1,7 @@ package io.avaje.jex.core; import io.avaje.jex.Context; -import io.avaje.jex.TemplateRender; +import io.avaje.jex.spi.TemplateRender; import java.util.HashMap; import java.util.HashSet; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java index e3ce91cb..14cd73b4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java @@ -5,9 +5,9 @@ import java.util.Optional; import java.util.ServiceLoader; -import io.avaje.jex.TemplateRender; import io.avaje.jex.spi.JexExtension; import io.avaje.jex.spi.JsonService; +import io.avaje.jex.spi.TemplateRender; /** Core implementation of SpiServiceManager provided to specific implementations like jetty etc. */ class CoreServiceLoader { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java index 91f6a8e3..3a57e696 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java @@ -8,7 +8,7 @@ import io.avaje.jex.spi.HeaderKeys; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.SpiContext; -import io.avaje.jex.spi.SpiServiceManager; +import io.avaje.jex.spi.TemplateRender; import java.io.UncheckedIOException; import java.io.UnsupportedEncodingException; @@ -20,7 +20,7 @@ /** * Core implementation of SpiServiceManager provided to specific implementations like jetty etc. */ -public class CoreServiceManager implements SpiServiceManager { +final class CoreServiceManager implements SpiServiceManager { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); public static final String UTF_8 = "UTF-8"; diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/SpiServiceManager.java similarity index 88% rename from avaje-jex/src/main/java/io/avaje/jex/spi/SpiServiceManager.java rename to avaje-jex/src/main/java/io/avaje/jex/core/internal/SpiServiceManager.java index 017f23ba..7eed68f8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/SpiServiceManager.java @@ -1,7 +1,9 @@ -package io.avaje.jex.spi; +package io.avaje.jex.core.internal; import io.avaje.jex.Context; import io.avaje.jex.Routing; +import io.avaje.jex.jdk.CtxServiceManager; +import io.avaje.jex.spi.SpiContext; import java.util.Iterator; import java.util.List; @@ -11,7 +13,7 @@ /** * Core service methods available to Context implementations. */ -public interface SpiServiceManager { +public sealed interface SpiServiceManager permits CoreServiceManager, CtxServiceManager { /** * Read and return the type from json request content. diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java b/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java index 1aef4859..7258f667 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java @@ -3,6 +3,10 @@ import java.util.Collections; import java.util.Map; +/** + * Throwing an uncaught {@code HttpResponseException} will interrupt http processing and set the + * status code and response body with the given message + */ public class HttpResponseException extends RuntimeException { private final int status; diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/ProxyServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java similarity index 67% rename from avaje-jex/src/main/java/io/avaje/jex/spi/ProxyServiceManager.java rename to avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java index aeb80c02..68926fe0 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/ProxyServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java @@ -1,25 +1,43 @@ -package io.avaje.jex.spi; +package io.avaje.jex.jdk; import io.avaje.jex.Context; import io.avaje.jex.Routing; +import io.avaje.jex.core.internal.SpiServiceManager; +import io.avaje.jex.spi.SpiContext; +import java.io.OutputStream; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.stream.Stream; -/** - * Provides a delegating proxy to a SpiServiceManager. - *

- * Can be used by specific implementations like Jetty and JDK Http Server to add core functionality - * to provide to the specific context implementation. - */ -public abstract class ProxyServiceManager implements SpiServiceManager { +public final class CtxServiceManager implements SpiServiceManager { - protected final SpiServiceManager delegate; + private final String scheme; + private final String contextPath; - protected ProxyServiceManager(SpiServiceManager delegate) { + private final SpiServiceManager delegate; + + CtxServiceManager(SpiServiceManager delegate, String scheme, String contextPath) { this.delegate = delegate; + this.scheme = scheme; + this.contextPath = contextPath; + } + + OutputStream createOutputStream(JdkContext jdkContext) { + return new BufferedOutStream(jdkContext); + } + + String scheme() { + return scheme; + } + + public String url() { + return scheme + "://"; + } + + public String contextPath() { + return contextPath; } @Override diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index 7dd5ec6b..0b916450 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -39,7 +39,7 @@ class JdkContext implements Context, SpiContext { private static final int SC_MOVED_TEMPORARILY = 302; private static final String SET_COOKIE = "Set-Cookie"; private static final String COOKIE = "Cookie"; - private final ServiceManager mgr; + private final CtxServiceManager mgr; private final String path; private final Map pathParams; private final Set roles; @@ -52,7 +52,7 @@ class JdkContext implements Context, SpiContext { private String characterEncoding; JdkContext( - ServiceManager mgr, + CtxServiceManager mgr, HttpExchange exchange, String path, Map pathParams, @@ -65,7 +65,7 @@ class JdkContext implements Context, SpiContext { } /** Create when no route matched. */ - JdkContext(ServiceManager mgr, HttpExchange exchange, String path, Set roles) { + JdkContext(CtxServiceManager mgr, HttpExchange exchange, String path, Set roles) { this.mgr = mgr; this.roles = roles; this.exchange = exchange; @@ -365,10 +365,8 @@ public Context write(byte[] bytes) { @Override public Context write(InputStream is) { - try (is; var os = exchange.getResponseBody()) { - exchange.sendResponseHeaders(statusCode(), 0); + try (is; var os = outputStream()) { is.transferTo(os); - os.flush(); } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index 5676b749..a331f46e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -6,22 +6,21 @@ import java.net.InetSocketAddress; import java.util.concurrent.Executors; -import com.sun.net.httpserver.*; +import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsServer; import io.avaje.applog.AppLog; import io.avaje.jex.AppLifecycle; import io.avaje.jex.Jex; +import io.avaje.jex.core.internal.SpiServiceManager; import io.avaje.jex.routes.SpiRoutes; -import io.avaje.jex.spi.SpiServiceManager; public class JdkServerStart { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceManager) { - final ServiceManager manager = new ServiceManager(serviceManager, "http", ""); try { final HttpServer server; @@ -29,14 +28,19 @@ public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceMana var port = new InetSocketAddress(jex.config().port()); final var sslContext = jex.config().sslContext(); + final String scheme; if (sslContext != null) { var httpsServer = HttpsServer.create(port, 0); httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext)); server = httpsServer; + scheme = "https"; } else { + scheme = "http"; server = HttpServer.create(port, 0); } + final var manager = new CtxServiceManager(serviceManager, scheme, ""); + var handler = new BaseHandler(routes); var context = server.createContext("/", handler); context.getFilters().add(new RoutingFilter(routes, manager)); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java index 32ecb31d..35498273 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java @@ -17,9 +17,9 @@ final class RoutingFilter extends Filter { private final SpiRoutes routes; - private final ServiceManager mgr; + private final CtxServiceManager mgr; - RoutingFilter(SpiRoutes routes, ServiceManager mgr) { + RoutingFilter(SpiRoutes routes, CtxServiceManager mgr) { this.mgr = mgr; this.routes = routes; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/ServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/ServiceManager.java deleted file mode 100644 index b1ce71c3..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/ServiceManager.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.avaje.jex.jdk; - -import io.avaje.jex.spi.ProxyServiceManager; -import io.avaje.jex.spi.SpiServiceManager; - -import java.io.OutputStream; - -final class ServiceManager extends ProxyServiceManager { - - private final String scheme; - private final String contextPath; - - ServiceManager(SpiServiceManager delegate, String scheme, String contextPath) { - super(delegate); - this.scheme = scheme; - this.contextPath = contextPath; - } - - OutputStream createOutputStream(JdkContext jdkContext) { - return new BufferedOutStream(jdkContext); - } - - String scheme() { - return scheme; - } - - public String url() { - return scheme + "://"; - } - - public String contextPath() { - return contextPath; - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java index 31b9c132..a0eb3947 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java @@ -1,6 +1,5 @@ package io.avaje.jex.spi; -import io.avaje.jex.TemplateRender; import io.avaje.spi.Service; @Service diff --git a/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java b/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java similarity index 92% rename from avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java rename to avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java index 562d3aaa..f0bebaa4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java @@ -1,8 +1,8 @@ -package io.avaje.jex; +package io.avaje.jex.spi; import java.util.Map; -import io.avaje.jex.spi.JexExtension; +import io.avaje.jex.Context; /** * Template rendering typically of html. From bf370fea8c090aa1d017fe1f9cc7cdb63a0c10b7 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 23 Nov 2024 12:51:52 -0500 Subject: [PATCH 036/250] Update CoreServiceManager.java --- .../java/io/avaje/jex/core/internal/CoreServiceManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java index 3a57e696..142cbef5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java @@ -20,7 +20,7 @@ /** * Core implementation of SpiServiceManager provided to specific implementations like jetty etc. */ -final class CoreServiceManager implements SpiServiceManager { +public final class CoreServiceManager implements SpiServiceManager { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); public static final String UTF_8 = "UTF-8"; From e214aa7a49ac3d994f0784f7685067e67b0c05fe Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 23 Nov 2024 12:53:14 -0500 Subject: [PATCH 037/250] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1d68ee95..a2474d39 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ +[![Discord](https://img.shields.io/discord/1074074312421683250?color=%237289da&label=discord)](https://discord.gg/Qcqf9R27BR) [![Build](https://github.com/avaje/avaje-jex/actions/workflows/build.yml/badge.svg)](https://github.com/avaje/avaje-jex/actions/workflows/build.yml) -[![Maven Central](https://img.shields.io/maven-central/v/io.avaje/avaje-jex-parent.svg?label=Maven%20Central)](https://mvnrepository.com/artifact/io.avaje/avaje-jex-parent) -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/avaje/avaje-jex/blob/master/LICENSE) [![JDK EA](https://github.com/avaje/avaje-jex/actions/workflows/jdk-ea.yml/badge.svg)](https://github.com/avaje/avaje-jex/actions/workflows/jdk-ea.yml) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/avaje/avaje-jex/blob/master/LICENSE) +[![Maven Central](https://img.shields.io/maven-central/v/io.avaje/avaje-jex.svg?label=Maven%20Central)](https://mvnrepository.com/artifact/io.avaje/avaje-jex) +[![javadoc](https://javadoc.io/badge2/io.avaje/avaje-jex/javadoc.svg?color=purple)](https://javadoc.io/doc/io.avaje/avaje-jex) # avaje-jex From da2a5753c53b801f96a161d26cc0fd951beb3905 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:49:05 -0500 Subject: [PATCH 038/250] refactor error handling --- .../src/main/java/io/avaje/jex/DJex.java | 31 +------------- .../io/avaje/jex/DefaultErrorHandling.java | 40 ------------------ .../java/io/avaje/jex/DefaultRouting.java | 19 ++++++++- .../main/java/io/avaje/jex/ErrorHandling.java | 42 ------------------- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 20 --------- .../src/main/java/io/avaje/jex/Routing.java | 13 +++++- .../main/java/io/avaje/jex/ServerConfig.java | 7 ---- .../{internal => }/CoreServiceLoader.java | 2 +- .../{internal => }/CoreServiceManager.java | 39 ++++++++++------- .../core/{internal => }/ExceptionManager.java | 32 ++++++++++---- .../core/{internal => }/HttpMethodMap.java | 6 +-- .../{internal => }/SpiServiceManager.java | 2 +- .../core/{ => json}/JacksonJsonService.java | 2 +- .../jex/core/{ => json}/JsonbJsonService.java | 2 +- .../io/avaje/jex/jdk/CtxServiceManager.java | 2 +- .../java/io/avaje/jex/jdk/JdkServerStart.java | 2 +- avaje-jex/src/main/java/module-info.java | 2 +- .../avaje/jex/DefaultErrorHandlingTest.java | 25 ++++++----- .../core/{internal => }/ContextUtilTest.java | 4 +- .../io/avaje/jex/jdk/JdkJexServerTest.java | 11 ++--- .../test/java/io/avaje/jex/jdk/JsonTest.java | 13 +++--- 21 files changed, 118 insertions(+), 198 deletions(-) delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/DefaultErrorHandling.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/ErrorHandling.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/ServerConfig.java rename avaje-jex/src/main/java/io/avaje/jex/core/{internal => }/CoreServiceLoader.java (96%) rename avaje-jex/src/main/java/io/avaje/jex/core/{internal => }/CoreServiceManager.java (88%) rename avaje-jex/src/main/java/io/avaje/jex/core/{internal => }/ExceptionManager.java (71%) rename avaje-jex/src/main/java/io/avaje/jex/core/{internal => }/HttpMethodMap.java (91%) rename avaje-jex/src/main/java/io/avaje/jex/core/{internal => }/SpiServiceManager.java (97%) rename avaje-jex/src/main/java/io/avaje/jex/core/{ => json}/JacksonJsonService.java (98%) rename avaje-jex/src/main/java/io/avaje/jex/core/{ => json}/JsonbJsonService.java (97%) rename avaje-jex/src/test/java/io/avaje/jex/core/{internal => }/ContextUtilTest.java (90%) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index 3cd3a211..1e6beab3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -1,8 +1,8 @@ package io.avaje.jex; import io.avaje.inject.BeanScope; +import io.avaje.jex.core.CoreServiceManager; import io.avaje.jex.core.HealthPlugin; -import io.avaje.jex.core.internal.CoreServiceManager; import io.avaje.jex.jdk.JdkServerStart; import io.avaje.jex.routes.RoutesBuilder; import io.avaje.jex.routes.SpiRoutes; @@ -14,12 +14,10 @@ final class DJex implements Jex { private final Routing routing = new DefaultRouting(); - private final ErrorHandling errorHandling = new DefaultErrorHandling(); private final AppLifecycle lifecycle = new DefaultLifecycle(); private final StaticFileConfig staticFiles; private final Map, Object> attributes = new HashMap<>(); private final DJexConfig config = new DJexConfig(); - private ServerConfig serverConfig; DJex() { this.staticFiles = new DefaultStaticFileConfig(this); @@ -43,28 +41,6 @@ public T attribute(Class cls) { return (T) attributes.get(cls); } - @Override - public Jex errorHandling(ErrorHandling.Service service) { - service.add(errorHandling); - return this; - } - - @Override - public ErrorHandling errorHandling() { - return errorHandling; - } - - @Override - public ServerConfig serverConfig() { - return serverConfig; - } - - @Override - public Jex serverConfig(ServerConfig serverConfig) { - this.serverConfig = serverConfig; - return this; - } - @Override public Jex routing(Routing.Service routes) { routing.add(routes); @@ -100,9 +76,6 @@ public Jex configureWith(BeanScope beanScope) { for (JexPlugin plugin : beanScope.list(JexPlugin.class)) { plugin.apply(this); } - for (ErrorHandling.Service service : beanScope.list(ErrorHandling.Service.class)) { - service.add(errorHandling); - } routing.addAll(beanScope.list(Routing.Service.class)); beanScope.getOptional(JsonService.class).ifPresent(this::jsonService); return this; @@ -116,7 +89,7 @@ public Jex configure(Consumer configure) { @Override public Jex exception(Class exceptionClass, ExceptionHandler handler) { - errorHandling.exception(exceptionClass, handler); + routing.exception(exceptionClass, handler); return this; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultErrorHandling.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultErrorHandling.java deleted file mode 100644 index 012eaf0e..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultErrorHandling.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.avaje.jex; - -import java.util.HashMap; -import java.util.Map; - -class DefaultErrorHandling implements ErrorHandling { - - private final Map, ExceptionHandler> handlers = new HashMap<>(); - - @Override - public ErrorHandling exception(Class type, ExceptionHandler handler) { - handlers.put(type, handler); - return this; - } - - @Override - public ErrorHandling error(int statusCode, Handler handler) { - return null; - } - - @Override - public ErrorHandling error(int statusCode, String contentType, Handler handler) { - return null; - } - - @Override - @SuppressWarnings("unchecked") - public ExceptionHandler find(Class exceptionType) { - Class type = exceptionType; - do { - final ExceptionHandler handler = handlers.get(type); - if (handler != null) { - return (ExceptionHandler) handler; - } - type = type.getSuperclass(); - } while (type != null); - return null; - } - -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index 2cf07e2f..f17eaeeb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -5,10 +5,15 @@ import java.util.Collection; import java.util.Collections; import java.util.Deque; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; -import java.util.function.Consumer; +import io.avaje.jex.Routing.Entry; +import io.avaje.jex.Routing.Group; +import io.avaje.jex.Routing.Service; +import io.avaje.jex.Routing.Type; import io.avaje.jex.security.Role; class DefaultRouting implements Routing { @@ -16,6 +21,7 @@ class DefaultRouting implements Routing { private final List handlers = new ArrayList<>(); private final List filters = new ArrayList<>(); private final Deque pathDeque = new ArrayDeque<>(); + private final Map, ExceptionHandler> exceptionHandlers = new HashMap<>(); /** * Last entry that we can add permitted roles to. @@ -32,6 +38,11 @@ public List filters() { return filters; } + @Override + public Map, ExceptionHandler> errorHandlers() { + return exceptionHandlers; + } + private String path(String path) { return String.join("", pathDeque) + ((path.startsWith("/") || path.isEmpty()) ? path : "/" + path); } @@ -57,6 +68,12 @@ public Routing addAll(Collection routes) { return this; } + @Override + public Routing exception(Class type, ExceptionHandler handler) { + exceptionHandlers.put(type, handler); + return this; + } + @Override public Routing path(String path, Group group) { addEndpoints(path, group); diff --git a/avaje-jex/src/main/java/io/avaje/jex/ErrorHandling.java b/avaje-jex/src/main/java/io/avaje/jex/ErrorHandling.java deleted file mode 100644 index 0df47c9c..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/ErrorHandling.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.avaje.jex; - -public interface ErrorHandling { - - /** - * Register an exception handler for the given exception type. - */ - ErrorHandling exception(Class exceptionClass, ExceptionHandler handler); - - /** - * Adds an error mapper to the instance. - * Useful for turning error-codes (404, 500) into standardized messages/pages - */ - ErrorHandling error(int statusCode, Handler handler); - - /** - * Adds an error mapper for the specified content-type to the instance. - * Useful for turning error-codes (404, 500) into standardized messages/pages - */ - ErrorHandling error(int statusCode, String contentType, Handler handler); - - /** - * Return a registered exception handler given the exception type or null - * if one is not found. - *

- * This includes searching the super types of the exception. - *

- */ - ExceptionHandler find(Class exceptionType); - - /** - * Adds to the Routing. - */ - @FunctionalInterface - interface Service { - - /** - * Add to the error handling. - */ - void add(ErrorHandling errorHandling); - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index a64824ba..0f33c1fe 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -55,26 +55,6 @@ static Jex create() { */ T attribute(Class cls); - /** - * Configure error handlers. - */ - Jex errorHandling(ErrorHandling.Service service); - - /** - * Return the Error handler to add error handlers. - */ - ErrorHandling errorHandling(); - - /** - * Return the server specific configuration. - */ - ServerConfig serverConfig(); - - /** - * Set the server specific configuration. - */ - Jex serverConfig(ServerConfig serverConfig); - /** * Add routes and handlers to the routing. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index c36022f6..65d9bc65 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -2,6 +2,7 @@ import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -49,6 +50,11 @@ public interface Routing { */ Routing withRoles(Role... permittedRoles); + /** + * Register an exception handler for the given exception type. + */ + Routing exception(Class exceptionClass, ExceptionHandler handler); + /** * Add a group of route handlers with a common path prefix. */ @@ -141,7 +147,7 @@ default Routing before(Consumer handler) { /** Add a post-processing filter for all requests. */ default Routing after(Consumer handler) { - + return filter( (ctx, chain) -> { chain.proceed(); @@ -159,6 +165,11 @@ default Routing after(Consumer handler) { */ List filters(); + /** + * Return all the registered Exception Handlers. + */ + Map, ExceptionHandler> errorHandlers(); + /** * A group of routing entries prefixed by a common path. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/ServerConfig.java b/avaje-jex/src/main/java/io/avaje/jex/ServerConfig.java deleted file mode 100644 index 7f860f57..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/ServerConfig.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.avaje.jex; - -/** - * Marker for server specific configuration. - */ -public interface ServerConfig { -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java similarity index 96% rename from avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java rename to avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java index 14cd73b4..459f056a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java @@ -1,4 +1,4 @@ -package io.avaje.jex.core.internal; +package io.avaje.jex.core; import java.util.ArrayList; import java.util.List; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java similarity index 88% rename from avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java rename to avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java index 142cbef5..c2376d33 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java @@ -1,22 +1,28 @@ -package io.avaje.jex.core.internal; - -import io.avaje.applog.AppLog; -import io.avaje.jex.*; -import io.avaje.jex.core.JacksonJsonService; -import io.avaje.jex.core.JsonbJsonService; -import io.avaje.jex.core.TemplateManager; -import io.avaje.jex.spi.HeaderKeys; -import io.avaje.jex.spi.JsonService; -import io.avaje.jex.spi.SpiContext; -import io.avaje.jex.spi.TemplateRender; +package io.avaje.jex.core; import java.io.UncheckedIOException; import java.io.UnsupportedEncodingException; import java.lang.System.Logger.Level; import java.net.URLDecoder; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.stream.Stream; +import io.avaje.applog.AppLog; +import io.avaje.jex.Context; +import io.avaje.jex.Jex; +import io.avaje.jex.Routing; +import io.avaje.jex.core.json.JacksonJsonService; +import io.avaje.jex.core.json.JsonbJsonService; +import io.avaje.jex.spi.HeaderKeys; +import io.avaje.jex.spi.JsonService; +import io.avaje.jex.spi.SpiContext; +import io.avaje.jex.spi.TemplateRender; + /** * Core implementation of SpiServiceManager provided to specific implementations like jetty etc. */ @@ -34,9 +40,9 @@ public static SpiServiceManager create(Jex jex) { return new Builder(jex).build(); } - public CoreServiceManager(JsonService jsonService, ErrorHandling errorHandling, TemplateManager templateManager) { + CoreServiceManager(JsonService jsonService, ExceptionManager manager, TemplateManager templateManager) { this.jsonService = jsonService; - this.exceptionHandler = new ExceptionManager(errorHandling); + this.exceptionHandler = manager; this.templateManager = templateManager; } @@ -142,7 +148,10 @@ private static class Builder { } SpiServiceManager build() { - return new CoreServiceManager(initJsonService(), jex.errorHandling(), initTemplateMgr()); + return new CoreServiceManager( + initJsonService(), + new ExceptionManager(jex.routing().errorHandlers()), + initTemplateMgr()); } JsonService initJsonService() { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java similarity index 71% rename from avaje-jex/src/main/java/io/avaje/jex/core/internal/ExceptionManager.java rename to avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index 767277fe..b328a4b9 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/internal/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -1,29 +1,43 @@ -package io.avaje.jex.core.internal; +package io.avaje.jex.core; + +import static java.lang.System.Logger.Level.WARNING; + +import java.util.Map; import io.avaje.applog.AppLog; -import io.avaje.jex.ErrorHandling; import io.avaje.jex.ExceptionHandler; import io.avaje.jex.http.ErrorCode; import io.avaje.jex.http.HttpResponseException; import io.avaje.jex.spi.HeaderKeys; import io.avaje.jex.spi.SpiContext; -import static java.lang.System.Logger.Level.WARNING; - -class ExceptionManager { +public class ExceptionManager { private static final String APPLICATION_JSON = "application/json"; private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); - private final ErrorHandling errorHandling; + private final Map, ExceptionHandler> handlers; + + public ExceptionManager(Map, ExceptionHandler> handlers) { + this.handlers = handlers; + } - ExceptionManager(ErrorHandling errorHandling) { - this.errorHandling = errorHandling; + @SuppressWarnings("unchecked") + public ExceptionHandler find(Class exceptionType) { + Class type = exceptionType; + do { + final ExceptionHandler handler = handlers.get(type); + if (handler != null) { + return (ExceptionHandler) handler; + } + type = type.getSuperclass(); + } while (type != null); + return null; } void handle(SpiContext ctx, Exception e) { - final ExceptionHandler handler = errorHandling.find(e.getClass()); + final ExceptionHandler handler = find(e.getClass()); if (handler != null) { handler.handle(e, ctx); } else if (e instanceof HttpResponseException ex) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/HttpMethodMap.java b/avaje-jex/src/main/java/io/avaje/jex/core/HttpMethodMap.java similarity index 91% rename from avaje-jex/src/main/java/io/avaje/jex/core/internal/HttpMethodMap.java rename to avaje-jex/src/main/java/io/avaje/jex/core/HttpMethodMap.java index f8d1c2c5..5f76650c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/internal/HttpMethodMap.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/HttpMethodMap.java @@ -1,10 +1,10 @@ -package io.avaje.jex.core.internal; - -import io.avaje.jex.Routing; +package io.avaje.jex.core; import java.util.HashMap; import java.util.Map; +import io.avaje.jex.Routing; + final class HttpMethodMap { private final Map map = new HashMap<>(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/SpiServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java similarity index 97% rename from avaje-jex/src/main/java/io/avaje/jex/core/internal/SpiServiceManager.java rename to avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java index 7eed68f8..b6b4822c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/internal/SpiServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java @@ -1,4 +1,4 @@ -package io.avaje.jex.core.internal; +package io.avaje.jex.core; import io.avaje.jex.Context; import io.avaje.jex.Routing; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JacksonJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java similarity index 98% rename from avaje-jex/src/main/java/io/avaje/jex/core/JacksonJsonService.java rename to avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java index 81b06cda..5b3f8d20 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JacksonJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java @@ -1,4 +1,4 @@ -package io.avaje.jex.core; +package io.avaje.jex.core.json; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.DeserializationFeature; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JsonbJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java similarity index 97% rename from avaje-jex/src/main/java/io/avaje/jex/core/JsonbJsonService.java rename to avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java index a3825c50..77eaca95 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JsonbJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java @@ -1,4 +1,4 @@ -package io.avaje.jex.core; +package io.avaje.jex.core.json; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.SpiContext; diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java index 68926fe0..93e93c33 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java @@ -2,7 +2,7 @@ import io.avaje.jex.Context; import io.avaje.jex.Routing; -import io.avaje.jex.core.internal.SpiServiceManager; +import io.avaje.jex.core.SpiServiceManager; import io.avaje.jex.spi.SpiContext; import java.io.OutputStream; diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index a331f46e..157c4890 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -13,7 +13,7 @@ import io.avaje.applog.AppLog; import io.avaje.jex.AppLifecycle; import io.avaje.jex.Jex; -import io.avaje.jex.core.internal.SpiServiceManager; +import io.avaje.jex.core.SpiServiceManager; import io.avaje.jex.routes.SpiRoutes; public class JdkServerStart { diff --git a/avaje-jex/src/main/java/module-info.java b/avaje-jex/src/main/java/module-info.java index d01cc5e0..ee1b7c88 100644 --- a/avaje-jex/src/main/java/module-info.java +++ b/avaje-jex/src/main/java/module-info.java @@ -4,7 +4,7 @@ exports io.avaje.jex; exports io.avaje.jex.http; - exports io.avaje.jex.core; + exports io.avaje.jex.core.json; exports io.avaje.jex.security; exports io.avaje.jex.spi; diff --git a/avaje-jex/src/test/java/io/avaje/jex/DefaultErrorHandlingTest.java b/avaje-jex/src/test/java/io/avaje/jex/DefaultErrorHandlingTest.java index bed06272..67dbf84d 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/DefaultErrorHandlingTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/DefaultErrorHandlingTest.java @@ -1,10 +1,12 @@ package io.avaje.jex; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.nio.file.DirectoryIteratorException; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; + +import io.avaje.jex.core.ExceptionManager; class DefaultErrorHandlingTest { @@ -14,8 +16,10 @@ class DefaultErrorHandlingTest { @Test void exception() { - DefaultErrorHandling handling = new DefaultErrorHandling(); - handling.exception(RuntimeException.class, rt); + Routing router = new DefaultRouting(); + router.exception(RuntimeException.class, rt); + + var handling = new ExceptionManager(router.errorHandlers()); assertThat(handling.find(RuntimeException.class)).isSameAs(rt); assertThat(handling.find(IllegalStateException.class)).isSameAs(rt); @@ -24,10 +28,11 @@ void exception() { @Test void exception_expect_highestMatch() { + Routing router = new DefaultRouting(); + router.exception(RuntimeException.class, rt); + router.exception(IllegalStateException.class, ise); - DefaultErrorHandling handling = new DefaultErrorHandling(); - handling.exception(RuntimeException.class, rt); - handling.exception(IllegalStateException.class, ise); + var handling = new ExceptionManager(router.errorHandlers()); assertThat(handling.find(IllegalStateException.class)).isSameAs(ise); assertThat(handling.find(RuntimeException.class)).isSameAs(rt); @@ -37,14 +42,12 @@ void exception_expect_highestMatch() { private static class RT implements ExceptionHandler { @Override - public void handle(RuntimeException exception, Context ctx) { - } + public void handle(RuntimeException exception, Context ctx) {} } private static class ISE implements ExceptionHandler { @Override - public void handle(IllegalStateException exception, Context ctx) { - } + public void handle(IllegalStateException exception, Context ctx) {} } } diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/internal/ContextUtilTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java similarity index 90% rename from avaje-jex/src/test/java/io/avaje/jex/core/internal/ContextUtilTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java index ada6e09f..79dcb785 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/internal/ContextUtilTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java @@ -1,8 +1,8 @@ -package io.avaje.jex.core.internal; +package io.avaje.jex.core; import org.junit.jupiter.api.Test; -import io.avaje.jex.core.internal.CoreServiceManager; +import io.avaje.jex.core.CoreServiceManager; import static org.assertj.core.api.Assertions.assertThat; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java index f284afcf..d13b5401 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java @@ -1,13 +1,14 @@ package io.avaje.jex.jdk; -import io.avaje.http.client.HttpClient; -import io.avaje.http.client.JacksonBodyAdapter; -import io.avaje.jex.Jex; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.net.http.HttpResponse; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; + +import io.avaje.http.client.HttpClient; +import io.avaje.http.client.JacksonBodyAdapter; +import io.avaje.jex.Jex; class JdkJexServerTest { diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java index 8a61e434..e52c8f14 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java @@ -1,17 +1,18 @@ package io.avaje.jex.jdk; -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; import java.net.http.HttpHeaders; import java.net.http.HttpResponse; import java.util.List; import java.util.stream.Stream; -import static java.util.Arrays.asList; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.avaje.jex.Jex; class JsonTest { From 1ebf10d29e9f6bf10cb1e85ea503f0d9b7404a91 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 23 Nov 2024 14:02:40 -0500 Subject: [PATCH 039/250] seal interfaces --- .../main/java/io/avaje/jex/AppLifecycle.java | 2 +- .../main/java/io/avaje/jex/BootJexState.java | 4 +- .../main/java/io/avaje/jex/DJexConfig.java | 2 +- .../java/io/avaje/jex/DefaultRouting.java | 2 +- .../io/avaje/jex/DefaultStaticFileConfig.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 2 +- .../src/main/java/io/avaje/jex/JexConfig.java | 2 +- .../src/main/java/io/avaje/jex/Routing.java | 2 +- .../java/io/avaje/jex/StaticFileConfig.java | 2 +- .../java/io/avaje/jex/StaticFileSource.java | 56 +----------------- .../main/java/io/avaje/jex/UploadConfig.java | 59 +------------------ .../io/avaje/jex/core/CoreServiceLoader.java | 2 +- .../io/avaje/jex/core/ExceptionManager.java | 4 +- .../io/avaje/jex/core/TemplateManager.java | 2 +- 14 files changed, 20 insertions(+), 123 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/AppLifecycle.java b/avaje-jex/src/main/java/io/avaje/jex/AppLifecycle.java index 469f6f56..3eb8eafe 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/AppLifecycle.java +++ b/avaje-jex/src/main/java/io/avaje/jex/AppLifecycle.java @@ -3,7 +3,7 @@ /** * Application lifecycle support. */ -public interface AppLifecycle { +public sealed interface AppLifecycle permits DefaultLifecycle { enum Status { STARTING, diff --git a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java b/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java index 34d9a0cb..49fb66c7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java +++ b/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java @@ -3,7 +3,7 @@ import io.avaje.config.Config; import io.avaje.inject.BeanScope; -class BootJexState { +final class BootJexState { private static State state; @@ -32,7 +32,7 @@ State create(BeanScope beanScope) { return new State(jex.start()); } - private static class State { + private static final class State { private final Jex.Server server; diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 51fe8356..50fc794b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -9,7 +9,7 @@ import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; -class DJexConfig implements JexConfig { +final class DJexConfig implements JexConfig { private int port = 8080; private String host; diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index f17eaeeb..4b2ef8a9 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -16,7 +16,7 @@ import io.avaje.jex.Routing.Type; import io.avaje.jex.security.Role; -class DefaultRouting implements Routing { +final class DefaultRouting implements Routing { private final List handlers = new ArrayList<>(); private final List filters = new ArrayList<>(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultStaticFileConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultStaticFileConfig.java index 9e332a6d..3cd40916 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultStaticFileConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultStaticFileConfig.java @@ -3,7 +3,7 @@ import java.util.ArrayList; import java.util.List; -class DefaultStaticFileConfig implements StaticFileConfig { +final class DefaultStaticFileConfig implements StaticFileConfig { private final List sources = new ArrayList<>(); private final Jex jex; diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 0f33c1fe..14140b23 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -23,7 +23,7 @@ * * } */ -public interface Jex { +public sealed interface Jex permits DJex { /** * Create Jex. diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index bbf3dbd1..2bec8634 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -12,7 +12,7 @@ /** * Jex configuration. */ -public interface JexConfig { +public sealed interface JexConfig permits DJexConfig { /** * Set the port to use. Defaults to 7001. diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index 65d9bc65..064715a6 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -8,7 +8,7 @@ import io.avaje.jex.security.Role; -public interface Routing { +public sealed interface Routing permits DefaultRouting { /** * Add the routes provided by the Routing Service. diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticFileConfig.java b/avaje-jex/src/main/java/io/avaje/jex/StaticFileConfig.java index 8138e0bb..a14f4f07 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticFileConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticFileConfig.java @@ -2,7 +2,7 @@ import java.util.List; -public interface StaticFileConfig { +public sealed interface StaticFileConfig permits DefaultStaticFileConfig { Jex addClasspath(String path); diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticFileSource.java b/avaje-jex/src/main/java/io/avaje/jex/StaticFileSource.java index 327264b2..e5bc6a3b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticFileSource.java +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticFileSource.java @@ -1,59 +1,9 @@ package io.avaje.jex; -import java.util.Objects; - -public class StaticFileSource { +public record StaticFileSource(String urlPathPrefix, String path, Location location) { public enum Location { - CLASSPATH, EXTERNAL + CLASSPATH, + EXTERNAL } - - private final String urlPathPrefix; - private final String path; - private final Location location; - - public StaticFileSource(String urlPathPrefix, String path, Location location) { - this.urlPathPrefix = urlPathPrefix; - this.path = path; - this.location = location; - } - - public String getUrlPathPrefix() { - return urlPathPrefix; - } - - public String getPath() { - return path; - } - - public Location getLocation() { - return location; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - StaticFileSource that = (StaticFileSource) o; - return urlPathPrefix.equals(that.urlPathPrefix) && - path.equals(that.path) && - location == that.location; - } - - @Override - public int hashCode() { - return Objects.hash(urlPathPrefix, path, location); - } - - @Override - public String toString() { - return "urlPathPrefix: " + urlPathPrefix - + ", path: " + path - + ", location: " + location; - } - } diff --git a/avaje-jex/src/main/java/io/avaje/jex/UploadConfig.java b/avaje-jex/src/main/java/io/avaje/jex/UploadConfig.java index 640bc08a..751c8172 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/UploadConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/UploadConfig.java @@ -1,58 +1,5 @@ package io.avaje.jex; -/** - * Configuration for server handling of Multipart file uploads etc. - */ -public class UploadConfig { - - private String location; - private long maxFileSize; - private long maxRequestSize; - private int fileSizeThreshold; - - public UploadConfig() { - } - - public UploadConfig(String location, long maxFileSize, long maxRequestSize, int fileSizeThreshold) { - this.location = location; - this.maxFileSize = maxFileSize; - this.maxRequestSize = maxRequestSize; - this.fileSizeThreshold = fileSizeThreshold; - } - - public String location() { - return location; - } - - public UploadConfig location(String location) { - this.location = location; - return this; - } - - public long maxFileSize() { - return maxFileSize; - } - - public UploadConfig maxFileSize(long maxFileSize) { - this.maxFileSize = maxFileSize; - return this; - } - - public long maxRequestSize() { - return maxRequestSize; - } - - public UploadConfig maxRequestSize(long maxRequestSize) { - this.maxRequestSize = maxRequestSize; - return this; - } - - public int fileSizeThreshold() { - return fileSizeThreshold; - } - - public UploadConfig fileSizeThreshold(int fileSizeThreshold) { - this.fileSizeThreshold = fileSizeThreshold; - return this; - } -} +/** Configuration for server handling of Multipart file uploads etc. */ +public record UploadConfig( + String location, long maxFileSize, long maxRequestSize, int fileSizeThreshold) {} diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java index 459f056a..56fe2962 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java @@ -10,7 +10,7 @@ import io.avaje.jex.spi.TemplateRender; /** Core implementation of SpiServiceManager provided to specific implementations like jetty etc. */ -class CoreServiceLoader { +final class CoreServiceLoader { private static final CoreServiceLoader INSTANCE = new CoreServiceLoader(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index b328a4b9..18db0f79 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -11,7 +11,7 @@ import io.avaje.jex.spi.HeaderKeys; import io.avaje.jex.spi.SpiContext; -public class ExceptionManager { +public final class ExceptionManager { private static final String APPLICATION_JSON = "application/json"; @@ -24,7 +24,7 @@ public ExceptionManager(Map, ExceptionHandler> handlers) { } @SuppressWarnings("unchecked") - public ExceptionHandler find(Class exceptionType) { + private ExceptionHandler find(Class exceptionType) { Class type = exceptionType; do { final ExceptionHandler handler = handlers.get(type); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java index 4de183ac..5bd38274 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java @@ -11,7 +11,7 @@ /** * Render templates typically as html. */ -public class TemplateManager { +public final class TemplateManager { private final Map map = new HashMap<>(); From 5cbc23e5381419430cd0d61284f5188352c0c4eb Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 23 Nov 2024 14:03:47 -0500 Subject: [PATCH 040/250] Update ExceptionManager.java --- avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index 18db0f79..481944ce 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -24,7 +24,7 @@ public ExceptionManager(Map, ExceptionHandler> handlers) { } @SuppressWarnings("unchecked") - private ExceptionHandler find(Class exceptionType) { + public ExceptionHandler find(Class exceptionType) { Class type = exceptionType; do { final ExceptionHandler handler = handlers.get(type); From d6a9fb1f09d3eb10d119062c7826d11be2c559d1 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 23 Nov 2024 16:13:57 -0500 Subject: [PATCH 041/250] remove unused code We'll have to rethink the static file configs --- README.md | 49 +------------------ .../src/main/java/io/avaje/jex/Context.java | 15 ------ .../src/main/java/io/avaje/jex/DJex.java | 10 ---- .../main/java/io/avaje/jex/DJexConfig.java | 24 --------- .../io/avaje/jex/DefaultStaticFileConfig.java | 42 ---------------- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 5 -- .../src/main/java/io/avaje/jex/JexConfig.java | 20 -------- .../java/io/avaje/jex/StaticFileConfig.java | 16 ------ .../java/io/avaje/jex/StaticFileSource.java | 9 ---- .../main/java/io/avaje/jex/UploadConfig.java | 5 -- .../main/java/io/avaje/jex/UploadedFile.java | 36 -------------- .../java/io/avaje/jex/jdk/JdkContext.java | 16 ------ 12 files changed, 2 insertions(+), 245 deletions(-) delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/DefaultStaticFileConfig.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/StaticFileConfig.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/StaticFileSource.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/UploadConfig.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/UploadedFile.java diff --git a/README.md b/README.md index a2474d39..362aa05b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ # avaje-jex -Java cut down version of https://javalin.io +Javalin style Wrapper over the JDK's `jdk.httpserver` server. ```java var app = Jex.create() @@ -15,54 +15,9 @@ var app = Jex.create() .get("/", ctx -> ctx.text("hello")) .get("/one/{id}", ctx -> ctx.text("one-" + ctx.pathParam("id"))) ) - .staticFiles().addClasspath("/static", "content") - .staticFiles().addExternal("/other", "/external") .port(8080) .start(); - ``` -### Goals / intention - -- Help progress converting Javalin internals from Kotlin to Java - - Convert bits of Javalin internals to Java - - Maybe get feedback from David if there is design impact - - Prepare small PR's to Javalin (this is going to take time) - -- Another goal is to explore some options for Javalin along the lines of - - matching routes (making use of path segment count) - - organisation of internals to reduce some statics (JavalinJson) - - modularisation of internals using ServiceLoader (for templating implementation, websockets and sse - make these all optional dependencies keeping core small) - -### Design Notes (different to Javalin): -- Context is an interface -- Routing, ErrorHandling, StaticFileConfig are interfaces -- PathParser - Has segment count which we use with RouteIndex -- RouteIndex - matching paths by method + number of segments -- Immutable routes on startup - no adding/removing routes after start() -- Context json() - call through to "ServiceManager" which has the JsonService (no static JavalinJson) - -### Differences to Javalin -- Uses `{}` rather than `:` for defining path parameters -- Supports use of regex in path segments e.g `{id:[0-9]+}` (provides tighter path matching) -- Added ctx.text(...) for plain text response -- Method name change to use ctx.write(...) rather than ctx.result(...) - ### TODO -- cookie store -- app attributes -- basicAuthCredentials/basicAuthCredentialsExist -- plugin api -- render in progress - FreeMarker and Mustache done -- web sockets -- sse - -### Intentionally excluded features -- - - -### To Review -- Javalin uses int getContentLength() rather than long getContentLengthLong() -- Javalin removeCookie should set null path to "/" -- endpointHandlerPath() -- bodyValidator +- static file configuration diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index 1b94b007..2e556b07 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -361,21 +361,6 @@ default Context render(String name) { */ String protocol(); - /** - * Return the first UploadedFile for the specified name or null. - */ - UploadedFile uploadedFile(String name); - - /** - * Return a list of UploadedFiles for the specified name, or empty list. - */ - List uploadedFiles(String name); - - /** - * Return a list of all UploadedFiles. - */ - List uploadedFiles(); - class Cookie { private static final ZonedDateTime EXPIRED = ZonedDateTime.of(LocalDateTime.of(2000, 1, 1, 0, 0, 0), ZoneId.of("GMT")); private static final DateTimeFormatter RFC_1123_DATE_TIME = DateTimeFormatter.RFC_1123_DATE_TIME; diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index 1e6beab3..7c765556 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -15,14 +15,9 @@ final class DJex implements Jex { private final Routing routing = new DefaultRouting(); private final AppLifecycle lifecycle = new DefaultLifecycle(); - private final StaticFileConfig staticFiles; private final Map, Object> attributes = new HashMap<>(); private final DJexConfig config = new DJexConfig(); - DJex() { - this.staticFiles = new DefaultStaticFileConfig(this); - } - @Override public DJexConfig config() { return config; @@ -105,11 +100,6 @@ public Jex context(String contextPath) { return this; } - @Override - public StaticFileConfig staticFiles() { - return staticFiles; - } - @Override public Jex register(TemplateRender renderer, String... extensions) { for (String extension : extensions) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 50fc794b..68ba543c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -20,8 +20,6 @@ final class DJexConfig implements JexConfig { private boolean preCompressStaticFiles; private JsonService jsonService; - private UploadConfig multipartConfig; - private int multipartFileThreshold = 8 * 1024; private final Map renderers = new HashMap<>(); private SSLContext sslContext; @@ -67,18 +65,6 @@ public JexConfig jsonService(JsonService jsonService) { return this; } - @Override - public JexConfig multipartConfig(UploadConfig multipartConfig) { - this.multipartConfig = multipartConfig; - return this; - } - - @Override - public JexConfig multipartFileThreshold(int multipartFileThreshold) { - this.multipartFileThreshold = multipartFileThreshold; - return this; - } - @Override public JexConfig renderer(String extension, TemplateRender renderer) { renderers.put(extension, renderer); @@ -137,16 +123,6 @@ public JsonService jsonService() { return jsonService; } - @Override - public UploadConfig multipartConfig() { - return multipartConfig; - } - - @Override - public int multipartFileThreshold() { - return multipartFileThreshold; - } - @Override public Map renderers() { return renderers; diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultStaticFileConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultStaticFileConfig.java deleted file mode 100644 index 3cd40916..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultStaticFileConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.avaje.jex; - -import java.util.ArrayList; -import java.util.List; - -final class DefaultStaticFileConfig implements StaticFileConfig { - - private final List sources = new ArrayList<>(); - private final Jex jex; - - DefaultStaticFileConfig(Jex jex) { - this.jex = jex; - } - - @Override - public Jex addClasspath(String path) { - return addClasspath("/", path); - } - - @Override - public Jex addClasspath(String urlPrefix, String path) { - sources.add(new StaticFileSource(urlPrefix, path, StaticFileSource.Location.CLASSPATH)); - return jex; - } - - @Override - public Jex addExternal(String path) { - return addExternal("/", path); - } - - @Override - public Jex addExternal(String urlPrefix, String path) { - sources.add(new StaticFileSource(urlPrefix, path, StaticFileSource.Location.EXTERNAL)); - return jex; - } - - @Override - public List getSources() { - return sources; - } - -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 14140b23..73bad389 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -107,11 +107,6 @@ static Jex create() { */ Jex context(String contextPath); - /** - * Return the static file configuration. - */ - StaticFileConfig staticFiles(); - /** * Explicitly register a template renderer. *

diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index 2bec8634..ff59760e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -49,16 +49,6 @@ public sealed interface JexConfig permits DJexConfig { */ JexConfig jsonService(JsonService jsonService); - /** - * Set the upload configuration. - */ - JexConfig multipartConfig(UploadConfig multipartConfig); - - /** - * Set the multipartFileThreshold. - */ - JexConfig multipartFileThreshold(int multipartFileThreshold); - /** * Register a template renderer explicitly. * @@ -118,16 +108,6 @@ public sealed interface JexConfig permits DJexConfig { /** Enable https with the provided SSLContext. */ JexConfig sslContext(SSLContext ssl); - /** - * Return the multipartConfig. - */ - UploadConfig multipartConfig(); - - /** - * Return the multipartFileThreshold. - */ - int multipartFileThreshold(); - /** * Return the template renderers registered by extension. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticFileConfig.java b/avaje-jex/src/main/java/io/avaje/jex/StaticFileConfig.java deleted file mode 100644 index a14f4f07..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticFileConfig.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.avaje.jex; - -import java.util.List; - -public sealed interface StaticFileConfig permits DefaultStaticFileConfig { - - Jex addClasspath(String path); - - Jex addClasspath(String urlPrefix, String path); - - Jex addExternal(String path); - - Jex addExternal(String urlPrefix, String path); - - List getSources(); -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticFileSource.java b/avaje-jex/src/main/java/io/avaje/jex/StaticFileSource.java deleted file mode 100644 index e5bc6a3b..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticFileSource.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.avaje.jex; - -public record StaticFileSource(String urlPathPrefix, String path, Location location) { - - public enum Location { - CLASSPATH, - EXTERNAL - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/UploadConfig.java b/avaje-jex/src/main/java/io/avaje/jex/UploadConfig.java deleted file mode 100644 index 751c8172..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/UploadConfig.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.avaje.jex; - -/** Configuration for server handling of Multipart file uploads etc. */ -public record UploadConfig( - String location, long maxFileSize, long maxRequestSize, int fileSizeThreshold) {} diff --git a/avaje-jex/src/main/java/io/avaje/jex/UploadedFile.java b/avaje-jex/src/main/java/io/avaje/jex/UploadedFile.java deleted file mode 100644 index bc3351e9..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/UploadedFile.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.avaje.jex; - -import java.io.InputStream; - -/** - * An uploaded file. - */ -public interface UploadedFile { - - /** - * Return the name of the part. - */ - String name(); - - /** - * Return the submitted file name. - */ - String fileName(); - - /** - * Return the file content as InputStream. - */ - InputStream content(); - - /** - * Return the content type for this part. - */ - String contentType(); - - /** - * Return the size. - */ - long size(); - - void delete(); -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index 0b916450..8a99a481 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -25,7 +25,6 @@ import io.avaje.jex.Context; import io.avaje.jex.Routing; -import io.avaje.jex.UploadedFile; import io.avaje.jex.http.ErrorCode; import io.avaje.jex.http.HttpResponseException; import io.avaje.jex.security.BasicAuthCredentials; @@ -441,21 +440,6 @@ public String protocol() { return exchange.getProtocol(); } - @Override - public UploadedFile uploadedFile(String name) { - throw new UnsupportedOperationException(); - } - - @Override - public List uploadedFiles(String name) { - throw new UnsupportedOperationException(); - } - - @Override - public List uploadedFiles() { - throw new UnsupportedOperationException(); - } - @Override public OutputStream outputStream() { return mgr.createOutputStream(this); From b33a99db364daa06220c3bbd40dded16bd6d2b71 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 23 Nov 2024 16:26:04 -0500 Subject: [PATCH 042/250] remove duplicate exception method --- README.md | 9 +++++++-- avaje-jex/src/main/java/io/avaje/jex/DJex.java | 7 ------- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 5 ----- .../test/java/io/avaje/jex/jdk/ExceptionManagerTest.java | 6 +++--- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 362aa05b..8bcc09c1 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,19 @@ # avaje-jex -Javalin style Wrapper over the JDK's `jdk.httpserver` server. +Javalin style wrapper over the JDK's [`jdk.httpserver`](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html) `HttpServer` classes. ```java var app = Jex.create() .routing(routing -> routing .get("/", ctx -> ctx.text("hello")) .get("/one/{id}", ctx -> ctx.text("one-" + ctx.pathParam("id"))) - ) + .filter( + (ctx, chain) -> { + System.out.println("before request"); + chain.proceed(); + System.out.println("after request"); + })) .port(8080) .start(); ``` diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index 7c765556..ed24cce8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -23,7 +23,6 @@ public DJexConfig config() { return config; } - @Override public Jex attribute(Class cls, T instance) { attributes.put(cls, instance); @@ -82,12 +81,6 @@ public Jex configure(Consumer configure) { return this; } - @Override - public Jex exception(Class exceptionClass, ExceptionHandler handler) { - routing.exception(exceptionClass, handler); - return this; - } - @Override public Jex port(int port) { this.config.port(port); diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 73bad389..b0e42346 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -92,11 +92,6 @@ static Jex create() { */ Jex configure(Consumer configure); - /** - * Add an exception handler for the given exception type. - */ - Jex exception(Class exceptionClass, ExceptionHandler handler); - /** * Set the port to use. */ diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java index 8b88365c..dcc3adea 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java @@ -29,9 +29,9 @@ static TestPair init() { }) .get("/fiveHundred", ctx -> { throw new IllegalArgumentException("Bar"); - })) - .exception(NullPointerException.class, (exception, ctx) -> ctx.text("npe")) - .exception(IllegalStateException.class, (exception, ctx) -> ctx.status(222).text("Handled IllegalStateException|" + exception.getMessage())); + }) + .exception(NullPointerException.class, (exception, ctx) -> ctx.text("npe")) + .exception(IllegalStateException.class, (exception, ctx) -> ctx.status(222).text("Handled IllegalStateException|" + exception.getMessage()))); return TestPair.create(app); } From ad4ad3e774e9e7d6f79790581e26c3963bcc0a3a Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 23 Nov 2024 16:44:04 -0500 Subject: [PATCH 043/250] fix name collision with java.util.logging --- .../java/io/avaje/jex/DefaultRouting.java | 36 +++++++++---------- .../{Handler.java => ExchangeHandler.java} | 2 +- .../src/main/java/io/avaje/jex/Routing.java | 30 ++++++++-------- .../java/io/avaje/jex/jdk/BaseHandler.java | 11 +++--- .../java/io/avaje/jex/jdk/RoutingFilter.java | 5 ++- .../java/io/avaje/jex/routes/RouteEntry.java | 6 ++-- .../java/io/avaje/jex/spi/SpiContext.java | 2 +- 7 files changed, 44 insertions(+), 48 deletions(-) rename avaje-jex/src/main/java/io/avaje/jex/{Handler.java => ExchangeHandler.java} (93%) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index 4b2ef8a9..999aaddd 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -94,7 +94,7 @@ public Routing withRoles(Role... permittedRoles) { return withRoles(Set.of(permittedRoles)); } - private void add(Type verb, String path, Handler handler) { + private void add(Type verb, String path, ExchangeHandler handler) { lastEntry = new Entry(verb, path(path), handler); handlers.add(lastEntry); } @@ -104,85 +104,85 @@ private void add(Type verb, String path, Handler handler) { // ******************************************************************************************** @Override - public Routing get(String path, Handler handler) { + public Routing get(String path, ExchangeHandler handler) { add(Type.GET, path, handler); return this; } @Override - public Routing get(Handler handler) { + public Routing get(ExchangeHandler handler) { get("", handler); return this; } @Override - public Routing post(String path, Handler handler) { + public Routing post(String path, ExchangeHandler handler) { add(Type.POST, path, handler); return this; } @Override - public Routing post(Handler handler) { + public Routing post(ExchangeHandler handler) { post("", handler); return this; } @Override - public Routing put(String path, Handler handler) { + public Routing put(String path, ExchangeHandler handler) { add(Type.PUT, path, handler); return this; } @Override - public Routing put(Handler handler) { + public Routing put(ExchangeHandler handler) { put("", handler); return this; } @Override - public Routing patch(String path, Handler handler) { + public Routing patch(String path, ExchangeHandler handler) { add(Type.PATCH, path, handler); return this; } @Override - public Routing patch(Handler handler) { + public Routing patch(ExchangeHandler handler) { patch("", handler); return this; } @Override - public Routing delete(String path, Handler handler) { + public Routing delete(String path, ExchangeHandler handler) { add(Type.DELETE, path, handler); return this; } @Override - public Routing delete(Handler handler) { + public Routing delete(ExchangeHandler handler) { delete("", handler); return this; } @Override - public Routing head(String path, Handler handler) { + public Routing head(String path, ExchangeHandler handler) { add(Type.HEAD, path, handler); return this; } @Override - public Routing head(Handler handler) { + public Routing head(ExchangeHandler handler) { head("", handler); return this; } @Override - public Routing trace(String path, Handler handler) { + public Routing trace(String path, ExchangeHandler handler) { add(Type.TRACE, path, handler); return this; } @Override - public Routing trace(Handler handler) { + public Routing trace(ExchangeHandler handler) { trace("", handler); return this; } @@ -201,10 +201,10 @@ private static class Entry implements Routing.Entry { private final Type type; private final String path; - private final Handler handler; + private final ExchangeHandler handler; private Set roles = Collections.emptySet(); - Entry(Type type, String path, Handler handler) { + Entry(Type type, String path, ExchangeHandler handler) { this.type = type; this.path = path; this.handler = handler; @@ -225,7 +225,7 @@ public String getPath() { } @Override - public Handler getHandler() { + public ExchangeHandler getHandler() { return handler; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/Handler.java b/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java similarity index 93% rename from avaje-jex/src/main/java/io/avaje/jex/Handler.java rename to avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java index aacbf15f..0a6a7d5c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Handler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java @@ -5,7 +5,7 @@ * these handlers. */ @FunctionalInterface -public interface Handler { +public interface ExchangeHandler { /** * Handle the given request and generate an appropriate response. See {@link Context} for a diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index 064715a6..8779eae5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -63,72 +63,72 @@ public sealed interface Routing permits DefaultRouting { /** * Add a HEAD handler. */ - Routing head(String path, Handler handler); + Routing head(String path, ExchangeHandler handler); /** * Add a HEAD handler for "/". */ - Routing head(Handler handler); + Routing head(ExchangeHandler handler); /** * Add a GET handler. */ - Routing get(String path, Handler handler); + Routing get(String path, ExchangeHandler handler); /** * Add a GET handler for "/". */ - Routing get(Handler handler); + Routing get(ExchangeHandler handler); /** * Add a POST handler. */ - Routing post(String path, Handler handler); + Routing post(String path, ExchangeHandler handler); /** * Add a POST handler for "/". */ - Routing post(Handler handler); + Routing post(ExchangeHandler handler); /** * Add a PUT handler. */ - Routing put(String path, Handler handler); + Routing put(String path, ExchangeHandler handler); /** * Add a PUT handler for "/". */ - Routing put(Handler handler); + Routing put(ExchangeHandler handler); /** * Add a PATCH handler. */ - Routing patch(String path, Handler handler); + Routing patch(String path, ExchangeHandler handler); /** * Add a PATCH handler for "/". */ - Routing patch(Handler handler); + Routing patch(ExchangeHandler handler); /** * Add a DELETE handler. */ - Routing delete(String path, Handler handler); + Routing delete(String path, ExchangeHandler handler); /** * Add a DELETE handler for "/". */ - Routing delete(Handler handler); + Routing delete(ExchangeHandler handler); /** * Add a TRACE handler. */ - Routing trace(String path, Handler handler); + Routing trace(String path, ExchangeHandler handler); /** * Add a TRACE handler for "/". */ - Routing trace(Handler handler); + Routing trace(ExchangeHandler handler); /** * Add a filter for all requests. @@ -214,7 +214,7 @@ interface Entry { /** * Return the handler. */ - Handler getHandler(); + ExchangeHandler getHandler(); /** * Return the roles. diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java index 04715a6b..4ae5d14c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java @@ -1,11 +1,9 @@ package io.avaje.jex.jdk; -import java.util.function.Consumer; - import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; -import io.avaje.jex.Context; +import io.avaje.jex.ExchangeHandler; import io.avaje.jex.Routing.Type; import io.avaje.jex.routes.SpiRoutes; @@ -25,12 +23,11 @@ void waitForIdle(long maxSeconds) { public void handle(HttpExchange exchange) { JdkContext ctx = (JdkContext) exchange.getAttribute("JdkContext"); - @SuppressWarnings("unchecked") - Consumer handlerConsumer = - (Consumer) exchange.getAttribute("SpiRoutes.Entry.Handler"); + ExchangeHandler handlerConsumer = + (ExchangeHandler) exchange.getAttribute("SpiRoutes.Entry.Handler"); ctx.setMode(null); - handlerConsumer.accept(ctx); + handlerConsumer.handle(ctx); ctx.setMode(Type.FILTER); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java index 35498273..a092f0cf 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java @@ -2,12 +2,11 @@ import java.util.Map; import java.util.Set; -import java.util.function.Consumer; import com.sun.net.httpserver.Filter; import com.sun.net.httpserver.HttpExchange; -import io.avaje.jex.Context; +import io.avaje.jex.ExchangeHandler; import io.avaje.jex.Routing; import io.avaje.jex.Routing.Type; import io.avaje.jex.http.HttpResponseException; @@ -56,7 +55,7 @@ public void doFilter(HttpExchange exchange, Filter.Chain chain) { try { ctx.setMode(Type.FILTER); exchange.setAttribute("JdkContext", ctx); - Consumer handlerConsumer = route::handle; + ExchangeHandler handlerConsumer = route::handle; exchange.setAttribute("SpiRoutes.Entry.Handler", handlerConsumer); chain.doFilter(exchange); } catch (Exception e) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java index 275989c8..48177a4f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java @@ -5,17 +5,17 @@ import java.util.concurrent.atomic.AtomicLong; import io.avaje.jex.Context; -import io.avaje.jex.Handler; +import io.avaje.jex.ExchangeHandler; import io.avaje.jex.security.Role; class RouteEntry implements SpiRoutes.Entry { private final AtomicLong active = new AtomicLong(); private final PathParser path; - private final Handler handler; + private final ExchangeHandler handler; private final Set roles; - RouteEntry(PathParser path, Handler handler, Set roles) { + RouteEntry(PathParser path, ExchangeHandler handler, Set roles) { this.path = path; this.handler = handler; this.roles = roles; diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiContext.java b/avaje-jex/src/main/java/io/avaje/jex/spi/SpiContext.java index c75c3360..29a6ef9e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/SpiContext.java @@ -29,7 +29,7 @@ public interface SpiContext extends Context { InputStream inputStream(); /** - * Set to indicate BEFORE, Handler and AFTER modes of the request. + * Set to indicate BEFORE, ExchangeHandler and AFTER modes of the request. */ void setMode(Routing.Type type); From 8bfc7eceab58697f867937434accbbbd3ca042af Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 23 Nov 2024 16:50:22 -0500 Subject: [PATCH 044/250] fix service collision --- .../avaje/jex/render/freemarker/FreeMarkerRenderTest.java | 6 +++--- avaje-jex/src/main/java/io/avaje/jex/DJex.java | 6 +++--- avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java | 8 ++++---- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 4 ++-- avaje-jex/src/main/java/io/avaje/jex/Routing.java | 8 ++++---- avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/avaje-jex-freemarker/src/test/java/io/avaje/jex/render/freemarker/FreeMarkerRenderTest.java b/avaje-jex-freemarker/src/test/java/io/avaje/jex/render/freemarker/FreeMarkerRenderTest.java index 73017de3..2b8cdff8 100644 --- a/avaje-jex-freemarker/src/test/java/io/avaje/jex/render/freemarker/FreeMarkerRenderTest.java +++ b/avaje-jex-freemarker/src/test/java/io/avaje/jex/render/freemarker/FreeMarkerRenderTest.java @@ -17,21 +17,21 @@ class FreeMarkerRenderTest { static TestPair pair = init(); static TestPair init() { - final List services = List.of(new NoModel(), new WithModel()); + final List services = List.of(new NoModel(), new WithModel()); var app = Jex.create() .routing(services) .register(new FreeMarkerRender(), "ftl"); return TestPair.create(app); } - static class NoModel implements Routing.Service { + static class NoModel implements Routing.HttpService { @Override public void add(Routing routing) { routing.get("/noModel", ctx -> ctx.render("one.ftl")); } } - static class WithModel implements Routing.Service { + static class WithModel implements Routing.HttpService { @Override public void add(Routing routing) { routing.get("/withModel", ctx -> ctx.render("two.ftl", Map.of("message", "hello"))); diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index ed24cce8..f5fd7320 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -36,13 +36,13 @@ public T attribute(Class cls) { } @Override - public Jex routing(Routing.Service routes) { + public Jex routing(Routing.HttpService routes) { routing.add(routes); return this; } @Override - public Jex routing(Collection routes) { + public Jex routing(Collection routes) { routing.addAll(routes); return this; } @@ -70,7 +70,7 @@ public Jex configureWith(BeanScope beanScope) { for (JexPlugin plugin : beanScope.list(JexPlugin.class)) { plugin.apply(this); } - routing.addAll(beanScope.list(Routing.Service.class)); + routing.addAll(beanScope.list(Routing.HttpService.class)); beanScope.getOptional(JsonService.class).ifPresent(this::jsonService); return this; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index 999aaddd..c0ef2a59 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -12,7 +12,7 @@ import io.avaje.jex.Routing.Entry; import io.avaje.jex.Routing.Group; -import io.avaje.jex.Routing.Service; +import io.avaje.jex.Routing.HttpService; import io.avaje.jex.Routing.Type; import io.avaje.jex.security.Role; @@ -55,14 +55,14 @@ private void addEndpoints(String path, Group group) { } @Override - public Routing add(Routing.Service routes) { + public Routing add(Routing.HttpService routes) { routes.add(this); return this; } @Override - public Routing addAll(Collection routes) { - for (Service route : routes) { + public Routing addAll(Collection routes) { + for (HttpService route : routes) { route.add(this); } return this; diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index b0e42346..459ccf91 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -58,12 +58,12 @@ static Jex create() { /** * Add routes and handlers to the routing. */ - Jex routing(Routing.Service routes); + Jex routing(Routing.HttpService routes); /** * Add many routes and handlers to the routing. */ - Jex routing(Collection routes); + Jex routing(Collection routes); /** * Return the Routing to configure. diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index 8779eae5..f0deaf3f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -11,14 +11,14 @@ public sealed interface Routing permits DefaultRouting { /** - * Add the routes provided by the Routing Service. + * Add the routes provided by the Routing HttpService. */ - Routing add(Routing.Service routes); + Routing add(Routing.HttpService routes); /** * Add all the routes provided by the Routing Services. */ - Routing addAll(Collection routes); + Routing addAll(Collection routes); /** * Specify permittedRoles for the last added handler. @@ -186,7 +186,7 @@ interface Group { * Adds to the Routing. */ @FunctionalInterface - interface Service { + interface HttpService { /** * Add to the routing. diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java b/avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java index f1123dd4..f6b4bad8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java @@ -16,7 +16,7 @@ public enum ErrorCode { // 5xx Server Error INTERNAL_SERVER_ERROR(500, "Internal Server Error"), BAD_GATEWAY(502, "Bad Gateway"), - SERVICE_UNAVAILABLE(503, "Service Unavailable"), + SERVICE_UNAVAILABLE(503, "HttpService Unavailable"), GATEWAY_TIMEOUT(504, "Gateway Timeout"), HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version Not Supported"), VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates"), diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java index 821c407c..91834abb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java @@ -3,7 +3,7 @@ import java.util.Iterator; /** - * Service used to convert request/response bodies to beans. + * HttpService used to convert request/response bodies to beans. */ public non-sealed interface JsonService extends JexExtension { From 3aa03c84c7bb24f8ee4020f41e8f1fe719788a07 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 25 Nov 2024 08:22:44 +1300 Subject: [PATCH 045/250] Tidy Context, imports, javadoc, whitespace (#70) --- .../src/main/java/io/avaje/jex/Context.java | 20 +++++++++---------- .../main/java/io/avaje/jex/DJexConfig.java | 3 --- .../java/io/avaje/jex/DefaultRouting.java | 4 ---- .../main/java/io/avaje/jex/HttpFilter.java | 6 ++---- 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index 2e556b07..6ba2bcf2 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -361,7 +361,15 @@ default Context render(String name) { */ String protocol(); - class Cookie { + /** + * Gets basic-auth credentials from the request, or throws. + * + *

Returns a wrapper object containing the Base64 decoded username + * and password from the Authorization header, or null if basic-auth is not properly configured + */ + BasicAuthCredentials basicAuthCredentials(); + + final class Cookie { private static final ZonedDateTime EXPIRED = ZonedDateTime.of(LocalDateTime.of(2000, 1, 1, 0, 0, 0), ZoneId.of("GMT")); private static final DateTimeFormatter RFC_1123_DATE_TIME = DateTimeFormatter.RFC_1123_DATE_TIME; private static final String PARAM_SEPARATOR = "; "; @@ -375,7 +383,7 @@ class Cookie { private boolean httpOnly; private Cookie(String name, String value) { - if (name == null || name.length() == 0) { + if (name == null || name.isEmpty()) { throw new IllegalArgumentException("name required"); } this.name = name; @@ -492,12 +500,4 @@ public String toString() { } } - /** - * Gets basic-auth credentials from the request, or throws. - * - *

Returns a wrapper object containing the Base64 decoded username - * and password from the Authorization header, or null if basic-auth is not properly configured - */ - BasicAuthCredentials basicAuthCredentials(); - } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 68ba543c..f9e3fd5c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -73,12 +73,10 @@ public JexConfig renderer(String extension, TemplateRender renderer) { @Override public ThreadFactory threadFactory() { - if (factory == null) { factory = Thread.ofVirtual().name("avaje-jex-http-", 0).factory(); } - return factory; } @@ -130,7 +128,6 @@ public Map renderers() { @Override public SSLContext sslContext() { - return this.sslContext; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index c0ef2a59..84e4cd7c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -10,10 +10,6 @@ import java.util.Map; import java.util.Set; -import io.avaje.jex.Routing.Entry; -import io.avaje.jex.Routing.Group; -import io.avaje.jex.Routing.HttpService; -import io.avaje.jex.Routing.Type; import io.avaje.jex.security.Role; final class DefaultRouting implements Routing { diff --git a/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java b/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java index d52b6539..982cff0a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java @@ -2,15 +2,13 @@ import java.io.IOException; -import com.sun.net.httpserver.Filter.Chain; - /** * A filter used to pre- and post-process incoming requests. Pre-processing occurs before the * application's exchange handler is invoked, and post-processing occurs after the exchange handler * returns. Filters are organized in chains, and are associated with {@link Context} instances. * *

Each {@code HttpFilter} in the chain, invokes the next filter within its own {@link - * #filter(Context, Chain)} implementation. The final {@code HttpFilter} in the chain invokes the + * #filter(Context, FilterChain)} implementation. The final {@code HttpFilter} in the chain invokes the * applications exchange handler. */ @FunctionalInterface @@ -26,7 +24,7 @@ public interface HttpFilter { *

    *
  1. Invoke the next filter in the chain, by calling {@link FilterChain#proceed}. *
  2. Terminate the chain of invocation, by not calling {@link - * FilterChain#filter}. + * FilterChain#proceed()}. *
*
  • If option 1. above is taken, then when filter() returns all subsequent filters in the * Chain have been called, and the response headers can be examined or modified. From 0fc9408f3992f0838a0bb0ca9205aa52d3ae66c1 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 25 Nov 2024 08:42:22 +1300 Subject: [PATCH 046/250] Change Context.Cookie into an interface --- .../src/main/java/io/avaje/jex/Context.java | 186 ++++++++---------- .../src/main/java/io/avaje/jex/DCookie.java | 139 +++++++++++++ 2 files changed, 216 insertions(+), 109 deletions(-) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/DCookie.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index 6ba2bcf2..1d4fc4d2 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -5,10 +5,7 @@ import java.io.InputStream; import java.time.Duration; -import java.time.LocalDateTime; -import java.time.ZoneId; import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -369,135 +366,106 @@ default Context render(String name) { */ BasicAuthCredentials basicAuthCredentials(); - final class Cookie { - private static final ZonedDateTime EXPIRED = ZonedDateTime.of(LocalDateTime.of(2000, 1, 1, 0, 0, 0), ZoneId.of("GMT")); - private static final DateTimeFormatter RFC_1123_DATE_TIME = DateTimeFormatter.RFC_1123_DATE_TIME; - private static final String PARAM_SEPARATOR = "; "; - private final String name; // NAME= ... "$Name" style is reserved - private final String value; // value of NAME - private String domain; // ;Domain=VALUE ... domain that sees cookie - private ZonedDateTime expires; - private Duration maxAge;// = -1; // ;Max-Age=VALUE ... cookies auto-expire - private String path; // ;Path=VALUE ... URLs that see the cookie - private boolean secure; // ;Secure ... e.g. use SSL - private boolean httpOnly; - - private Cookie(String name, String value) { - if (name == null || name.isEmpty()) { - throw new IllegalArgumentException("name required"); - } - this.name = name; - this.value = value; - } + interface Cookie { - public static Cookie expired(String name) { - return new Cookie(name, "").expires(EXPIRED); + /** + * Return an expired cookie given the name. + * + * @param name The name of the cookie. + * @return The expired cookie + */ + static Cookie expired(String name) { + return DCookie.expired(name); } - public static Cookie of(String name, String value) { - return new Cookie(name, value); + /** + * Return a new cookie given the name and value. + * + * @param name The name of the cookie + * @param value The cookie value + * @return The new cookie + */ + static Cookie of(String name, String value) { + return DCookie.of(name, value); } - public String name() { - return name; - } + /** + * Return the name. + */ + String name(); - public String value() { - return value; - } + /** + * Return the value. + */ + String value(); - public String domain() { - return domain; - } + /** + * Return the domain. + */ + String domain(); - public Cookie domain(String domain) { - this.domain = domain; - return this; - } + /** + * Set the domain. + */ + Cookie domain(String domain); - public Duration maxAge() { - return maxAge; - } + /** + * Return the max age. + */ + Duration maxAge(); - public Cookie maxAge(Duration maxAge) { - this.maxAge = maxAge; - return this; - } + /** + * Set the max age. + */ + Cookie maxAge(Duration maxAge); - public ZonedDateTime expires() { - return expires; - } + /** + * Return the cookie expiration. + */ + ZonedDateTime expires(); - public Cookie expires(ZonedDateTime expires) { - this.expires = expires; - return this; - } + /** + * Set when the cookie expires. + */ + Cookie expires(ZonedDateTime expires); - public String path() { - return path; - } + /** + * Return the path. + */ + String path(); - public Cookie path(String path) { - this.path = path; - return this; - } + /** + * Set the path. + */ + Cookie path(String path); - public boolean secure() { - return secure; - } + /** + * Return the secure attribute of the cookie. + */ + boolean secure(); - public Cookie secure(boolean secure) { - this.secure = secure; - return this; - } + /** + * Set the secure attribute of the cookie. + */ + Cookie secure(boolean secure); - public boolean httpOnly() { - return httpOnly; - } + /** + * Return the httpOnly attribute of the cookie. + */ + boolean httpOnly(); - public Cookie httpOnly(boolean httpOnly) { - this.httpOnly = httpOnly; - return this; - } + /** + * Set the httpOnly attribute of the cookie. + */ + Cookie httpOnly(boolean httpOnly); /** - * Returns content of this instance as a 'Set-Cookie:' header value specified + * Returns content of the cookie as a 'Set-Cookie:' header value specified * by RFC6265. */ @Override - public String toString() { - StringBuilder result = new StringBuilder(60); - result.append(name).append('=').append(value); - if (expires != null) { - result.append(PARAM_SEPARATOR); - result.append("Expires="); - result.append(expires.format(RFC_1123_DATE_TIME)); - } - if ((maxAge != null) && !maxAge.isNegative() && !maxAge.isZero()) { - result.append(PARAM_SEPARATOR); - result.append("Max-Age="); - result.append(maxAge.getSeconds()); - } - if (domain != null) { - result.append(PARAM_SEPARATOR); - result.append("Domain="); - result.append(domain); - } - if (path != null) { - result.append(PARAM_SEPARATOR); - result.append("Path="); - result.append(path); - } - if (secure) { - result.append(PARAM_SEPARATOR); - result.append("Secure"); - } - if (httpOnly) { - result.append(PARAM_SEPARATOR); - result.append("HttpOnly"); - } - return result.toString(); - } + String toString(); + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DCookie.java b/avaje-jex/src/main/java/io/avaje/jex/DCookie.java new file mode 100644 index 00000000..0c94a06e --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/DCookie.java @@ -0,0 +1,139 @@ +package io.avaje.jex; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +final class DCookie implements Context.Cookie { + + private static final ZonedDateTime EXPIRED = ZonedDateTime.of(LocalDateTime.of(2000, 1, 1, 0, 0, 0), ZoneId.of("GMT")); + private static final DateTimeFormatter RFC_1123_DATE_TIME = DateTimeFormatter.RFC_1123_DATE_TIME; + private static final String PARAM_SEPARATOR = "; "; + private final String name; // NAME= ... "$Name" style is reserved + private final String value; // value of NAME + private String domain; // ;Domain=VALUE ... domain that sees cookie + private ZonedDateTime expires; + private Duration maxAge;// = -1; // ;Max-Age=VALUE ... cookies auto-expire + private String path; // ;Path=VALUE ... URLs that see the cookie + private boolean secure; // ;Secure ... e.g. use SSL + private boolean httpOnly; + + private DCookie(String name, String value) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("name required"); + } + this.name = name; + this.value = value; + } + + static Context.Cookie expired(String name) { + return new DCookie(name, "").expires(EXPIRED); + } + + static Context.Cookie of(String name, String value) { + return new DCookie(name, value); + } + + @Override + public String name() { + return name; + } + + @Override + public String value() { + return value; + } + + @Override + public String domain() { + return domain; + } + + @Override + public Context.Cookie domain(String domain) { + this.domain = domain; + return this; + } + + @Override + public Duration maxAge() { + return maxAge; + } + + @Override + public Context.Cookie maxAge(Duration maxAge) { + this.maxAge = maxAge; + return this; + } + + @Override + public ZonedDateTime expires() { + return expires; + } + + @Override + public Context.Cookie expires(ZonedDateTime expires) { + this.expires = expires; + return this; + } + + @Override + public String path() { + return path; + } + + @Override + public Context.Cookie path(String path) { + this.path = path; + return this; + } + + @Override + public boolean secure() { + return secure; + } + + @Override + public Context.Cookie secure(boolean secure) { + this.secure = secure; + return this; + } + + @Override + public boolean httpOnly() { + return httpOnly; + } + + @Override + public Context.Cookie httpOnly(boolean httpOnly) { + this.httpOnly = httpOnly; + return this; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(60); + result.append(name).append('=').append(value); + if (expires != null) { + result.append(PARAM_SEPARATOR).append("Expires=").append(expires.format(RFC_1123_DATE_TIME)); + } + if ((maxAge != null) && !maxAge.isNegative() && !maxAge.isZero()) { + result.append(PARAM_SEPARATOR).append("Max-Age=").append(maxAge.getSeconds()); + } + if (domain != null) { + result.append(PARAM_SEPARATOR).append("Domain=").append(domain); + } + if (path != null) { + result.append(PARAM_SEPARATOR).append("Path=").append(path); + } + if (secure) { + result.append(PARAM_SEPARATOR).append("Secure"); + } + if (httpOnly) { + result.append(PARAM_SEPARATOR).append("HttpOnly"); + } + return result.toString(); + } +} From 90dd5ca1203da418f9131998e9bacf930537dafb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 03:50:02 +0000 Subject: [PATCH 047/250] Bump the dependencies group with 2 updates Bumps the dependencies group with 2 updates: [io.avaje:avaje-inject](https://github.com/avaje/avaje-inject) and io.avaje:avaje-inject-test. Updates `io.avaje:avaje-inject` from 10.6 to 11.0 - [Release notes](https://github.com/avaje/avaje-inject/releases) - [Commits](https://github.com/avaje/avaje-inject/compare/10.6...11.0) Updates `io.avaje:avaje-inject-test` from 10.6 to 11.0 --- updated-dependencies: - dependency-name: io.avaje:avaje-inject dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-test dependency-type: direct:production update-type: version-update:semver-major dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 8e7ebf7e..87aaa183 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -28,7 +28,7 @@ io.avaje avaje-inject-test - 10.6 + 11.0 true diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 5e1a4f64..bccc6c62 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -30,7 +30,7 @@ io.avaje avaje-inject - 10.6 + 11.0 true From e14efa5acf31e8501b0c53158279646b2e4853ed Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:07:39 -0500 Subject: [PATCH 048/250] Re-Add Static File support (#73) * Merge pull request #66 from SentryMan/internals Refactor Error Handling registration * static resources * use interface * rename * work with jlink * doc * add another assert * Update StaticResourceHandlerBuilder.java * Update README.md * get built in mime types * get exact size * simplify builder * Update JdkServerStart.java * Update StaticResourceHandlerBuilder.java * handle jar uris * Update StaticResourceHandlerBuilder.java * fix jars --- README.md | 3 - .../io/avaje/jex/AbstractStaticHandler.java | 69 ++++++ .../io/avaje/jex/ClassResourceLoader.java | 25 ++ .../src/main/java/io/avaje/jex/Context.java | 6 + .../java/io/avaje/jex/DefaultLifecycle.java | 1 + .../io/avaje/jex/DefaultResourceLoader.java | 47 ++++ .../java/io/avaje/jex/ExchangeHandler.java | 4 +- .../java/io/avaje/jex/JarResourceHandler.java | 80 ++++++ avaje-jex/src/main/java/io/avaje/jex/Jex.java | 20 +- .../io/avaje/jex/PathResourceHandler.java | 84 +++++++ .../java/io/avaje/jex/ResourceLocation.java | 6 + .../io/avaje/jex/StaticContentConfig.java | 93 +++++++ .../java/io/avaje/jex/StaticFileHandler.java | 86 +++++++ .../jex/StaticResourceHandlerBuilder.java | 228 ++++++++++++++++++ .../io/avaje/jex/core/CoreServiceLoader.java | 4 +- .../io/avaje/jex/core/CoreServiceManager.java | 2 +- .../io/avaje/jex/core/ExceptionManager.java | 3 +- .../avaje/jex/http/BadRequestException.java | 9 + .../avaje/jex/http/HttpResponseException.java | 8 - .../http/InternalServerErrorException.java | 9 + .../io/avaje/jex/http/NotFoundException.java | 9 + .../io/avaje/jex/http/RedirectException.java | 9 + .../java/io/avaje/jex/jdk/BaseHandler.java | 4 +- .../java/io/avaje/jex/jdk/JdkContext.java | 5 +- .../java/io/avaje/jex/jdk/JdkServerStart.java | 5 +- .../java/io/avaje/jex/jdk/RoutingFilter.java | 3 +- .../java/io/avaje/jex/routes/RouteEntry.java | 3 +- .../java/io/avaje/jex/routes/SpiRoutes.java | 3 +- .../java/io/avaje/jex/StaticFileTest.java | 126 ++++++++++ .../avaje/jex/jdk/ExceptionManagerTest.java | 2 +- .../java/io/avaje/jex/jdk/FilterTest.java | 1 + .../src/test/resources/public/index.html | 9 + avaje-jex/src/test/resources/public/sus.txt | 1 + 33 files changed, 939 insertions(+), 28 deletions(-) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/AbstractStaticHandler.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/DefaultResourceLoader.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/JarResourceHandler.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/PathResourceHandler.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/ResourceLocation.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/BadRequestException.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorException.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/NotFoundException.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/RedirectException.java create mode 100644 avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java create mode 100644 avaje-jex/src/test/resources/public/index.html create mode 100644 avaje-jex/src/test/resources/public/sus.txt diff --git a/README.md b/README.md index 8bcc09c1..05433ead 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,3 @@ var app = Jex.create() .port(8080) .start(); ``` - -### TODO -- static file configuration diff --git a/avaje-jex/src/main/java/io/avaje/jex/AbstractStaticHandler.java b/avaje-jex/src/main/java/io/avaje/jex/AbstractStaticHandler.java new file mode 100644 index 00000000..3e8c8861 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/AbstractStaticHandler.java @@ -0,0 +1,69 @@ +package io.avaje.jex; + +import java.net.FileNameMap; +import java.net.URLConnection; +import java.util.Map; +import java.util.Objects; +import java.util.function.Predicate; + +import com.sun.net.httpserver.HttpExchange; + +import io.avaje.jex.http.BadRequestException; +import io.avaje.jex.http.NotFoundException; + +abstract sealed class AbstractStaticHandler implements ExchangeHandler + permits StaticFileHandler, PathResourceHandler, JarResourceHandler { + + protected final Map mimeTypes; + protected final String filesystemRoot; + protected final String urlPrefix; + protected final Predicate skipFilePredicate; + protected final Map headers; + private static final FileNameMap MIME_MAP = URLConnection.getFileNameMap(); + + protected AbstractStaticHandler( + String urlPrefix, + String filesystemRoot, + Map mimeTypes, + Map headers, + Predicate skipFilePredicate) { + this.filesystemRoot = filesystemRoot; + this.urlPrefix = urlPrefix; + this.skipFilePredicate = skipFilePredicate; + this.headers = headers; + this.mimeTypes = mimeTypes; + } + + protected void throw404(HttpExchange jdkExchange) { + throw new NotFoundException("File Not Found for request: " + jdkExchange.getRequestURI()); + } + + // This is one function to avoid giving away where we failed + protected void reportPathTraversal() { + throw new BadRequestException("Path traversal attempt detected"); + } + + protected String getExt(String path) { + int slashIndex = path.lastIndexOf('/'); + String basename = (slashIndex < 0) ? path : path.substring(slashIndex + 1); + + int dotIndex = basename.lastIndexOf('.'); + if (dotIndex >= 0) { + return basename.substring(dotIndex + 1); + } else { + return ""; + } + } + + protected String lookupMime(String path) { + var lower = path.toLowerCase(); + + return Objects.requireNonNullElseGet( + MIME_MAP.getContentTypeFor(path), + () -> { + String ext = getExt(lower); + + return mimeTypes.getOrDefault(ext, "application/octet-stream"); + }); + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java new file mode 100644 index 00000000..904866a9 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java @@ -0,0 +1,25 @@ +package io.avaje.jex; + +import java.io.InputStream; +import java.net.URL; + +/** + * Loading resources from the classpath or module path. + * + *

    When not specified Avaje Jex provides a default implementation that looks to find resources + * using the class loader associated with the ClassResourceLoader. + * + *

    As a fallback, {@link ClassLoader#getSystemResourceAsStream(String)} is used if the loader returns null. + */ +public interface ClassResourceLoader { + + static ClassResourceLoader fromClass(Class clazz) { + + return new DefaultResourceLoader(clazz); + } + + /** Return the URL for the given resource or return null if it cannot be found. */ + URL getResource(String resourcePath); + + InputStream getResourceAsStream(String resourcePath); +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index 1d4fc4d2..a774c141 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -323,6 +323,12 @@ default Context render(String name) { */ Context header(String key, String value); + /** Set the response headers using the provided map. */ + default Context headers(Map headers) { + headers.forEach(this::header); + return this; + } + /** * Return the response header. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultLifecycle.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultLifecycle.java index f19622a2..02746e7c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultLifecycle.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultLifecycle.java @@ -3,6 +3,7 @@ import io.avaje.applog.AppLog; import java.lang.System.Logger.Level; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultResourceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultResourceLoader.java new file mode 100644 index 00000000..0a82317a --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultResourceLoader.java @@ -0,0 +1,47 @@ +package io.avaje.jex; + +import java.io.InputStream; +import java.net.URL; +import java.util.Objects; +import java.util.Optional; + +final class DefaultResourceLoader implements ClassResourceLoader { + + private final Class clazz; + + DefaultResourceLoader(Class clazz) { + + this.clazz = clazz; + } + + @Override + public URL getResource(String resourcePath) { + + var url = clazz.getResource(resourcePath); + if (url == null) { + // search the module path for top level resource + url = + Optional.ofNullable(ClassLoader.getSystemResource(resourcePath)) + .orElseGet( + () -> Thread.currentThread().getContextClassLoader().getResource(resourcePath)); + } + return Objects.requireNonNull(url, "Unable to locate resource: " + resourcePath); + } + + @Override + public InputStream getResourceAsStream(String resourcePath) { + + var url = clazz.getResourceAsStream(resourcePath); + if (url == null) { + // search the module path for top level resource + url = + Optional.ofNullable(ClassLoader.getSystemResourceAsStream(resourcePath)) + .orElseGet( + () -> + Thread.currentThread() + .getContextClassLoader() + .getResourceAsStream(resourcePath)); + } + return Objects.requireNonNull(url, "Unable to locate resource: " + resourcePath); + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java b/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java index 0a6a7d5c..f3680a70 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java @@ -1,5 +1,7 @@ package io.avaje.jex; +import java.io.IOException; + /** * A handler which is invoked to process HTTP exchanges. Each HTTP exchange is handled by one of * these handlers. @@ -14,5 +16,5 @@ public interface ExchangeHandler { * @param ctx the request context containing the request from the client and used to send the * response */ - void handle(Context ctx); + void handle(Context ctx) throws IOException; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/JarResourceHandler.java b/avaje-jex/src/main/java/io/avaje/jex/JarResourceHandler.java new file mode 100644 index 00000000..4d91db4d --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/JarResourceHandler.java @@ -0,0 +1,80 @@ +package io.avaje.jex; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Paths; +import java.util.Map; +import java.util.function.Predicate; + +final class JarResourceHandler extends AbstractStaticHandler implements ExchangeHandler { + + private final URL indexFile; + private final URL singleFile; + private final ClassResourceLoader resourceLoader; + + JarResourceHandler( + String urlPrefix, + String filesystemRoot, + Map mimeTypes, + Map headers, + Predicate skipFilePredicate, + ClassResourceLoader resourceLoader, + URL indexFile, + URL singleFile) { + super(urlPrefix, filesystemRoot, mimeTypes, headers, skipFilePredicate); + + this.resourceLoader = resourceLoader; + this.indexFile = indexFile; + this.singleFile = singleFile; + } + + @Override + public void handle(Context ctx) throws IOException { + + final var jdkExchange = ctx.jdkExchange(); + + if (singleFile != null) { + sendURL(ctx, singleFile.getPath(), singleFile); + return; + } + + if (skipFilePredicate.test(ctx)) { + throw404(jdkExchange); + } + + final String wholeUrlPath = jdkExchange.getRequestURI().getPath(); + + if (wholeUrlPath.endsWith("/") || wholeUrlPath.equals(urlPrefix)) { + sendURL(ctx, indexFile.getPath(), indexFile); + return; + } + + final String urlPath = wholeUrlPath.substring(urlPrefix.length()); + + final String normalizedPath = + Paths.get(filesystemRoot, urlPath).normalize().toString().replace("\\", "/"); + + if (!normalizedPath.startsWith(filesystemRoot)) { + reportPathTraversal(); + } + + try (var fis = resourceLoader.getResourceAsStream(normalizedPath)) { + ctx.header("Content-Type", lookupMime(normalizedPath)); + ctx.headers(headers); + ctx.write(fis); + } catch (final Exception e) { + throw404(ctx.jdkExchange()); + } + } + + private void sendURL(Context ctx, String urlPath, URL path) throws IOException { + + try (var fis = path.openStream()) { + ctx.header("Content-Type", lookupMime(urlPath)); + ctx.headers(headers); + ctx.write(fis); + } catch (final Exception e) { + throw404(ctx.jdkExchange()); + } + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 459ccf91..8610fd57 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -1,12 +1,12 @@ package io.avaje.jex; +import java.util.Collection; +import java.util.function.Consumer; + import io.avaje.inject.BeanScope; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; -import java.util.Collection; -import java.util.function.Consumer; - /** * Create configure and start Jex. * @@ -70,6 +70,20 @@ static Jex create() { */ Routing routing(); + /** Add a static resource route */ + default Jex staticResource(StaticContentConfig config) { + routing().get(config.httpPath(), config.createHandler()); + return this; + } + + /** Add a static resource route using a consumer */ + default Jex staticResource(Consumer consumer) { + var builder = StaticResourceHandlerBuilder.builder(); + consumer.accept(builder); + + return staticResource(builder); + } + /** * Set the JsonService. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/PathResourceHandler.java b/avaje-jex/src/main/java/io/avaje/jex/PathResourceHandler.java new file mode 100644 index 00000000..ff5ae3da --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/PathResourceHandler.java @@ -0,0 +1,84 @@ +package io.avaje.jex; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.function.Predicate; + +final class PathResourceHandler extends AbstractStaticHandler implements ExchangeHandler { + + private final Path indexFile; + private final Path singleFile; + + PathResourceHandler( + String urlPrefix, + String filesystemRoot, + Map mimeTypes, + Map headers, + Predicate skipFilePredicate, + Path indexFile, + Path singleFile) { + super(urlPrefix, filesystemRoot, mimeTypes, headers, skipFilePredicate); + + this.indexFile = indexFile; + this.singleFile = singleFile; + } + + @Override + public void handle(Context ctx) throws IOException { + + if (singleFile != null) { + sendPathIS(ctx, singleFile.toString(), singleFile); + return; + } + + final var jdkExchange = ctx.jdkExchange(); + if (skipFilePredicate.test(ctx)) { + throw404(jdkExchange); + } + + final String wholeUrlPath = jdkExchange.getRequestURI().getPath(); + + if (wholeUrlPath.endsWith("/") || wholeUrlPath.equals(urlPrefix)) { + sendPathIS(ctx, indexFile.toString(), indexFile); + + return; + } + + final String urlPath = wholeUrlPath.substring(urlPrefix.length()); + + Path path; + try { + path = Path.of(filesystemRoot, urlPath).toRealPath(); + + } catch (final IOException e) { + // This may be more benign (i.e. not an attack, just a 403), + // but we don't want an attacker to be able to discern the difference. + reportPathTraversal(); + return; + } + + final String canonicalPath = path.toString(); + if (!canonicalPath.startsWith(filesystemRoot)) { + reportPathTraversal(); + } + + sendPathIS(ctx, urlPath, path); + } + + private void sendPathIS(Context ctx, String urlPath, Path path) throws IOException { + final var exchange = ctx.jdkExchange(); + final String mimeType = lookupMime(urlPath); + ctx.header("Content-Type", mimeType); + ctx.headers(headers); + exchange.sendResponseHeaders(200, Files.size(path)); + try (var fis = Files.newInputStream(path); + var os = exchange.getResponseBody()) { + + fis.transferTo(os); + } catch (final Exception e) { + throw404(ctx.jdkExchange()); + } + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/ResourceLocation.java b/avaje-jex/src/main/java/io/avaje/jex/ResourceLocation.java new file mode 100644 index 00000000..cd5da741 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/ResourceLocation.java @@ -0,0 +1,6 @@ +package io.avaje.jex; + +public enum ResourceLocation { + CLASS_PATH, + FILE +} \ No newline at end of file diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java b/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java new file mode 100644 index 00000000..14aa1e11 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java @@ -0,0 +1,93 @@ +package io.avaje.jex; + +import java.net.URLConnection; +import java.util.function.Predicate; + +/** Builder for a static resource exchange handler. */ +public sealed interface StaticContentConfig permits StaticResourceHandlerBuilder { + + static StaticContentConfig create() { + return StaticResourceHandlerBuilder.builder(); + } + + /** Return a new ExchangeHandler that will serve the resources */ + ExchangeHandler createHandler(); + + /** + * Sets the HTTP path for the static resource handler. + * + * @param path the HTTP path prefix + * @return the updated configuration + */ + StaticContentConfig httpPath(String path); + + /** + * Gets the current HTTP path. + * + * @return the current HTTP path + */ + String httpPath(); + + /** + * Sets the file to serve, or the folder your files are located in. (default: "/public/") + * + * @param root the root directory + * @return the updated configuration + */ + StaticContentConfig resource(String resource); + + /** + * Sets the index file to be served when a directory is requests. + * + * @param directoryIndex the index file + * @return the updated configuration + */ + StaticContentConfig directoryIndex(String directoryIndex); + + /** + * Sets a custom resource loader for loading class/module path resources. This is normally used + * when running the application on the module path when files cannot be discovered. + * + *

    Example usage: {@code config.resourceLoader(ClassResourceLoader.create(getClass())) } + * + * @param resourceLoader the custom resource loader + * @return the updated configuration + */ + StaticContentConfig resourceLoader(ClassResourceLoader resourceLoader); + + /** + * Adds a new MIME type mapping to the configuration. (Default: uses {@link + * URLConnection#getFileNameMap()} + * + * @param ext the file extension (e.g., "html", "css", "js") + * @param mimeType the corresponding MIME type (e.g., "text/html", "text/css", + * "application/javascript") + * @return the updated configuration + */ + StaticContentConfig putMimeTypeMapping(String ext, String mimeType); + + /** + * Adds a new response header to the configuration. + * + * @param key the header name + * @param value the header value + * @return the updated configuration + */ + StaticContentConfig putResponseHeader(String key, String value); + + /** + * Sets a predicate to filter files based on the request context. + * + * @param skipFilePredicate the predicate to use + * @return the updated configuration + */ + StaticContentConfig skipFilePredicate(Predicate skipFilePredicate); + + /** + * Sets the resource location (CLASSPATH or FILE). + * + * @param location the resource location + * @return the updated configuration + */ + StaticContentConfig location(ResourceLocation location); +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java b/avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java new file mode 100644 index 00000000..2e728956 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java @@ -0,0 +1,86 @@ +package io.avaje.jex; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Map; +import java.util.function.Predicate; + +import com.sun.net.httpserver.HttpExchange; + +final class StaticFileHandler extends AbstractStaticHandler implements ExchangeHandler { + + private final File indexFile; + private final File singleFile; + + StaticFileHandler( + String urlPrefix, + String filesystemRoot, + Map mimeTypes, + Map headers, + Predicate skipFilePredicate, + File welcomeFile, + File singleFile) { + super(urlPrefix, filesystemRoot, mimeTypes, headers, skipFilePredicate); + this.indexFile = welcomeFile; + this.singleFile = singleFile; + } + + @Override + public void handle(Context ctx) throws IOException { + + final var jdkExchange = ctx.jdkExchange(); + + if (singleFile != null) { + sendFile(ctx, jdkExchange, singleFile.getPath(), singleFile); + return; + } + + if (skipFilePredicate.test(ctx)) { + throw404(jdkExchange); + } + + final String wholeUrlPath = jdkExchange.getRequestURI().getPath(); + + if (wholeUrlPath.endsWith("/") || wholeUrlPath.equals(urlPrefix)) { + sendFile(ctx, jdkExchange, indexFile.getPath(), indexFile); + + return; + } + + final String urlPath = wholeUrlPath.substring(urlPrefix.length()); + + File canonicalFile; + try { + canonicalFile = new File(filesystemRoot, urlPath).getCanonicalFile(); + + } catch (IOException e) { + // This may be more benign (i.e. not an attack, just a 403), + // but we don't want an attacker to be able to discern the difference. + reportPathTraversal(); + return; + } + + String canonicalPath = canonicalFile.getPath(); + if (!canonicalPath.startsWith(filesystemRoot)) { + reportPathTraversal(); + } + + sendFile(ctx, jdkExchange, urlPath, canonicalFile); + } + + private void sendFile(Context ctx, HttpExchange jdkExchange, String urlPath, File canonicalFile) + throws IOException { + try (var fis = new FileInputStream(canonicalFile)) { + + String mimeType = lookupMime(urlPath); + ctx.header("Content-Type", mimeType); + ctx.headers(headers); + jdkExchange.sendResponseHeaders(200, canonicalFile.length()); + fis.transferTo(jdkExchange.getResponseBody()); + } catch (FileNotFoundException e) { + throw404(jdkExchange); + } + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java new file mode 100644 index 00000000..8dc482a5 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java @@ -0,0 +1,228 @@ +package io.avaje.jex; + +import static io.avaje.jex.ResourceLocation.CLASS_PATH; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; + +final class StaticResourceHandlerBuilder implements StaticContentConfig { + + private static final String FAILED_TO_LOCATE_FILE = "Failed to locate file: "; + private static final String DIRECTORY_INDEX_FAILURE = + "Failed to locate Directory Index Resource: "; + private static final Predicate NO_OP_PREDICATE = ctx -> false; + private static final ClassResourceLoader DEFALT_LOADER = + ClassResourceLoader.fromClass(StaticResourceHandlerBuilder.class); + + private String path = "/"; + private String root = "/public/"; + private String directoryIndex = null; + private ClassResourceLoader resourceLoader = DEFALT_LOADER; + private final Map mimeTypes = new HashMap<>(); + private final Map headers = new HashMap<>(); + private Predicate skipFilePredicate = NO_OP_PREDICATE; + private ResourceLocation location = CLASS_PATH; + + private StaticResourceHandlerBuilder() {} + + public static StaticResourceHandlerBuilder builder() { + return new StaticResourceHandlerBuilder(); + } + + @Override + public ExchangeHandler createHandler() { + + path = + Objects.requireNonNull(path) + .transform(this::prependSlash) + .transform(s -> s.endsWith("/*") ? s.substring(0, s.length() - 2) : s); + + final var isClasspath = location == CLASS_PATH; + + root = isClasspath ? root.transform(this::prependSlash) : root; + if (isClasspath && "/".equals(root)) { + throw new IllegalArgumentException( + "Cannot serve full classpath, please configure a classpath prefix"); + } + + if (root.endsWith("/") && directoryIndex == null) { + throw new IllegalArgumentException( + "Directory Index file is required when serving directories"); + } + + if (location == ResourceLocation.FILE) { + return fileLoader(File::new); + } + + return classPathHandler(); + } + + @Override + public StaticResourceHandlerBuilder httpPath(String path) { + this.path = path; + return this; + } + + @Override + public String httpPath() { + return path; + } + + @Override + public StaticResourceHandlerBuilder resource(String directory) { + this.root = directory; + return this; + } + + @Override + public StaticResourceHandlerBuilder directoryIndex(String directoryIndex) { + this.directoryIndex = directoryIndex; + return this; + } + + @Override + public StaticResourceHandlerBuilder resourceLoader(ClassResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + return this; + } + + @Override + public StaticResourceHandlerBuilder putMimeTypeMapping(String key, String value) { + this.mimeTypes.put(key, value); + return this; + } + + @Override + public StaticResourceHandlerBuilder putResponseHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + @Override + public StaticResourceHandlerBuilder skipFilePredicate(Predicate skipFilePredicate) { + this.skipFilePredicate = skipFilePredicate; + return this; + } + + @Override + public StaticResourceHandlerBuilder location(ResourceLocation location) { + this.location = location; + return this; + } + + private String prependSlash(String s) { + return s.startsWith("/") ? s : "/" + s; + } + + private String appendSlash(String s) { + return s.endsWith("/") ? s : s + "/"; + } + + private ExchangeHandler fileLoader(Function fileLoader) { + String fsRoot; + File dirIndex = null; + File singleFile = null; + if (directoryIndex != null) { + try { + + dirIndex = + fileLoader.apply(root.transform(this::appendSlash) + directoryIndex).getCanonicalFile(); + + fsRoot = dirIndex.getParentFile().getPath(); + } catch (Exception e) { + throw new IllegalStateException( + DIRECTORY_INDEX_FAILURE + root.transform(this::appendSlash) + directoryIndex, e); + } + } else { + try { + + singleFile = fileLoader.apply(root).getCanonicalFile(); + + fsRoot = singleFile.getParentFile().getPath(); + } catch (Exception e) { + throw new IllegalStateException(FAILED_TO_LOCATE_FILE + root, e); + } + } + + return new StaticFileHandler( + path, fsRoot, mimeTypes, headers, skipFilePredicate, dirIndex, singleFile); + } + + private ExchangeHandler classPathHandler() { + Function urlFunc = resourceLoader::getResource; + + Function loaderFunc = urlFunc.andThen(this::toURI); + String fsRoot; + Path dirIndex = null; + Path singleFile = null; + if (directoryIndex != null) { + try { + var uri = loaderFunc.apply(root.transform(this::appendSlash) + directoryIndex); + + if ("jar".equals(uri.getScheme())) { + + var url = uri.toURL(); + return jarLoader(url.toString().transform(this::getJARRoot), url, null); + } + dirIndex = Paths.get(uri).toRealPath(); + fsRoot = Paths.get(uri).getParent().toString(); + + } catch (Exception e) { + + throw new IllegalStateException( + DIRECTORY_INDEX_FAILURE + root.transform(this::appendSlash) + directoryIndex, e); + } + } else { + try { + var uri = loaderFunc.apply(root); + + if ("jar".equals(uri.getScheme())) { + + var url = uri.toURL(); + + return jarLoader(url.toString().transform(this::getJARRoot), null, uri.toURL()); + } + + singleFile = Paths.get(uri).toRealPath(); + + fsRoot = singleFile.getParent().toString(); + + } catch (Exception e) { + + throw new IllegalStateException(FAILED_TO_LOCATE_FILE + root, e); + } + } + + return new PathResourceHandler( + path, fsRoot, mimeTypes, headers, skipFilePredicate, dirIndex, singleFile); + } + + private String getJARRoot(String s) { + return s.substring(0, s.lastIndexOf("/")).substring(s.indexOf("jar!") + 4); + } + + private ExchangeHandler jarLoader(String fsRoot, URL dirIndex, URL singleFile) { + + return new JarResourceHandler( + path, fsRoot, mimeTypes, headers, skipFilePredicate, resourceLoader, dirIndex, singleFile); + } + + private URI toURI(URL url) { + + try { + + return url.toURI(); + } catch (URISyntaxException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java index 56fe2962..99ac7348 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java @@ -10,7 +10,7 @@ import io.avaje.jex.spi.TemplateRender; /** Core implementation of SpiServiceManager provided to specific implementations like jetty etc. */ -final class CoreServiceLoader { +public final class CoreServiceLoader { private static final CoreServiceLoader INSTANCE = new CoreServiceLoader(); @@ -30,7 +30,7 @@ final class CoreServiceLoader { jsonService = spiJsonService; } - public static Optional getJsonService() { + public static Optional jsonService() { return Optional.ofNullable(INSTANCE.jsonService); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java index c2376d33..74a3082b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java @@ -159,7 +159,7 @@ JsonService initJsonService() { if (jsonService != null) { return jsonService; } - return CoreServiceLoader.getJsonService() + return CoreServiceLoader.jsonService() .orElseGet(this::defaultJsonService); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index 481944ce..ddafb842 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -8,6 +8,7 @@ import io.avaje.jex.ExceptionHandler; import io.avaje.jex.http.ErrorCode; import io.avaje.jex.http.HttpResponseException; +import io.avaje.jex.http.InternalServerErrorException; import io.avaje.jex.spi.HeaderKeys; import io.avaje.jex.spi.SpiContext; @@ -49,7 +50,7 @@ void handle(SpiContext ctx, Exception e) { private void unhandledException(SpiContext ctx, Exception e) { log.log(WARNING, "Uncaught exception", e); - defaultHandling(ctx, new HttpResponseException(ErrorCode.INTERNAL_SERVER_ERROR)); + defaultHandling(ctx, new InternalServerErrorException(ErrorCode.INTERNAL_SERVER_ERROR.message())); } private void defaultHandling(SpiContext ctx, HttpResponseException exception) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/BadRequestException.java b/avaje-jex/src/main/java/io/avaje/jex/http/BadRequestException.java new file mode 100644 index 00000000..c9cc523a --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/http/BadRequestException.java @@ -0,0 +1,9 @@ +package io.avaje.jex.http; + +/** Thrown when unable to find a route/resource */ +public class BadRequestException extends HttpResponseException { + + public BadRequestException(String message) { + super(400, message); + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java b/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java index 7258f667..613894f2 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java @@ -22,14 +22,6 @@ public HttpResponseException(int status, String message) { this(status, message, Collections.emptyMap()); } - public HttpResponseException(ErrorCode code) { - this(code.status(), code.message()); - } - - public HttpResponseException(ErrorCode code, Map details) { - this(code.status(), code.message(), details); - } - public int getStatus() { return status; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorException.java b/avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorException.java new file mode 100644 index 00000000..4ddc7716 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorException.java @@ -0,0 +1,9 @@ +package io.avaje.jex.http; + +/** Thrown when unable to find a route/resource */ +public class InternalServerErrorException extends HttpResponseException { + + public InternalServerErrorException(String message) { + super(500, message); + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/NotFoundException.java b/avaje-jex/src/main/java/io/avaje/jex/http/NotFoundException.java new file mode 100644 index 00000000..5a0caa5a --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/http/NotFoundException.java @@ -0,0 +1,9 @@ +package io.avaje.jex.http; + +/** Thrown when unable to find a route/resource */ +public class NotFoundException extends HttpResponseException { + + public NotFoundException(String message) { + super(404, message); + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/RedirectException.java b/avaje-jex/src/main/java/io/avaje/jex/http/RedirectException.java new file mode 100644 index 00000000..6bdaaefb --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/http/RedirectException.java @@ -0,0 +1,9 @@ +package io.avaje.jex.http; + +/** Thrown when redirecting */ +public class RedirectException extends HttpResponseException { + + public RedirectException(String message) { + super(302, message); + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java index 4ae5d14c..ca744b4a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java @@ -1,5 +1,7 @@ package io.avaje.jex.jdk; +import java.io.IOException; + import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; @@ -20,7 +22,7 @@ void waitForIdle(long maxSeconds) { } @Override - public void handle(HttpExchange exchange) { + public void handle(HttpExchange exchange) throws IOException { JdkContext ctx = (JdkContext) exchange.getAttribute("JdkContext"); ExchangeHandler handlerConsumer = diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index 8a99a481..6eb2473e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -27,6 +27,7 @@ import io.avaje.jex.Routing; import io.avaje.jex.http.ErrorCode; import io.avaje.jex.http.HttpResponseException; +import io.avaje.jex.http.RedirectException; import io.avaje.jex.security.BasicAuthCredentials; import io.avaje.jex.security.Role; import io.avaje.jex.spi.HeaderKeys; @@ -150,7 +151,7 @@ public void redirect(String location, int statusCode) { header(HeaderKeys.LOCATION, location); status(statusCode); if (mode == Routing.Type.FILTER) { - throw new HttpResponseException(ErrorCode.REDIRECT); + throw new RedirectException(ErrorCode.REDIRECT.message()); } else { performRedirect(); } @@ -351,7 +352,7 @@ public Context write(String content) { public Context write(byte[] bytes) { try (var os = exchange.getResponseBody()) { - exchange.sendResponseHeaders(statusCode(), bytes.length); + exchange.sendResponseHeaders(statusCode(), bytes.length == 0 ? -1 : bytes.length); os.write(bytes); os.flush(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index 157c4890..9b0e11f0 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -49,8 +49,9 @@ public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceMana server.start(); jex.lifecycle().status(AppLifecycle.Status.STARTED); - String jexVersion = Jex.class.getPackage().getImplementationVersion(); - log.log(Level.INFO, "started server on port {0,number,#} version {1}", port, jexVersion); + log.log( + Level.INFO, + "started com.sun.net.httpserver.HttpServer on port %s://%s".formatted(scheme, port)); return new JdkJexServer(server, jex.lifecycle(), handler); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java index a092f0cf..04dd04a6 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java @@ -10,6 +10,7 @@ import io.avaje.jex.Routing; import io.avaje.jex.Routing.Type; import io.avaje.jex.http.HttpResponseException; +import io.avaje.jex.http.NotFoundException; import io.avaje.jex.routes.SpiRoutes; import io.avaje.jex.spi.SpiContext; @@ -77,7 +78,7 @@ private void processNoRoute(JdkContext ctx, String uri, Routing.Type routeType) ctx.status(200); return; } - throw new HttpResponseException(404, "uri: " + uri); + throw new NotFoundException("uri: " + uri); } private boolean hasGetHandler(String uri) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java index 48177a4f..25eae142 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java @@ -1,5 +1,6 @@ package io.avaje.jex.routes; +import java.io.IOException; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; @@ -42,7 +43,7 @@ public boolean matches(String requestUri) { } @Override - public void handle(Context ctx) { + public void handle(Context ctx) throws IOException { handler.handle(ctx); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java b/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java index be4f2bfc..54068511 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java @@ -1,5 +1,6 @@ package io.avaje.jex.routes; +import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Set; @@ -57,7 +58,7 @@ interface Entry { /** * Handle the request. */ - void handle(Context ctx); + void handle(Context ctx) throws IOException; /** * Return the path parameter map given the uri. diff --git a/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java b/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java new file mode 100644 index 00000000..e3cf73ce --- /dev/null +++ b/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java @@ -0,0 +1,126 @@ +package io.avaje.jex; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.http.HttpResponse; +import java.time.Duration; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.avaje.jex.jdk.TestPair; + +class StaticFileTest { + + static TestPair pair = init(); + + static TestPair init() { + + final Jex app = + Jex.create() + .staticResource(b -> defaultCP(b.httpPath("/index"))) + .staticResource(b -> defaultFile(b.httpPath("/indexFile"))) + .staticResource(b -> defaultCP(b.httpPath("/indexWild/*"))) + .staticResource(b -> defaultFile(b.httpPath("/indexWildFile/*"))) + .staticResource(b -> defaultCP(b.httpPath("/sus/*"))) + .staticResource(b -> defaultFile(b.httpPath("/susFile/*"))) + .staticResource(b -> b.httpPath("/single").resource("/logback.xml")) + .staticResource( + b -> + b.location(ResourceLocation.FILE) + .httpPath("/singleFile") + .resource("src/test/resources/logback.xml")); + + return TestPair.create(app); + } + + private static StaticContentConfig defaultFile(StaticContentConfig b) { + return b.location(ResourceLocation.FILE) + .resource("src/test/resources/public") + .directoryIndex("index.html"); + } + + private static StaticContentConfig defaultCP(StaticContentConfig b) { + return b.resource("/public").directoryIndex("index.html"); + } + + @AfterAll + static void end() { + pair.shutdown(); + } + + @Test + void testGet() { + HttpResponse res = pair.request().path("index").GET().asString(); + assertThat(res.statusCode()).isEqualTo(200); + } + + @Test + void testTraversal() { + HttpResponse res = pair.request().path("indexWild/../hmm").GET().asString(); + assertThat(res.statusCode()).isEqualTo(400); + } + + @Test + void getIndexWildCP() { + HttpResponse res = pair.request().path("indexWild/").GET().asString(); + assertThat(res.statusCode()).isEqualTo(200); + assertThat(res.headers().firstValue("Content-Type").orElseThrow()).contains("html"); + } + + @Test + void getIndex404() { + HttpResponse res = pair.request().path("index").path("index.html").GET().asString(); + assertThat(res.statusCode()).isEqualTo(404); + } + + @Test + void getDirContentCP() { + HttpResponse res = + pair.request().requestTimeout(Duration.ofHours(1)).path("sus/sus.txt").GET().asString(); + assertThat(res.statusCode()).isEqualTo(200); + assertThat(res.body()).contains("ඞ"); + } + + @Test + void getSingleFileCP() { + HttpResponse res = pair.request().path("single").GET().asString(); + assertThat(res.statusCode()).isEqualTo(200); + assertThat(res.headers().firstValue("Content-Type").orElseThrow()).contains("xml"); + } + + @Test + void getIndexFile() { + HttpResponse res = pair.request().path("indexFile").GET().asString(); + assertThat(res.statusCode()).isEqualTo(200); + assertThat(res.headers().firstValue("Content-Type").orElseThrow()).contains("html"); + } + + @Test + void getDirContentFile() { + HttpResponse res = + pair.request().requestTimeout(Duration.ofHours(1)).path("susFile/sus.txt").GET().asString(); + assertThat(res.statusCode()).isEqualTo(200); + assertThat(res.body()).contains("ඞ"); + } + + @Test + void getSingleResourceFile() { + HttpResponse res = pair.request().path("singleFile").GET().asString(); + assertThat(res.statusCode()).isEqualTo(200); + assertThat(res.headers().firstValue("Content-Type").orElseThrow()).contains("xml"); + } + + @Test + void getIndexWildFile() { + HttpResponse res = pair.request().path("indexWildFile/").GET().asString(); + assertThat(res.statusCode()).isEqualTo(200); + assertThat(res.headers().firstValue("Content-Type").orElseThrow()).contains("html"); + } + + @Test + void testFileTraversal() { + HttpResponse res = pair.request().path("indexWildFile/../traverse").GET().asString(); + assertThat(res.statusCode()).isEqualTo(400); + } +} diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java index dcc3adea..446526c1 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java @@ -19,7 +19,7 @@ static TestPair init() { final Jex app = Jex.create() .routing(routing -> routing .get("/", ctx -> { - throw new HttpResponseException(ErrorCode.FORBIDDEN); + throw new HttpResponseException(ErrorCode.FORBIDDEN.status(), ErrorCode.FORBIDDEN.message()); }) .post("/", ctx -> { throw new IllegalStateException("foo"); diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java index a40afe17..bb1d1fcf 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java @@ -32,6 +32,7 @@ static TestPair init() { if (ctx.url().contains("/two/")) { ctx.header("before-two", "set"); } + ctx.jdkExchange().getRequestURI().getPath(); chain.proceed(); }) .after(ctx -> afterAll.set("set")) diff --git a/avaje-jex/src/test/resources/public/index.html b/avaje-jex/src/test/resources/public/index.html new file mode 100644 index 00000000..41abec16 --- /dev/null +++ b/avaje-jex/src/test/resources/public/index.html @@ -0,0 +1,9 @@ + + + + Index.html + + +

    This ia my first page.

    + + \ No newline at end of file diff --git a/avaje-jex/src/test/resources/public/sus.txt b/avaje-jex/src/test/resources/public/sus.txt new file mode 100644 index 00000000..b20ae04e --- /dev/null +++ b/avaje-jex/src/test/resources/public/sus.txt @@ -0,0 +1 @@ +ඞ \ No newline at end of file From 793e5db6a1966f3e1d3236f713f860c3e9be7f0c Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 25 Nov 2024 18:33:09 -0500 Subject: [PATCH 049/250] Add OPTIONS --- .../java/io/avaje/jex/DefaultRouting.java | 40 +--- .../src/main/java/io/avaje/jex/Routing.java | 189 +++++------------- 2 files changed, 49 insertions(+), 180 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index 84e4cd7c..5529c5db 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -105,72 +105,36 @@ public Routing get(String path, ExchangeHandler handler) { return this; } - @Override - public Routing get(ExchangeHandler handler) { - get("", handler); - return this; - } - @Override public Routing post(String path, ExchangeHandler handler) { add(Type.POST, path, handler); return this; } - @Override - public Routing post(ExchangeHandler handler) { - post("", handler); - return this; - } - @Override public Routing put(String path, ExchangeHandler handler) { add(Type.PUT, path, handler); return this; } - @Override - public Routing put(ExchangeHandler handler) { - put("", handler); - return this; - } - @Override public Routing patch(String path, ExchangeHandler handler) { add(Type.PATCH, path, handler); return this; } - @Override - public Routing patch(ExchangeHandler handler) { - patch("", handler); - return this; - } - @Override public Routing delete(String path, ExchangeHandler handler) { add(Type.DELETE, path, handler); return this; } - @Override - public Routing delete(ExchangeHandler handler) { - delete("", handler); - return this; - } - @Override public Routing head(String path, ExchangeHandler handler) { add(Type.HEAD, path, handler); return this; } - @Override - public Routing head(ExchangeHandler handler) { - head("", handler); - return this; - } - @Override public Routing trace(String path, ExchangeHandler handler) { add(Type.TRACE, path, handler); @@ -178,8 +142,8 @@ public Routing trace(String path, ExchangeHandler handler) { } @Override - public Routing trace(ExchangeHandler handler) { - trace("", handler); + public Routing options(String path, ExchangeHandler handler) { + add(Type.OPTIONS, path, handler); return this; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index f0deaf3f..9fcdb7be 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -10,24 +10,20 @@ public sealed interface Routing permits DefaultRouting { - /** - * Add the routes provided by the Routing HttpService. - */ + /** Add the routes provided by the Routing HttpService. */ Routing add(Routing.HttpService routes); - /** - * Add all the routes provided by the Routing Services. - */ + /** Add all the routes provided by the Routing Services. */ Routing addAll(Collection routes); /** * Specify permittedRoles for the last added handler. - *
    {@code
        *
    -   *  routing
    -   *  .get("/customers", getHandler).withRoles(readRoles)
    -   *  .post("/customers", postHandler).withRoles(writeRoles)
    -   *  ...
    +   * 
    {@code
    +   * routing
    +   * .get("/customers", getHandler).withRoles(readRoles)
    +   * .post("/customers", postHandler).withRoles(writeRoles)
    +   * ...
        *
        * }
    * @@ -37,12 +33,12 @@ public sealed interface Routing permits DefaultRouting { /** * Specify permittedRoles for the last added handler using varargs. - *
    {@code
        *
    -   *  routing
    -   *  .get("/customers", getHandler).withRoles(ADMIN, USER)
    -   *  .post("/customers", postHandler).withRoles(ADMIN)
    -   *  ...
    +   * 
    {@code
    +   * routing
    +   * .get("/customers", getHandler).withRoles(ADMIN, USER)
    +   * .post("/customers", postHandler).withRoles(ADMIN)
    +   * ...
        *
        * }
    * @@ -50,89 +46,37 @@ public sealed interface Routing permits DefaultRouting { */ Routing withRoles(Role... permittedRoles); - /** - * Register an exception handler for the given exception type. - */ + /** Register an exception handler for the given exception type. */ Routing exception(Class exceptionClass, ExceptionHandler handler); - /** - * Add a group of route handlers with a common path prefix. - */ + /** Add a group of route handlers with a common path prefix. */ Routing path(String path, Group group); - /** - * Add a HEAD handler. - */ + /** Add a HEAD handler. */ Routing head(String path, ExchangeHandler handler); - /** - * Add a HEAD handler for "/". - */ - Routing head(ExchangeHandler handler); - - /** - * Add a GET handler. - */ + /** Add a GET handler. */ Routing get(String path, ExchangeHandler handler); - /** - * Add a GET handler for "/". - */ - Routing get(ExchangeHandler handler); - - /** - * Add a POST handler. - */ + /** Add a POST handler. */ Routing post(String path, ExchangeHandler handler); - /** - * Add a POST handler for "/". - */ - Routing post(ExchangeHandler handler); - - /** - * Add a PUT handler. - */ + /** Add a PUT handler. */ Routing put(String path, ExchangeHandler handler); - /** - * Add a PUT handler for "/". - */ - Routing put(ExchangeHandler handler); - - /** - * Add a PATCH handler. - */ + /** Add a PATCH handler. */ Routing patch(String path, ExchangeHandler handler); - /** - * Add a PATCH handler for "/". - */ - Routing patch(ExchangeHandler handler); - - /** - * Add a DELETE handler. - */ + /** Add a DELETE handler. */ Routing delete(String path, ExchangeHandler handler); - /** - * Add a DELETE handler for "/". - */ - Routing delete(ExchangeHandler handler); - - /** - * Add a TRACE handler. - */ + /** Add a TRACE handler. */ Routing trace(String path, ExchangeHandler handler); - /** - * Add a TRACE handler for "/". - */ - Routing trace(ExchangeHandler handler); + /** Add an OPTIONS handler. */ + Routing options(String path, ExchangeHandler handler); - /** - * Add a filter for all requests. - */ + /** Add a filter for all requests. */ Routing filter(HttpFilter handler); /** Add a preprocessing filter for all requests. */ @@ -155,36 +99,24 @@ default Routing after(Consumer handler) { }); } - /** - * Return all the registered handlers. - */ + /** Return all the registered handlers. */ List handlers(); - /** - * Return all the registered filters. - */ + /** Return all the registered filters. */ List filters(); - /** - * Return all the registered Exception Handlers. - */ + /** Return all the registered Exception Handlers. */ Map, ExceptionHandler> errorHandlers(); - /** - * A group of routing entries prefixed by a common path. - */ + /** A group of routing entries prefixed by a common path. */ @FunctionalInterface interface Group { - /** - * Add the group of entries with a common prefix. - */ + /** Add the group of entries with a common prefix. */ void addGroup(); } - /** - * Adds to the Routing. - */ + /** Adds to the Routing. */ @FunctionalInterface interface HttpService { @@ -196,68 +128,41 @@ interface HttpService { void add(Routing routing); } - /** - * A routing entry. - */ + /** A routing entry. */ interface Entry { - /** - * Return the type of entry. - */ + /** Return the type of entry. */ Type getType(); - /** - * Return the full path of the entry. - */ + /** Return the full path of the entry. */ String getPath(); - /** - * Return the handler. - */ + /** Return the handler. */ ExchangeHandler getHandler(); - /** - * Return the roles. - */ + /** Return the roles. */ Set getRoles(); } - /** - * The type of route entry. - */ + /** The type of route entry. */ enum Type { - /** - * Http Filter. - */ + /** Http Filter. */ FILTER, - /** - * Http GET. - */ + /** Http GET. */ GET, - /** - * Http POST. - */ + /** Http POST. */ POST, - /** - * HTTP PUT. - */ + /** HTTP PUT. */ PUT, - /** - * HTTP PATCH. - */ + /** HTTP PATCH. */ PATCH, - /** - * HTTP DELETE. - */ + /** HTTP DELETE. */ DELETE, - /** - * HTTP HEAD. - */ + /** HTTP HEAD. */ HEAD, - /** - * HTTP TRACE. - */ - TRACE//, CONNECT, OPTIONS, INVALID; + /** HTTP TRACE. */ + TRACE, + /** HTTP OPTIONS. */ + OPTIONS; } - } From 4caccf6695d26eced1853389e1bf5c0f61604a01 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 25 Nov 2024 18:35:50 -0500 Subject: [PATCH 050/250] Update NestedRoutesTest.java --- .../src/test/java/io/avaje/jex/jdk/NestedRoutesTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/NestedRoutesTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/NestedRoutesTest.java index f12a1469..84630d92 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/NestedRoutesTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/NestedRoutesTest.java @@ -17,11 +17,11 @@ static TestPair init() { .routing(routing -> routing .get("/", ctx -> ctx.text("hello")) .path("api", () -> { - routing.get(ctx -> ctx.text("apiRoot")); + routing.get("/", ctx -> ctx.text("apiRoot")); routing.get("{id}", ctx -> ctx.text("api-" + ctx.pathParam("id"))); }) .path("extra", () -> { - routing.get(ctx -> ctx.text("extraRoot")); + routing.get("/", ctx -> ctx.text("extraRoot")); routing.get("{id}", ctx -> ctx.text("extra-id-" + ctx.pathParam("id"))); routing.get("more/{id}", ctx -> ctx.text("extraMore-" + ctx.pathParam("id"))); })); From de90a91bd093626c65cb08382277435839c7eb8b Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:33:23 -0500 Subject: [PATCH 051/250] add `bodyAsInputStream` --- avaje-jex/src/main/java/io/avaje/jex/Context.java | 5 +++++ avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index a774c141..00d8cec9 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -93,6 +93,11 @@ public interface Context { */ byte[] bodyAsBytes(); + /** + * Return the request body as an inputStream. + */ + InputStream bodyAsInputStream(); + /*** * Return the request body as bean. * diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index 6eb2473e..7381317d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -181,6 +181,11 @@ public byte[] bodyAsBytes() { } } + @Override + public InputStream bodyAsInputStream() { + return exchange.getRequestBody(); + } + private String characterEncoding() { if (characterEncoding == null) { characterEncoding = mgr.requestCharset(this); From e121b7ea0d27c48889a121115902ffab3b8ba7c9 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:59:23 -0500 Subject: [PATCH 052/250] rename exception method (#77) --- avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/Routing.java | 2 +- .../test/java/io/avaje/jex/DefaultErrorHandlingTest.java | 6 +++--- .../test/java/io/avaje/jex/jdk/ExceptionManagerTest.java | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index 5529c5db..3ed7a415 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -65,7 +65,7 @@ public Routing addAll(Collection routes) { } @Override - public Routing exception(Class type, ExceptionHandler handler) { + public Routing error(Class type, ExceptionHandler handler) { exceptionHandlers.put(type, handler); return this; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index 9fcdb7be..bcccd07b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -47,7 +47,7 @@ public sealed interface Routing permits DefaultRouting { Routing withRoles(Role... permittedRoles); /** Register an exception handler for the given exception type. */ - Routing exception(Class exceptionClass, ExceptionHandler handler); + Routing error(Class exceptionClass, ExceptionHandler handler); /** Add a group of route handlers with a common path prefix. */ Routing path(String path, Group group); diff --git a/avaje-jex/src/test/java/io/avaje/jex/DefaultErrorHandlingTest.java b/avaje-jex/src/test/java/io/avaje/jex/DefaultErrorHandlingTest.java index 67dbf84d..0c59e178 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/DefaultErrorHandlingTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/DefaultErrorHandlingTest.java @@ -17,7 +17,7 @@ class DefaultErrorHandlingTest { void exception() { Routing router = new DefaultRouting(); - router.exception(RuntimeException.class, rt); + router.error(RuntimeException.class, rt); var handling = new ExceptionManager(router.errorHandlers()); @@ -29,8 +29,8 @@ void exception() { @Test void exception_expect_highestMatch() { Routing router = new DefaultRouting(); - router.exception(RuntimeException.class, rt); - router.exception(IllegalStateException.class, ise); + router.error(RuntimeException.class, rt); + router.error(IllegalStateException.class, ise); var handling = new ExceptionManager(router.errorHandlers()); diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java index 446526c1..22e3acf5 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java @@ -30,8 +30,8 @@ static TestPair init() { .get("/fiveHundred", ctx -> { throw new IllegalArgumentException("Bar"); }) - .exception(NullPointerException.class, (exception, ctx) -> ctx.text("npe")) - .exception(IllegalStateException.class, (exception, ctx) -> ctx.status(222).text("Handled IllegalStateException|" + exception.getMessage()))); + .error(NullPointerException.class, (exception, ctx) -> ctx.text("npe")) + .error(IllegalStateException.class, (exception, ctx) -> ctx.status(222).text("Handled IllegalStateException|" + exception.getMessage()))); return TestPair.create(app); } From 8975701fbba7437f9cbad0fe021d3c12931d9667 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 25 Nov 2024 20:31:10 -0500 Subject: [PATCH 053/250] handle no response (#78) --- .../src/main/java/io/avaje/jex/jdk/JdkContext.java | 2 +- .../src/main/java/io/avaje/jex/jdk/RoutingFilter.java | 10 ++++++++++ .../src/test/java/io/avaje/jex/jdk/FilterTest.java | 10 ++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index 7381317d..2192ecb7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -48,7 +48,7 @@ class JdkContext implements Context, SpiContext { private Map> formParams; private Map> queryParams; private Map cookieMap; - private int statusCode; + private int statusCode = 200; private String characterEncoding; JdkContext( diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java index 04dd04a6..d93c1f53 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java @@ -1,5 +1,6 @@ package io.avaje.jex.jdk; +import java.io.IOException; import java.util.Map; import java.util.Set; @@ -42,6 +43,7 @@ public void doFilter(HttpExchange exchange, Filter.Chain chain) { processNoRoute(ctx, uri, routeType); exchange.setAttribute("JdkContext", ctx); chain.doFilter(exchange); + handleNoResponse(exchange); } catch (Exception e) { handleException(ctx, e); } finally { @@ -59,6 +61,7 @@ public void doFilter(HttpExchange exchange, Filter.Chain chain) { ExchangeHandler handlerConsumer = route::handle; exchange.setAttribute("SpiRoutes.Entry.Handler", handlerConsumer); chain.doFilter(exchange); + handleNoResponse(exchange); } catch (Exception e) { handleException(ctx, e); } @@ -69,6 +72,13 @@ public void doFilter(HttpExchange exchange, Filter.Chain chain) { } } + private void handleNoResponse(HttpExchange exchange) throws IOException { + + if (exchange.getResponseCode() == -1) { + exchange.sendResponseHeaders(204, -1); + } + } + private void handleException(SpiContext ctx, Exception e) { mgr.handleException(ctx, e); } diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java index bb1d1fcf..afce6f70 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java @@ -23,6 +23,7 @@ static TestPair init() { routing -> routing .get("/", ctx -> ctx.text("roo")) + .get("/noResponse", ctx -> {}) .get("/one", ctx -> ctx.text("one")) .get("/two", ctx -> ctx.text("two")) .get("/two/{id}", ctx -> ctx.text("two-id")) @@ -77,6 +78,15 @@ void get() { assertNoBeforeAfterTwo(res); } + @Test + void getNoResponse() { + clearAfter(); + HttpResponse res = pair.request().path("noResponse").GET().asString(); + assertThat(res.statusCode()).isEqualTo(204); + assertHasBeforeAfterAll(res); + assertNoBeforeAfterTwo(res); + } + @Test void get_two_expect_extraFilters() { clearAfter(); From 073653793a1840d12f0b0a5b2985218a5cd12061 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 26 Nov 2024 01:03:22 -0500 Subject: [PATCH 054/250] Simplify JsonService interface (#79) * simplify JsonService interface * Update JsonService.java --- .../src/main/java/io/avaje/jex/Context.java | 2 +- .../io/avaje/jex/core/CoreServiceManager.java | 19 ++++---- .../io/avaje/jex/core/ExceptionManager.java | 1 - .../avaje/jex/{spi => core}/HeaderKeys.java | 2 +- .../io/avaje/jex/core/SpiServiceManager.java | 10 +++-- .../jex/core/json/JacksonJsonService.java | 43 ++++++++++--------- .../avaje/jex/core/json/JsonbJsonService.java | 18 ++++---- .../io/avaje/jex/jdk/CtxServiceManager.java | 17 ++++---- .../java/io/avaje/jex/jdk/JdkContext.java | 11 +++-- .../java/io/avaje/jex/spi/JexExtension.java | 6 +++ .../java/io/avaje/jex/spi/JsonService.java | 39 +++++++++++++---- 11 files changed, 101 insertions(+), 67 deletions(-) rename avaje-jex/src/main/java/io/avaje/jex/{spi => core}/HeaderKeys.java (96%) diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index 00d8cec9..399448c4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -14,9 +14,9 @@ import com.sun.net.httpserver.HttpExchange; +import io.avaje.jex.core.HeaderKeys; import io.avaje.jex.security.BasicAuthCredentials; import io.avaje.jex.security.Role; -import io.avaje.jex.spi.HeaderKeys; /** * Provides access to functions for handling the request and response. diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java index 74a3082b..503ef50b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java @@ -1,5 +1,7 @@ package io.avaje.jex.core; +import java.io.InputStream; +import java.io.OutputStream; import java.io.UncheckedIOException; import java.io.UnsupportedEncodingException; import java.lang.System.Logger.Level; @@ -18,7 +20,6 @@ import io.avaje.jex.Routing; import io.avaje.jex.core.json.JacksonJsonService; import io.avaje.jex.core.json.JsonbJsonService; -import io.avaje.jex.spi.HeaderKeys; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.SpiContext; import io.avaje.jex.spi.TemplateRender; @@ -47,26 +48,26 @@ public static SpiServiceManager create(Jex jex) { } @Override - public T jsonRead(Class clazz, SpiContext ctx) { - return jsonService.jsonRead(clazz, ctx); + public T jsonRead(Class clazz, InputStream is) { + return jsonService.jsonRead(clazz, is); } @Override - public void jsonWrite(Object bean, SpiContext ctx) { - jsonService.jsonWrite(bean, ctx); + public void jsonWrite(Object bean, OutputStream os) { + jsonService.jsonWrite(bean, os); } @Override - public void jsonWriteStream(Stream stream, SpiContext ctx) { + public void jsonWriteStream(Stream stream, OutputStream os) { try (stream) { - jsonService.jsonWriteStream(stream.iterator(), ctx); + jsonService.jsonWriteStream(stream.iterator(), os); } } @Override - public void jsonWriteStream(Iterator iterator, SpiContext ctx) { + public void jsonWriteStream(Iterator iterator, OutputStream os) { try { - jsonService.jsonWriteStream(iterator, ctx); + jsonService.jsonWriteStream(iterator, os); } finally { maybeClose(iterator); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index ddafb842..f70f0b57 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -9,7 +9,6 @@ import io.avaje.jex.http.ErrorCode; import io.avaje.jex.http.HttpResponseException; import io.avaje.jex.http.InternalServerErrorException; -import io.avaje.jex.spi.HeaderKeys; import io.avaje.jex.spi.SpiContext; public final class ExceptionManager { diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/HeaderKeys.java b/avaje-jex/src/main/java/io/avaje/jex/core/HeaderKeys.java similarity index 96% rename from avaje-jex/src/main/java/io/avaje/jex/spi/HeaderKeys.java rename to avaje-jex/src/main/java/io/avaje/jex/core/HeaderKeys.java index c7eaaf71..c44b8c0e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/HeaderKeys.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/HeaderKeys.java @@ -1,4 +1,4 @@ -package io.avaje.jex.spi; +package io.avaje.jex.core; public class HeaderKeys { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java index b6b4822c..3586ce15 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java @@ -5,6 +5,8 @@ import io.avaje.jex.jdk.CtxServiceManager; import io.avaje.jex.spi.SpiContext; +import java.io.InputStream; +import java.io.OutputStream; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -18,22 +20,22 @@ public sealed interface SpiServiceManager permits CoreServiceManager, CtxService /** * Read and return the type from json request content. */ - T jsonRead(Class clazz, SpiContext ctx); + T jsonRead(Class clazz, InputStream ctx); /** * Write as json to response content. */ - void jsonWrite(Object bean, SpiContext ctx); + void jsonWrite(Object bean, OutputStream ctx); /** * Write as json stream to response content. */ - void jsonWriteStream(Stream stream, SpiContext ctx); + void jsonWriteStream(Stream stream, OutputStream ctx); /** * Write as json stream to response content. */ - void jsonWriteStream(Iterator iterator, SpiContext ctx); + void jsonWriteStream(Iterator iterator, OutputStream ctx); /** * Maybe close if iterator is a AutoClosable. diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java index 5b3f8d20..dfb05387 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java @@ -1,16 +1,17 @@ package io.avaje.jex.core.json; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.avaje.jex.spi.JsonService; -import io.avaje.jex.spi.SpiContext; - import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.io.UncheckedIOException; import java.util.Iterator; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.avaje.jex.spi.JsonService; + public class JacksonJsonService implements JsonService { private final ObjectMapper mapper; @@ -25,22 +26,18 @@ public JacksonJsonService(ObjectMapper mapper) { } @Override - public T jsonRead(Class clazz, SpiContext ctx) { + public T jsonRead(Class clazz, InputStream is) { try { - // TODO: Handle gzipped content // read direct - return mapper.readValue(ctx.inputStream(), clazz); - //return mapper.readValue(ctx.bodyAsBytes(), clazz); + return mapper.readValue(is, clazz); } catch (IOException e) { throw new UncheckedIOException(e); } } @Override - public void jsonWrite(Object bean, SpiContext ctx) { + public void jsonWrite(Object bean, OutputStream os) { try { - // gzip compression etc ? - OutputStream os = ctx.outputStream(); try (JsonGenerator generator = mapper.createGenerator(os)) { // only flush to underlying OutputStream on success generator.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); @@ -57,19 +54,14 @@ public void jsonWrite(Object bean, SpiContext ctx) { } @Override - public void jsonWriteStream(Iterator iterator, SpiContext ctx) { + public void jsonWriteStream(Iterator iterator, OutputStream os) { final JsonGenerator generator; try { - generator = mapper.createGenerator(ctx.outputStream()); + generator = mapper.createGenerator(os); generator.setPrettyPrinter(null); try { while (iterator.hasNext()) { - try { - mapper.writeValue(generator, iterator.next()); - generator.writeRaw('\n'); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + write(iterator, generator); } } finally { generator.flush(); @@ -79,4 +71,13 @@ public void jsonWriteStream(Iterator iterator, SpiContext ctx) { throw new UncheckedIOException(e); } } + + private void write(Iterator iterator, final JsonGenerator generator) { + try { + mapper.writeValue(generator, iterator.next()); + generator.writeRaw('\n'); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java index 77eaca95..cf09975d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java @@ -6,6 +6,8 @@ import io.avaje.jsonb.JsonWriter; import io.avaje.jsonb.Jsonb; +import java.io.InputStream; +import java.io.OutputStream; import java.util.Iterator; /** @@ -30,20 +32,20 @@ public JsonbJsonService(Jsonb jsonb) { } @Override - public T jsonRead(Class clazz, SpiContext ctx) { - // TODO: Handle gzipped content - return jsonb.type(clazz).fromJson(ctx.inputStream()); + public T jsonRead(Class clazz, InputStream is) { + + return jsonb.type(clazz).fromJson(is); } @Override - public void jsonWrite(Object bean, SpiContext ctx) { - // gzip compression etc ? - jsonb.toJson(bean, ctx.outputStream()); + public void jsonWrite(Object bean, OutputStream os) { + + jsonb.toJson(bean, os); } @Override - public void jsonWriteStream(Iterator iterator, SpiContext ctx) { - try (JsonWriter writer = jsonb.writer(ctx.outputStream())) { + public void jsonWriteStream(Iterator iterator, OutputStream os) { + try (JsonWriter writer = jsonb.writer(os)) { writer.pretty(false); if (iterator.hasNext()) { T first = iterator.next(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java index 93e93c33..522578e1 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java @@ -5,6 +5,7 @@ import io.avaje.jex.core.SpiServiceManager; import io.avaje.jex.spi.SpiContext; +import java.io.InputStream; import java.io.OutputStream; import java.util.Iterator; import java.util.List; @@ -41,23 +42,23 @@ public String contextPath() { } @Override - public T jsonRead(Class clazz, SpiContext ctx) { - return delegate.jsonRead(clazz, ctx); + public T jsonRead(Class clazz, InputStream is) { + return delegate.jsonRead(clazz, is); } @Override - public void jsonWrite(Object bean, SpiContext ctx) { - delegate.jsonWrite(bean, ctx); + public void jsonWrite(Object bean, OutputStream os) { + delegate.jsonWrite(bean, os); } @Override - public void jsonWriteStream(Stream stream, SpiContext ctx) { - delegate.jsonWriteStream(stream, ctx); + public void jsonWriteStream(Stream stream, OutputStream os) { + delegate.jsonWriteStream(stream, os); } @Override - public void jsonWriteStream(Iterator iterator, SpiContext ctx) { - delegate.jsonWriteStream(iterator, ctx); + public void jsonWriteStream(Iterator iterator, OutputStream os) { + delegate.jsonWriteStream(iterator, os); } @Override diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index 2192ecb7..05597c68 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -25,12 +25,11 @@ import io.avaje.jex.Context; import io.avaje.jex.Routing; +import io.avaje.jex.core.HeaderKeys; import io.avaje.jex.http.ErrorCode; -import io.avaje.jex.http.HttpResponseException; import io.avaje.jex.http.RedirectException; import io.avaje.jex.security.BasicAuthCredentials; import io.avaje.jex.security.Role; -import io.avaje.jex.spi.HeaderKeys; import io.avaje.jex.spi.SpiContext; class JdkContext implements Context, SpiContext { @@ -169,7 +168,7 @@ public void performRedirect() { @Override public T bodyAsClass(Class beanType) { - return mgr.jsonRead(beanType, this); + return mgr.jsonRead(beanType, inputStream()); } @Override @@ -316,21 +315,21 @@ public int status() { @Override public Context json(Object bean) { contentType(APPLICATION_JSON); - mgr.jsonWrite(bean, this); + mgr.jsonWrite(bean, outputStream()); return this; } @Override public Context jsonStream(Stream stream) { contentType(APPLICATION_X_JSON_STREAM); - mgr.jsonWriteStream(stream, this); + mgr.jsonWriteStream(stream, outputStream()); return this; } @Override public Context jsonStream(Iterator iterator) { contentType(APPLICATION_X_JSON_STREAM); - mgr.jsonWriteStream(iterator, this); + mgr.jsonWriteStream(iterator, outputStream()); return this; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java index a0eb3947..bc179508 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java @@ -2,5 +2,11 @@ import io.avaje.spi.Service; +/** + * Extension point for all Jex SPI interfaces + * + *

    All types that implement this interface must be registered as an entry in {@code + * META-INF/services/io.avaje.jex.spi.JexExtension } for it to be loaded by Jex + */ @Service public sealed interface JexExtension permits JsonService, TemplateRender {} diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java index 91834abb..d068622d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java @@ -1,25 +1,48 @@ package io.avaje.jex.spi; +import java.io.InputStream; +import java.io.OutputStream; import java.util.Iterator; /** - * HttpService used to convert request/response bodies to beans. + * **JsonService** + * + *

    A service responsible for handling JSON-based request and response bodies. + * + * @see {@link JexExtension} for SPI registration details. */ public non-sealed interface JsonService extends JexExtension { /** - * Read the request body as a bean and return the bean. + * **Reads JSON from an InputStream** + * + *

    Reads a JSON-formatted input stream and deserializes it into a Java object of the specified + * type. + * + * @param type the Class object of the desired type + * @param is the input stream containing the JSON data + * @return the deserialized object */ - T jsonRead(Class type, SpiContext ctx); + T jsonRead(Class type, InputStream is); /** - * Write the bean as JSON response content. + * **Writes a Java Object as JSON to an OutputStream** + * + *

    Serializes a Java object into JSON format and writes the resulting JSON to the specified + * output stream. + * + * @param bean the Java object to be serialized + * @param os the output stream to write the JSON data to */ - void jsonWrite(Object bean, SpiContext ctx); + void jsonWrite(Object bean, OutputStream os); /** - * Write the beans as {@literal x-json-stream } JSON with new line delimiter. + * Serializes a stream of Java objects into a JSON-Stream format, using the {@code x-json-stream} + * media type. Each object in the stream is serialized as a separate JSON object, and the objects + * are separated by newlines. + * + * @param iterator the stream of objects to be serialized + * @param os the output stream to write the JSON-Stream data to */ - void jsonWriteStream(Iterator stream, SpiContext ctx); - + void jsonWriteStream(Iterator iterator, OutputStream os); } From c3e6297cc631dea8265efca93eb3a389bd958595 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 26 Nov 2024 20:16:35 +1300 Subject: [PATCH 055/250] Update test for AutoCloseIterator (#82) --- avaje-jex/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java index 36ce4986..01528d66 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java @@ -5,7 +5,7 @@ public class AutoCloseIterator implements Iterator, AutoCloseable { private final Iterator it; - private boolean closed; + private volatile boolean closed; public AutoCloseIterator(Iterator it) { this.it = it; From 43950bdc0dd666ff63142bf53feadad6a2cacf1c Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 26 Nov 2024 21:50:10 +1300 Subject: [PATCH 056/250] =?UTF-8?q?Use=20explicit=20close=20rather=20than?= =?UTF-8?q?=20try-with-resources=20for=20AutoClosable=20it=E2=80=A6=20(#83?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use explicit close rather than try-with-resources for AutoClosable iterator This way the close() is executed before the catch block rather than after * JsonTest - add a wait? * FilterTest - add a wait? * FilterTest - wait time on tests with races --- .../java/io/avaje/jex/core/CoreServiceManager.java | 4 ++-- .../java/io/avaje/jex/jdk/AutoCloseIterator.java | 7 ++++--- .../src/test/java/io/avaje/jex/jdk/FilterTest.java | 12 +++++++----- .../src/test/java/io/avaje/jex/jdk/JsonTest.java | 3 +++ 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java index 503ef50b..c57f8048 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java @@ -76,8 +76,8 @@ public void jsonWriteStream(Iterator iterator, OutputStream os) { @Override public void maybeClose(Object iterator) { if (iterator instanceof AutoCloseable closeable) { - try (closeable) { - // nothing + try { + closeable.close(); } catch (Exception e) { throw new RuntimeException("Error closing iterator " + iterator, e); } diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java index 01528d66..74541670 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java @@ -1,11 +1,12 @@ package io.avaje.jex.jdk; import java.util.Iterator; +import java.util.concurrent.atomic.AtomicBoolean; public class AutoCloseIterator implements Iterator, AutoCloseable { private final Iterator it; - private volatile boolean closed; + private final AtomicBoolean closed = new AtomicBoolean(false); public AutoCloseIterator(Iterator it) { this.it = it; @@ -23,10 +24,10 @@ public E next() { @Override public void close() { - closed = true; + closed.set(true); } public boolean isClosed() { - return closed; + return closed.get(); } } diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java index afce6f70..74d82a70 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java @@ -6,15 +6,17 @@ import java.net.http.HttpHeaders; import java.net.http.HttpResponse; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; import static org.assertj.core.api.Assertions.assertThat; class FilterTest { - static TestPair pair = init(); - static AtomicReference afterAll = new AtomicReference<>(); - static AtomicReference afterTwo = new AtomicReference<>(); + static final TestPair pair = init(); + static final AtomicReference afterAll = new AtomicReference<>(); + static final AtomicReference afterTwo = new AtomicReference<>(); static TestPair init() { final Jex app = @@ -33,7 +35,7 @@ static TestPair init() { if (ctx.url().contains("/two/")) { ctx.header("before-two", "set"); } - ctx.jdkExchange().getRequestURI().getPath(); + // ctx.jdkExchange().getRequestURI().getPath(); chain.proceed(); }) .after(ctx -> afterAll.set("set")) @@ -41,7 +43,6 @@ static TestPair init() { (ctx, chain) -> { chain.proceed(); if (ctx.url().contains("/two/")) { - afterTwo.set("set"); } }) @@ -74,6 +75,7 @@ void get() { clearAfter(); res = pair.request().path("two").GET().asString(); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(10)); assertHasBeforeAfterAll(res); assertNoBeforeAfterTwo(res); } diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java index e52c8f14..1ab2dcab 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java @@ -7,6 +7,8 @@ import java.net.http.HttpHeaders; import java.net.http.HttpResponse; import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; import java.util.stream.Stream; import org.junit.jupiter.api.AfterAll; @@ -69,6 +71,7 @@ void stream_viaIterator() { // expect client gets the expected stream of beans assertCollectedStream(beanStream); // assert AutoCloseable iterator on the server-side was closed + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(10)); assertThat(ITERATOR.isClosed()).isTrue(); } From 81252d6a1b08910c2ad8cb639c3ae052bc1fab8b Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 27 Nov 2024 03:48:21 +1300 Subject: [PATCH 057/250] Tidy core - final classes and whitespace (#80) --- .../src/main/java/io/avaje/jex/core/CoreServiceLoader.java | 2 -- .../src/main/java/io/avaje/jex/core/CoreServiceManager.java | 2 +- .../src/main/java/io/avaje/jex/core/ExceptionManager.java | 1 - avaje-jex/src/main/java/io/avaje/jex/core/HeaderKeys.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java | 2 +- .../src/main/java/io/avaje/jex/core/TemplateManager.java | 2 +- .../main/java/io/avaje/jex/core/json/JacksonJsonService.java | 2 +- .../main/java/io/avaje/jex/core/json/JsonbJsonService.java | 5 +---- 8 files changed, 6 insertions(+), 12 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java index 99ac7348..386df653 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java @@ -19,9 +19,7 @@ public final class CoreServiceLoader { CoreServiceLoader() { JsonService spiJsonService = null; - for (var spi : ServiceLoader.load(JexExtension.class)) { - switch (spi) { case JsonService s -> spiJsonService = s; case TemplateRender r -> renders.add(r); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java index c57f8048..3ddd1e83 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java @@ -41,7 +41,7 @@ public static SpiServiceManager create(Jex jex) { return new Builder(jex).build(); } - CoreServiceManager(JsonService jsonService, ExceptionManager manager, TemplateManager templateManager) { + CoreServiceManager(JsonService jsonService, ExceptionManager manager, TemplateManager templateManager) { this.jsonService = jsonService; this.exceptionHandler = manager; this.templateManager = templateManager; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index f70f0b57..68831aa6 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -53,7 +53,6 @@ private void unhandledException(SpiContext ctx, Exception e) { } private void defaultHandling(SpiContext ctx, HttpResponseException exception) { - ctx.status(exception.getStatus()); if (exception.getStatus() == ErrorCode.REDIRECT.status()) { ctx.performRedirect(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/HeaderKeys.java b/avaje-jex/src/main/java/io/avaje/jex/core/HeaderKeys.java index c44b8c0e..3d07b4d4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/HeaderKeys.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/HeaderKeys.java @@ -1,6 +1,6 @@ package io.avaje.jex.core; -public class HeaderKeys { +public final class HeaderKeys { private HeaderKeys() {} diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java b/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java index 6a84c555..67835081 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java @@ -9,7 +9,7 @@ * Health plugin with liveness and readiness support based on * the application lifecycle support. */ -public class HealthPlugin implements JexPlugin { +public final class HealthPlugin implements JexPlugin { private AppLifecycle lifecycle; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java index 5bd38274..65e1ec44 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java @@ -22,7 +22,7 @@ public final class TemplateManager { */ public void register(Map source) { map.putAll(source); - map.values().stream().forEach(templateRender -> renderTypes.add(templateRender.getClass())); + map.values().forEach(templateRender -> renderTypes.add(templateRender.getClass())); } /** diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java index dfb05387..f7f6003c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java @@ -12,7 +12,7 @@ import io.avaje.jex.spi.JsonService; -public class JacksonJsonService implements JsonService { +public final class JacksonJsonService implements JsonService { private final ObjectMapper mapper; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java index cf09975d..7eae6747 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java @@ -1,7 +1,6 @@ package io.avaje.jex.core.json; import io.avaje.jex.spi.JsonService; -import io.avaje.jex.spi.SpiContext; import io.avaje.jsonb.JsonType; import io.avaje.jsonb.JsonWriter; import io.avaje.jsonb.Jsonb; @@ -13,7 +12,7 @@ /** * Provides JsonService using avaje-jsonb. */ -public class JsonbJsonService implements JsonService { +public final class JsonbJsonService implements JsonService { private final Jsonb jsonb; @@ -33,13 +32,11 @@ public JsonbJsonService(Jsonb jsonb) { @Override public T jsonRead(Class clazz, InputStream is) { - return jsonb.type(clazz).fromJson(is); } @Override public void jsonWrite(Object bean, OutputStream os) { - jsonb.toJson(bean, os); } From 1048e1f6e160ad1974119b7d8f04a16587d772f6 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 27 Nov 2024 05:45:40 +1300 Subject: [PATCH 058/250] Tidy jdk package - final classes, getFirst, isEmpty, whitespace (#81) * Tidy jdk package - final classes, getFirst, isEmpty, whitespace * Revert RoutingFilter use of try-with-resources --------- Co-authored-by: Josiah Noel <32279667+SentryMan@users.noreply.github.com> --- .../main/java/io/avaje/jex/jdk/BaseHandler.java | 3 +-- .../java/io/avaje/jex/jdk/BufferedOutStream.java | 2 +- .../main/java/io/avaje/jex/jdk/CookieParser.java | 8 ++++---- .../src/main/java/io/avaje/jex/jdk/JdkContext.java | 14 +++++--------- .../src/main/java/io/avaje/jex/jdk/JdkFilter.java | 2 -- .../main/java/io/avaje/jex/jdk/JdkJexServer.java | 2 +- .../main/java/io/avaje/jex/jdk/JdkServerStart.java | 6 ++---- .../main/java/io/avaje/jex/jdk/RoutingFilter.java | 3 --- 8 files changed, 14 insertions(+), 26 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java index ca744b4a..34fc3db2 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java @@ -9,7 +9,7 @@ import io.avaje.jex.Routing.Type; import io.avaje.jex.routes.SpiRoutes; -class BaseHandler implements HttpHandler { +final class BaseHandler implements HttpHandler { private final SpiRoutes routes; @@ -23,7 +23,6 @@ void waitForIdle(long maxSeconds) { @Override public void handle(HttpExchange exchange) throws IOException { - JdkContext ctx = (JdkContext) exchange.getAttribute("JdkContext"); ExchangeHandler handlerConsumer = (ExchangeHandler) exchange.getAttribute("SpiRoutes.Entry.Handler"); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java index 0387eddd..47b1c601 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java @@ -6,7 +6,7 @@ import com.sun.net.httpserver.HttpExchange; -class BufferedOutStream extends OutputStream { +final class BufferedOutStream extends OutputStream { private static final long MAX = Long.getLong("jex.outputBuffer.max", 1024); private static final int INITIAL = diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/CookieParser.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/CookieParser.java index 62115541..aa2e3bdd 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/CookieParser.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/CookieParser.java @@ -10,7 +10,7 @@ /** * Parse cookies based on RFC6265 skipping parameters. */ -class CookieParser { +final class CookieParser { private static final String QUOTE = "\""; private static final char[] QUOTE_CHARS = QUOTE.toCharArray(); @@ -30,7 +30,7 @@ private CookieParser() { * * @param rawHeader a value of '{@code Cookie:}' header. */ - public static Map parse(String rawHeader) { + static Map parse(String rawHeader) { if (rawHeader == null) { return emptyMap(); } @@ -102,7 +102,7 @@ static List tokenize(char separator, String text) { } token.append(ch); } else if (ch == separator) { - if (token.length() > 0) { + if (!token.isEmpty()) { result.add(token.toString()); } token.setLength(0); @@ -117,7 +117,7 @@ static List tokenize(char separator, String text) { token.append(ch); } } - if (token.length() > 0) { + if (!token.isEmpty()) { result.add(token.toString()); } return result; diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index 05597c68..36c3efef 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -32,7 +32,7 @@ import io.avaje.jex.security.Role; import io.avaje.jex.spi.SpiContext; -class JdkContext implements Context, SpiContext { +final class JdkContext implements Context, SpiContext { private static final String UTF8 = "UTF8"; private static final int SC_MOVED_TEMPORARILY = 302; @@ -215,7 +215,7 @@ public String responseHeader(String key) { private String header(Headers headers, String name) { final List values = headers.get(name); - return (values == null || values.isEmpty()) ? null : values.get(0); + return (values == null || values.isEmpty()) ? null : values.getFirst(); } @Override @@ -237,7 +237,7 @@ public String pathParam(String name) { @Override public String queryParam(String name) { final List vals = queryParams(name); - return vals == null || vals.isEmpty() ? null : vals.get(0); + return vals == null || vals.isEmpty() ? null : vals.getFirst(); } private Map> queryParams() { @@ -263,7 +263,7 @@ public Map queryParamMap() { for (Map.Entry> entry : map.entrySet()) { final List value = entry.getValue(); if (value != null && !value.isEmpty()) { - single.put(entry.getKey(), value.get(0)); + single.put(entry.getKey(), value.getFirst()); } } return single; @@ -347,17 +347,14 @@ public Context html(String content) { @Override public Context write(String content) { - write(content.getBytes(StandardCharsets.UTF_8)); return this; } @Override public Context write(byte[] bytes) { - try (var os = exchange.getResponseBody()) { exchange.sendResponseHeaders(statusCode(), bytes.length == 0 ? -1 : bytes.length); - os.write(bytes); os.flush(); } catch (IOException e) { @@ -368,7 +365,6 @@ public Context write(byte[] bytes) { @Override public Context write(InputStream is) { - try (is; var os = outputStream()) { is.transferTo(os); } catch (IOException e) { @@ -393,7 +389,7 @@ public Map headerMap() { for (Map.Entry> entry : exchange.getRequestHeaders().entrySet()) { final List value = entry.getValue(); if (!value.isEmpty()) { - map.put(entry.getKey(), value.get(0)); + map.put(entry.getKey(), value.getFirst()); } } return map; diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkFilter.java index 8305f1b6..152702c7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkFilter.java @@ -17,9 +17,7 @@ public JdkFilter(HttpFilter handler) { @Override public void doFilter(HttpExchange exchange, Chain chain) throws IOException { - var ctx = (JdkContext) exchange.getAttribute("JdkContext"); - handler.filter(ctx, () -> chain.doFilter(exchange)); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java index d07d019c..3b98da0a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java @@ -7,7 +7,7 @@ import java.lang.System.Logger.Level; -class JdkJexServer implements Jex.Server { +final class JdkJexServer implements Jex.Server { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index 9b0e11f0..de5fbc11 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -16,18 +16,16 @@ import io.avaje.jex.core.SpiServiceManager; import io.avaje.jex.routes.SpiRoutes; -public class JdkServerStart { +public final class JdkServerStart { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceManager) { - try { - final HttpServer server; - var port = new InetSocketAddress(jex.config().port()); final var sslContext = jex.config().sslContext(); + final HttpServer server; final String scheme; if (sslContext != null) { var httpsServer = HttpsServer.create(port, 0); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java index d93c1f53..dd01ba7f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java @@ -10,7 +10,6 @@ import io.avaje.jex.ExchangeHandler; import io.avaje.jex.Routing; import io.avaje.jex.Routing.Type; -import io.avaje.jex.http.HttpResponseException; import io.avaje.jex.http.NotFoundException; import io.avaje.jex.routes.SpiRoutes; import io.avaje.jex.spi.SpiContext; @@ -31,7 +30,6 @@ void waitForIdle(long maxSeconds) { @Override public void doFilter(HttpExchange exchange, Filter.Chain chain) { - final String uri = exchange.getRequestURI().getPath(); final Routing.Type routeType = mgr.lookupRoutingType(exchange.getRequestMethod()); final SpiRoutes.Entry route = routes.match(routeType, uri); @@ -73,7 +71,6 @@ public void doFilter(HttpExchange exchange, Filter.Chain chain) { } private void handleNoResponse(HttpExchange exchange) throws IOException { - if (exchange.getResponseCode() == -1) { exchange.sendResponseHeaders(204, -1); } From e73397ed820044d8ae1537c342452ff0d91125a6 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:24:33 -0500 Subject: [PATCH 059/250] Add Compression (#85) * add compression * Update CompressionTest.java * Update CompressedOutputStream.java * re add jar loader * Revert "re add jar loader" This reverts commit be382f378a8ec6d0805a2ac48e69795bdbb7e8b7. * try public? * case --- .../io/avaje/jex/AbstractStaticHandler.java | 2 +- .../io/avaje/jex/ClassResourceLoader.java | 4 +- .../src/main/java/io/avaje/jex/Context.java | 2 +- .../main/java/io/avaje/jex/DJexConfig.java | 34 +- .../io/avaje/jex/DefaultResourceLoader.java | 9 +- .../src/main/java/io/avaje/jex/JexConfig.java | 15 +- .../io/avaje/jex/PathResourceHandler.java | 84 - ...r.java => StaticClassResourceHandler.java} | 12 +- .../java/io/avaje/jex/StaticFileHandler.java | 5 +- .../jex/StaticResourceHandlerBuilder.java | 75 +- .../compression/CompressedOutputStream.java | 92 ++ .../jex/compression/CompressionConfig.java | 68 + .../io/avaje/jex/compression/Compressor.java | 26 + .../avaje/jex/compression/GzipCompressor.java | 40 + .../java/io/avaje/jex/core/HeaderKeys.java | 1 + .../java/io/avaje/jex/jdk/JdkContext.java | 25 +- .../java/io/avaje/jex/jdk/JdkServerStart.java | 5 +- .../java/io/avaje/jex/jdk/RoutingFilter.java | 11 +- avaje-jex/src/main/java/module-info.java | 1 + .../jex/compression/CompressionTest.java | 66 + .../java/io/avaje/jex/jdk/FilterTest.java | 1 - avaje-jex/src/test/resources/64KB.json | 1381 +++++++++++++++++ 22 files changed, 1767 insertions(+), 192 deletions(-) delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/PathResourceHandler.java rename avaje-jex/src/main/java/io/avaje/jex/{JarResourceHandler.java => StaticClassResourceHandler.java} (86%) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/compression/Compressor.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/compression/GzipCompressor.java create mode 100644 avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java create mode 100644 avaje-jex/src/test/resources/64KB.json diff --git a/avaje-jex/src/main/java/io/avaje/jex/AbstractStaticHandler.java b/avaje-jex/src/main/java/io/avaje/jex/AbstractStaticHandler.java index 3e8c8861..e57f69f9 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/AbstractStaticHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/AbstractStaticHandler.java @@ -12,7 +12,7 @@ import io.avaje.jex.http.NotFoundException; abstract sealed class AbstractStaticHandler implements ExchangeHandler - permits StaticFileHandler, PathResourceHandler, JarResourceHandler { + permits StaticFileHandler, StaticClassResourceHandler { protected final Map mimeTypes; protected final String filesystemRoot; diff --git a/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java index 904866a9..fe6e4c11 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java @@ -19,7 +19,7 @@ static ClassResourceLoader fromClass(Class clazz) { } /** Return the URL for the given resource or return null if it cannot be found. */ - URL getResource(String resourcePath); + URL loadResource(String resourcePath); - InputStream getResourceAsStream(String resourcePath); + InputStream loadResourceAsStream(String resourcePath); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index 399448c4..cd6c85ec 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -202,7 +202,7 @@ default List formParams(String key) { /** * Return the underlying JDK {@link HttpExchange} object backing the context */ - HttpExchange jdkExchange(); + HttpExchange exchange(); /** * Return the request scheme. diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index f9e3fd5c..f67c624f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -2,10 +2,15 @@ import java.util.HashMap; import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import java.util.function.Consumer; import javax.net.ssl.SSLContext; +import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; @@ -16,12 +21,13 @@ final class DJexConfig implements JexConfig { private String contextPath = "/"; private boolean health = true; private boolean ignoreTrailingSlashes = true; - private ThreadFactory factory; + private Executor executor; private boolean preCompressStaticFiles; private JsonService jsonService; private final Map renderers = new HashMap<>(); private SSLContext sslContext; + private final CompressionConfig compression= new CompressionConfig(); @Override public JexConfig port(int port) { @@ -72,17 +78,18 @@ public JexConfig renderer(String extension, TemplateRender renderer) { } @Override - public ThreadFactory threadFactory() { - if (factory == null) { - factory = - Thread.ofVirtual().name("avaje-jex-http-", 0).factory(); + public Executor executor() { + if (executor == null) { + executor = + Executors.newThreadPerTaskExecutor( + Thread.ofVirtual().name("avaje-jex-http-", 0).factory()); } - return factory; + return executor; } @Override - public JexConfig threadFactory(ThreadFactory factory) { - this.factory = factory; + public JexConfig executor(Executor factory) { + this.executor = factory; return this; } @@ -136,4 +143,15 @@ public JexConfig sslContext(SSLContext ssl) { this.sslContext = ssl; return this; } + + @Override + public JexConfig compression(Consumer consumer) { + consumer.accept(compression); + return this; + } + + @Override + public CompressionConfig compression() { + return compression; + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultResourceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultResourceLoader.java index 0a82317a..d3473a52 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultResourceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultResourceLoader.java @@ -9,13 +9,18 @@ final class DefaultResourceLoader implements ClassResourceLoader { private final Class clazz; + DefaultResourceLoader() { + + this.clazz = DefaultResourceLoader.class; + } + DefaultResourceLoader(Class clazz) { this.clazz = clazz; } @Override - public URL getResource(String resourcePath) { + public URL loadResource(String resourcePath) { var url = clazz.getResource(resourcePath); if (url == null) { @@ -29,7 +34,7 @@ public URL getResource(String resourcePath) { } @Override - public InputStream getResourceAsStream(String resourcePath) { + public InputStream loadResourceAsStream(String resourcePath) { var url = clazz.getResourceAsStream(resourcePath); if (url == null) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index ff59760e..d9290bc2 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -1,11 +1,14 @@ package io.avaje.jex; import java.util.Map; +import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import java.util.function.Consumer; import javax.net.ssl.SSLContext; +import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; @@ -58,14 +61,14 @@ public sealed interface JexConfig permits DJexConfig { JexConfig renderer(String extension, TemplateRender renderer); /** - * ThreadFactory for serving requests. Defaults to a {@link Thread#ofVirtual()} factory + * Set executor for serving requests. */ - JexConfig threadFactory(ThreadFactory executor); + JexConfig executor(Executor executor); /** - * Executor for serving requests. Defaults to {@link Executors#newVirtualThreadPerTaskExecutor()} + * Executor for serving requests. Defaults to a {@link Executors#newVirtualThreadPerTaskExecutor()} */ - ThreadFactory threadFactory(); + Executor executor(); /** * Return the port to use. @@ -113,4 +116,8 @@ public sealed interface JexConfig permits DJexConfig { */ Map renderers(); + JexConfig compression(Consumer consumer); + + CompressionConfig compression(); + } diff --git a/avaje-jex/src/main/java/io/avaje/jex/PathResourceHandler.java b/avaje-jex/src/main/java/io/avaje/jex/PathResourceHandler.java deleted file mode 100644 index ff5ae3da..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/PathResourceHandler.java +++ /dev/null @@ -1,84 +0,0 @@ -package io.avaje.jex; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Map; -import java.util.function.Predicate; - -final class PathResourceHandler extends AbstractStaticHandler implements ExchangeHandler { - - private final Path indexFile; - private final Path singleFile; - - PathResourceHandler( - String urlPrefix, - String filesystemRoot, - Map mimeTypes, - Map headers, - Predicate skipFilePredicate, - Path indexFile, - Path singleFile) { - super(urlPrefix, filesystemRoot, mimeTypes, headers, skipFilePredicate); - - this.indexFile = indexFile; - this.singleFile = singleFile; - } - - @Override - public void handle(Context ctx) throws IOException { - - if (singleFile != null) { - sendPathIS(ctx, singleFile.toString(), singleFile); - return; - } - - final var jdkExchange = ctx.jdkExchange(); - if (skipFilePredicate.test(ctx)) { - throw404(jdkExchange); - } - - final String wholeUrlPath = jdkExchange.getRequestURI().getPath(); - - if (wholeUrlPath.endsWith("/") || wholeUrlPath.equals(urlPrefix)) { - sendPathIS(ctx, indexFile.toString(), indexFile); - - return; - } - - final String urlPath = wholeUrlPath.substring(urlPrefix.length()); - - Path path; - try { - path = Path.of(filesystemRoot, urlPath).toRealPath(); - - } catch (final IOException e) { - // This may be more benign (i.e. not an attack, just a 403), - // but we don't want an attacker to be able to discern the difference. - reportPathTraversal(); - return; - } - - final String canonicalPath = path.toString(); - if (!canonicalPath.startsWith(filesystemRoot)) { - reportPathTraversal(); - } - - sendPathIS(ctx, urlPath, path); - } - - private void sendPathIS(Context ctx, String urlPath, Path path) throws IOException { - final var exchange = ctx.jdkExchange(); - final String mimeType = lookupMime(urlPath); - ctx.header("Content-Type", mimeType); - ctx.headers(headers); - exchange.sendResponseHeaders(200, Files.size(path)); - try (var fis = Files.newInputStream(path); - var os = exchange.getResponseBody()) { - - fis.transferTo(os); - } catch (final Exception e) { - throw404(ctx.jdkExchange()); - } - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/JarResourceHandler.java b/avaje-jex/src/main/java/io/avaje/jex/StaticClassResourceHandler.java similarity index 86% rename from avaje-jex/src/main/java/io/avaje/jex/JarResourceHandler.java rename to avaje-jex/src/main/java/io/avaje/jex/StaticClassResourceHandler.java index 4d91db4d..f8583f1b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JarResourceHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticClassResourceHandler.java @@ -6,13 +6,13 @@ import java.util.Map; import java.util.function.Predicate; -final class JarResourceHandler extends AbstractStaticHandler implements ExchangeHandler { +final class StaticClassResourceHandler extends AbstractStaticHandler implements ExchangeHandler { private final URL indexFile; private final URL singleFile; private final ClassResourceLoader resourceLoader; - JarResourceHandler( + StaticClassResourceHandler( String urlPrefix, String filesystemRoot, Map mimeTypes, @@ -31,7 +31,7 @@ final class JarResourceHandler extends AbstractStaticHandler implements Exchange @Override public void handle(Context ctx) throws IOException { - final var jdkExchange = ctx.jdkExchange(); + final var jdkExchange = ctx.exchange(); if (singleFile != null) { sendURL(ctx, singleFile.getPath(), singleFile); @@ -58,12 +58,12 @@ public void handle(Context ctx) throws IOException { reportPathTraversal(); } - try (var fis = resourceLoader.getResourceAsStream(normalizedPath)) { + try (var fis = resourceLoader.loadResourceAsStream(normalizedPath)) { ctx.header("Content-Type", lookupMime(normalizedPath)); ctx.headers(headers); ctx.write(fis); } catch (final Exception e) { - throw404(ctx.jdkExchange()); + throw404(ctx.exchange()); } } @@ -74,7 +74,7 @@ private void sendURL(Context ctx, String urlPath, URL path) throws IOException { ctx.headers(headers); ctx.write(fis); } catch (final Exception e) { - throw404(ctx.jdkExchange()); + throw404(ctx.exchange()); } } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java b/avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java index 2e728956..46527427 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java @@ -30,7 +30,7 @@ final class StaticFileHandler extends AbstractStaticHandler implements ExchangeH @Override public void handle(Context ctx) throws IOException { - final var jdkExchange = ctx.jdkExchange(); + final var jdkExchange = ctx.exchange(); if (singleFile != null) { sendFile(ctx, jdkExchange, singleFile.getPath(), singleFile); @@ -77,8 +77,7 @@ private void sendFile(Context ctx, HttpExchange jdkExchange, String urlPath, Fil String mimeType = lookupMime(urlPath); ctx.header("Content-Type", mimeType); ctx.headers(headers); - jdkExchange.sendResponseHeaders(200, canonicalFile.length()); - fis.transferTo(jdkExchange.getResponseBody()); + ctx.write(fis); } catch (FileNotFoundException e) { throw404(jdkExchange); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java index 8dc482a5..67541e5e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java @@ -3,11 +3,7 @@ import static io.avaje.jex.ResourceLocation.CLASS_PATH; import java.io.File; -import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -20,13 +16,12 @@ final class StaticResourceHandlerBuilder implements StaticContentConfig { private static final String DIRECTORY_INDEX_FAILURE = "Failed to locate Directory Index Resource: "; private static final Predicate NO_OP_PREDICATE = ctx -> false; - private static final ClassResourceLoader DEFALT_LOADER = - ClassResourceLoader.fromClass(StaticResourceHandlerBuilder.class); + private static final ClassResourceLoader DEFAULT_LOADER = new DefaultResourceLoader(); private String path = "/"; private String root = "/public/"; private String directoryIndex = null; - private ClassResourceLoader resourceLoader = DEFALT_LOADER; + private ClassResourceLoader resourceLoader = DEFAULT_LOADER; private final Map mimeTypes = new HashMap<>(); private final Map headers = new HashMap<>(); private Predicate skipFilePredicate = NO_OP_PREDICATE; @@ -158,71 +153,17 @@ private ExchangeHandler fileLoader(Function fileLoader) { } private ExchangeHandler classPathHandler() { - Function urlFunc = resourceLoader::getResource; - Function loaderFunc = urlFunc.andThen(this::toURI); - String fsRoot; - Path dirIndex = null; - Path singleFile = null; + URL dirIndex = null; + URL singleFile = null; if (directoryIndex != null) { - try { - var uri = loaderFunc.apply(root.transform(this::appendSlash) + directoryIndex); - - if ("jar".equals(uri.getScheme())) { - - var url = uri.toURL(); - return jarLoader(url.toString().transform(this::getJARRoot), url, null); - } - dirIndex = Paths.get(uri).toRealPath(); - fsRoot = Paths.get(uri).getParent().toString(); + dirIndex = resourceLoader.loadResource(root.transform(this::appendSlash) + directoryIndex); - } catch (Exception e) { - - throw new IllegalStateException( - DIRECTORY_INDEX_FAILURE + root.transform(this::appendSlash) + directoryIndex, e); - } } else { - try { - var uri = loaderFunc.apply(root); - - if ("jar".equals(uri.getScheme())) { - - var url = uri.toURL(); - - return jarLoader(url.toString().transform(this::getJARRoot), null, uri.toURL()); - } - - singleFile = Paths.get(uri).toRealPath(); - - fsRoot = singleFile.getParent().toString(); - - } catch (Exception e) { - - throw new IllegalStateException(FAILED_TO_LOCATE_FILE + root, e); - } + singleFile = resourceLoader.loadResource(root); } - return new PathResourceHandler( - path, fsRoot, mimeTypes, headers, skipFilePredicate, dirIndex, singleFile); - } - - private String getJARRoot(String s) { - return s.substring(0, s.lastIndexOf("/")).substring(s.indexOf("jar!") + 4); - } - - private ExchangeHandler jarLoader(String fsRoot, URL dirIndex, URL singleFile) { - - return new JarResourceHandler( - path, fsRoot, mimeTypes, headers, skipFilePredicate, resourceLoader, dirIndex, singleFile); - } - - private URI toURI(URL url) { - - try { - - return url.toURI(); - } catch (URISyntaxException e) { - throw new IllegalStateException(e); - } + return new StaticClassResourceHandler( + path, root, mimeTypes, headers, skipFilePredicate, resourceLoader, dirIndex, singleFile); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java new file mode 100644 index 00000000..44ebb36f --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java @@ -0,0 +1,92 @@ +package io.avaje.jex.compression; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Optional; + +import io.avaje.jex.Context; +import io.avaje.jex.core.HeaderKeys; +import io.avaje.jex.spi.SpiContext; + +/** + * CompressedOutputStream class that conditionally compresses the output based on configuration and + * request headers. + */ +public class CompressedOutputStream extends OutputStream { + + private final int minSizeForCompression; + private final CompressionConfig compression; + private final Context ctx; + + private final OutputStream originStream; + private OutputStream compressedStream; + private boolean compressionDecided; + + public CompressedOutputStream( + CompressionConfig compression, SpiContext ctx, OutputStream originStream) { + this.minSizeForCompression = compression.minSizeForCompression(); + this.compression = compression; + this.ctx = ctx; + this.originStream = originStream; + } + + private void decideCompression(int length) throws IOException { + if (!compressionDecided) { + var encoding = ctx.responseHeader(HeaderKeys.CONTENT_ENCODING); + + if (encoding != null) { + + this.compressedStream = + findMatchingCompressor(encoding) + .orElseThrow( + () -> + new IllegalStateException( + "No compressor found for Content-Encoding:" + encoding)) + .compress(originStream); + } + + boolean compressionAllowed = + compressedStream == null && compression.allowsForCompression(ctx.contentType()); + + if (compressionAllowed && length >= minSizeForCompression) { + Optional compressor; + compressor = findMatchingCompressor(ctx.header(HeaderKeys.ACCEPT_ENCODING)); + if (compressor.isPresent()) { + this.compressedStream = compressor.get().compress(originStream); + ctx.header(HeaderKeys.CONTENT_ENCODING, compressor.get().encoding()); + } + } + compressionDecided = true; + } + } + + @Override + public void write(byte[] bytes, int offset, int length) throws IOException { + decideCompression(length); + (compressedStream != null ? compressedStream : originStream).write(bytes, offset, length); + } + + @Override + public void write(int byteVal) throws IOException { + decideCompression(1); + (compressedStream != null ? compressedStream : originStream).write(byteVal); + } + + @Override + public void close() throws IOException { + if (compressedStream != null) { + compressedStream.close(); + } + originStream.close(); + } + + private Optional findMatchingCompressor(String acceptedEncoding) { + + if (acceptedEncoding != null) { + + return Arrays.stream(acceptedEncoding.split(",")).map(compression::forType).findFirst(); + } + return Optional.empty(); + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java new file mode 100644 index 00000000..33223c40 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java @@ -0,0 +1,68 @@ +package io.avaje.jex.compression; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class CompressionConfig { + + private static final int HTTP_PACKET_SIZE = 1500; + private static final Set excludedMimeTypes = + Set.of( + "application/compress", + "application/zip", + "application/gzip", + "application/bzip2", + "application/brotli", + "application/x-xz", + "application/x-rar-compressed"); + private boolean enabled = true; + private int minSizeForCompression = HTTP_PACKET_SIZE; + private final Map compressors = + new HashMap<>(Map.of(GzipCompressor.ENCODING, new GzipCompressor())); + private final Set allowedExcludedTypes = Set.of("image/svg+xml"); + + /** + * set default gzip compressor level + * + * @param level the new compression level (0-9) + */ + public void gzipCompressionLevel(int level) { + compressors.put(GzipCompressor.ENCODING, new GzipCompressor(level)); + } + + public void disableCompression() { + enabled = false; + compressors.clear(); + } + + public int minSizeForCompression() { + return minSizeForCompression; + } + + public CompressionConfig minSizeForCompression(int minSizeForCompression) { + this.minSizeForCompression = minSizeForCompression; + if (minSizeForCompression < HTTP_PACKET_SIZE) + throw new IllegalArgumentException( + "Compression should only happen on payloads bigger than an http packect"); + return this; + } + + public boolean compressionEnabled() { + return enabled; + } + + public boolean allowsForCompression(String contentType) { + + return contentType == null + || allowedExcludedTypes.contains(contentType) + || !excludedMimeTypes.contains(contentType) + && !contentType.startsWith("image/") + && !contentType.startsWith("audio/") + && !contentType.startsWith("video/"); + } + + Compressor forType(String type) { + return compressors.get(type.toLowerCase()); + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/Compressor.java b/avaje-jex/src/main/java/io/avaje/jex/compression/Compressor.java new file mode 100644 index 00000000..7bd1588c --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/Compressor.java @@ -0,0 +1,26 @@ +package io.avaje.jex.compression; + +import java.io.IOException; +import java.io.OutputStream; + +/** Compressor interface defines methods for compressing an output stream. */ +public interface Compressor { + + /** + * Gets the content encoding for this compressor (e.g., "gzip"). + * + * @see MDN + * Content-Encoding + * @return the content encoding + */ + String encoding(); + + /** + * Compresses the provided output stream. + * + * @param out the output stream to compress + * @return the compressed output stream + * @throws IOException if an error occurs during compression + */ + OutputStream compress(OutputStream out) throws IOException; +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/GzipCompressor.java b/avaje-jex/src/main/java/io/avaje/jex/compression/GzipCompressor.java new file mode 100644 index 00000000..6c52e9ea --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/GzipCompressor.java @@ -0,0 +1,40 @@ +package io.avaje.jex.compression; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.GZIPOutputStream; + +final class GzipCompressor implements Compressor { + static final String ENCODING = "gzip"; + private static final String EXTENSION = ".gz"; + private final int level; + + GzipCompressor() { + level = 6; + } + + GzipCompressor(int level) { + if (level < 0 || level > 9) { + throw new IllegalArgumentException("Valid range for parameter level is 0 to 9"); + } + this.level = level; + } + + @Override + public String encoding() { + return ENCODING; + } + + @Override + public OutputStream compress(OutputStream out) throws IOException { + return new LeveledGzipStream(out, level); + } + + static class LeveledGzipStream extends GZIPOutputStream { + + public LeveledGzipStream(OutputStream out, int level) throws IOException { + super(out); + this.def.setLevel(level); + } + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/HeaderKeys.java b/avaje-jex/src/main/java/io/avaje/jex/core/HeaderKeys.java index 3d07b4d4..71a6b839 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/HeaderKeys.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/HeaderKeys.java @@ -15,4 +15,5 @@ private HeaderKeys() {} public static final String LOCATION = "Location"; public static final String HOST = "Host"; public static final String USER_AGENT = "User-Agent"; + public static final String ACCEPT_ENCODING = "Accept-Encoding"; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index 36c3efef..0b0efeab 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -25,6 +25,8 @@ import io.avaje.jex.Context; import io.avaje.jex.Routing; +import io.avaje.jex.compression.CompressedOutputStream; +import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.core.HeaderKeys; import io.avaje.jex.http.ErrorCode; import io.avaje.jex.http.RedirectException; @@ -39,6 +41,7 @@ final class JdkContext implements Context, SpiContext { private static final String SET_COOKIE = "Set-Cookie"; private static final String COOKIE = "Cookie"; private final CtxServiceManager mgr; + private final CompressionConfig compressionConfig; private final String path; private final Map pathParams; private final Set roles; @@ -52,11 +55,13 @@ final class JdkContext implements Context, SpiContext { JdkContext( CtxServiceManager mgr, + CompressionConfig compressionConfig, HttpExchange exchange, String path, Map pathParams, Set roles) { this.mgr = mgr; + this.compressionConfig = compressionConfig; this.roles = roles; this.exchange = exchange; this.path = path; @@ -64,8 +69,14 @@ final class JdkContext implements Context, SpiContext { } /** Create when no route matched. */ - JdkContext(CtxServiceManager mgr, HttpExchange exchange, String path, Set roles) { + JdkContext( + CtxServiceManager mgr, + CompressionConfig compressionConfig, + HttpExchange exchange, + String path, + Set roles) { this.mgr = mgr; + this.compressionConfig = compressionConfig; this.roles = roles; this.exchange = exchange; this.path = path; @@ -443,7 +454,11 @@ public String protocol() { @Override public OutputStream outputStream() { - return mgr.createOutputStream(this); + var out = mgr.createOutputStream(this); + if (compressionConfig.compressionEnabled()) { + return new CompressedOutputStream(compressionConfig, this,out); + } + return out; } @Override @@ -456,12 +471,8 @@ public void setMode(Routing.Type type) { this.mode = type; } - HttpExchange exchange() { - return exchange; - } - @Override - public HttpExchange jdkExchange() { + public HttpExchange exchange() { return exchange; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index de5fbc11..e58cec93 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -4,7 +4,6 @@ import java.io.UncheckedIOException; import java.lang.System.Logger.Level; import java.net.InetSocketAddress; -import java.util.concurrent.Executors; import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpsConfigurator; @@ -41,9 +40,9 @@ public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceMana var handler = new BaseHandler(routes); var context = server.createContext("/", handler); - context.getFilters().add(new RoutingFilter(routes, manager)); + context.getFilters().add(new RoutingFilter(routes, manager, jex.config().compression())); context.getFilters().addAll(routes.filters()); - server.setExecutor(Executors.newThreadPerTaskExecutor(jex.config().threadFactory())); + server.setExecutor(jex.config().executor()); server.start(); jex.lifecycle().status(AppLifecycle.Status.STARTED); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java index dd01ba7f..16e0ffe3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java @@ -10,6 +10,7 @@ import io.avaje.jex.ExchangeHandler; import io.avaje.jex.Routing; import io.avaje.jex.Routing.Type; +import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.http.NotFoundException; import io.avaje.jex.routes.SpiRoutes; import io.avaje.jex.spi.SpiContext; @@ -18,10 +19,12 @@ final class RoutingFilter extends Filter { private final SpiRoutes routes; private final CtxServiceManager mgr; + private final CompressionConfig compressionConfig; - RoutingFilter(SpiRoutes routes, CtxServiceManager mgr) { + RoutingFilter(SpiRoutes routes, CtxServiceManager mgr, CompressionConfig compressionConfig) { this.mgr = mgr; this.routes = routes; + this.compressionConfig = compressionConfig; } void waitForIdle(long maxSeconds) { @@ -35,7 +38,7 @@ public void doFilter(HttpExchange exchange, Filter.Chain chain) { final SpiRoutes.Entry route = routes.match(routeType, uri); if (route == null) { - var ctx = new JdkContext(mgr, exchange, uri, Set.of()); + var ctx = new JdkContext(mgr, compressionConfig, exchange, uri, Set.of()); routes.inc(); try { processNoRoute(ctx, uri, routeType); @@ -52,7 +55,9 @@ public void doFilter(HttpExchange exchange, Filter.Chain chain) { route.inc(); try { final Map params = route.pathParams(uri); - JdkContext ctx = new JdkContext(mgr, exchange, route.matchPath(), params, route.roles()); + JdkContext ctx = + new JdkContext( + mgr, compressionConfig, exchange, route.matchPath(), params, route.roles()); try { ctx.setMode(Type.FILTER); exchange.setAttribute("JdkContext", ctx); diff --git a/avaje-jex/src/main/java/module-info.java b/avaje-jex/src/main/java/module-info.java index ee1b7c88..4ee418a8 100644 --- a/avaje-jex/src/main/java/module-info.java +++ b/avaje-jex/src/main/java/module-info.java @@ -3,6 +3,7 @@ module io.avaje.jex { exports io.avaje.jex; + exports io.avaje.jex.compression; exports io.avaje.jex.http; exports io.avaje.jex.core.json; exports io.avaje.jex.security; diff --git a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java new file mode 100644 index 00000000..2f775c4c --- /dev/null +++ b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java @@ -0,0 +1,66 @@ +package io.avaje.jex.compression; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.http.HttpResponse; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.avaje.jex.Jex; +import io.avaje.jex.ResourceLocation; +import io.avaje.jex.core.HeaderKeys; +import io.avaje.jex.jdk.TestPair; + +class CompressionTest { + + static TestPair pair = init(); + + static TestPair init() { + + final Jex app = + Jex.create() + .staticResource(b -> b.httpPath("/compress").resource("/64KB.json")) + .routing( + r -> + r.get( + "/forced", + ctx -> ctx.header(HeaderKeys.CONTENT_ENCODING, "gzip").text("hi"))) + .staticResource( + b -> + b.location(ResourceLocation.FILE) + .httpPath("/sus") + .resource("src/test/resources/public/sus.txt")); + + return TestPair.create(app); + } + + @AfterAll + static void end() { + pair.shutdown(); + } + + @Test + void testCompression() { + HttpResponse res = + pair.request().header(HeaderKeys.ACCEPT_ENCODING, "gzip").path("compress").GET().asString(); + assertThat(res.statusCode()).isEqualTo(200); + assertThat(res.headers().firstValue(HeaderKeys.CONTENT_ENCODING)).contains("gzip"); + } + + @Test + void testNoCompression() { + HttpResponse res = + pair.request().header(HeaderKeys.ACCEPT_ENCODING, "gzip").path("sus").GET().asString(); + assertThat(res.statusCode()).isEqualTo(200); + assertThat(res.headers().firstValue(HeaderKeys.CONTENT_ENCODING)).isEmpty(); + } + + @Test + void testForcedCompression() { + HttpResponse res = + pair.request().header(HeaderKeys.ACCEPT_ENCODING, "gzip").path("forced").GET().asString(); + assertThat(res.statusCode()).isEqualTo(200); + assertThat(res.headers().firstValue(HeaderKeys.CONTENT_ENCODING)).contains("gzip"); + } +} diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java index 74d82a70..5d7823fc 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java @@ -35,7 +35,6 @@ static TestPair init() { if (ctx.url().contains("/two/")) { ctx.header("before-two", "set"); } - // ctx.jdkExchange().getRequestURI().getPath(); chain.proceed(); }) .after(ctx -> afterAll.set("set")) diff --git a/avaje-jex/src/test/resources/64KB.json b/avaje-jex/src/test/resources/64KB.json new file mode 100644 index 00000000..c2a33dc0 --- /dev/null +++ b/avaje-jex/src/test/resources/64KB.json @@ -0,0 +1,1381 @@ +[ + { + "name": "Adeel Solangi", + "language": "Sindhi", + "id": "V59OF92YF627HFY0", + "bio": "Donec lobortis eleifend condimentum. Cras dictum dolor lacinia lectus vehicula rutrum. Maecenas quis nisi nunc. Nam tristique feugiat est vitae mollis. Maecenas quis nisi nunc.", + "version": 6.1 + }, + { + "name": "Afzal Ghaffar", + "language": "Sindhi", + "id": "ENTOCR13RSCLZ6KU", + "bio": "Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et. Pellentesque massa sem, scelerisque sit amet odio id, cursus tempor urna. Etiam congue dignissim volutpat. Vestibulum pharetra libero et velit gravida euismod.", + "version": 1.88 + }, + { + "name": "Aamir Solangi", + "language": "Sindhi", + "id": "IAKPO3R4761JDRVG", + "bio": "Vestibulum pharetra libero et velit gravida euismod. Quisque mauris ligula, efficitur porttitor sodales ac, lacinia non ex. Fusce eu ultrices elit, vel posuere neque.", + "version": 7.27 + }, + { + "name": "Abla Dilmurat", + "language": "Uyghur", + "id": "5ZVOEPMJUI4MB4EN", + "bio": "Donec lobortis eleifend condimentum. Morbi ac tellus erat.", + "version": 2.53 + }, + { + "name": "Adil Eli", + "language": "Uyghur", + "id": "6VTI8X6LL0MMPJCC", + "bio": "Vivamus id faucibus velit, id posuere leo. Morbi vitae nisi lacinia, laoreet lorem nec, egestas orci. Suspendisse potenti.", + "version": 6.49 + }, + { + "name": "Adile Qadir", + "language": "Uyghur", + "id": "F2KEU5L7EHYSYFTT", + "bio": "Duis commodo orci ut dolor iaculis facilisis. Morbi ultricies consequat ligula posuere eleifend. Aenean finibus in tortor vel aliquet. Fusce eu ultrices elit, vel posuere neque.", + "version": 1.9 + }, + { + "name": "Abdukerim Ibrahim", + "language": "Uyghur", + "id": "LO6DVTZLRK68528I", + "bio": "Vivamus id faucibus velit, id posuere leo. Nunc aliquet sodales nunc a pulvinar. Nunc aliquet sodales nunc a pulvinar. Ut viverra quis eros eu tincidunt.", + "version": 5.9 + }, + { + "name": "Adil Abro", + "language": "Sindhi", + "id": "LJRIULRNJFCNZJAJ", + "bio": "Etiam malesuada blandit erat, nec ultricies leo maximus sed. Fusce congue aliquam elit ut luctus. Etiam malesuada blandit erat, nec ultricies leo maximus sed. Cras dictum dolor lacinia lectus vehicula rutrum. Integer vehicula, arcu sit amet egestas efficitur, orci justo interdum massa, eget ullamcorper risus ligula tristique libero.", + "version": 9.32 + }, + { + "name": "Afonso Vilarchán", + "language": "Galician", + "id": "JMCL0CXNXHPL1GBC", + "bio": "Fusce eu ultrices elit, vel posuere neque. Morbi ac tellus erat. Nunc tincidunt laoreet laoreet.", + "version": 5.21 + }, + { + "name": "Mark Schembri", + "language": "Maltese", + "id": "KU4T500C830697CW", + "bio": "Nam laoreet, nunc non suscipit interdum, justo turpis vestibulum massa, non vulputate ex urna at purus. Morbi ultricies consequat ligula posuere eleifend. Vivamus id faucibus velit, id posuere leo. Sed laoreet posuere sapien, ut feugiat nibh gravida at. Ut maximus, libero nec facilisis fringilla, ex sem sollicitudin leo, non congue tortor ligula in eros.", + "version": 3.17 + }, + { + "name": "Antía Sixirei", + "language": "Galician", + "id": "XOF91ZR7MHV1TXRS", + "bio": "Pellentesque massa sem, scelerisque sit amet odio id, cursus tempor urna. Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula. Morbi finibus dui sed est fringilla ornare. Duis pellentesque ultrices convallis. Morbi ultricies consequat ligula posuere eleifend.", + "version": 6.44 + }, + { + "name": "Aygul Mutellip", + "language": "Uyghur", + "id": "FTSNV411G5MKLPDT", + "bio": "Duis commodo orci ut dolor iaculis facilisis. Nam semper gravida nunc, sit amet elementum ipsum. Donec pellentesque ultrices mi, non consectetur eros luctus non. Pellentesque massa sem, scelerisque sit amet odio id, cursus tempor urna.", + "version": 9.1 + }, + { + "name": "Awais Shaikh", + "language": "Sindhi", + "id": "OJMWMEEQWMLDU29P", + "bio": "Nunc aliquet sodales nunc a pulvinar. Ut dictum, ligula eget sagittis maximus, tellus mi varius ex, a accumsan justo tellus vitae leo. Donec pellentesque ultrices mi, non consectetur eros luctus non. Nulla finibus massa at viverra facilisis. Nunc tincidunt laoreet laoreet.", + "version": 1.59 + }, + { + "name": "Ambreen Ahmed", + "language": "Sindhi", + "id": "5G646V7E6TJW8X2M", + "bio": "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam consequat enim lorem, at tincidunt velit ultricies et. Ut maximus, libero nec facilisis fringilla, ex sem sollicitudin leo, non congue tortor ligula in eros.", + "version": 2.35 + }, + { + "name": "Celtia Anes", + "language": "Galician", + "id": "Z53AJY7WUYPLAWC9", + "bio": "Nullam ac sodales dolor, eu facilisis dui. Maecenas non arcu nulla. Ut viverra quis eros eu tincidunt. Curabitur quis commodo quam.", + "version": 8.34 + }, + { + "name": "George Mifsud", + "language": "Maltese", + "id": "N1AS6UFULO6WGTLB", + "bio": "Phasellus tincidunt sollicitudin posuere. Ut accumsan, est vel fringilla varius, purus augue blandit nisl, eu rhoncus ligula purus vel dolor. Donec congue sapien vel euismod interdum. Cras dictum dolor lacinia lectus vehicula rutrum. Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula.", + "version": 7.47 + }, + { + "name": "Aytürk Qasim", + "language": "Uyghur", + "id": "70RODUVRD95CLOJL", + "bio": "Curabitur ultricies id urna nec ultrices. Aliquam scelerisque pretium tellus, sed accumsan est ultrices id. Duis commodo orci ut dolor iaculis facilisis.", + "version": 1.32 + }, + { + "name": "Dialè Meso", + "language": "Sesotho sa Leboa", + "id": "VBLI24FKF7VV6BWE", + "bio": "Maecenas non arcu nulla. Vivamus id faucibus velit, id posuere leo. Nullam sodales convallis mauris, sit amet lobortis magna auctor sit amet.", + "version": 6.29 + }, + { + "name": "Breixo Galáns", + "language": "Galician", + "id": "4VRLON0GPEZYFCVL", + "bio": "Integer vehicula, arcu sit amet egestas efficitur, orci justo interdum massa, eget ullamcorper risus ligula tristique libero. Morbi ac tellus erat. In id elit malesuada, pulvinar mi eu, imperdiet nulla. Vestibulum pharetra libero et velit gravida euismod. Cras dictum dolor lacinia lectus vehicula rutrum.", + "version": 1.62 + }, + { + "name": "Bieito Lorme", + "language": "Galician", + "id": "5DRDI1QLRGLP29RC", + "bio": "Ut viverra quis eros eu tincidunt. Morbi vitae nisi lacinia, laoreet lorem nec, egestas orci. Curabitur quis commodo quam. Morbi ac tellus erat.", + "version": 4.45 + }, + { + "name": "Azrugul Osman", + "language": "Uyghur", + "id": "5RCTVD3C5QGVAKTQ", + "bio": "Maecenas tempus neque ut porttitor malesuada. Donec lobortis eleifend condimentum.", + "version": 3.18 + }, + { + "name": "Brais Verdiñas", + "language": "Galician", + "id": "BT407GHCC0IHXCD3", + "bio": "Quisque maximus sodales mauris ut elementum. Ut viverra quis eros eu tincidunt. Sed eu libero maximus nunc lacinia lobortis et sit amet nisi. In id elit malesuada, pulvinar mi eu, imperdiet nulla. Curabitur quis commodo quam.", + "version": 5.01 + }, + { + "name": "Ekber Sadir", + "language": "Uyghur", + "id": "AGZDAP8D8OVRRLTY", + "bio": "Quisque efficitur vel sapien ut imperdiet. Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula. In id elit malesuada, pulvinar mi eu, imperdiet nulla. Sed nec suscipit ligula. Integer vehicula, arcu sit amet egestas efficitur, orci justo interdum massa, eget ullamcorper risus ligula tristique libero.", + "version": 2.04 + }, + { + "name": "Doreen Bartolo", + "language": "Maltese", + "id": "59QSX02O2XOZGRLH", + "bio": "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam consequat enim lorem, at tincidunt velit ultricies et. Nam semper gravida nunc, sit amet elementum ipsum. Ut viverra quis eros eu tincidunt. Curabitur sed condimentum felis, ut luctus eros.", + "version": 9.31 + }, + { + "name": "Ali Ayaz", + "language": "Sindhi", + "id": "3WNLUZ5LT2F7MYVU", + "bio": "Cras dictum dolor lacinia lectus vehicula rutrum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam consequat enim lorem, at tincidunt velit ultricies et. Etiam malesuada blandit erat, nec ultricies leo maximus sed.", + "version": 7.8 + }, + { + "name": "Guzelnur Polat", + "language": "Uyghur", + "id": "I6QQHAEGV4CYDXLP", + "bio": "Nam laoreet, nunc non suscipit interdum, justo turpis vestibulum massa, non vulputate ex urna at purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam consequat enim lorem, at tincidunt velit ultricies et. Nulla finibus massa at viverra facilisis.", + "version": 8.56 + }, + { + "name": "John Falzon", + "language": "Maltese", + "id": "U3AWXHDTSU0H82SL", + "bio": "Sed nec suscipit ligula. Nullam sodales convallis mauris, sit amet lobortis magna auctor sit amet.", + "version": 9.96 + }, + { + "name": "Erkin Qadir", + "language": "Uyghur", + "id": "GV6TA1AATZYBJ3VR", + "bio": "Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula. .", + "version": 3.53 + }, + { + "name": "Anita Rajput", + "language": "Sindhi", + "id": "XLLVD0NO2ZFEP4AK", + "bio": "Nam semper gravida nunc, sit amet elementum ipsum. Etiam congue dignissim volutpat.", + "version": 5.16 + }, + { + "name": "Ayesha Khalique", + "language": "Sindhi", + "id": "Q9A5QNGA0OSU8P6Y", + "bio": "Morbi vitae nisi lacinia, laoreet lorem nec, egestas orci. Etiam mauris magna, fermentum vitae aliquet eu, cursus vitae sapien.", + "version": 3.9 + }, + { + "name": "Pheladi Rammala", + "language": "Sesotho sa Leboa", + "id": "EELSIRT2T4Q0M3M4", + "bio": "Quisque efficitur vel sapien ut imperdiet. Morbi ac tellus erat. Aliquam scelerisque pretium tellus, sed accumsan est ultrices id. Ut maximus, libero nec facilisis fringilla, ex sem sollicitudin leo, non congue tortor ligula in eros.", + "version": 1.88 + }, + { + "name": "Antón Caneiro", + "language": "Galician", + "id": "ENTAPNU3MMFUGM1W", + "bio": "Integer vehicula, arcu sit amet egestas efficitur, orci justo interdum massa, eget ullamcorper risus ligula tristique libero. Vestibulum pharetra libero et velit gravida euismod.", + "version": 4.84 + }, + { + "name": "Qahar Abdulla", + "language": "Uyghur", + "id": "OGLODUPEHKEW0K83", + "bio": "Duis commodo orci ut dolor iaculis facilisis. Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et. Fusce congue aliquam elit ut luctus. Integer vehicula, arcu sit amet egestas efficitur, orci justo interdum massa, eget ullamcorper risus ligula tristique libero. Quisque maximus sodales mauris ut elementum.", + "version": 3.65 + }, + { + "name": "Reyhan Murat", + "language": "Uyghur", + "id": "Y91F4D54794E9ANT", + "bio": "Suspendisse sit amet ullamcorper sem. Curabitur sed condimentum felis, ut luctus eros.", + "version": 2.69 + }, + { + "name": "Tatapi Phogole", + "language": "Sesotho sa Leboa", + "id": "7JA42P5CMCWDVPNR", + "bio": "Duis luctus, lacus eu aliquet convallis, purus elit malesuada ex, vitae rutrum ipsum dui ut magna. Nullam ac sodales dolor, eu facilisis dui. Ut viverra quis eros eu tincidunt.", + "version": 3.78 + }, + { + "name": "Marcos Amboade", + "language": "Galician", + "id": "WPX7H97C7D70CZJR", + "bio": "Nulla finibus massa at viverra facilisis. Pellentesque massa sem, scelerisque sit amet odio id, cursus tempor urna. Curabitur ultricies id urna nec ultrices. Ut maximus, libero nec facilisis fringilla, ex sem sollicitudin leo, non congue tortor ligula in eros. Nunc aliquet sodales nunc a pulvinar.", + "version": 7.37 + }, + { + "name": "Grace Tabone", + "language": "Maltese", + "id": "K4XO8G8DMRNSHF2B", + "bio": "Curabitur sed condimentum felis, ut luctus eros. Duis luctus, lacus eu aliquet convallis, purus elit malesuada ex, vitae rutrum ipsum dui ut magna.", + "version": 5.36 + }, + { + "name": "Shafqat Memon", + "language": "Sindhi", + "id": "D8VFLVRXBXMVBRVI", + "bio": "Aliquam scelerisque pretium tellus, sed accumsan est ultrices id. . Curabitur quis commodo quam. Quisque maximus sodales mauris ut elementum. Quisque mauris ligula, efficitur porttitor sodales ac, lacinia non ex.", + "version": 8.95 + }, + { + "name": "Zeynep Semet", + "language": "Uyghur", + "id": "Z324TZV8S0FGDSAO", + "bio": "Quisque mauris ligula, efficitur porttitor sodales ac, lacinia non ex. Fusce eu ultrices elit, vel posuere neque. Nulla finibus massa at viverra facilisis.", + "version": 1.03 + }, + { + "name": "Meladi Papo", + "language": "Sesotho sa Leboa", + "id": "RJAZQ6BBLRT72CD9", + "bio": "Quisque efficitur vel sapien ut imperdiet. Pellentesque massa sem, scelerisque sit amet odio id, cursus tempor urna. Ut accumsan, est vel fringilla varius, purus augue blandit nisl, eu rhoncus ligula purus vel dolor. Etiam congue dignissim volutpat. Donec congue sapien vel euismod interdum.", + "version": 7.22 + }, + { + "name": "Semet Alim", + "language": "Uyghur", + "id": "HI7L2SR4RCS8C8CS", + "bio": "Duis commodo orci ut dolor iaculis facilisis. Ut viverra quis eros eu tincidunt. Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "version": 1.01 + }, + { + "name": "Sabela Veloso", + "language": "Galician", + "id": "QA55WXDLC7SRH97X", + "bio": "Duis commodo orci ut dolor iaculis facilisis. Suspendisse potenti. Cras dictum dolor lacinia lectus vehicula rutrum.", + "version": 7.32 + }, + { + "name": "Madule Ledimo", + "language": "Sesotho sa Leboa", + "id": "IHJN2DGJB5O1Y00D", + "bio": "Maecenas non arcu nulla. Aliquam scelerisque pretium tellus, sed accumsan est ultrices id.", + "version": 7.47 + }, + { + "name": "Michelle Caruana", + "language": "Maltese", + "id": "EG1I21R75IV9Q0Q8", + "bio": "Nam tristique feugiat est vitae mollis. Morbi ultricies consequat ligula posuere eleifend. Pellentesque massa sem, scelerisque sit amet odio id, cursus tempor urna.", + "version": 4.95 + }, + { + "name": "Philip Camilleri", + "language": "Maltese", + "id": "FCO0URUHARX5FDFW", + "bio": "Quisque efficitur vel sapien ut imperdiet. Suspendisse sit amet ullamcorper sem. Aliquam scelerisque pretium tellus, sed accumsan est ultrices id. . Aenean finibus in tortor vel aliquet.", + "version": 9.97 + }, + { + "name": "Olalla Romeu", + "language": "Galician", + "id": "WOCMVO6CYPG01ZHY", + "bio": "Maecenas tempus neque ut porttitor malesuada. Sed nec suscipit ligula. Morbi vitae nisi lacinia, laoreet lorem nec, egestas orci. Nullam sodales convallis mauris, sit amet lobortis magna auctor sit amet.", + "version": 1.98 + }, + { + "name": "Gulnur Perhat", + "language": "Uyghur", + "id": "VO3M22TTQMBA2XEM", + "bio": "Nullam ac sodales dolor, eu facilisis dui. Etiam mauris magna, fermentum vitae aliquet eu, cursus vitae sapien. Ut accumsan, est vel fringilla varius, purus augue blandit nisl, eu rhoncus ligula purus vel dolor. Maecenas quis nisi nunc. Duis pellentesque ultrices convallis.", + "version": 5.03 + }, + { + "name": "Hunadi Makgatho", + "language": "Sesotho sa Leboa", + "id": "MRJDOV2MU7PTCDXE", + "bio": "Phasellus tincidunt sollicitudin posuere. Maecenas quis nisi nunc. Duis luctus, lacus eu aliquet convallis, purus elit malesuada ex, vitae rutrum ipsum dui ut magna.", + "version": 8.18 + }, + { + "name": "Charmaine Abela", + "language": "Maltese", + "id": "F6FJP1QDJL944X4Z", + "bio": "Nam rutrum sollicitudin ante tempus consequat. Suspendisse sit amet ullamcorper sem. Morbi ac tellus erat. Sed nec suscipit ligula.", + "version": 6.95 + }, + { + "name": "Tumelò Letamo", + "language": "Sesotho sa Leboa", + "id": "F8BL9NPIKV0OWO1X", + "bio": "Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et. Etiam congue dignissim volutpat. Sed nec suscipit ligula. Nullam sodales convallis mauris, sit amet lobortis magna auctor sit amet.", + "version": 7.17 + }, + { + "name": "Aneela Mohan", + "language": "Sindhi", + "id": "CRYN52CXKNJU0YXU", + "bio": "Sed nec suscipit ligula. Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula. Maecenas tempus neque ut porttitor malesuada.", + "version": 4.45 + }, + { + "name": "Koketšo Montjane", + "language": "Sesotho sa Leboa", + "id": "0TTAMXC9TENQCA2O", + "bio": "Curabitur sed condimentum felis, ut luctus eros. Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et. Etiam mauris magna, fermentum vitae aliquet eu, cursus vitae sapien. Ut maximus, libero nec facilisis fringilla, ex sem sollicitudin leo, non congue tortor ligula in eros.", + "version": 3.61 + }, + { + "name": "Tegra Núnez", + "language": "Galician", + "id": "NC1ZUV6B853BZZCW", + "bio": "Maecenas tempus neque ut porttitor malesuada. Pellentesque massa sem, scelerisque sit amet odio id, cursus tempor urna.", + "version": 6.68 + }, + { + "name": "Dilnur Qeyser", + "language": "Uyghur", + "id": "JVQ8RQ4YRPGLFMR8", + "bio": "Maecenas non arcu nulla. Nulla finibus massa at viverra facilisis. Integer vehicula, arcu sit amet egestas efficitur, orci justo interdum massa, eget ullamcorper risus ligula tristique libero. Ut maximus, libero nec facilisis fringilla, ex sem sollicitudin leo, non congue tortor ligula in eros.", + "version": 7.93 + }, + { + "name": "Tania Agius", + "language": "Maltese", + "id": "WTDGKLDWJLR1BJKR", + "bio": "Etiam congue dignissim volutpat. Pellentesque massa sem, scelerisque sit amet odio id, cursus tempor urna.", + "version": 4.78 + }, + { + "name": "Iago Peirallo", + "language": "Galician", + "id": "D51G7XQTX2SPHR52", + "bio": "Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et. Donec congue sapien vel euismod interdum. Suspendisse potenti. Quisque maximus sodales mauris ut elementum. Quisque maximus sodales mauris ut elementum.", + "version": 6.3 + }, + { + "name": "Mpho Lamola", + "language": "Sesotho sa Leboa", + "id": "UGL8EOTXYBW1ILLW", + "bio": "In id elit malesuada, pulvinar mi eu, imperdiet nulla. Curabitur ultricies id urna nec ultrices. Maecenas tempus neque ut porttitor malesuada. In sed ultricies lorem. Nullam sodales convallis mauris, sit amet lobortis magna auctor sit amet.", + "version": 2.05 + }, + { + "name": "Josephine Balzan", + "language": "Maltese", + "id": "4OLTG6QD0A2VB432", + "bio": "Maecenas tempus neque ut porttitor malesuada. Sed eu libero maximus nunc lacinia lobortis et sit amet nisi. Maecenas non arcu nulla. Ut accumsan, est vel fringilla varius, purus augue blandit nisl, eu rhoncus ligula purus vel dolor. Curabitur quis commodo quam.", + "version": 7.64 + }, + { + "name": "Thabò Motongwane", + "language": "Sesotho sa Leboa", + "id": "NROE4ZZVGKZGDFNO", + "bio": "Donec pellentesque ultrices mi, non consectetur eros luctus non. Suspendisse potenti. Suspendisse potenti.", + "version": 2.07 + }, + { + "name": "Mmathabò Mojapelo", + "language": "Sesotho sa Leboa", + "id": "VXJDXYPV5L300IFW", + "bio": "Sed laoreet posuere sapien, ut feugiat nibh gravida at. Duis luctus, lacus eu aliquet convallis, purus elit malesuada ex, vitae rutrum ipsum dui ut magna. Nunc tincidunt laoreet laoreet. .", + "version": 9.36 + }, + { + "name": "Kgabo Lerumo", + "language": "Sesotho sa Leboa", + "id": "D63WWKQE2R4TFDIL", + "bio": "Vestibulum pharetra libero et velit gravida euismod. Maecenas tempus neque ut porttitor malesuada. Morbi ultricies consequat ligula posuere eleifend. Quisque efficitur vel sapien ut imperdiet. Nam rutrum sollicitudin ante tempus consequat.", + "version": 6.69 + }, + { + "name": "Lawrence Scicluna", + "language": "Maltese", + "id": "0KDA7XKZNNZWL2SR", + "bio": "Donec pellentesque ultrices mi, non consectetur eros luctus non. In id elit malesuada, pulvinar mi eu, imperdiet nulla. Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et.", + "version": 6.53 + }, + { + "name": "Iria Xamardo", + "language": "Galician", + "id": "ULUDKBP9PHBGHX2J", + "bio": "Vivamus id faucibus velit, id posuere leo. Sed eu libero maximus nunc lacinia lobortis et sit amet nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam malesuada blandit erat, nec ultricies leo maximus sed. Ut viverra quis eros eu tincidunt.", + "version": 3.42 + }, + { + "name": "Joseph Grech", + "language": "Maltese", + "id": "T4P1164RJBJ8S6XD", + "bio": "Aliquam scelerisque pretium tellus, sed accumsan est ultrices id. Donec lobortis eleifend condimentum.", + "version": 7.68 + }, + { + "name": "Napogadi Selepe", + "language": "Sesotho sa Leboa", + "id": "AJK91MKRFIHAQHHG", + "bio": "Quisque maximus sodales mauris ut elementum. Maecenas quis nisi nunc.", + "version": 4.95 + }, + { + "name": "Lesetja Theko", + "language": "Sesotho sa Leboa", + "id": "AATM20BURO1DHDAE", + "bio": "Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula. Etiam mauris magna, fermentum vitae aliquet eu, cursus vitae sapien. Nulla finibus massa at viverra facilisis. Morbi finibus dui sed est fringilla ornare.", + "version": 6.81 + }, + { + "name": "Martiño Arxíz", + "language": "Galician", + "id": "CQ56N9MH3WK7H5YQ", + "bio": "Proin tempus eu risus nec mattis. Morbi vitae nisi lacinia, laoreet lorem nec, egestas orci. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam consequat enim lorem, at tincidunt velit ultricies et. Nam rutrum sollicitudin ante tempus consequat. .", + "version": 7.13 + }, + { + "name": "Malehumò Ledwaba", + "language": "Sesotho sa Leboa", + "id": "E4F3HGRTKQKCT1SE", + "bio": "Ut accumsan, est vel fringilla varius, purus augue blandit nisl, eu rhoncus ligula purus vel dolor. Curabitur quis commodo quam. Quisque maximus sodales mauris ut elementum. Curabitur sed condimentum felis, ut luctus eros. Curabitur ultricies id urna nec ultrices.", + "version": 6.52 + }, + { + "name": "Musa Yasin", + "language": "Uyghur", + "id": "1AF8GIQZ1LF8QW0U", + "bio": "Phasellus tincidunt sollicitudin posuere. Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula. Ut maximus, libero nec facilisis fringilla, ex sem sollicitudin leo, non congue tortor ligula in eros. Ut accumsan, est vel fringilla varius, purus augue blandit nisl, eu rhoncus ligula purus vel dolor.", + "version": 1.54 + }, + { + "name": "Lajwanti Kumari", + "language": "Sindhi", + "id": "INRW3R54RAY7J9IS", + "bio": "In sed ultricies lorem. Sed eu libero maximus nunc lacinia lobortis et sit amet nisi. Quisque mauris ligula, efficitur porttitor sodales ac, lacinia non ex. Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "version": 9.34 + }, + { + "name": "Maria Sammut", + "language": "Maltese", + "id": "BJRF0BWIHJ0Q12A1", + "bio": "Maecenas tempus neque ut porttitor malesuada. Curabitur ultricies id urna nec ultrices.", + "version": 6.83 + }, + { + "name": "Rita Busuttil", + "language": "Maltese", + "id": "1QLMU6QZ7EYUNNZV", + "bio": "Phasellus tincidunt sollicitudin posuere. Quisque efficitur vel sapien ut imperdiet. Vestibulum pharetra libero et velit gravida euismod. Maecenas tempus neque ut porttitor malesuada.", + "version": 2.09 + }, + { + "name": "Roi Fraguela", + "language": "Galician", + "id": "UAT0M2O42E9M4SFT", + "bio": "Donec congue sapien vel euismod interdum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce congue aliquam elit ut luctus. Morbi ac tellus erat. Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "version": 1.08 + }, + { + "name": "Matome Molamo", + "language": "Sesotho sa Leboa", + "id": "7HI0UZZLRB9N5CBI", + "bio": "Vestibulum pharetra libero et velit gravida euismod. Fusce eu ultrices elit, vel posuere neque. Duis pellentesque ultrices convallis.", + "version": 9.55 + }, + { + "name": "Mapula Selokela", + "language": "Sesotho sa Leboa", + "id": "6ZQTOKQI6K82EE9Q", + "bio": "Duis pellentesque ultrices convallis. Nam laoreet, nunc non suscipit interdum, justo turpis vestibulum massa, non vulputate ex urna at purus. Ut viverra quis eros eu tincidunt. Proin tempus eu risus nec mattis.", + "version": 5.27 + }, + { + "name": "Noa Ervello", + "language": "Galician", + "id": "W9FR842CI16V8NU3", + "bio": "Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam consequat enim lorem, at tincidunt velit ultricies et. Suspendisse sit amet ullamcorper sem. Quisque mauris ligula, efficitur porttitor sodales ac, lacinia non ex.", + "version": 9.33 + }, + { + "name": "Naseem Kakepoto", + "language": "Sindhi", + "id": "6C7HZV4WPV9C9KS6", + "bio": "Morbi ultricies consequat ligula posuere eleifend. Fusce congue aliquam elit ut luctus. . Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula.", + "version": 1.4 + }, + { + "name": "sayama Amir", + "language": "Sindhi", + "id": "7K4IJT1X7G0EK9WC", + "bio": "Morbi ac tellus erat. Aliquam scelerisque pretium tellus, sed accumsan est ultrices id. Maecenas quis nisi nunc. Etiam congue dignissim volutpat. Sed nec suscipit ligula.", + "version": 9.48 + }, + { + "name": "Mariña Quintá", + "language": "Galician", + "id": "7GXC4OQYXX5JJY9F", + "bio": "Phasellus tincidunt sollicitudin posuere. Morbi ac tellus erat. Nullam ac sodales dolor, eu facilisis dui.", + "version": 8.81 + }, + { + "name": "Memet Tursun", + "language": "Uyghur", + "id": "KSFMV2JK2D553083", + "bio": "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam consequat enim lorem, at tincidunt velit ultricies et. Morbi finibus dui sed est fringilla ornare. Suspendisse sit amet ullamcorper sem.", + "version": 7.56 + }, + { + "name": "Carmen Vella", + "language": "Maltese", + "id": "WUALBIMS4E8JS4L2", + "bio": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc aliquet sodales nunc a pulvinar. Morbi vitae nisi lacinia, laoreet lorem nec, egestas orci. Vestibulum pharetra libero et velit gravida euismod.", + "version": 4.55 + }, + { + "name": "Sobia Khanam", + "language": "Sindhi", + "id": "YG1ERFWBJ7TIW35D", + "bio": "Phasellus tincidunt sollicitudin posuere. Aliquam scelerisque pretium tellus, sed accumsan est ultrices id. Morbi ultricies consequat ligula posuere eleifend. Curabitur sed condimentum felis, ut luctus eros.", + "version": 4.59 + }, + { + "name": "Raheela Ali", + "language": "Sindhi", + "id": "7JGX9SMLD5DE2IMG", + "bio": "Morbi finibus dui sed est fringilla ornare. Maecenas quis nisi nunc. Maecenas tempus neque ut porttitor malesuada. Curabitur ultricies id urna nec ultrices.", + "version": 4.75 + }, + { + "name": "Rashid Rajput", + "language": "Sindhi", + "id": "UNBGUGDUATATCLS4", + "bio": "Donec congue sapien vel euismod interdum. Maecenas quis nisi nunc.", + "version": 8.51 + }, + { + "name": "Uxía Feal", + "language": "Galician", + "id": "35ZPXUNH1M6W3ZJP", + "bio": "Vestibulum pharetra libero et velit gravida euismod. Vivamus id faucibus velit, id posuere leo.", + "version": 1.31 + }, + { + "name": "Andrew Fenech", + "language": "Maltese", + "id": "VEYKDKL8L0R0C7GQ", + "bio": "In sed ultricies lorem. Etiam mauris magna, fermentum vitae aliquet eu, cursus vitae sapien. Sed laoreet posuere sapien, ut feugiat nibh gravida at.", + "version": 2.5 + }, + { + "name": "Nicholas Micallef", + "language": "Maltese", + "id": "ZYCAI905154LSICR", + "bio": "Nam tristique feugiat est vitae mollis. Curabitur ultricies id urna nec ultrices. Morbi finibus dui sed est fringilla ornare.", + "version": 6.47 + }, + { + "name": "Paul Borg", + "language": "Maltese", + "id": "8AD5MMJ0TD0NJ6H2", + "bio": "Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula. Etiam mauris magna, fermentum vitae aliquet eu, cursus vitae sapien.", + "version": 3.77 + }, + { + "name": "Sara Saleem", + "language": "Sindhi", + "id": "5LPKMTZI7OPSJRBA", + "bio": "Maecenas tempus neque ut porttitor malesuada. Etiam congue dignissim volutpat. Proin tempus eu risus nec mattis. Morbi vitae nisi lacinia, laoreet lorem nec, egestas orci. Duis commodo orci ut dolor iaculis facilisis.", + "version": 5.31 + }, + { + "name": "Xurxo Golán", + "language": "Galician", + "id": "526ZUSGXEETODHJK", + "bio": "Ut viverra quis eros eu tincidunt. Morbi finibus dui sed est fringilla ornare. Sed laoreet posuere sapien, ut feugiat nibh gravida at. Duis commodo orci ut dolor iaculis facilisis. In sed ultricies lorem.", + "version": 1.75 + }, + { + "name": "Peter Zammit", + "language": "Maltese", + "id": "NNRT5QWNWO2WLS5V", + "bio": "Duis commodo orci ut dolor iaculis facilisis. Maecenas quis nisi nunc.", + "version": 8.23 + }, + { + "name": "Maname Mohlare", + "language": "Sesotho sa Leboa", + "id": "KZJZ9SD0DIWTIBUC", + "bio": "Quisque mauris ligula, efficitur porttitor sodales ac, lacinia non ex. Vestibulum pharetra libero et velit gravida euismod. Ut accumsan, est vel fringilla varius, purus augue blandit nisl, eu rhoncus ligula purus vel dolor. Sed eu libero maximus nunc lacinia lobortis et sit amet nisi.", + "version": 8.95 + }, + { + "name": "Tshepè Mobu", + "language": "Sesotho sa Leboa", + "id": "8CH586LQR7ZCP73P", + "bio": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus massa at viverra facilisis.", + "version": 7.82 + }, + { + "name": "Monica Lohana", + "language": "Sindhi", + "id": "KP1C2WN3DN1R3Y52", + "bio": "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam consequat enim lorem, at tincidunt velit ultricies et. Aenean finibus in tortor vel aliquet. Nam laoreet, nunc non suscipit interdum, justo turpis vestibulum massa, non vulputate ex urna at purus. Morbi vitae nisi lacinia, laoreet lorem nec, egestas orci.", + "version": 7.95 + }, + { + "name": "Patigul Rahman", + "language": "Uyghur", + "id": "NXMNLB0SOYET1VMN", + "bio": "In sed ultricies lorem. Proin tempus eu risus nec mattis. Nam rutrum sollicitudin ante tempus consequat. Aliquam scelerisque pretium tellus, sed accumsan est ultrices id.", + "version": 2.98 + }, + { + "name": "Joanne Scerri", + "language": "Maltese", + "id": "H8FJ2WKLGGF3K26U", + "bio": "Fusce eu ultrices elit, vel posuere neque. Nulla finibus massa at viverra facilisis. Duis commodo orci ut dolor iaculis facilisis. Nam laoreet, nunc non suscipit interdum, justo turpis vestibulum massa, non vulputate ex urna at purus. Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula.", + "version": 8.4 + }, + { + "name": "Ratanang Maphutha", + "language": "Sesotho sa Leboa", + "id": "EZXJTQQ2JWPB5DI3", + "bio": "Vivamus id faucibus velit, id posuere leo. Phasellus tincidunt sollicitudin posuere. Duis pellentesque ultrices convallis.", + "version": 9.17 + }, + { + "name": "Kamil Mehmud", + "language": "Uyghur", + "id": "M24A9OMYPSX7FD16", + "bio": "Donec congue sapien vel euismod interdum. Suspendisse potenti. In id elit malesuada, pulvinar mi eu, imperdiet nulla. Nunc aliquet sodales nunc a pulvinar. Ut viverra quis eros eu tincidunt.", + "version": 4.66 + }, + { + "name": "Thobile Mbele", + "language": "isiZulu", + "id": "631M00M8YFFBC5NC", + "bio": "Nunc aliquet sodales nunc a pulvinar. Proin tempus eu risus nec mattis. Proin tempus eu risus nec mattis. Aliquam scelerisque pretium tellus, sed accumsan est ultrices id. Nam laoreet, nunc non suscipit interdum, justo turpis vestibulum massa, non vulputate ex urna at purus.", + "version": 8.96 + }, + { + "name": "Kristján Kristjánsson", + "language": "Icelandic", + "id": "0WT0ZW50DNSTCHKW", + "bio": "Quisque maximus sodales mauris ut elementum. Pellentesque massa sem, scelerisque sit amet odio id, cursus tempor urna. Donec congue sapien vel euismod interdum. Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula. Donec lobortis eleifend condimentum.", + "version": 8.82 + }, + { + "name": "Stefán Stefánsson", + "language": "Icelandic", + "id": "1UOL8UK8BWAOSYTC", + "bio": "Suspendisse potenti. Duis luctus, lacus eu aliquet convallis, purus elit malesuada ex, vitae rutrum ipsum dui ut magna. Morbi ultricies consequat ligula posuere eleifend.", + "version": 7.87 + }, + { + "name": "Preeti Rajdan", + "language": "Hindi", + "id": "3UN0X88Y4WYH3X8X", + "bio": "In sed ultricies lorem. Vivamus id faucibus velit, id posuere leo. Duis commodo orci ut dolor iaculis facilisis. Nam rutrum sollicitudin ante tempus consequat.", + "version": 9.17 + }, + { + "name": "Sanjay Trivedi", + "language": "Hindi", + "id": "CPHR246457BD01KY", + "bio": "Quisque maximus sodales mauris ut elementum. Morbi ac tellus erat. Maecenas tempus neque ut porttitor malesuada. Cras dictum dolor lacinia lectus vehicula rutrum.", + "version": 8.3 + }, + { + "name": "Smiriti Sisodiya", + "language": "Hindi", + "id": "X3KWIL5KEHTMCKOM", + "bio": "Integer vehicula, arcu sit amet egestas efficitur, orci justo interdum massa, eget ullamcorper risus ligula tristique libero. Morbi finibus dui sed est fringilla ornare.", + "version": 3.27 + }, + { + "name": "Sandeep Benarjee", + "language": "Hindi", + "id": "9TS6CIE3UAIFG2IB", + "bio": "Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et. Sed nec suscipit ligula. Quisque efficitur vel sapien ut imperdiet. Suspendisse sit amet ullamcorper sem.", + "version": 3.86 + }, + { + "name": "Damir Benic", + "language": "Bosnian", + "id": "QUNL9VBRHUGNOFMJ", + "bio": ". Ut dictum, ligula eget sagittis maximus, tellus mi varius ex, a accumsan justo tellus vitae leo.", + "version": 9.56 + }, + { + "name": "Sigrún Kristjánsdóttir", + "language": "Icelandic", + "id": "BT1Q0NUPKHDVCFLE", + "bio": "Ut dictum, ligula eget sagittis maximus, tellus mi varius ex, a accumsan justo tellus vitae leo. Nulla finibus massa at viverra facilisis.", + "version": 6.78 + }, + { + "name": "Basetsana Thage", + "language": "Setswana", + "id": "R9P3P2IAN7NY2X2Y", + "bio": "Pellentesque massa sem, scelerisque sit amet odio id, cursus tempor urna. Nulla finibus massa at viverra facilisis. Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula.", + "version": 3.97 + }, + { + "name": "Rajesh Santoshi", + "language": "Hindi", + "id": "OXQTFZHZW8SVE3SY", + "bio": "Donec lobortis eleifend condimentum. Nam rutrum sollicitudin ante tempus consequat. Nullam sodales convallis mauris, sit amet lobortis magna auctor sit amet.", + "version": 8.35 + }, + { + "name": "Margrét Magnúsdóttir", + "language": "Icelandic", + "id": "1P6VZEDGK2XUU97L", + "bio": "Sed eu libero maximus nunc lacinia lobortis et sit amet nisi. Duis pellentesque ultrices convallis. Donec lobortis eleifend condimentum.", + "version": 3.76 + }, + { + "name": "Makhosi Ngiba", + "language": "isiZulu", + "id": "CTM3Y3TZOLC7TPDU", + "bio": "Ut dictum, ligula eget sagittis maximus, tellus mi varius ex, a accumsan justo tellus vitae leo. Suspendisse sit amet ullamcorper sem. Donec lobortis eleifend condimentum. Aenean finibus in tortor vel aliquet. Proin tempus eu risus nec mattis.", + "version": 1.18 + }, + { + "name": "Lorato Bogosi", + "language": "Setswana", + "id": "EEZ0KS5E0RXACAIA", + "bio": "Morbi ultricies consequat ligula posuere eleifend. Nam rutrum sollicitudin ante tempus consequat. Ut dictum, ligula eget sagittis maximus, tellus mi varius ex, a accumsan justo tellus vitae leo. Curabitur ultricies id urna nec ultrices.", + "version": 5.48 + }, + { + "name": "Modisaotsile Bolokwe", + "language": "Setswana", + "id": "DN068KNEOAQ8LM19", + "bio": "Nullam ac sodales dolor, eu facilisis dui. Duis commodo orci ut dolor iaculis facilisis. In id elit malesuada, pulvinar mi eu, imperdiet nulla. Donec congue sapien vel euismod interdum. Sed nec suscipit ligula.", + "version": 4.23 + }, + { + "name": "Mxolisi Mhlongo", + "language": "isiZulu", + "id": "Q2HFB19RPLHIZXKH", + "bio": "Aliquam scelerisque pretium tellus, sed accumsan est ultrices id. Maecenas tempus neque ut porttitor malesuada. . Duis commodo orci ut dolor iaculis facilisis.", + "version": 7.49 + }, + { + "name": "Moni Sisodiya", + "language": "Hindi", + "id": "3CR7CN74GCKXWUQF", + "bio": "Vestibulum pharetra libero et velit gravida euismod. Donec congue sapien vel euismod interdum. Fusce congue aliquam elit ut luctus. Ut viverra quis eros eu tincidunt. Phasellus tincidunt sollicitudin posuere.", + "version": 4.58 + }, + { + "name": "Anna Jónsdóttir", + "language": "Icelandic", + "id": "CKJW1XVW90VWO4Y1", + "bio": "Etiam mauris magna, fermentum vitae aliquet eu, cursus vitae sapien. Donec lobortis eleifend condimentum. Etiam mauris magna, fermentum vitae aliquet eu, cursus vitae sapien.", + "version": 5.78 + }, + { + "name": "Darko Basic", + "language": "Bosnian", + "id": "FWT1CZQOIVRJTXRD", + "bio": "Donec congue sapien vel euismod interdum. Fusce eu ultrices elit, vel posuere neque. Duis luctus, lacus eu aliquet convallis, purus elit malesuada ex, vitae rutrum ipsum dui ut magna.", + "version": 2.27 + }, + { + "name": "Kedibonye Magogwe", + "language": "Setswana", + "id": "PCT0HLRPZLDSSDU1", + "bio": "Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et. Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et. Quisque maximus sodales mauris ut elementum.", + "version": 5.57 + }, + { + "name": "Nobuhle Xaba", + "language": "isiZulu", + "id": "5K1K8V1OUUFKQ2UV", + "bio": "Maecenas non arcu nulla. Morbi ac tellus erat.", + "version": 1.18 + }, + { + "name": "Monty Dubey", + "language": "Hindi", + "id": "B7SF955NFGAEBRXU", + "bio": "Maecenas quis nisi nunc. Maecenas tempus neque ut porttitor malesuada. Morbi ultricies consequat ligula posuere eleifend. Ut accumsan, est vel fringilla varius, purus augue blandit nisl, eu rhoncus ligula purus vel dolor.", + "version": 6.69 + }, + { + "name": "Richa Choukse", + "language": "Hindi", + "id": "BADWLBP8CNJNBEC8", + "bio": "Nunc tincidunt laoreet laoreet. Ut dictum, ligula eget sagittis maximus, tellus mi varius ex, a accumsan justo tellus vitae leo. Curabitur quis commodo quam. Morbi vitae nisi lacinia, laoreet lorem nec, egestas orci.", + "version": 7.8 + }, + { + "name": "Dzenan Imamovic", + "language": "Bosnian", + "id": "FVAHD0OY99X9DIRW", + "bio": "Nam tristique feugiat est vitae mollis. Quisque mauris ligula, efficitur porttitor sodales ac, lacinia non ex. Nullam ac sodales dolor, eu facilisis dui. Morbi finibus dui sed est fringilla ornare. Quisque efficitur vel sapien ut imperdiet.", + "version": 1.64 + }, + { + "name": "Amol Bhatnagar", + "language": "Hindi", + "id": "3HPSETKL9VOW2WTL", + "bio": "Vestibulum pharetra libero et velit gravida euismod. Nam semper gravida nunc, sit amet elementum ipsum.", + "version": 3.28 + }, + { + "name": "Ingibjörg Ólafsdóttir", + "language": "Icelandic", + "id": "9BXLMMM1PQOZRHCR", + "bio": "Maecenas non arcu nulla. Sed nec suscipit ligula. Fusce congue aliquam elit ut luctus.", + "version": 9.59 + }, + { + "name": "Shweta Chourasia", + "language": "Hindi", + "id": "9GAO62FXPQMUTTLJ", + "bio": "Duis luctus, lacus eu aliquet convallis, purus elit malesuada ex, vitae rutrum ipsum dui ut magna. Integer vehicula, arcu sit amet egestas efficitur, orci justo interdum massa, eget ullamcorper risus ligula tristique libero. Quisque maximus sodales mauris ut elementum. Sed eu libero maximus nunc lacinia lobortis et sit amet nisi.", + "version": 5.84 + }, + { + "name": "Ayanda Ndimande", + "language": "isiZulu", + "id": "VPK9MQRKX2L847HQ", + "bio": "Duis commodo orci ut dolor iaculis facilisis. Nam laoreet, nunc non suscipit interdum, justo turpis vestibulum massa, non vulputate ex urna at purus.", + "version": 2.89 + }, + { + "name": "Sigurjón Guðmundsson", + "language": "Icelandic", + "id": "IAYT285H2U8JU94F", + "bio": "Sed eu libero maximus nunc lacinia lobortis et sit amet nisi. Ut viverra quis eros eu tincidunt. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam consequat enim lorem, at tincidunt velit ultricies et.", + "version": 4.85 + }, + { + "name": "Jóhannes Jóhannsson", + "language": "Icelandic", + "id": "J2RAROEJGKMR72I8", + "bio": "Duis pellentesque ultrices convallis. Nulla finibus massa at viverra facilisis. Ut dictum, ligula eget sagittis maximus, tellus mi varius ex, a accumsan justo tellus vitae leo.", + "version": 4.83 + }, + { + "name": "Neo Dikgaka", + "language": "Setswana", + "id": "OQRF6Y37N20JILOC", + "bio": "Nam tristique feugiat est vitae mollis. Sed nec suscipit ligula. Nam laoreet, nunc non suscipit interdum, justo turpis vestibulum massa, non vulputate ex urna at purus. Duis pellentesque ultrices convallis. Maecenas quis nisi nunc.", + "version": 1.07 + }, + { + "name": "Sanja Jankovic", + "language": "Bosnian", + "id": "HD94EKIPA6WAL05C", + "bio": "Phasellus tincidunt sollicitudin posuere. Nam laoreet, nunc non suscipit interdum, justo turpis vestibulum massa, non vulputate ex urna at purus. In id elit malesuada, pulvinar mi eu, imperdiet nulla. Donec congue sapien vel euismod interdum. Nullam ac sodales dolor, eu facilisis dui.", + "version": 1.06 + }, + { + "name": "Mogorosi Bakwena", + "language": "Setswana", + "id": "FTZM8YDJJUH1OEM7", + "bio": "Vestibulum pharetra libero et velit gravida euismod. Suspendisse sit amet ullamcorper sem.", + "version": 6.03 + }, + { + "name": "Ronak Gupta", + "language": "Hindi", + "id": "ZYPDGK8UDYJPTRKN", + "bio": "Sed laoreet posuere sapien, ut feugiat nibh gravida at. Quisque mauris ligula, efficitur porttitor sodales ac, lacinia non ex. In sed ultricies lorem. Pellentesque massa sem, scelerisque sit amet odio id, cursus tempor urna.", + "version": 7.18 + }, + { + "name": "Ditiro Kgosi", + "language": "Setswana", + "id": "67C5ET66U59WYJ6K", + "bio": "Fusce congue aliquam elit ut luctus. Ut dictum, ligula eget sagittis maximus, tellus mi varius ex, a accumsan justo tellus vitae leo. Cras dictum dolor lacinia lectus vehicula rutrum. Etiam congue dignissim volutpat.", + "version": 4.56 + }, + { + "name": "Jelena Maric", + "language": "Bosnian", + "id": "JTW9DH3B9QGB39JY", + "bio": "Vestibulum pharetra libero et velit gravida euismod. Etiam malesuada blandit erat, nec ultricies leo maximus sed.", + "version": 3.39 + }, + { + "name": "Esha Sastry", + "language": "Hindi", + "id": "4OJULHY03Z6XTRMW", + "bio": "Morbi vitae nisi lacinia, laoreet lorem nec, egestas orci. Nullam ac sodales dolor, eu facilisis dui.", + "version": 5.1 + }, + { + "name": "Chetana Hegde", + "language": "Hindi", + "id": "J9GS1RODDZL325LK", + "bio": "Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et. Nulla finibus massa at viverra facilisis. Nam tristique feugiat est vitae mollis. Phasellus tincidunt sollicitudin posuere.", + "version": 9.99 + }, + { + "name": "Rahul Shukla", + "language": "Hindi", + "id": "2ANVMAVG6YX2VT6N", + "bio": "Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula. Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula.", + "version": 1.72 + }, + { + "name": "Samra Delic", + "language": "Bosnian", + "id": "BXJWNTJ2TDID61PJ", + "bio": "Donec pellentesque ultrices mi, non consectetur eros luctus non. Sed nec suscipit ligula.", + "version": 2.5 + }, + { + "name": "Mohan Pandey", + "language": "Hindi", + "id": "XAHKVLM3I1WSPNIW", + "bio": "Maecenas quis nisi nunc. Ut dictum, ligula eget sagittis maximus, tellus mi varius ex, a accumsan justo tellus vitae leo. Ut maximus, libero nec facilisis fringilla, ex sem sollicitudin leo, non congue tortor ligula in eros. Morbi ac tellus erat.", + "version": 8.1 + }, + { + "name": "Haris Osmanovic", + "language": "Bosnian", + "id": "ZDXF5KESMW9XF2TJ", + "bio": "Nam rutrum sollicitudin ante tempus consequat. Etiam mauris magna, fermentum vitae aliquet eu, cursus vitae sapien.", + "version": 9.41 + }, + { + "name": "Kenosi Kwenaemang", + "language": "Setswana", + "id": "DX2IYTQ9IMY75W08", + "bio": "Sed laoreet posuere sapien, ut feugiat nibh gravida at. Donec lobortis eleifend condimentum.", + "version": 9.01 + }, + { + "name": "Nontobeko Nzimande", + "language": "isiZulu", + "id": "Y9C4HQHTOP74DFZT", + "bio": "Nullam sodales convallis mauris, sit amet lobortis magna auctor sit amet. Morbi vitae nisi lacinia, laoreet lorem nec, egestas orci. Integer vehicula, arcu sit amet egestas efficitur, orci justo interdum massa, eget ullamcorper risus ligula tristique libero. Nam laoreet, nunc non suscipit interdum, justo turpis vestibulum massa, non vulputate ex urna at purus.", + "version": 4.77 + }, + { + "name": "Sanjay Puranik", + "language": "Hindi", + "id": "WF2WP6S0HX8GR8GZ", + "bio": "Ut viverra quis eros eu tincidunt. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam consequat enim lorem, at tincidunt velit ultricies et. Nam semper gravida nunc, sit amet elementum ipsum.", + "version": 3.37 + }, + { + "name": "Sethunya Mpšwe", + "language": "Setswana", + "id": "85MVUXVQ5H5HPA4F", + "bio": "Quisque maximus sodales mauris ut elementum. Duis commodo orci ut dolor iaculis facilisis. Sed eu libero maximus nunc lacinia lobortis et sit amet nisi.", + "version": 1.75 + }, + { + "name": "Dileep Chaturvedi", + "language": "Hindi", + "id": "O95BY1KDMCEYQRFH", + "bio": "Phasellus tincidunt sollicitudin posuere. In id elit malesuada, pulvinar mi eu, imperdiet nulla. Vivamus id faucibus velit, id posuere leo. Nullam ac sodales dolor, eu facilisis dui. Ut dictum, ligula eget sagittis maximus, tellus mi varius ex, a accumsan justo tellus vitae leo.", + "version": 4.94 + }, + { + "name": "Adnan Spahic", + "language": "Bosnian", + "id": "97IIDMHAJMBPI4ON", + "bio": "Duis commodo orci ut dolor iaculis facilisis. Vivamus id faucibus velit, id posuere leo.", + "version": 9.1 + }, + { + "name": "Madhur Jain", + "language": "Hindi", + "id": "FM300CZ0VU9LTNTE", + "bio": "Fusce eu ultrices elit, vel posuere neque. Donec congue sapien vel euismod interdum. Vivamus id faucibus velit, id posuere leo. Integer vehicula, arcu sit amet egestas efficitur, orci justo interdum massa, eget ullamcorper risus ligula tristique libero. Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et.", + "version": 4.99 + }, + { + "name": "Nayan Mittal", + "language": "Hindi", + "id": "S879KFFIHDNK8GSE", + "bio": "Suspendisse sit amet ullamcorper sem. In id elit malesuada, pulvinar mi eu, imperdiet nulla. Duis commodo orci ut dolor iaculis facilisis.", + "version": 3.99 + }, + { + "name": "Kabelo Morwe", + "language": "Setswana", + "id": "JJDPB2983QRVATD3", + "bio": "Nullam ac sodales dolor, eu facilisis dui. Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula. . Integer vehicula, arcu sit amet egestas efficitur, orci justo interdum massa, eget ullamcorper risus ligula tristique libero. Curabitur ultricies id urna nec ultrices.", + "version": 8.86 + }, + { + "name": "Einar Einarsson", + "language": "Icelandic", + "id": "ZWMFEUEBNYTW2WPB", + "bio": "Etiam mauris magna, fermentum vitae aliquet eu, cursus vitae sapien. Duis pellentesque ultrices convallis. Nullam sodales convallis mauris, sit amet lobortis magna auctor sit amet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam consequat enim lorem, at tincidunt velit ultricies et. Donec congue sapien vel euismod interdum.", + "version": 9.05 + }, + { + "name": "Luka Lovren", + "language": "Bosnian", + "id": "9S4SGEQWBKMRISYZ", + "bio": "Maecenas tempus neque ut porttitor malesuada. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur quis commodo quam. Nam rutrum sollicitudin ante tempus consequat.", + "version": 5.22 + }, + { + "name": "Sigríður Einarsdóttir", + "language": "Icelandic", + "id": "4IJVD6OE3C7IX3ZG", + "bio": "Aenean finibus in tortor vel aliquet. Nam tristique feugiat est vitae mollis.", + "version": 6.63 + }, + { + "name": "Sonu Jain", + "language": "Hindi", + "id": "0OIB5SU9JB2PBJDV", + "bio": "Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula. Curabitur ultricies id urna nec ultrices.", + "version": 9.66 + }, + { + "name": "Boitumelo Ngwako", + "language": "Setswana", + "id": "INZITSS95L9V52JE", + "bio": "Ut maximus, libero nec facilisis fringilla, ex sem sollicitudin leo, non congue tortor ligula in eros. Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et. Nam tristique feugiat est vitae mollis. Quisque mauris ligula, efficitur porttitor sodales ac, lacinia non ex. In sed ultricies lorem.", + "version": 9.07 + }, + { + "name": "Shilpa Bhatia", + "language": "Hindi", + "id": "SU0W3T6TF8G3JY5M", + "bio": "Morbi ultricies consequat ligula posuere eleifend. Donec pellentesque ultrices mi, non consectetur eros luctus non. Quisque efficitur vel sapien ut imperdiet. Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "version": 4.43 + }, + { + "name": "Modise Tau", + "language": "Setswana", + "id": "U6SF3N4JXJEQSC1P", + "bio": "Vivamus id faucibus velit, id posuere leo. Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula. Fusce eu ultrices elit, vel posuere neque. Nunc tincidunt laoreet laoreet.", + "version": 6.23 + }, + { + "name": "Reena Shrivastav", + "language": "Hindi", + "id": "Y57EEOVURYX1OA1P", + "bio": "Donec lobortis eleifend condimentum. Curabitur ultricies id urna nec ultrices. Maecenas non arcu nulla.", + "version": 3.07 + }, + { + "name": "Thabani Ngubani", + "language": "isiZulu", + "id": "LR7FI8WEE3SLTW02", + "bio": "Cras dictum dolor lacinia lectus vehicula rutrum. Nulla finibus massa at viverra facilisis.", + "version": 5.99 + }, + { + "name": "Gunnar Gunnarsson", + "language": "Icelandic", + "id": "UVI6EKJNMC3VE3WU", + "bio": "In sed ultricies lorem. Donec congue sapien vel euismod interdum. Duis commodo orci ut dolor iaculis facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam consequat enim lorem, at tincidunt velit ultricies et.", + "version": 8.7 + }, + { + "name": "Lejla Selimagic", + "language": "Bosnian", + "id": "ESBBT644VZ64SSEN", + "bio": "Vivamus id faucibus velit, id posuere leo. Etiam congue dignissim volutpat. Donec lobortis eleifend condimentum. Fusce eu ultrices elit, vel posuere neque.", + "version": 5.59 + }, + { + "name": "Kgosietsile Bogatsu", + "language": "Setswana", + "id": "0B8IOVL2NSVJVV6T", + "bio": "Curabitur quis commodo quam. In id elit malesuada, pulvinar mi eu, imperdiet nulla. Nullam ac sodales dolor, eu facilisis dui. Duis commodo orci ut dolor iaculis facilisis.", + "version": 6.78 + }, + { + "name": "Sushant Bhargav", + "language": "Hindi", + "id": "PRWA7HE1GJ7OCYQM", + "bio": "Proin tempus eu risus nec mattis. Maecenas tempus neque ut porttitor malesuada. Quisque efficitur vel sapien ut imperdiet. Quisque efficitur vel sapien ut imperdiet.", + "version": 5.36 + }, + { + "name": "Monika Nayak", + "language": "Hindi", + "id": "RO0ZCWFTY6MJ66AZ", + "bio": "Sed eu libero maximus nunc lacinia lobortis et sit amet nisi. Quisque efficitur vel sapien ut imperdiet. Nam rutrum sollicitudin ante tempus consequat. Curabitur ultricies id urna nec ultrices. Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula.", + "version": 7.58 + }, + { + "name": "Guðrún Guðmundsdóttir", + "language": "Icelandic", + "id": "R1TRJT5TWANYO88D", + "bio": "Maecenas non arcu nulla. In sed ultricies lorem.", + "version": 4.65 + }, + { + "name": "Shakti Menon", + "language": "Hindi", + "id": "J1NSHQXRWA7CY0AZ", + "bio": "Vivamus id faucibus velit, id posuere leo. Etiam malesuada blandit erat, nec ultricies leo maximus sed. Nam semper gravida nunc, sit amet elementum ipsum.", + "version": 5.16 + }, + { + "name": "Ndumiso Hlatshwayo", + "language": "isiZulu", + "id": "533XA8H67VO8CSGQ", + "bio": "Quisque efficitur vel sapien ut imperdiet. Nam semper gravida nunc, sit amet elementum ipsum. Donec pellentesque ultrices mi, non consectetur eros luctus non. Vestibulum pharetra libero et velit gravida euismod. Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "version": 5.24 + }, + { + "name": "Lucky Shastry", + "language": "Hindi", + "id": "3OBF3U08WI1QF63N", + "bio": "Morbi ultricies consequat ligula posuere eleifend. Suspendisse sit amet ullamcorper sem.", + "version": 7.86 + }, + { + "name": "Pule Matlhaku", + "language": "Setswana", + "id": "UPATVXM44DAFUDI7", + "bio": "Maecenas tempus neque ut porttitor malesuada. Vivamus id faucibus velit, id posuere leo. Morbi finibus dui sed est fringilla ornare.", + "version": 4.12 + }, + { + "name": "Raju Rathore", + "language": "Hindi", + "id": "QQMNYP788DEFG4IS", + "bio": "Nam rutrum sollicitudin ante tempus consequat. Pellentesque massa sem, scelerisque sit amet odio id, cursus tempor urna. Integer vehicula, arcu sit amet egestas efficitur, orci justo interdum massa, eget ullamcorper risus ligula tristique libero.", + "version": 9.86 + }, + { + "name": "Xolani Ngcobo", + "language": "isiZulu", + "id": "SXWZ4IYT5VZA6WEE", + "bio": "Ut dictum, ligula eget sagittis maximus, tellus mi varius ex, a accumsan justo tellus vitae leo. Fusce eu ultrices elit, vel posuere neque. Curabitur quis commodo quam.", + "version": 4.77 + }, + { + "name": "Meenakshi Benjaree", + "language": "Hindi", + "id": "933PPBA946YX1K4X", + "bio": "Maecenas tempus neque ut porttitor malesuada. Duis pellentesque ultrices convallis.", + "version": 7.9 + }, + { + "name": "Ólafur Magnússon", + "language": "Icelandic", + "id": "NWY9HV455M3W8QKY", + "bio": "Morbi ultricies consequat ligula posuere eleifend. Duis pellentesque ultrices convallis. Vestibulum pharetra libero et velit gravida euismod. Ut dictum, ligula eget sagittis maximus, tellus mi varius ex, a accumsan justo tellus vitae leo. Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et.", + "version": 2.09 + }, + { + "name": "Samir Simic", + "language": "Bosnian", + "id": "6H2IO7A62ZVUXGKZ", + "bio": "Etiam malesuada blandit erat, nec ultricies leo maximus sed. Quisque maximus sodales mauris ut elementum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "version": 6.93 + }, + { + "name": "Swarnika Soni", + "language": "Hindi", + "id": "4GJF8C6P1Y5RFPMC", + "bio": "Ut maximus, libero nec facilisis fringilla, ex sem sollicitudin leo, non congue tortor ligula in eros. Nunc tincidunt laoreet laoreet.", + "version": 4.82 + }, + { + "name": "Lavanya Mittal", + "language": "Hindi", + "id": "4Z09CO5IJH7CEUD2", + "bio": "Suspendisse sit amet ullamcorper sem. Ut dictum, ligula eget sagittis maximus, tellus mi varius ex, a accumsan justo tellus vitae leo.", + "version": 1.08 + }, + { + "name": "Bontle Mokgatle", + "language": "Setswana", + "id": "4Y497GAOTAFUJDIC", + "bio": "Maecenas non arcu nulla. Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "version": 1.92 + }, + { + "name": "Prashant Chourey", + "language": "Hindi", + "id": "J4NMMNAALGOIZY8V", + "bio": "Etiam malesuada blandit erat, nec ultricies leo maximus sed. Suspendisse potenti. Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula. Ut viverra quis eros eu tincidunt.", + "version": 8.59 + }, + { + "name": "Prakash Malviya", + "language": "Hindi", + "id": "P442H9CEHIU6HAFV", + "bio": "Proin tempus eu risus nec mattis. Integer vehicula, arcu sit amet egestas efficitur, orci justo interdum massa, eget ullamcorper risus ligula tristique libero. Vivamus id faucibus velit, id posuere leo. In id elit malesuada, pulvinar mi eu, imperdiet nulla. Donec pellentesque ultrices mi, non consectetur eros luctus non.", + "version": 8.21 + }, + { + "name": "Ivana Kalic", + "language": "Bosnian", + "id": "31VIE8WWDJWKE5YL", + "bio": "Quisque efficitur vel sapien ut imperdiet. Duis luctus, lacus eu aliquet convallis, purus elit malesuada ex, vitae rutrum ipsum dui ut magna.", + "version": 6.99 + }, + { + "name": "Ajeet Vasav", + "language": "Hindi", + "id": "ODNPTWVSRBPII0BH", + "bio": "Aenean finibus in tortor vel aliquet. Integer vehicula, arcu sit amet egestas efficitur, orci justo interdum massa, eget ullamcorper risus ligula tristique libero. Morbi finibus dui sed est fringilla ornare. Morbi finibus dui sed est fringilla ornare. Ut maximus, libero nec facilisis fringilla, ex sem sollicitudin leo, non congue tortor ligula in eros.", + "version": 3.6 + }, + { + "name": "Jóhanna Jóhannsdóttir", + "language": "Icelandic", + "id": "ZI21GM8B08FVLMF0", + "bio": "In sed ultricies lorem. Etiam malesuada blandit erat, nec ultricies leo maximus sed.", + "version": 4.93 + }, + { + "name": "Seema Thapar", + "language": "Hindi", + "id": "IZSO10C5ZHVYQ5O2", + "bio": "Duis commodo orci ut dolor iaculis facilisis. Etiam mauris magna, fermentum vitae aliquet eu, cursus vitae sapien. Maecenas tempus neque ut porttitor malesuada. Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam consequat enim lorem, at tincidunt velit ultricies et.", + "version": 1.79 + }, + { + "name": "María Stefánsdóttir", + "language": "Icelandic", + "id": "KWH2RVHSB25MYGL9", + "bio": "In id elit malesuada, pulvinar mi eu, imperdiet nulla. Sed eu libero maximus nunc lacinia lobortis et sit amet nisi. Ut viverra quis eros eu tincidunt. Nam rutrum sollicitudin ante tempus consequat.", + "version": 5.21 + }, + { + "name": "Denis Terzic", + "language": "Bosnian", + "id": "1WQO4VGBS2U7DOSL", + "bio": "Ut accumsan, est vel fringilla varius, purus augue blandit nisl, eu rhoncus ligula purus vel dolor. Curabitur quis commodo quam. Curabitur ultricies id urna nec ultrices. Nam rutrum sollicitudin ante tempus consequat. Morbi finibus dui sed est fringilla ornare.", + "version": 6.32 + }, + { + "name": "Ana Livic", + "language": "Bosnian", + "id": "8JYVK7SM07YQOVQ3", + "bio": "Nam tristique feugiat est vitae mollis. Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et. Pellentesque massa sem, scelerisque sit amet odio id, cursus tempor urna. Proin tempus eu risus nec mattis. Sed eu libero maximus nunc lacinia lobortis et sit amet nisi.", + "version": 5.93 + }, + { + "name": "Bukhosi Bhengu", + "language": "isiZulu", + "id": "AFYXL0UNGMU0B1H2", + "bio": "Curabitur quis commodo quam. Curabitur sed condimentum felis, ut luctus eros. Aliquam scelerisque pretium tellus, sed accumsan est ultrices id. Aliquam scelerisque pretium tellus, sed accumsan est ultrices id. Sed nec suscipit ligula.", + "version": 9.37 + }, + { + "name": "Siyabonga Sithole", + "language": "isiZulu", + "id": "NJDX77JXV51CNGF5", + "bio": "Quisque mauris ligula, efficitur porttitor sodales ac, lacinia non ex. Sed laoreet posuere sapien, ut feugiat nibh gravida at.", + "version": 8.22 + }, + { + "name": "Meena Dubey", + "language": "Hindi", + "id": "GCJGYXSPDEFF9BTN", + "bio": "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam consequat enim lorem, at tincidunt velit ultricies et. Donec lobortis eleifend condimentum. Morbi ac tellus erat. Maecenas quis nisi nunc.", + "version": 2.95 + }, + { + "name": "Chandrika Gupta", + "language": "Hindi", + "id": "7KFJHS86WKTL6Q12", + "bio": "Aliquam sollicitudin ante ligula, eget malesuada nibh efficitur et. Suspendisse sit amet ullamcorper sem. Etiam mauris magna, fermentum vitae aliquet eu, cursus vitae sapien.", + "version": 5.35 + }, + { + "name": "Akhilesh Khare", + "language": "Hindi", + "id": "ATINHMT01VNMMDCP", + "bio": "Donec congue sapien vel euismod interdum. Suspendisse potenti. Nullam ac sodales dolor, eu facilisis dui. Nam tristique feugiat est vitae mollis. Curabitur ultricies id urna nec ultrices.", + "version": 3.68 + }, + { + "name": "Motsumi Basiang", + "language": "Setswana", + "id": "MUELSFQENUOHGBZ3", + "bio": "Cras dictum dolor lacinia lectus vehicula rutrum. Ut maximus, libero nec facilisis fringilla, ex sem sollicitudin leo, non congue tortor ligula in eros. Donec congue sapien vel euismod interdum.", + "version": 5.23 + }, + { + "name": "Neha Benjaree", + "language": "Hindi", + "id": "5VTSZUD0SA9JVL40", + "bio": "Morbi ultricies consequat ligula posuere eleifend. Nulla finibus massa at viverra facilisis. Nam tristique feugiat est vitae mollis.", + "version": 5.73 + }, + { + "name": "Kristín Sigurðardóttir", + "language": "Icelandic", + "id": "ZP5TBBYX6RI2UJ31", + "bio": "Cras dictum dolor lacinia lectus vehicula rutrum. Cras dictum dolor lacinia lectus vehicula rutrum. Duis luctus, lacus eu aliquet convallis, purus elit malesuada ex, vitae rutrum ipsum dui ut magna. Fusce congue aliquam elit ut luctus. Duis commodo orci ut dolor iaculis facilisis.", + "version": 2.8 + }, + { + "name": "Rohini Vasav", + "language": "Hindi", + "id": "UEFML43TCGS04KWM", + "bio": "Ut accumsan, est vel fringilla varius, purus augue blandit nisl, eu rhoncus ligula purus vel dolor. Ut maximus, libero nec facilisis fringilla, ex sem sollicitudin leo, non congue tortor ligula in eros. Nam rutrum sollicitudin ante tempus consequat. Aliquam scelerisque pretium tellus, sed accumsan est ultrices id. Suspendisse sit amet ullamcorper sem.", + "version": 9.3 + }, + { + "name": "Sunil Kapoor", + "language": "Hindi", + "id": "VY2A0APGVHK5NAW2", + "bio": "Proin tempus eu risus nec mattis. Ut dictum, ligula eget sagittis maximus, tellus mi varius ex, a accumsan justo tellus vitae leo. In id elit malesuada, pulvinar mi eu, imperdiet nulla.", + "version": 8.04 + }, + { + "name": "Zamokuhle Zulu", + "language": "isiZulu", + "id": "XU7BX2F8M5PVZ1EF", + "bio": "Etiam congue dignissim volutpat. Phasellus tincidunt sollicitudin posuere. Phasellus tincidunt sollicitudin posuere. Nam tristique feugiat est vitae mollis.", + "version": 8.39 + }, + { + "name": "Bhupesh Menon", + "language": "Hindi", + "id": "0CEPNRDV98KT3ORP", + "bio": "Maecenas tempus neque ut porttitor malesuada. Phasellus massa ligula, hendrerit eget efficitur eget, tincidunt in ligula. Quisque mauris ligula, efficitur porttitor sodales ac, lacinia non ex. Maecenas quis nisi nunc.", + "version": 2.69 + } +] \ No newline at end of file From 96d25fe31e22556fca6710bd5fd28cd5a6e2c847 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 27 Nov 2024 08:31:12 +1300 Subject: [PATCH 060/250] Add htmx module (#84) Need to follow up with an integration test under examples --- avaje-jex-htmx/pom.xml | 31 ++++++++++ .../java/io/avaje/jex/htmx/DHxHandler.java | 45 ++++++++++++++ .../io/avaje/jex/htmx/DHxHandlerBuilder.java | 38 ++++++++++++ .../java/io/avaje/jex/htmx/HxHandler.java | 46 ++++++++++++++ .../java/io/avaje/jex/htmx/HxHeaders.java | 62 +++++++++++++++++++ .../main/java/io/avaje/jex/htmx/HxReq.java | 49 +++++++++++++++ .../avaje/jex/htmx/TemplateContentCache.java | 30 +++++++++ .../io/avaje/jex/htmx/TemplateRender.java | 12 ++++ avaje-jex-htmx/src/main/java/module-info.java | 8 +++ pom.xml | 1 + 10 files changed, 322 insertions(+) create mode 100644 avaje-jex-htmx/pom.xml create mode 100644 avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandler.java create mode 100644 avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandlerBuilder.java create mode 100644 avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxHandler.java create mode 100644 avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxHeaders.java create mode 100644 avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxReq.java create mode 100644 avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/TemplateContentCache.java create mode 100644 avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/TemplateRender.java create mode 100644 avaje-jex-htmx/src/main/java/module-info.java diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml new file mode 100644 index 00000000..5fa2e175 --- /dev/null +++ b/avaje-jex-htmx/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + io.avaje + avaje-jex-parent + 3.0-SNAPSHOT + + + avaje-jex-htmx + + + 2.8 + + + + + io.avaje + avaje-htmx-api + ${avaje-htmx-api.version} + + + io.avaje + avaje-jex + 3.0-SNAPSHOT + + + + diff --git a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandler.java b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandler.java new file mode 100644 index 00000000..2f85fefb --- /dev/null +++ b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandler.java @@ -0,0 +1,45 @@ +package io.avaje.jex.htmx; + +import io.avaje.jex.Context; +import io.avaje.jex.ExchangeHandler; + +import java.io.IOException; + +import static io.avaje.jex.htmx.HxHeaders.*; + +final class DHxHandler implements ExchangeHandler { + + private final ExchangeHandler delegate; + private final String target; + private final String trigger; + private final String triggerName; + + DHxHandler(ExchangeHandler delegate, String target, String trigger, String triggerName) { + this.delegate = delegate; + this.target = target; + this.trigger = trigger; + this.triggerName = triggerName; + } + + @Override + public void handle(Context ctx) throws IOException { + if (ctx.header(HX_REQUEST) != null && matched(ctx)) { + delegate.handle(ctx); + } + } + + private boolean matched(Context ctx) { + if (target != null && notMatched(ctx.header(HX_TARGET), target)) { + return false; + } + if (trigger != null && notMatched(ctx.header(HX_TRIGGER), trigger)) { + return false; + } + return triggerName == null || !notMatched(ctx.header(HX_TRIGGER_NAME), triggerName); + } + + private boolean notMatched(String header, String matchValue) { + return header == null || !matchValue.equals(header); + } + +} diff --git a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandlerBuilder.java b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandlerBuilder.java new file mode 100644 index 00000000..5cc18313 --- /dev/null +++ b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandlerBuilder.java @@ -0,0 +1,38 @@ +package io.avaje.jex.htmx; + +import io.avaje.jex.ExchangeHandler; + +final class DHxHandlerBuilder implements HxHandler.Builder { + + private final ExchangeHandler delegate; + private String target; + private String trigger; + private String triggerName; + + DHxHandlerBuilder(ExchangeHandler delegate) { + this.delegate = delegate; + } + + @Override + public DHxHandlerBuilder target(String target) { + this.target = target; + return this; + } + + @Override + public DHxHandlerBuilder trigger(String trigger) { + this.trigger = trigger; + return this; + } + + @Override + public DHxHandlerBuilder triggerName(String triggerName) { + this.triggerName = triggerName; + return this; + } + + @Override + public ExchangeHandler build() { + return new DHxHandler(delegate, target, trigger, triggerName); + } +} diff --git a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxHandler.java b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxHandler.java new file mode 100644 index 00000000..55b61293 --- /dev/null +++ b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxHandler.java @@ -0,0 +1,46 @@ +package io.avaje.jex.htmx; + +import io.avaje.jex.ExchangeHandler; + +/** + * Wrap a Handler with filtering for Htmx specific headers. + *

    + * The underlying Handler will not be invoked unless the request + * is a Htmx request and matches the required attributes. + */ +public interface HxHandler { + + /** + * Create a builder that wraps the underlying handler with Htmx + * specific attribute matching. + */ + static Builder builder(ExchangeHandler delegate) { + return new DHxHandlerBuilder(delegate); + } + + /** + * Build the Htmx request handler. + */ + interface Builder { + + /** + * Match on the given target. + */ + Builder target(String target); + + /** + * Match on the given trigger. + */ + Builder trigger(String trigger); + + /** + * Match on the given trigger name. + */ + Builder triggerName(String triggerName); + + /** + * Build and return the Handler. + */ + ExchangeHandler build(); + } +} diff --git a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxHeaders.java b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxHeaders.java new file mode 100644 index 00000000..9d2a4da5 --- /dev/null +++ b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxHeaders.java @@ -0,0 +1,62 @@ +package io.avaje.jex.htmx; + +/** + * HTMX request headers. + * + * @see Request Headers Reference + */ +public interface HxHeaders { + + /** + * Indicates that the request comes from an element that uses hx-boost. + * + * @see HX-Boosted + */ + String HX_BOOSTED = "HX-Boosted"; + + /** + * The current URL of the browser + * + * @see HX-Current-URL + */ + String HX_CURRENT_URL = "HX-Current-URL"; + + /** + * Indicates if the request is for history restoration after a miss in the local history cache. + * + * @see HX-History-Restore-Request + */ + String HX_HISTORY_RESTORE_REQUEST = "HX-History-Restore-Request"; + + /** + * Contains the user response to a hx-prompt. + * + * @see HX-Prompt + */ + String HX_PROMPT = "HX-Prompt"; + /** + * Only present and {@code true} if the request is issued by htmx. + * + * @see HX-Request + */ + String HX_REQUEST = "HX-Request"; + /** + * The {@code id} of the target element if it exists. + * + * @see HX-Target + */ + String HX_TARGET = "HX-Target"; + /** + * The {@code name} of the triggered element if it exists + * + * @see HX-Trigger-Name + */ + String HX_TRIGGER_NAME = "HX-Trigger-Name"; + /** + * The {@code id} of the triggered element if it exists. + * + * @see HX-Trigger + */ + String HX_TRIGGER = "HX-Trigger"; + +} diff --git a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxReq.java b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxReq.java new file mode 100644 index 00000000..e51274ab --- /dev/null +++ b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxReq.java @@ -0,0 +1,49 @@ +package io.avaje.jex.htmx; + +import io.avaje.htmx.api.HtmxRequest; +import io.avaje.jex.Context; + +/** + * Obtain the HtmxRequest for the given Jex Context. + */ +public final class HxReq { + + /** + * Create given the server request. + */ + public static HtmxRequest of(Context ctx) { + String header = ctx.header(HxHeaders.HX_REQUEST); + if (header == null) { + return HtmxRequest.EMPTY; + } + + var builder = HtmxRequest.builder(); + if (ctx.header(HxHeaders.HX_BOOSTED) != null) { + builder.boosted(true); + } + if (ctx.header(HxHeaders.HX_HISTORY_RESTORE_REQUEST) != null) { + builder.historyRestoreRequest(true); + } + var currentUrl = ctx.header(HxHeaders.HX_CURRENT_URL); + if (currentUrl != null) { + builder.currentUrl(currentUrl); + } + var prompt = ctx.header(HxHeaders.HX_PROMPT); + if (prompt != null) { + builder.promptResponse(prompt); + } + var target = ctx.header(HxHeaders.HX_TARGET); + if (target != null) { + builder.target(target); + } + var triggerName = ctx.header(HxHeaders.HX_TRIGGER_NAME); + if (triggerName != null) { + builder.triggerName(triggerName); + } + var trigger = ctx.header(HxHeaders.HX_TRIGGER); + if (trigger != null) { + builder.triggerId(trigger); + } + return builder.build(); + } +} diff --git a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/TemplateContentCache.java b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/TemplateContentCache.java new file mode 100644 index 00000000..a2df8740 --- /dev/null +++ b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/TemplateContentCache.java @@ -0,0 +1,30 @@ +package io.avaje.jex.htmx; + +import io.avaje.jex.Context; + +/** + * Defines caching of template content. + */ +public interface TemplateContentCache { + + /** + * Return the key given the request. + */ + String key(Context req); + + /** + * Return the key given the request with form parameters. + */ + String key(Context req, Object formParams); + + /** + * Return the content given the key. + */ + String content(String key); + + /** + * Put the content into the cache. + */ + void contentPut(String key, String content); + +} diff --git a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/TemplateRender.java b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/TemplateRender.java new file mode 100644 index 00000000..9dd8f9d2 --- /dev/null +++ b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/TemplateRender.java @@ -0,0 +1,12 @@ +package io.avaje.jex.htmx; + +/** + * Template render API. + */ +public interface TemplateRender { + + /** + * Render the given template view model to the server response. + */ + String render(Object viewModel); +} diff --git a/avaje-jex-htmx/src/main/java/module-info.java b/avaje-jex-htmx/src/main/java/module-info.java new file mode 100644 index 00000000..5dea08d2 --- /dev/null +++ b/avaje-jex-htmx/src/main/java/module-info.java @@ -0,0 +1,8 @@ +module io.avaje.jex.htmx { + + requires transitive io.avaje.jex; + requires transitive io.avaje.htmx.api; + requires transitive jdk.httpserver; + + exports io.avaje.jex.htmx; +} diff --git a/pom.xml b/pom.xml index 913c9c77..5954a6f6 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ avaje-jex-test avaje-jex-freemarker avaje-jex-mustache + avaje-jex-htmx From 98f23dd7fa33b8d0c15beaba3313feeb76389e7b Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 27 Nov 2024 08:43:03 +1300 Subject: [PATCH 061/250] Version 3.0-RC1 --- avaje-jex-freemarker/pom.xml | 6 +++--- avaje-jex-htmx/pom.xml | 4 ++-- avaje-jex-mustache/pom.xml | 6 +++--- avaje-jex-test/pom.xml | 4 ++-- avaje-jex/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 4 ++-- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 2006c846..9299ffdc 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-SNAPSHOT + 3.0-RC1 avaje-jex-freemarker @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-SNAPSHOT + 3.0-RC1 provided @@ -39,7 +39,7 @@ io.avaje avaje-jex-test - 3.0-SNAPSHOT + 3.0-RC1 test diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 5fa2e175..5b227653 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-SNAPSHOT + 3.0-RC1 avaje-jex-htmx @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-SNAPSHOT + 3.0-RC1 diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 07c6dc50..fc08e244 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-SNAPSHOT + 3.0-RC1 avaje-jex-mustache @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-SNAPSHOT + 3.0-RC1 provided @@ -40,7 +40,7 @@ io.avaje avaje-jex-test - 3.0-SNAPSHOT + 3.0-RC1 test diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 87aaa183..339052fd 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-SNAPSHOT + 3.0-RC1 avaje-jex-test @@ -14,7 +14,7 @@ io.avaje avaje-jex - 3.0-SNAPSHOT + 3.0-RC1 provided diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index bccc6c62..37a1e7ad 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-SNAPSHOT + 3.0-RC1 avaje-jex diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 5eb24826..a786d3e3 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-SNAPSHOT + 3.0-RC1 diff --git a/examples/pom.xml b/examples/pom.xml index 4bf8bc13..9c05dae8 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-SNAPSHOT + 3.0-RC1 examples diff --git a/pom.xml b/pom.xml index 5954a6f6..ce1fb0e8 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.avaje avaje-jex-parent - 3.0-SNAPSHOT + 3.0-RC1 pom @@ -22,7 +22,7 @@ 2.18.1 false 21 - 2024-10-25T04:21:12Z + 2024-11-26T19:40:11Z From 223a4a99f7da4bc377b297cc819ad1638384454e Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:05:08 -0500 Subject: [PATCH 062/250] add some header methods (#86) --- .../src/main/java/io/avaje/jex/BootJexState.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/Context.java | 11 +++++++++++ avaje-jex/src/main/java/io/avaje/jex/Jex.java | 1 + .../main/java/io/avaje/jex/core/HealthPlugin.java | 2 +- .../src/main/java/io/avaje/jex/jdk/JdkContext.java | 12 ++++++++++++ .../main/java/io/avaje/jex/{ => spi}/JexPlugin.java | 4 +++- 6 files changed, 29 insertions(+), 3 deletions(-) rename avaje-jex/src/main/java/io/avaje/jex/{ => spi}/JexPlugin.java (78%) diff --git a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java b/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java index 49fb66c7..c98435e8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java +++ b/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java @@ -26,7 +26,7 @@ State create(BeanScope beanScope) { JexConfig config = jex.config(); int port = config.port(); - config.port(Config.getInt("jex.port", port)); + config.port(Config.getInt("server.port", port)); jex.lifecycle().onShutdown(beanScope::close); return new State(jex.start()); diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index cd6c85ec..fcdf0309 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -328,12 +328,23 @@ default Context render(String name) { */ Context header(String key, String value); + /** + * Set the response header. + * + * @param key The header key + * @param value The header value + */ + Context header(String key, List value); + /** Set the response headers using the provided map. */ default Context headers(Map headers) { headers.forEach(this::header); return this; } + /** Set the response headers using the provided map. */ + Context headerMap(Map> headers); + /** * Return the response header. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 8610fd57..f42f5218 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -4,6 +4,7 @@ import java.util.function.Consumer; import io.avaje.inject.BeanScope; +import io.avaje.jex.spi.JexPlugin; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java b/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java index 67835081..bc8aca4f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java @@ -3,7 +3,7 @@ import io.avaje.jex.AppLifecycle; import io.avaje.jex.Context; import io.avaje.jex.Jex; -import io.avaje.jex.JexPlugin; +import io.avaje.jex.spi.JexPlugin; /** * Health plugin with liveness and readiness support based on diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index 0b0efeab..3561ec81 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -417,6 +417,18 @@ public Context header(String key, String value) { return this; } + @Override + public Context header(String key, List value) { + exchange.getResponseHeaders().put(key, value); + return this; + } + + @Override + public Context headerMap(Map> map) { + exchange.getResponseHeaders().putAll(map); + return this; + } + @Override public String host() { return header(HeaderKeys.HOST); diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexPlugin.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java similarity index 78% rename from avaje-jex/src/main/java/io/avaje/jex/JexPlugin.java rename to avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java index 805402cb..7fda05b6 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexPlugin.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java @@ -1,4 +1,6 @@ -package io.avaje.jex; +package io.avaje.jex.spi; + +import io.avaje.jex.Jex; /** * A plugin that can register things like routes, exception handlers etc. From d662b6029e14f8fd91cced58aa7b0e156f29454d Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:25:39 -0500 Subject: [PATCH 063/250] ServiceLoad JexPlugins (#87) --- .../src/main/java/io/avaje/jex/DJex.java | 6 ++ .../main/java/io/avaje/jex/DJexConfig.java | 26 +++--- .../src/main/java/io/avaje/jex/JexConfig.java | 80 +++++-------------- .../io/avaje/jex/core/CoreServiceLoader.java | 7 ++ .../java/io/avaje/jex/spi/JexExtension.java | 2 +- .../main/java/io/avaje/jex/spi/JexPlugin.java | 3 +- 6 files changed, 50 insertions(+), 74 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index f5fd7320..cededa19 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -1,6 +1,7 @@ package io.avaje.jex; import io.avaje.inject.BeanScope; +import io.avaje.jex.core.CoreServiceLoader; import io.avaje.jex.core.CoreServiceManager; import io.avaje.jex.core.HealthPlugin; import io.avaje.jex.jdk.JdkServerStart; @@ -111,6 +112,11 @@ public Server start() { if (config.health()) { plugin(new HealthPlugin()); } + + if (!config.useSpiPlugins()) { + CoreServiceLoader.plugins().forEach(p -> p.apply(this)); + } + final SpiRoutes routes = new RoutesBuilder( this.routing, this.config.ignoreTrailingSlashes()) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index f67c624f..41c8b214 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -22,12 +22,11 @@ final class DJexConfig implements JexConfig { private boolean health = true; private boolean ignoreTrailingSlashes = true; private Executor executor; - - private boolean preCompressStaticFiles; private JsonService jsonService; private final Map renderers = new HashMap<>(); private SSLContext sslContext; - private final CompressionConfig compression= new CompressionConfig(); + private boolean useJexSpi = true; + private final CompressionConfig compression = new CompressionConfig(); @Override public JexConfig port(int port) { @@ -59,12 +58,6 @@ public JexConfig ignoreTrailingSlashes(boolean ignoreTrailingSlashes) { return this; } - @Override - public JexConfig preCompressStaticFiles(boolean preCompressStaticFiles) { - this.preCompressStaticFiles = preCompressStaticFiles; - return this; - } - @Override public JexConfig jsonService(JsonService jsonService) { this.jsonService = jsonService; @@ -118,11 +111,6 @@ public boolean ignoreTrailingSlashes() { return ignoreTrailingSlashes; } - @Override - public boolean preCompressStaticFiles() { - return preCompressStaticFiles; - } - @Override public JsonService jsonService() { return jsonService; @@ -154,4 +142,14 @@ public JexConfig compression(Consumer consumer) { public CompressionConfig compression() { return compression; } + + @Override + public DJexConfig disableSpiPlugins() { + useJexSpi = false; + return this; + } + + boolean useSpiPlugins() { + return useJexSpi; + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index d9290bc2..cc29dfc5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -3,7 +3,6 @@ import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; import java.util.function.Consumer; import javax.net.ssl.SSLContext; @@ -12,97 +11,60 @@ import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; -/** - * Jex configuration. - */ +/** Jex configuration. */ public sealed interface JexConfig permits DJexConfig { - /** - * Set the port to use. Defaults to 7001. - */ + /** Set the port to use. Defaults to 7001. */ JexConfig port(int port); - /** - * Set the host to bind to. - */ + /** Set the host to bind to. */ JexConfig host(String host); - /** - * Set the contextPath. - */ + /** Set the contextPath. */ JexConfig contextPath(String contextPath); - /** - * Set to true to include the health endpoint. Defaults to true. - */ + /** Set to true to include the health endpoint. Defaults to true. */ JexConfig health(boolean health); - /** - * Set to true to ignore trailing slashes. Defaults to true. - */ + /** Set to true to ignore trailing slashes. Defaults to true. */ JexConfig ignoreTrailingSlashes(boolean ignoreTrailingSlashes); - /** - * Set to true to pre compress static files. Defaults to false. - */ - JexConfig preCompressStaticFiles(boolean preCompressStaticFiles); - - /** - * Set the JsonService to use. - */ + /** Set the JsonService to use. */ JexConfig jsonService(JsonService jsonService); /** * Register a template renderer explicitly. * * @param extension The extension the renderer applies to. - * @param renderer The template render to use for the given extension. + * @param renderer The template render to use for the given extension. */ JexConfig renderer(String extension, TemplateRender renderer); - /** - * Set executor for serving requests. - */ + /** Set executor for serving requests. */ JexConfig executor(Executor executor); /** - * Executor for serving requests. Defaults to a {@link Executors#newVirtualThreadPerTaskExecutor()} + * Executor for serving requests. Defaults to a {@link + * Executors#newVirtualThreadPerTaskExecutor()} */ Executor executor(); - /** - * Return the port to use. - */ + /** Return the port to use. */ int port(); - /** - * Return the host to bind to. - */ + /** Return the host to bind to. */ String host(); - /** - * Return the contextPath to use. - */ + /** Return the contextPath to use. */ String contextPath(); - /** - * Return true to include the health endpoint. - */ + /** Return true to include the health endpoint. */ boolean health(); - /** - * Return true to ignore trailing slashes. - */ + /** Return true to ignore trailing slashes. */ boolean ignoreTrailingSlashes(); - /** - * Return true if static files should be pre compressed. - */ - boolean preCompressStaticFiles(); - - /** - * Return the JsonService. - */ + /** Return the JsonService. */ JsonService jsonService(); /** Return the ssl context if https is enabled. */ @@ -111,13 +73,15 @@ public sealed interface JexConfig permits DJexConfig { /** Enable https with the provided SSLContext. */ JexConfig sslContext(SSLContext ssl); - /** - * Return the template renderers registered by extension. - */ + /** Return the template renderers registered by extension. */ Map renderers(); + /** configure compression via consumer */ JexConfig compression(Consumer consumer); + /** get compression configuration */ CompressionConfig compression(); + /** whether to disable JexPlugins loaded from ServiceLoader */ + JexConfig disableSpiPlugins(); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java index 386df653..422be797 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java @@ -6,6 +6,7 @@ import java.util.ServiceLoader; import io.avaje.jex.spi.JexExtension; +import io.avaje.jex.spi.JexPlugin; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; @@ -16,6 +17,7 @@ public final class CoreServiceLoader { private final JsonService jsonService; private final List renders = new ArrayList<>(); + private final List plugins = new ArrayList<>(); CoreServiceLoader() { JsonService spiJsonService = null; @@ -23,6 +25,7 @@ public final class CoreServiceLoader { switch (spi) { case JsonService s -> spiJsonService = s; case TemplateRender r -> renders.add(r); + case JexPlugin p -> plugins.add(p); } } jsonService = spiJsonService; @@ -35,4 +38,8 @@ public static Optional jsonService() { public static List getRenders() { return INSTANCE.renders; } + + public static List plugins() { + return INSTANCE.plugins; + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java index bc179508..6ed8e586 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java @@ -9,4 +9,4 @@ * META-INF/services/io.avaje.jex.spi.JexExtension } for it to be loaded by Jex */ @Service -public sealed interface JexExtension permits JsonService, TemplateRender {} +public sealed interface JexExtension permits JsonService, TemplateRender, JexPlugin {} diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java index 7fda05b6..7c476f80 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java @@ -5,7 +5,8 @@ /** * A plugin that can register things like routes, exception handlers etc. */ -public interface JexPlugin { +@FunctionalInterface +public non-sealed interface JexPlugin extends JexExtension{ /** * Register the plugin features with jex. From 7a83a2685da67cb83bf9126e0733b76a9000f377 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 26 Nov 2024 20:18:31 -0500 Subject: [PATCH 064/250] Handle Exception Handlers Throwing Exceptions (#88) * handle exception handler errors * test * re-order exception handler parameter * prevent recursive loop --- avaje-jex/src/main/java/io/avaje/jex/DJex.java | 2 +- .../src/main/java/io/avaje/jex/DJexConfig.java | 2 -- .../java/io/avaje/jex/ExceptionHandler.java | 18 +++++++++++++++--- .../src/main/java/io/avaje/jex/Routing.java | 10 +++++++++- .../io/avaje/jex/core/ExceptionManager.java | 6 +++++- .../io/avaje/jex/DefaultErrorHandlingTest.java | 4 ++-- .../io/avaje/jex/jdk/ExceptionManagerTest.java | 16 ++++++++++++++-- 7 files changed, 46 insertions(+), 12 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index cededa19..dea0300b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -113,7 +113,7 @@ public Server start() { plugin(new HealthPlugin()); } - if (!config.useSpiPlugins()) { + if (config.useSpiPlugins()) { CoreServiceLoader.plugins().forEach(p -> p.apply(this)); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 41c8b214..b9f04b46 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -3,9 +3,7 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; -import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; import java.util.function.Consumer; import javax.net.ssl.SSLContext; diff --git a/avaje-jex/src/main/java/io/avaje/jex/ExceptionHandler.java b/avaje-jex/src/main/java/io/avaje/jex/ExceptionHandler.java index 1bd9666e..07aafaad 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/ExceptionHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/ExceptionHandler.java @@ -1,7 +1,19 @@ package io.avaje.jex; -public interface ExceptionHandler { - - void handle(T exception, Context ctx); +/** + * The routing error handler. Can be mapped to the error cause in {@link Routing}. + * + * @param type of throwable handled by this handler + */ +@FunctionalInterface +public interface ExceptionHandler { + /** + * Error handling consumer. Do not throw an exception from an error handler, it would make this + * error handler invalid and the exception would be ignored. + * + * @param ctx the server context + * @param exception the cause of the error + */ + void handle(Context ctx, T exception); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index bcccd07b..c9ee0de6 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -46,7 +46,15 @@ public sealed interface Routing permits DefaultRouting { */ Routing withRoles(Role... permittedRoles); - /** Register an exception handler for the given exception type. */ + /** + * Registers an error handler that handles the given type of exceptions. + * This will replace an existing error handler for the same exception class. + * + * @param exceptionClass the type of exception to handle by this handler + * @param handler the error handler + * @param exception type + * @return updated routing + */ Routing error(Class exceptionClass, ExceptionHandler handler); /** Add a group of route handlers with a common path prefix. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index 68831aa6..02871762 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -39,7 +39,11 @@ public ExceptionHandler find(Class exception void handle(SpiContext ctx, Exception e) { final ExceptionHandler handler = find(e.getClass()); if (handler != null) { - handler.handle(e, ctx); + try { + handler.handle(ctx, e); + } catch (Exception ex) { + unhandledException(ctx, ex); + } } else if (e instanceof HttpResponseException ex) { defaultHandling(ctx, ex); } else { diff --git a/avaje-jex/src/test/java/io/avaje/jex/DefaultErrorHandlingTest.java b/avaje-jex/src/test/java/io/avaje/jex/DefaultErrorHandlingTest.java index 0c59e178..7a3f983e 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/DefaultErrorHandlingTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/DefaultErrorHandlingTest.java @@ -42,12 +42,12 @@ void exception_expect_highestMatch() { private static class RT implements ExceptionHandler { @Override - public void handle(RuntimeException exception, Context ctx) {} + public void handle(Context ctx, RuntimeException exception) {} } private static class ISE implements ExceptionHandler { @Override - public void handle(IllegalStateException exception, Context ctx) {} + public void handle(Context ctx, IllegalStateException exception) {} } } diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java index 22e3acf5..e2a2a08c 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java @@ -3,6 +3,7 @@ import io.avaje.jex.Jex; import io.avaje.jex.http.ErrorCode; import io.avaje.jex.http.HttpResponseException; +import io.avaje.jsonb.JsonException; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; @@ -30,8 +31,12 @@ static TestPair init() { .get("/fiveHundred", ctx -> { throw new IllegalArgumentException("Bar"); }) - .error(NullPointerException.class, (exception, ctx) -> ctx.text("npe")) - .error(IllegalStateException.class, (exception, ctx) -> ctx.status(222).text("Handled IllegalStateException|" + exception.getMessage()))); + .put("/nested", ctx -> { + throw new JsonException("hmm"); + }) + .error(NullPointerException.class, (ctx, exception) -> ctx.text("npe")) + .error(IllegalStateException.class, (ctx, exception) -> ctx.status(222).text("Handled IllegalStateException|" + exception.getMessage())) + .error(JsonException.class, (ctx, exception) -> {throw new IllegalStateException();})); return TestPair.create(app); } @@ -55,6 +60,13 @@ void post() { assertThat(res.body()).isEqualTo("Handled IllegalStateException|foo"); } + @Test + void expect_fallback_to_fallback() { + HttpResponse res = pair.request().path("nested").PUT().asString(); + assertThat(res.statusCode()).isEqualTo(500); + assertThat(res.body()).isEqualTo("Internal Server Error"); + } + @Test void expect_fallback_to_default_asPlainText() { HttpResponse res = pair.request().path("conflict").GET().asString(); From 71de282cadddab06e4794bb2bca2e7babb1313d5 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 26 Nov 2024 20:45:03 -0500 Subject: [PATCH 065/250] fix static directories (#89) --- .../main/java/io/avaje/jex/StaticResourceHandlerBuilder.java | 2 +- avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java index 67541e5e..241cef96 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java @@ -63,7 +63,7 @@ public ExchangeHandler createHandler() { @Override public StaticResourceHandlerBuilder httpPath(String path) { - this.path = path; + this.path = path.endsWith("/") ? path + "*" : path; return this; } diff --git a/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java b/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java index e3cf73ce..9af60c62 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java @@ -22,7 +22,7 @@ static TestPair init() { .staticResource(b -> defaultFile(b.httpPath("/indexFile"))) .staticResource(b -> defaultCP(b.httpPath("/indexWild/*"))) .staticResource(b -> defaultFile(b.httpPath("/indexWildFile/*"))) - .staticResource(b -> defaultCP(b.httpPath("/sus/*"))) + .staticResource(b -> defaultCP(b.httpPath("/sus/"))) .staticResource(b -> defaultFile(b.httpPath("/susFile/*"))) .staticResource(b -> b.httpPath("/single").resource("/logback.xml")) .staticResource( From cef9bdbca4bef9d507b3d0fbe327f99f9346f20b Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 27 Nov 2024 17:27:57 +1300 Subject: [PATCH 066/250] Tidy routes with final classes and remove unnecessary public access (#91) --- .../java/io/avaje/jex/jdk/CtxServiceManager.java | 1 - .../main/java/io/avaje/jex/routes/PathParser.java | 14 +++++++------- .../main/java/io/avaje/jex/routes/PathSegment.java | 10 +++++----- .../io/avaje/jex/routes/PathSegmentParser.java | 2 +- .../main/java/io/avaje/jex/routes/RegBuilder.java | 2 +- .../main/java/io/avaje/jex/routes/RouteEntry.java | 2 +- .../main/java/io/avaje/jex/routes/RouteIndex.java | 2 +- .../java/io/avaje/jex/routes/RoutesBuilder.java | 2 +- .../main/java/io/avaje/jex/routes/UrlDecode.java | 13 +++++-------- 9 files changed, 22 insertions(+), 26 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java index 522578e1..7358ddaa 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java @@ -16,7 +16,6 @@ public final class CtxServiceManager implements SpiServiceManager { private final String scheme; private final String contextPath; - private final SpiServiceManager delegate; CtxServiceManager(SpiServiceManager delegate, String scheme, String contextPath) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/PathParser.java b/avaje-jex/src/main/java/io/avaje/jex/routes/PathParser.java index 231b12c0..e70402b3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/PathParser.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/PathParser.java @@ -4,7 +4,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -class PathParser { +final class PathParser { private final String rawPath; private final List paramNames = new ArrayList<>(); @@ -33,11 +33,11 @@ class PathParser { this.literal = segmentCount > 1 && regBuilder.literal(); } - public boolean matches(String url) { + boolean matches(String url) { return matchRegex.matcher(url).matches(); } - public Map extractPathParams(String uri) { + Map extractPathParams(String uri) { Map pathMap = new LinkedHashMap<>(); final List values = values(uri); for (int i = 0; i < values.size(); i++) { @@ -70,28 +70,28 @@ private PathSegment parseSegment(String segment) { /** * Return the raw path that was parsed (match path). */ - public String raw() { + String raw() { return rawPath; } /** * Return the number of path segments. */ - public int segmentCount() { + int segmentCount() { return segmentCount; } /** * Return true if one of the segments is wildcard or slash accepting. */ - public boolean multiSlash() { + boolean multiSlash() { return multiSlash; } /** * Return true if all path segments are literal. */ - public boolean literal() { + boolean literal() { return literal; } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegment.java b/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegment.java index e82fba7f..777efde6 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegment.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegment.java @@ -18,13 +18,13 @@ boolean multiSlash() { return false; } - static class SlashIgnoringParameter extends Parameter { + static final class SlashIgnoringParameter extends Parameter { SlashIgnoringParameter(String param) { super(param, "[^/]+?"); // Accepting everything except slash;); } } - static class SlashAcceptingParameter extends Parameter { + static final class SlashAcceptingParameter extends Parameter { SlashAcceptingParameter(String param) { super(param, ".+?"); // Accept everything } @@ -60,7 +60,7 @@ public void addParamName(List paramNames) { } } - static class Multi extends PathSegment { + static final class Multi extends PathSegment { private final List segments; @@ -93,7 +93,7 @@ void addParamName(List paramNames) { } } - static class Literal extends PathSegment { + static final class Literal extends PathSegment { private final String content; Literal(String content) { @@ -116,7 +116,7 @@ public void addParamName(List paramNames) { } } - static class Wildcard extends PathSegment { + static final class Wildcard extends PathSegment { @Override boolean multiSlash() { diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java b/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java index b25a69dc..f450f449 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java @@ -7,7 +7,7 @@ import static java.util.stream.Collectors.toList; -class PathSegmentParser { +final class PathSegmentParser { private static final PathSegment WILDCARD = new PathSegment.Wildcard(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RegBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RegBuilder.java index ae400018..1c97de19 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RegBuilder.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RegBuilder.java @@ -7,7 +7,7 @@ /** * Helper for PathParser to build regex for the path. */ -class RegBuilder { +final class RegBuilder { private final StringJoiner full = new StringJoiner("/"); private final StringJoiner extract = new StringJoiner("/"); private boolean trailingSlash; diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java index 25eae142..3a9ee4a3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java @@ -9,7 +9,7 @@ import io.avaje.jex.ExchangeHandler; import io.avaje.jex.security.Role; -class RouteEntry implements SpiRoutes.Entry { +final class RouteEntry implements SpiRoutes.Entry { private final AtomicLong active = new AtomicLong(); private final PathParser path; diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java index ba90afaf..a26e90c6 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java @@ -3,7 +3,7 @@ import java.util.ArrayList; import java.util.List; -class RouteIndex { +final class RouteIndex { /** * Partition entries by the number of path segments. diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java index fd614327..7d54d775 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java @@ -7,7 +7,7 @@ import java.util.EnumMap; import java.util.List; -public class RoutesBuilder { +public final class RoutesBuilder { private final EnumMap typeMap = new EnumMap<>(Routing.Type.class); private final boolean ignoreTrailingSlashes; diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/UrlDecode.java b/avaje-jex/src/main/java/io/avaje/jex/routes/UrlDecode.java index 00f0f3ef..2c58df3a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/UrlDecode.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/UrlDecode.java @@ -1,16 +1,13 @@ package io.avaje.jex.routes; -import java.io.UnsupportedEncodingException; import java.net.URLDecoder; -public class UrlDecode { +import static java.nio.charset.StandardCharsets.UTF_8; - public static String decode(String s) { - try { - return URLDecoder.decode(s.replace("+", "%2B"), "UTF-8").replace("%2B", "+"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Not expected", e); - } +final class UrlDecode { + + static String decode(String s) { + return URLDecoder.decode(s.replace("+", "%2B"), UTF_8).replace("%2B", "+"); } } From b875f0711d841064d720de9ae11d4bec8c61aed1 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 27 Nov 2024 17:40:57 +1300 Subject: [PATCH 067/250] Tidy top package with final classes and remove unnecessary public access (#92) --- .../src/main/java/io/avaje/jex/BootJexState.java | 1 - .../main/java/io/avaje/jex/ClassResourceLoader.java | 1 - avaje-jex/src/main/java/io/avaje/jex/DJex.java | 9 ++++----- .../src/main/java/io/avaje/jex/DefaultLifecycle.java | 6 +++--- .../java/io/avaje/jex/DefaultResourceLoader.java | 12 ++++-------- .../src/main/java/io/avaje/jex/DefaultRouting.java | 2 +- .../io/avaje/jex/StaticClassResourceHandler.java | 12 +++--------- .../main/java/io/avaje/jex/StaticFileHandler.java | 7 ------- .../io/avaje/jex/StaticResourceHandlerBuilder.java | 8 +------- 9 files changed, 16 insertions(+), 42 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java b/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java index c98435e8..b2c2947c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java +++ b/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java @@ -20,7 +20,6 @@ static void restart() { } State create(BeanScope beanScope) { - Jex jex = beanScope.getOptional(Jex.class).orElse(Jex.create()); jex.configureWith(beanScope); diff --git a/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java index fe6e4c11..cacf5e34 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java @@ -14,7 +14,6 @@ public interface ClassResourceLoader { static ClassResourceLoader fromClass(Class clazz) { - return new DefaultResourceLoader(clazz); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index dea0300b..a5bf11dc 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -55,7 +55,7 @@ public Routing routing() { @Override public Jex jsonService(JsonService jsonService) { - this.config.jsonService(jsonService); + config.jsonService(jsonService); return this; } @@ -84,13 +84,13 @@ public Jex configure(Consumer configure) { @Override public Jex port(int port) { - this.config.port(port); + config.port(port); return this; } @Override public Jex context(String contextPath) { - this.config.contextPath(contextPath); + config.contextPath(contextPath); return this; } @@ -118,8 +118,7 @@ public Server start() { } final SpiRoutes routes = - new RoutesBuilder( - this.routing, this.config.ignoreTrailingSlashes()) + new RoutesBuilder(routing, config.ignoreTrailingSlashes()) .build(); return new JdkServerStart().start(this, routes, CoreServiceManager.create(this)); diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultLifecycle.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultLifecycle.java index 02746e7c..8b0f9478 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultLifecycle.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultLifecycle.java @@ -18,9 +18,9 @@ final class DefaultLifecycle implements AppLifecycle { private final List shutdownRunnable = new ArrayList<>(); private final ReentrantLock lock = new ReentrantLock(); private final AtomicInteger next = new AtomicInteger(1000); + private final AtomicBoolean jvmStop = new AtomicBoolean(); private Status status = Status.STARTING; private Hook shutdownHook; - private final AtomicBoolean jvmStop = new AtomicBoolean(); @Override public void onShutdown(Runnable onShutdown) { @@ -50,7 +50,7 @@ public void registerShutdownHook(Runnable onShutdown) { } } - static class Hook extends Thread { + static final class Hook extends Thread { private final AtomicBoolean jvmStop; Hook(Runnable runnable, AtomicBoolean jvmStop) { @@ -106,7 +106,7 @@ private void removeShutdownHook() { } } - static class Pair implements Comparable { + static final class Pair implements Comparable { private final Runnable callback; private final int order; diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultResourceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultResourceLoader.java index d3473a52..29dade88 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultResourceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultResourceLoader.java @@ -10,18 +10,15 @@ final class DefaultResourceLoader implements ClassResourceLoader { private final Class clazz; DefaultResourceLoader() { - this.clazz = DefaultResourceLoader.class; } DefaultResourceLoader(Class clazz) { - this.clazz = clazz; } @Override public URL loadResource(String resourcePath) { - var url = clazz.getResource(resourcePath); if (url == null) { // search the module path for top level resource @@ -35,11 +32,10 @@ public URL loadResource(String resourcePath) { @Override public InputStream loadResourceAsStream(String resourcePath) { - - var url = clazz.getResourceAsStream(resourcePath); - if (url == null) { + var resourceStream = clazz.getResourceAsStream(resourcePath); + if (resourceStream == null) { // search the module path for top level resource - url = + resourceStream = Optional.ofNullable(ClassLoader.getSystemResourceAsStream(resourcePath)) .orElseGet( () -> @@ -47,6 +43,6 @@ public InputStream loadResourceAsStream(String resourcePath) { .getContextClassLoader() .getResourceAsStream(resourcePath)); } - return Objects.requireNonNull(url, "Unable to locate resource: " + resourcePath); + return Objects.requireNonNull(resourceStream, "Unable to locate resource: " + resourcePath); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index 3ed7a415..4237a12e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -157,7 +157,7 @@ public Routing filter(HttpFilter handler) { return this; } - private static class Entry implements Routing.Entry { + private static final class Entry implements Routing.Entry { private final Type type; private final String path; diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticClassResourceHandler.java b/avaje-jex/src/main/java/io/avaje/jex/StaticClassResourceHandler.java index f8583f1b..75f46208 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticClassResourceHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticClassResourceHandler.java @@ -1,6 +1,5 @@ package io.avaje.jex; -import java.io.IOException; import java.net.URL; import java.nio.file.Paths; import java.util.Map; @@ -29,28 +28,24 @@ final class StaticClassResourceHandler extends AbstractStaticHandler implements } @Override - public void handle(Context ctx) throws IOException { - - final var jdkExchange = ctx.exchange(); - + public void handle(Context ctx) { if (singleFile != null) { sendURL(ctx, singleFile.getPath(), singleFile); return; } + final var jdkExchange = ctx.exchange(); if (skipFilePredicate.test(ctx)) { throw404(jdkExchange); } final String wholeUrlPath = jdkExchange.getRequestURI().getPath(); - if (wholeUrlPath.endsWith("/") || wholeUrlPath.equals(urlPrefix)) { sendURL(ctx, indexFile.getPath(), indexFile); return; } final String urlPath = wholeUrlPath.substring(urlPrefix.length()); - final String normalizedPath = Paths.get(filesystemRoot, urlPath).normalize().toString().replace("\\", "/"); @@ -67,8 +62,7 @@ public void handle(Context ctx) throws IOException { } } - private void sendURL(Context ctx, String urlPath, URL path) throws IOException { - + private void sendURL(Context ctx, String urlPath, URL path) { try (var fis = path.openStream()) { ctx.header("Content-Type", lookupMime(urlPath)); ctx.headers(headers); diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java b/avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java index 46527427..22e7ee1e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java @@ -29,9 +29,7 @@ final class StaticFileHandler extends AbstractStaticHandler implements ExchangeH @Override public void handle(Context ctx) throws IOException { - final var jdkExchange = ctx.exchange(); - if (singleFile != null) { sendFile(ctx, jdkExchange, singleFile.getPath(), singleFile); return; @@ -42,19 +40,15 @@ public void handle(Context ctx) throws IOException { } final String wholeUrlPath = jdkExchange.getRequestURI().getPath(); - if (wholeUrlPath.endsWith("/") || wholeUrlPath.equals(urlPrefix)) { sendFile(ctx, jdkExchange, indexFile.getPath(), indexFile); - return; } final String urlPath = wholeUrlPath.substring(urlPrefix.length()); - File canonicalFile; try { canonicalFile = new File(filesystemRoot, urlPath).getCanonicalFile(); - } catch (IOException e) { // This may be more benign (i.e. not an attack, just a 403), // but we don't want an attacker to be able to discern the difference. @@ -73,7 +67,6 @@ public void handle(Context ctx) throws IOException { private void sendFile(Context ctx, HttpExchange jdkExchange, String urlPath, File canonicalFile) throws IOException { try (var fis = new FileInputStream(canonicalFile)) { - String mimeType = lookupMime(urlPath); ctx.header("Content-Type", mimeType); ctx.headers(headers); diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java index 241cef96..f58ec14f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java @@ -29,20 +29,18 @@ final class StaticResourceHandlerBuilder implements StaticContentConfig { private StaticResourceHandlerBuilder() {} - public static StaticResourceHandlerBuilder builder() { + static StaticResourceHandlerBuilder builder() { return new StaticResourceHandlerBuilder(); } @Override public ExchangeHandler createHandler() { - path = Objects.requireNonNull(path) .transform(this::prependSlash) .transform(s -> s.endsWith("/*") ? s.substring(0, s.length() - 2) : s); final var isClasspath = location == CLASS_PATH; - root = isClasspath ? root.transform(this::prependSlash) : root; if (isClasspath && "/".equals(root)) { throw new IllegalArgumentException( @@ -128,7 +126,6 @@ private ExchangeHandler fileLoader(Function fileLoader) { File singleFile = null; if (directoryIndex != null) { try { - dirIndex = fileLoader.apply(root.transform(this::appendSlash) + directoryIndex).getCanonicalFile(); @@ -139,7 +136,6 @@ private ExchangeHandler fileLoader(Function fileLoader) { } } else { try { - singleFile = fileLoader.apply(root).getCanonicalFile(); fsRoot = singleFile.getParentFile().getPath(); @@ -153,12 +149,10 @@ private ExchangeHandler fileLoader(Function fileLoader) { } private ExchangeHandler classPathHandler() { - URL dirIndex = null; URL singleFile = null; if (directoryIndex != null) { dirIndex = resourceLoader.loadResource(root.transform(this::appendSlash) + directoryIndex); - } else { singleFile = resourceLoader.loadResource(root); } From 1b3cf8b5c2a7bae1e0f0c109792e35584bd84423 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 27 Nov 2024 00:11:54 -0500 Subject: [PATCH 068/250] remove SpiContext (#93) --- .../src/main/java/io/avaje/jex/Context.java | 4 +- .../main/java/io/avaje/jex/FilterChain.java | 7 +-- .../src/main/java/io/avaje/jex/Routing.java | 2 - .../compression/CompressedOutputStream.java | 13 +++-- .../jex/compression/CompressionConfig.java | 50 ++++++++++++++++--- .../avaje/jex/compression/package-info.java | 2 + .../core/{HeaderKeys.java => Constants.java} | 12 ++++- .../io/avaje/jex/core/CoreServiceManager.java | 6 +-- .../io/avaje/jex/core/ExceptionManager.java | 15 +++--- .../io/avaje/jex/core/SpiServiceManager.java | 12 ++--- .../java/io/avaje/jex/jdk/BaseHandler.java | 5 +- .../io/avaje/jex/jdk/CtxServiceManager.java | 11 ++-- .../java/io/avaje/jex/jdk/JdkContext.java | 42 +++++++--------- .../src/main/java/io/avaje/jex/jdk/Mode.java | 8 +++ .../java/io/avaje/jex/jdk/RoutingFilter.java | 7 ++- .../main/java/io/avaje/jex/package-info.java | 16 ++++++ .../jex/security/BasicAuthCredentials.java | 6 +++ .../java/io/avaje/jex/spi/JexExtension.java | 2 +- .../main/java/io/avaje/jex/spi/JexPlugin.java | 11 ++-- .../java/io/avaje/jex/spi/JsonService.java | 3 +- .../java/io/avaje/jex/spi/SpiContext.java | 40 --------------- .../java/io/avaje/jex/spi/TemplateRender.java | 6 ++- .../java/io/avaje/jex/spi/package-info.java | 2 + .../jex/compression/CompressionTest.java | 16 +++--- 24 files changed, 164 insertions(+), 134 deletions(-) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/compression/package-info.java rename avaje-jex/src/main/java/io/avaje/jex/core/{HeaderKeys.java => Constants.java} (61%) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/jdk/Mode.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/package-info.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/spi/SpiContext.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/spi/package-info.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index fcdf0309..fed8f2e8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -14,7 +14,7 @@ import com.sun.net.httpserver.HttpExchange; -import io.avaje.jex.core.HeaderKeys; +import io.avaje.jex.core.Constants; import io.avaje.jex.security.BasicAuthCredentials; import io.avaje.jex.security.Role; @@ -232,7 +232,7 @@ default String fullUrl() { * Return the request user agent, or null. */ default String userAgent() { - return header(HeaderKeys.USER_AGENT); + return header(Constants.USER_AGENT); } /** diff --git a/avaje-jex/src/main/java/io/avaje/jex/FilterChain.java b/avaje-jex/src/main/java/io/avaje/jex/FilterChain.java index 033f6aaf..2e636459 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/FilterChain.java +++ b/avaje-jex/src/main/java/io/avaje/jex/FilterChain.java @@ -4,14 +4,15 @@ import com.sun.net.httpserver.HttpExchange; +/** Filter chain that contains all subsequent filters that are configured, as well as the final route. */ @FunctionalInterface public interface FilterChain { /** * Calls the next filter in the chain, or else the user's exchange handler, if this is the final - * filter in the chain. The {@link HttpFilter} may decide to terminate the chain, by not - * calling this method. In this case, the filter must send the response to the request, - * because the application's {@linkplain HttpExchange exchange} handler will not be invoked. + * filter in the chain. The {@link HttpFilter} may decide to terminate the chain, by not calling + * this method. In this case, the filter must send the response to the request, because the + * application's {@linkplain HttpExchange exchange} handler will not be invoked. * * @throws IOException if an I/O error occurs */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index c9ee0de6..40c7cd39 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -154,8 +154,6 @@ interface Entry { /** The type of route entry. */ enum Type { - /** Http Filter. */ - FILTER, /** Http GET. */ GET, /** Http POST. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java index 44ebb36f..a067c21d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java @@ -6,11 +6,10 @@ import java.util.Optional; import io.avaje.jex.Context; -import io.avaje.jex.core.HeaderKeys; -import io.avaje.jex.spi.SpiContext; +import io.avaje.jex.core.Constants; /** - * CompressedOutputStream class that conditionally compresses the output based on configuration and + * OutputStream implementation that conditionally compresses the output based on configuration and * request headers. */ public class CompressedOutputStream extends OutputStream { @@ -24,7 +23,7 @@ public class CompressedOutputStream extends OutputStream { private boolean compressionDecided; public CompressedOutputStream( - CompressionConfig compression, SpiContext ctx, OutputStream originStream) { + CompressionConfig compression, Context ctx, OutputStream originStream) { this.minSizeForCompression = compression.minSizeForCompression(); this.compression = compression; this.ctx = ctx; @@ -33,7 +32,7 @@ public CompressedOutputStream( private void decideCompression(int length) throws IOException { if (!compressionDecided) { - var encoding = ctx.responseHeader(HeaderKeys.CONTENT_ENCODING); + var encoding = ctx.responseHeader(Constants.CONTENT_ENCODING); if (encoding != null) { @@ -51,10 +50,10 @@ private void decideCompression(int length) throws IOException { if (compressionAllowed && length >= minSizeForCompression) { Optional compressor; - compressor = findMatchingCompressor(ctx.header(HeaderKeys.ACCEPT_ENCODING)); + compressor = findMatchingCompressor(ctx.header(Constants.ACCEPT_ENCODING)); if (compressor.isPresent()) { this.compressedStream = compressor.get().compress(originStream); - ctx.header(HeaderKeys.CONTENT_ENCODING, compressor.get().encoding()); + ctx.header(Constants.CONTENT_ENCODING, compressor.get().encoding()); } } compressionDecided = true; diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java index 33223c40..50293f50 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java @@ -4,9 +4,11 @@ import java.util.Map; import java.util.Set; +/** Configuration class for compression settings. */ public class CompressionConfig { private static final int HTTP_PACKET_SIZE = 1500; + private static final Set excludedMimeTypes = Set.of( "application/compress", @@ -16,44 +18,72 @@ public class CompressionConfig { "application/brotli", "application/x-xz", "application/x-rar-compressed"); + private boolean enabled = true; + private int minSizeForCompression = HTTP_PACKET_SIZE; + private final Map compressors = new HashMap<>(Map.of(GzipCompressor.ENCODING, new GzipCompressor())); + private final Set allowedExcludedTypes = Set.of("image/svg+xml"); /** - * set default gzip compressor level + * Sets the default GZIP compression level. * - * @param level the new compression level (0-9) + * @param level The new compression level (0-9). */ public void gzipCompressionLevel(int level) { compressors.put(GzipCompressor.ENCODING, new GzipCompressor(level)); } + /** Disables compression. */ public void disableCompression() { enabled = false; compressors.clear(); } + /** + * Gets the minimum size for compression. + * + * @return The minimum size for compression. + */ public int minSizeForCompression() { return minSizeForCompression; } + /** + * Sets the minimum size for compression and returns the updated configuration. + * + * @param minSizeForCompression The new minimum size for compression. + * @return The updated configuration. + * @throws IllegalArgumentException If the minimum size is less than a network packet size. + */ public CompressionConfig minSizeForCompression(int minSizeForCompression) { this.minSizeForCompression = minSizeForCompression; - if (minSizeForCompression < HTTP_PACKET_SIZE) + if (minSizeForCompression < HTTP_PACKET_SIZE) { throw new IllegalArgumentException( - "Compression should only happen on payloads bigger than an http packect"); + "Compression should only happen on payloads bigger than an http packet"); + } return this; } + /** + * Checks if compression is enabled. + * + * @return True if compression is enabled, false otherwise. + */ public boolean compressionEnabled() { return enabled; } + /** + * Determines if a given content type is allowed for compression. + * + * @param contentType The content type to check. + * @return True if the content type is allowed for compression, false otherwise. + */ public boolean allowsForCompression(String contentType) { - return contentType == null || allowedExcludedTypes.contains(contentType) || !excludedMimeTypes.contains(contentType) @@ -62,7 +92,13 @@ public boolean allowsForCompression(String contentType) { && !contentType.startsWith("video/"); } - Compressor forType(String type) { - return compressors.get(type.toLowerCase()); + /** + * Gets the appropriate compressor for a given encoding type. + * + * @param encoding The Content-Encoding value. + * @return The compressor for the given Content-Encoding value, or null if not found. + */ + Compressor forType(String encoding) { + return compressors.get(encoding.toLowerCase()); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/package-info.java b/avaje-jex/src/main/java/io/avaje/jex/compression/package-info.java new file mode 100644 index 00000000..e054aa39 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/package-info.java @@ -0,0 +1,2 @@ +/** Classes Governing Http Compression */ +package io.avaje.jex.compression; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/HeaderKeys.java b/avaje-jex/src/main/java/io/avaje/jex/core/Constants.java similarity index 61% rename from avaje-jex/src/main/java/io/avaje/jex/core/HeaderKeys.java rename to avaje-jex/src/main/java/io/avaje/jex/core/Constants.java index 71a6b839..3d644975 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/HeaderKeys.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/Constants.java @@ -1,8 +1,8 @@ package io.avaje.jex.core; -public final class HeaderKeys { +public final class Constants { - private HeaderKeys() {} + private Constants() {} public static final String ACCEPT = "Accept"; public static final String CONTENT_ENCODING = "Content-Encoding"; @@ -16,4 +16,12 @@ private HeaderKeys() {} public static final String HOST = "Host"; public static final String USER_AGENT = "User-Agent"; public static final String ACCEPT_ENCODING = "Accept-Encoding"; + + public static final String TEXT_HTML = "text/html"; + public static final String TEXT_PLAIN = "text/plain"; + public static final String TEXT_HTML_UTF8 = "text/html;charset=utf-8"; + public static final String TEXT_PLAIN_UTF8 = "text/plain;charset=utf-8"; + public static final String APPLICATION_JSON = "application/json"; + public static final String APPLICATION_X_JSON_STREAM = "application/x-json-stream"; + } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java index 3ddd1e83..1ef59b1d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java @@ -20,8 +20,8 @@ import io.avaje.jex.Routing; import io.avaje.jex.core.json.JacksonJsonService; import io.avaje.jex.core.json.JsonbJsonService; +import io.avaje.jex.jdk.JdkContext; import io.avaje.jex.spi.JsonService; -import io.avaje.jex.spi.SpiContext; import io.avaje.jex.spi.TemplateRender; /** @@ -90,7 +90,7 @@ public Routing.Type lookupRoutingType(String method) { } @Override - public void handleException(SpiContext ctx, Exception e) { + public void handleException(JdkContext ctx, Exception e) { exceptionHandler.handle(ctx, e); } @@ -102,7 +102,7 @@ public void render(Context ctx, String name, Map model) { @Override public String requestCharset(Context ctx) { - return parseCharset(ctx.header(HeaderKeys.CONTENT_TYPE)); + return parseCharset(ctx.header(Constants.CONTENT_TYPE)); } static String parseCharset(String header) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index 02871762..fa956fe7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -5,11 +5,12 @@ import java.util.Map; import io.avaje.applog.AppLog; +import io.avaje.jex.Context; import io.avaje.jex.ExceptionHandler; import io.avaje.jex.http.ErrorCode; import io.avaje.jex.http.HttpResponseException; import io.avaje.jex.http.InternalServerErrorException; -import io.avaje.jex.spi.SpiContext; +import io.avaje.jex.jdk.JdkContext; public final class ExceptionManager { @@ -36,7 +37,7 @@ public ExceptionHandler find(Class exception return null; } - void handle(SpiContext ctx, Exception e) { + void handle(JdkContext ctx, Exception e) { final ExceptionHandler handler = find(e.getClass()); if (handler != null) { try { @@ -51,12 +52,12 @@ void handle(SpiContext ctx, Exception e) { } } - private void unhandledException(SpiContext ctx, Exception e) { + private void unhandledException(JdkContext ctx, Exception e) { log.log(WARNING, "Uncaught exception", e); defaultHandling(ctx, new InternalServerErrorException(ErrorCode.INTERNAL_SERVER_ERROR.message())); } - private void defaultHandling(SpiContext ctx, HttpResponseException exception) { + private void defaultHandling(JdkContext ctx, HttpResponseException exception) { ctx.status(exception.getStatus()); if (exception.getStatus() == ErrorCode.REDIRECT.status()) { ctx.performRedirect(); @@ -80,9 +81,9 @@ private String jsonEscape(String message) { return message; } - private boolean useJson(SpiContext ctx) { - final String acceptHeader = ctx.header(HeaderKeys.ACCEPT); + private boolean useJson(Context ctx) { + final String acceptHeader = ctx.header(Constants.ACCEPT); return (acceptHeader != null && acceptHeader.contains(APPLICATION_JSON) - || APPLICATION_JSON.equals(ctx.responseHeader(HeaderKeys.CONTENT_TYPE))); + || APPLICATION_JSON.equals(ctx.responseHeader(Constants.CONTENT_TYPE))); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java index 3586ce15..ab60303b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java @@ -1,10 +1,5 @@ package io.avaje.jex.core; -import io.avaje.jex.Context; -import io.avaje.jex.Routing; -import io.avaje.jex.jdk.CtxServiceManager; -import io.avaje.jex.spi.SpiContext; - import java.io.InputStream; import java.io.OutputStream; import java.util.Iterator; @@ -12,6 +7,11 @@ import java.util.Map; import java.util.stream.Stream; +import io.avaje.jex.Context; +import io.avaje.jex.Routing; +import io.avaje.jex.jdk.CtxServiceManager; +import io.avaje.jex.jdk.JdkContext; + /** * Core service methods available to Context implementations. */ @@ -50,7 +50,7 @@ public sealed interface SpiServiceManager permits CoreServiceManager, CtxService /** * Handle the exception. */ - void handleException(SpiContext ctx, Exception e); + void handleException(JdkContext ctx, Exception e); /** * Render using template manager. diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java index 34fc3db2..cec1cd79 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java @@ -6,7 +6,6 @@ import com.sun.net.httpserver.HttpHandler; import io.avaje.jex.ExchangeHandler; -import io.avaje.jex.Routing.Type; import io.avaje.jex.routes.SpiRoutes; final class BaseHandler implements HttpHandler { @@ -27,8 +26,8 @@ public void handle(HttpExchange exchange) throws IOException { ExchangeHandler handlerConsumer = (ExchangeHandler) exchange.getAttribute("SpiRoutes.Entry.Handler"); - ctx.setMode(null); + ctx.setMode(Mode.EXCHANGE); handlerConsumer.handle(ctx); - ctx.setMode(Type.FILTER); + ctx.setMode(Mode.AFTER); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java index 7358ddaa..ce74f049 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java @@ -1,10 +1,5 @@ package io.avaje.jex.jdk; -import io.avaje.jex.Context; -import io.avaje.jex.Routing; -import io.avaje.jex.core.SpiServiceManager; -import io.avaje.jex.spi.SpiContext; - import java.io.InputStream; import java.io.OutputStream; import java.util.Iterator; @@ -12,6 +7,10 @@ import java.util.Map; import java.util.stream.Stream; +import io.avaje.jex.Context; +import io.avaje.jex.Routing; +import io.avaje.jex.core.SpiServiceManager; + public final class CtxServiceManager implements SpiServiceManager { private final String scheme; @@ -71,7 +70,7 @@ public Routing.Type lookupRoutingType(String method) { } @Override - public void handleException(SpiContext ctx, Exception e) { + public void handleException(JdkContext ctx, Exception e) { delegate.handleException(ctx, e); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index 3561ec81..49d9eae4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -1,5 +1,9 @@ package io.avaje.jex.jdk; +import static io.avaje.jex.core.Constants.APPLICATION_JSON; +import static io.avaje.jex.core.Constants.APPLICATION_X_JSON_STREAM; +import static io.avaje.jex.core.Constants.TEXT_HTML_UTF8; +import static io.avaje.jex.core.Constants.TEXT_PLAIN_UTF8; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; @@ -24,17 +28,15 @@ import com.sun.net.httpserver.HttpExchange; import io.avaje.jex.Context; -import io.avaje.jex.Routing; import io.avaje.jex.compression.CompressedOutputStream; import io.avaje.jex.compression.CompressionConfig; -import io.avaje.jex.core.HeaderKeys; +import io.avaje.jex.core.Constants; import io.avaje.jex.http.ErrorCode; import io.avaje.jex.http.RedirectException; import io.avaje.jex.security.BasicAuthCredentials; import io.avaje.jex.security.Role; -import io.avaje.jex.spi.SpiContext; -final class JdkContext implements Context, SpiContext { +public final class JdkContext implements Context { private static final String UTF8 = "UTF8"; private static final int SC_MOVED_TEMPORARILY = 302; @@ -46,11 +48,11 @@ final class JdkContext implements Context, SpiContext { private final Map pathParams; private final Set roles; private final HttpExchange exchange; - private Routing.Type mode; + private Mode mode; private Map> formParams; private Map> queryParams; private Map cookieMap; - private int statusCode = 200; + private int statusCode; private String characterEncoding; JdkContext( @@ -158,19 +160,18 @@ public void redirect(String location) { @Override public void redirect(String location, int statusCode) { - header(HeaderKeys.LOCATION, location); + header(Constants.LOCATION, location); status(statusCode); - if (mode == Routing.Type.FILTER) { + if (mode != Mode.EXCHANGE) { throw new RedirectException(ErrorCode.REDIRECT.message()); } else { performRedirect(); } } - @Override public void performRedirect() { try { - exchange.sendResponseHeaders(statusCode(), 0); + exchange.sendResponseHeaders(statusCode(), -1); exchange.getResponseBody().close(); } catch (IOException e) { throw new UncheckedIOException(e); @@ -179,7 +180,7 @@ public void performRedirect() { @Override public T bodyAsClass(Class beanType) { - return mgr.jsonRead(beanType, inputStream()); + return mgr.jsonRead(beanType, bodyAsInputStream()); } @Override @@ -210,13 +211,13 @@ public String body() { @Override public long contentLength() { - final String len = header(HeaderKeys.CONTENT_LENGTH); + final String len = header(Constants.CONTENT_LENGTH); return len == null ? 0 : Long.parseLong(len); } @Override public String contentType() { - return header(exchange.getRequestHeaders(), HeaderKeys.CONTENT_TYPE); + return header(exchange.getRequestHeaders(), Constants.CONTENT_TYPE); } @Override @@ -231,7 +232,7 @@ private String header(Headers headers, String name) { @Override public Context contentType(String contentType) { - exchange.getResponseHeaders().set(HeaderKeys.CONTENT_TYPE, contentType); + exchange.getResponseHeaders().set(Constants.CONTENT_TYPE, contentType); return this; } @@ -431,7 +432,7 @@ public Context headerMap(Map> map) { @Override public String host() { - return header(HeaderKeys.HOST); + return header(Constants.HOST); } @Override @@ -464,22 +465,15 @@ public String protocol() { return exchange.getProtocol(); } - @Override public OutputStream outputStream() { var out = mgr.createOutputStream(this); if (compressionConfig.compressionEnabled()) { - return new CompressedOutputStream(compressionConfig, this,out); + return new CompressedOutputStream(compressionConfig, this, out); } return out; } - @Override - public InputStream inputStream() { - return exchange.getRequestBody(); - } - - @Override - public void setMode(Routing.Type type) { + public void setMode(Mode type) { this.mode = type; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/Mode.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/Mode.java new file mode 100644 index 00000000..bd0e6fd5 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/Mode.java @@ -0,0 +1,8 @@ +package io.avaje.jex.jdk; + +/** status of the request */ +enum Mode { + BEFORE, + EXCHANGE, + AFTER +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java index 16e0ffe3..551e248a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java @@ -9,11 +9,9 @@ import io.avaje.jex.ExchangeHandler; import io.avaje.jex.Routing; -import io.avaje.jex.Routing.Type; import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.http.NotFoundException; import io.avaje.jex.routes.SpiRoutes; -import io.avaje.jex.spi.SpiContext; final class RoutingFilter extends Filter { @@ -41,6 +39,7 @@ public void doFilter(HttpExchange exchange, Filter.Chain chain) { var ctx = new JdkContext(mgr, compressionConfig, exchange, uri, Set.of()); routes.inc(); try { + ctx.setMode(Mode.BEFORE); processNoRoute(ctx, uri, routeType); exchange.setAttribute("JdkContext", ctx); chain.doFilter(exchange); @@ -59,7 +58,7 @@ public void doFilter(HttpExchange exchange, Filter.Chain chain) { new JdkContext( mgr, compressionConfig, exchange, route.matchPath(), params, route.roles()); try { - ctx.setMode(Type.FILTER); + ctx.setMode(Mode.BEFORE); exchange.setAttribute("JdkContext", ctx); ExchangeHandler handlerConsumer = route::handle; exchange.setAttribute("SpiRoutes.Entry.Handler", handlerConsumer); @@ -81,7 +80,7 @@ private void handleNoResponse(HttpExchange exchange) throws IOException { } } - private void handleException(SpiContext ctx, Exception e) { + private void handleException(JdkContext ctx, Exception e) { mgr.handleException(ctx, e); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/package-info.java b/avaje-jex/src/main/java/io/avaje/jex/package-info.java new file mode 100644 index 00000000..2d8b21c2 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/package-info.java @@ -0,0 +1,16 @@ +/** + * Avaje Jex API - see {@link io.avaje.jex.Jex}. + * + *

    {@code
    + * final Jex.Server app = Jex.create()
    + *   .routing(routing -> routing
    + *     .get("/", ctx -> ctx.text("hello world"))
    + *     .get("/one", ctx -> ctx.text("one"))
    + *   .port(8080)
    + *   .start();
    + *
    + * app.shutdown();
    + *
    + * }
    + */ +package io.avaje.jex; diff --git a/avaje-jex/src/main/java/io/avaje/jex/security/BasicAuthCredentials.java b/avaje-jex/src/main/java/io/avaje/jex/security/BasicAuthCredentials.java index 97d2a20b..9b8e75df 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/security/BasicAuthCredentials.java +++ b/avaje-jex/src/main/java/io/avaje/jex/security/BasicAuthCredentials.java @@ -1,3 +1,9 @@ package io.avaje.jex.security; +/** + * Http Basic Auth credentials + * + * @param userName the username + * @param password the password + */ public record BasicAuthCredentials(String userName, String password) {} diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java index 6ed8e586..0c0f465e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java @@ -6,7 +6,7 @@ * Extension point for all Jex SPI interfaces * *

    All types that implement this interface must be registered as an entry in {@code - * META-INF/services/io.avaje.jex.spi.JexExtension } for it to be loaded by Jex + * META-INF/services/io.avaje.jex.spi.JexExtension } for it to be auto loaded */ @Service public sealed interface JexExtension permits JsonService, TemplateRender, JexPlugin {} diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java index 7c476f80..035f9581 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java @@ -3,13 +3,14 @@ import io.avaje.jex.Jex; /** - * A plugin that can register things like routes, exception handlers etc. + * A plugin that can register things like routes, exception handlers and configure the current Jex + * instance. + * + * @see {@link JexExtension} for SPI registration details. */ @FunctionalInterface -public non-sealed interface JexPlugin extends JexExtension{ +public non-sealed interface JexPlugin extends JexExtension { - /** - * Register the plugin features with jex. - */ + /** Register the plugin features with jex. */ void apply(Jex jex); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java index d068622d..ed0d22d4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java @@ -5,9 +5,8 @@ import java.util.Iterator; /** - * **JsonService** * - *

    A service responsible for handling JSON-based request and response bodies. + *

    Service responsible for handling JSON-based request and response bodies. * * @see {@link JexExtension} for SPI registration details. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiContext.java b/avaje-jex/src/main/java/io/avaje/jex/spi/SpiContext.java deleted file mode 100644 index 29a6ef9e..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiContext.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.avaje.jex.spi; - -import io.avaje.jex.Context; -import io.avaje.jex.Routing; - -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Extension to Context for processing the request. - */ -public interface SpiContext extends Context { - - String TEXT_HTML = "text/html"; - String TEXT_PLAIN = "text/plain"; - String TEXT_HTML_UTF8 = "text/html;charset=utf-8"; - String TEXT_PLAIN_UTF8 = "text/plain;charset=utf-8"; - String APPLICATION_JSON = "application/json"; - String APPLICATION_X_JSON_STREAM = "application/x-json-stream"; - - /** - * Return the response outputStream to write content to. - */ - OutputStream outputStream(); - - /** - * Return the request inputStream to read content from. - */ - InputStream inputStream(); - - /** - * Set to indicate BEFORE, ExchangeHandler and AFTER modes of the request. - */ - void setMode(Routing.Type type); - - /** - * Perform the redirect as part of Exception handling. Typically due to before handler. - */ - void performRedirect(); -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java b/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java index f0bebaa4..c1cd94c2 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java @@ -6,6 +6,8 @@ /** * Template rendering typically of html. + * + * @see {@link JexExtension} for SPI registration details. */ public non-sealed interface TemplateRender extends JexExtension { @@ -21,8 +23,8 @@ public non-sealed interface TemplateRender extends JexExtension { * Render the template and model typically as html to the given context. * * @param context The context to render the template to - * @param name The template name - * @param model The model of key value pairs used when rendering the template + * @param name The template name + * @param model The model of key value pairs used when rendering the template */ void render(Context context, String name, Map model); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/package-info.java b/avaje-jex/src/main/java/io/avaje/jex/spi/package-info.java new file mode 100644 index 00000000..9a38d0bc --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/package-info.java @@ -0,0 +1,2 @@ +/** SPI extension interfaces */ +package io.avaje.jex.spi; diff --git a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java index 2f775c4c..f53529a7 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java @@ -9,7 +9,7 @@ import io.avaje.jex.Jex; import io.avaje.jex.ResourceLocation; -import io.avaje.jex.core.HeaderKeys; +import io.avaje.jex.core.Constants; import io.avaje.jex.jdk.TestPair; class CompressionTest { @@ -25,7 +25,7 @@ static TestPair init() { r -> r.get( "/forced", - ctx -> ctx.header(HeaderKeys.CONTENT_ENCODING, "gzip").text("hi"))) + ctx -> ctx.header(Constants.CONTENT_ENCODING, "gzip").text("hi"))) .staticResource( b -> b.location(ResourceLocation.FILE) @@ -43,24 +43,24 @@ static void end() { @Test void testCompression() { HttpResponse res = - pair.request().header(HeaderKeys.ACCEPT_ENCODING, "gzip").path("compress").GET().asString(); + pair.request().header(Constants.ACCEPT_ENCODING, "gzip").path("compress").GET().asString(); assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.headers().firstValue(HeaderKeys.CONTENT_ENCODING)).contains("gzip"); + assertThat(res.headers().firstValue(Constants.CONTENT_ENCODING)).contains("gzip"); } @Test void testNoCompression() { HttpResponse res = - pair.request().header(HeaderKeys.ACCEPT_ENCODING, "gzip").path("sus").GET().asString(); + pair.request().header(Constants.ACCEPT_ENCODING, "gzip").path("sus").GET().asString(); assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.headers().firstValue(HeaderKeys.CONTENT_ENCODING)).isEmpty(); + assertThat(res.headers().firstValue(Constants.CONTENT_ENCODING)).isEmpty(); } @Test void testForcedCompression() { HttpResponse res = - pair.request().header(HeaderKeys.ACCEPT_ENCODING, "gzip").path("forced").GET().asString(); + pair.request().header(Constants.ACCEPT_ENCODING, "gzip").path("forced").GET().asString(); assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.headers().firstValue(HeaderKeys.CONTENT_ENCODING)).contains("gzip"); + assertThat(res.headers().firstValue(Constants.CONTENT_ENCODING)).contains("gzip"); } } From ee6c65d87f9486d006486ff9c53d084ae90d73b1 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 27 Nov 2024 18:34:54 +1300 Subject: [PATCH 069/250] Version 3.0-RC2 --- avaje-jex-freemarker/pom.xml | 6 +++--- avaje-jex-htmx/pom.xml | 4 ++-- avaje-jex-mustache/pom.xml | 6 +++--- avaje-jex-test/pom.xml | 4 ++-- avaje-jex/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 4 ++-- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 9299ffdc..77262b1a 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC1 + 3.0-RC2 avaje-jex-freemarker @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-RC1 + 3.0-RC2 provided @@ -39,7 +39,7 @@ io.avaje avaje-jex-test - 3.0-RC1 + 3.0-RC2 test diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 5b227653..a4a6b683 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC1 + 3.0-RC2 avaje-jex-htmx @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC1 + 3.0-RC2 diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index fc08e244..272656fa 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC1 + 3.0-RC2 avaje-jex-mustache @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-RC1 + 3.0-RC2 provided @@ -40,7 +40,7 @@ io.avaje avaje-jex-test - 3.0-RC1 + 3.0-RC2 test diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 339052fd..85efa16c 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC1 + 3.0-RC2 avaje-jex-test @@ -14,7 +14,7 @@ io.avaje avaje-jex - 3.0-RC1 + 3.0-RC2 provided diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 37a1e7ad..c149565f 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC1 + 3.0-RC2 avaje-jex diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index a786d3e3..3b3eb358 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC1 + 3.0-RC2 diff --git a/examples/pom.xml b/examples/pom.xml index 9c05dae8..f051ad89 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC1 + 3.0-RC2 examples diff --git a/pom.xml b/pom.xml index ce1fb0e8..502b6bd0 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.avaje avaje-jex-parent - 3.0-RC1 + 3.0-RC2 pom @@ -22,7 +22,7 @@ 2.18.1 false 21 - 2024-11-26T19:40:11Z + 2024-11-27T04:53:12Z From c983500f4c8ca4080e53068b208aa2d4d598d907 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 27 Nov 2024 00:48:47 -0500 Subject: [PATCH 070/250] Update README.md --- README.md | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 05433ead..ac4affff 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,17 @@ [![Maven Central](https://img.shields.io/maven-central/v/io.avaje/avaje-jex.svg?label=Maven%20Central)](https://mvnrepository.com/artifact/io.avaje/avaje-jex) [![javadoc](https://javadoc.io/badge2/io.avaje/avaje-jex/javadoc.svg?color=purple)](https://javadoc.io/doc/io.avaje/avaje-jex) -# avaje-jex +# Avaje-Jex -Javalin style wrapper over the JDK's [`jdk.httpserver`](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html) `HttpServer` classes. +Lightweight (~120KB) wrapper over the JDK's [`jdk.httpserver`](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html) `HttpServer` classes. + +Features: + +- Static resource handling +- Compression SPI (will use gzip by default) +- Json SPI (will use either Avaje JsonB or Jackson if available) +- Virtual threads enabled by default +- [Context](https://javadoc.io/doc/io.avaje/avaje-jex/latest/io.avaje.jex/io/avaje/jex/Context.html) abstraction over `HttpExchange` to easily retrieve and send request/response data. ```java var app = Jex.create() @@ -20,6 +28,29 @@ var app = Jex.create() chain.proceed(); System.out.println("after request"); })) + .staticResource( + b -> + b.httpPath("/myResource") + .resource("/public") + .directoryIndex("index.html")) .port(8080) .start(); ``` + +### Alternate `HttpServer` Implementations + +As the JDK provides an SPI to swap the underlying `HttpServer`, you can easily use jex with alternate implementations by adding them as a dependency. + +An example would be [@robaho's implementation](https://github.com/robaho/httpserver?tab=readme-ov-file#performance) where performance seems to be increased by 10x in certain benchmarks. + +```xml + + io.github.robaho + httpserver + 1.0.9 + +``` + +See also: + +- [Javalin](https://github.com/javalin/javalin) (A lightweight wrapper over Jetty) From f98b4e396fe1f9143484930b20db05e4f2c542c4 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:54:05 -0500 Subject: [PATCH 071/250] add avaje http instruction --- README.md | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 125 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ac4affff..30f6508c 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,12 @@ Lightweight (~120KB) wrapper over the JDK's [`jdk.httpserver`](https://docs.orac Features: +- [Context](https://javadoc.io/doc/io.avaje/avaje-jex/latest/io.avaje.jex/io/avaje/jex/Context.html) abstraction over `HttpExchange` to easily retrieve and send request/response data. +- Fluent API - Static resource handling -- Compression SPI (will use gzip by default) +- Compression SPI (will use gzip by default if the caller accepts it and the response exceeds a certain size) - Json SPI (will use either Avaje JsonB or Jackson if available) - Virtual threads enabled by default -- [Context](https://javadoc.io/doc/io.avaje/avaje-jex/latest/io.avaje.jex/io/avaje/jex/Context.html) abstraction over `HttpExchange` to easily retrieve and send request/response data. ```java var app = Jex.create() @@ -37,13 +38,19 @@ var app = Jex.create() .start(); ``` -### Alternate `HttpServer` Implementations +## Alternate `HttpServer` Implementations -As the JDK provides an SPI to swap the underlying `HttpServer`, you can easily use jex with alternate implementations by adding them as a dependency. +The JDK provides an SPI to swap the underlying `HttpServer`, so you can easily use jex with alternate implementations by adding them as a dependency. -An example would be [@robaho's implementation](https://github.com/robaho/httpserver?tab=readme-ov-file#performance) where performance seems to be increased by 10x in certain benchmarks. +An example would be [@robaho's implementation](https://github.com/robaho/httpserver?tab=readme-ov-file#performance) where performance seems to be increased by 10x over the default in certain benchmarks. ```xml + + io.avaje + avaje-jex + 3.0-RC2 + + io.github.robaho httpserver @@ -51,6 +58,119 @@ An example would be [@robaho's implementation](https://github.com/robaho/httpser ``` +## Use with [Avaje Http](https://avaje.io/http/) + +If you find yourself pining for the JAX-RS style of controllers, you can have avaje http generate jex adapters for your annotated classes. + +### Add dependencies +```xml + + io.avaje + avaje-jex + 3.0-RC2 + + + + io.avaje + avaje-http-api + 2.9-RC2 + + + + + io.avaje + avaje-http-sigma-generator + 2.9-RC2 + provided + true + +``` + +#### JDK 23+ + +In JDK 23+, annotation processors are disabled by default, you will need to add a flag to re-enable. +```xml + + full + +``` + +### Define a Controller +```java +package org.example.hello; + +import io.avaje.http.api.Controller; +import io.avaje.http.api.Get; +import java.util.List; + +@Controller("/widgets") +public class WidgetController { + private final HelloComponent hello; + public WidgetController(HelloComponent hello) { + this.hello = hello; + } + + @Get("/{id}") + Widget getById(int id) { + return new Widget(id, "you got it"+ hello.hello()); + } + + @Get() + List getAll() { + return List.of(new Widget(1, "Rob"), new Widget(2, "Fi")); + } + + record Widget(int id, String name){}; +} +``` + +This will generate routing code that we can register using any JSR-330 compliant DI: + +```java +@Generated("avaje-jex-generator") +@Singleton +public class WidgetController$Route implements Routing.HttpService { + + private final WidgetController controller; + + public WidgetController$Route(WidgetController controller) { + this.controller = controller; + } + + @Override + public void add(Routing routing) { + routing.get("/widgets/{id}", this::_getById); + routing.get("/widgets", this::_getAll); + } + + private void _getById(Context ctx) throws IOException { + ctx.status(200); + var id = asInt(ctx.pathParam("id")); + ctx.json(controller.getById(id)); + } + + private void _getAll(Context ctx) throws IOException { + ctx.status(200); + ctx.json(controller.getAll()); + } + +} +``` + +### JSR-330 DI Usage +You can use whatever DI library you like. + +```java +public class Main { + + public static void main(String[] args ) { + + List services = // Retrieve HttpServices via DI; + Jex.create().routing(services).start(); + } +} +``` + See also: - [Javalin](https://github.com/javalin/javalin) (A lightweight wrapper over Jetty) From a6cc29a3b5b80705a942be8dec7e4b8ccdfd1b28 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:18:04 -0500 Subject: [PATCH 072/250] move filterChain and ResourceLocation (#94) --- .../main/java/io/avaje/jex/FilterChain.java | 20 ---------------- .../main/java/io/avaje/jex/HttpFilter.java | 24 +++++++++++++++++-- .../java/io/avaje/jex/ResourceLocation.java | 6 ----- .../io/avaje/jex/StaticContentConfig.java | 5 ++++ .../jex/StaticResourceHandlerBuilder.java | 2 +- .../java/io/avaje/jex/StaticFileTest.java | 1 + .../jex/compression/CompressionTest.java | 2 +- 7 files changed, 30 insertions(+), 30 deletions(-) delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/FilterChain.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/ResourceLocation.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/FilterChain.java b/avaje-jex/src/main/java/io/avaje/jex/FilterChain.java deleted file mode 100644 index 2e636459..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/FilterChain.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.avaje.jex; - -import java.io.IOException; - -import com.sun.net.httpserver.HttpExchange; - -/** Filter chain that contains all subsequent filters that are configured, as well as the final route. */ -@FunctionalInterface -public interface FilterChain { - - /** - * Calls the next filter in the chain, or else the user's exchange handler, if this is the final - * filter in the chain. The {@link HttpFilter} may decide to terminate the chain, by not calling - * this method. In this case, the filter must send the response to the request, because the - * application's {@linkplain HttpExchange exchange} handler will not be invoked. - * - * @throws IOException if an I/O error occurs - */ - void proceed() throws IOException; -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java b/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java index 982cff0a..5d5b56b4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java @@ -2,14 +2,16 @@ import java.io.IOException; +import com.sun.net.httpserver.HttpExchange; + /** * A filter used to pre- and post-process incoming requests. Pre-processing occurs before the * application's exchange handler is invoked, and post-processing occurs after the exchange handler * returns. Filters are organized in chains, and are associated with {@link Context} instances. * *

    Each {@code HttpFilter} in the chain, invokes the next filter within its own {@link - * #filter(Context, FilterChain)} implementation. The final {@code HttpFilter} in the chain invokes the - * applications exchange handler. + * #filter(Context, FilterChain)} implementation. The final {@code HttpFilter} in the chain invokes + * the applications exchange handler. */ @FunctionalInterface public interface HttpFilter { @@ -36,4 +38,22 @@ public interface HttpFilter { * @param chain the {@code FilterChain} which allows the next filter to be invoked */ void filter(Context ctx, FilterChain chain) throws IOException; + + /** + * Filter chain that contains all subsequent filters that are configured, as well as the final + * route. + */ + @FunctionalInterface + public interface FilterChain { + + /** + * Calls the next filter in the chain, or else the user's exchange handler, if this is the final + * filter in the chain. The {@link HttpFilter} may decide to terminate the chain, by not calling + * this method. In this case, the filter must send the response to the request, because + * the application's {@linkplain HttpExchange exchange} handler will not be invoked. + * + * @throws IOException if an I/O error occurs + */ + void proceed() throws IOException; + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/ResourceLocation.java b/avaje-jex/src/main/java/io/avaje/jex/ResourceLocation.java deleted file mode 100644 index cd5da741..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/ResourceLocation.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.avaje.jex; - -public enum ResourceLocation { - CLASS_PATH, - FILE -} \ No newline at end of file diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java b/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java index 14aa1e11..1f666375 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java @@ -90,4 +90,9 @@ static StaticContentConfig create() { * @return the updated configuration */ StaticContentConfig location(ResourceLocation location); + + public enum ResourceLocation { + CLASS_PATH, + FILE + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java index f58ec14f..832ce2f3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java @@ -1,6 +1,6 @@ package io.avaje.jex; -import static io.avaje.jex.ResourceLocation.CLASS_PATH; +import static io.avaje.jex.StaticContentConfig.ResourceLocation.CLASS_PATH; import java.io.File; import java.net.URL; diff --git a/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java b/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java index 9af60c62..e470ad66 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; +import io.avaje.jex.StaticContentConfig.ResourceLocation; import io.avaje.jex.jdk.TestPair; class StaticFileTest { diff --git a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java index f53529a7..ac9ab042 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.Test; import io.avaje.jex.Jex; -import io.avaje.jex.ResourceLocation; +import io.avaje.jex.StaticContentConfig.ResourceLocation; import io.avaje.jex.core.Constants; import io.avaje.jex.jdk.TestPair; From a42478468ef7164773fe6e8497480d92aa5d9564 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:39:39 -0500 Subject: [PATCH 073/250] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 30f6508c..fb078a5b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ # Avaje-Jex -Lightweight (~120KB) wrapper over the JDK's [`jdk.httpserver`](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html) `HttpServer` classes. +Lightweight (~120KB) wrapper over the JDK's [`jdk.httpserver`](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html) classes. Features: @@ -15,7 +15,7 @@ Features: - Fluent API - Static resource handling - Compression SPI (will use gzip by default if the caller accepts it and the response exceeds a certain size) -- Json SPI (will use either Avaje JsonB or Jackson if available) +- Json SPI (will use either Avaje Jsonb or Jackson if available) - Virtual threads enabled by default ```java From d22688b09ac7a4f4cc583bfe0fd5f7058aab1f0b Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Thu, 28 Nov 2024 07:36:41 +1300 Subject: [PATCH 074/250] Version 3.0-RC3 --- avaje-jex-freemarker/pom.xml | 6 +++--- avaje-jex-htmx/pom.xml | 4 ++-- avaje-jex-mustache/pom.xml | 6 +++--- avaje-jex-test/pom.xml | 4 ++-- avaje-jex/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 4 ++-- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 77262b1a..7dccbe40 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC2 + 3.0-RC3 avaje-jex-freemarker @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-RC2 + 3.0-RC3 provided @@ -39,7 +39,7 @@ io.avaje avaje-jex-test - 3.0-RC2 + 3.0-RC3 test diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index a4a6b683..2eb460c8 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC2 + 3.0-RC3 avaje-jex-htmx @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC2 + 3.0-RC3 diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 272656fa..950c03bb 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC2 + 3.0-RC3 avaje-jex-mustache @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-RC2 + 3.0-RC3 provided @@ -40,7 +40,7 @@ io.avaje avaje-jex-test - 3.0-RC2 + 3.0-RC3 test diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 85efa16c..f008fe43 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC2 + 3.0-RC3 avaje-jex-test @@ -14,7 +14,7 @@ io.avaje avaje-jex - 3.0-RC2 + 3.0-RC3 provided diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index c149565f..28115d84 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC2 + 3.0-RC3 avaje-jex diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 3b3eb358..480770dc 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC2 + 3.0-RC3 diff --git a/examples/pom.xml b/examples/pom.xml index f051ad89..6744ee72 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC2 + 3.0-RC3 examples diff --git a/pom.xml b/pom.xml index 502b6bd0..b41733e1 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.avaje avaje-jex-parent - 3.0-RC2 + 3.0-RC3 pom @@ -22,7 +22,7 @@ 2.18.1 false 21 - 2024-11-27T04:53:12Z + 2024-11-27T18:36:08Z From efbaf5e60ee9461064c30e3c1c7c658eb75d2b26 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 27 Nov 2024 13:52:42 -0500 Subject: [PATCH 075/250] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fb078a5b..6fd1f2d7 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ An example would be [@robaho's implementation](https://github.com/robaho/httpser io.avaje avaje-jex - 3.0-RC2 + 3.0-RC3 @@ -67,20 +67,20 @@ If you find yourself pining for the JAX-RS style of controllers, you can have av io.avaje avaje-jex - 3.0-RC2 + 3.0-RC3 io.avaje avaje-http-api - 2.9-RC2 + 2.9-RC3 io.avaje avaje-http-sigma-generator - 2.9-RC2 + 2.9-RC3 provided true From d6f05425f2205157a4023628b7e798dc7c044835 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:23:30 -0500 Subject: [PATCH 076/250] Javadocs/Fix Port (#96) * javadocs * Update Jex.java * Update JdkJexServer.java --- .../main/java/io/avaje/jex/AppLifecycle.java | 52 ++- .../main/java/io/avaje/jex/BootJexState.java | 8 - .../io/avaje/jex/ClassResourceLoader.java | 22 +- .../src/main/java/io/avaje/jex/Context.java | 350 +++++++++++------- .../java/io/avaje/jex/ExchangeHandler.java | 19 +- .../main/java/io/avaje/jex/HttpFilter.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 151 ++++---- .../io/avaje/jex/StaticContentConfig.java | 1 + .../avaje/jex/compression/GzipCompressor.java | 1 - .../jex/core/json/JacksonJsonService.java | 8 +- .../avaje/jex/core/json/JsonbJsonService.java | 12 +- .../java/io/avaje/jex/http/ErrorCode.java | 1 + .../java/io/avaje/jex/jdk/JdkContext.java | 7 +- .../java/io/avaje/jex/jdk/JdkJexServer.java | 5 + .../java/io/avaje/jex/spi/JsonService.java | 3 +- 15 files changed, 393 insertions(+), 249 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/AppLifecycle.java b/avaje-jex/src/main/java/io/avaje/jex/AppLifecycle.java index 3eb8eafe..ae02cf73 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/AppLifecycle.java +++ b/avaje-jex/src/main/java/io/avaje/jex/AppLifecycle.java @@ -1,10 +1,9 @@ package io.avaje.jex; -/** - * Application lifecycle support. - */ +/** Defines the lifecycle configuration for an application. */ public sealed interface AppLifecycle permits DefaultLifecycle { + /** Represents the possible states of the application server. */ enum Status { STARTING, STARTED, @@ -13,41 +12,55 @@ enum Status { } /** - * Register a Runnable to run on shutdown of the server. - *

    - * This will execute after the server has deemed there are no active requests. + * Registers a runnable to be executed when the application server is shutting down. + * + *

    This runnable will be executed after all active requests have been processed. + * + * @param onShutdown The runnable to execute on shutdown. */ void onShutdown(Runnable onShutdown); /** - * Register a Runnable to run on shutdown of the server with ordering. - *

    - * The runnables are executed with order from low to high (0 means run first). - *

    - * This will execute after the server has deemed there are no active requests. + * Registers a runnable to be executed when the application server is shutting down, with a + * specific order. + * + *

    Runnables with lower order values will be executed first. * - * @param onShutdown The function to run on shutdown - * @param order The relative order to execute with 0 meaning run first + *

    This runnable will be executed after all active requests have been processed. + * + * @param onShutdown The runnable to execute on shutdown. + * @param order The order in which to execute the runnable. */ void onShutdown(Runnable onShutdown, int order); /** - * Register the runnable with the Runtime as a shutdown hook. + * Registers a runnable as a shutdown hook with the JVM. + * + *

    This runnable will be executed when the JVM is shutting down, regardless of the application + * server's state. + * + * @param onShutdown The runnable to register as a shutdown hook. */ void registerShutdownHook(Runnable onShutdown); /** - * Return the current status. + * Returns the current status of the application server. + * + * @return The current status of the server. */ Status status(); /** - * Set the current status. + * Sets the current status of the application server. + * + * @param newStatus The new status to set. */ void status(Status newStatus); /** - * Return true if status starting or started (the server is coming up). + * Indicates whether the application server is currently starting or has started. + * + * @return true if the server is starting or started, false otherwise. */ default boolean isAlive() { Status status = status(); @@ -55,11 +68,12 @@ default boolean isAlive() { } /** - * Return true the server has started. + * Indicates whether the application server has fully started. + * + * @return true if the server has started, false otherwise. */ default boolean isReady() { Status status = status(); return status == Status.STARTED; } - } diff --git a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java b/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java index b2c2947c..43e15905 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java +++ b/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java @@ -15,10 +15,6 @@ static void stop() { state.stop(); } - static void restart() { - state.restart(); - } - State create(BeanScope beanScope) { Jex jex = beanScope.getOptional(Jex.class).orElse(Jex.create()); jex.configureWith(beanScope); @@ -42,9 +38,5 @@ private static final class State { void stop() { server.shutdown(); } - - public void restart() { - server.restart(); - } } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java index cacf5e34..10bbb3ae 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java @@ -9,16 +9,34 @@ *

    When not specified Avaje Jex provides a default implementation that looks to find resources * using the class loader associated with the ClassResourceLoader. * - *

    As a fallback, {@link ClassLoader#getSystemResourceAsStream(String)} is used if the loader returns null. + *

    As a fallback, {@link ClassLoader#getSystemResourceAsStream(String)} is used if the loader + * returns null. */ public interface ClassResourceLoader { + /** + * Create a {@code ClassResourceLoader} instance based on a given Class. + * + * @param clazz The class to use for resource loading. + * @return A new {@code ClassResourceLoader} instance. + */ static ClassResourceLoader fromClass(Class clazz) { return new DefaultResourceLoader(clazz); } - /** Return the URL for the given resource or return null if it cannot be found. */ + /** + * Loads the specified resource and returns its URL. + * + * @param resourcePath The path to the resource. + * @return The URL of the resource. + */ URL loadResource(String resourcePath); + /** + * Loads the specified resource and returns an input stream to read its contents. + * + * @param resourcePath The path to the resource. + * @return An InputStream to read the resource. + */ InputStream loadResourceAsStream(String resourcePath); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index fed8f2e8..11f52c32 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -11,90 +11,127 @@ import java.util.Map; import java.util.Set; import java.util.stream.Stream; - +import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import io.avaje.jex.core.Constants; import io.avaje.jex.security.BasicAuthCredentials; import io.avaje.jex.security.Role; -/** - * Provides access to functions for handling the request and response. - */ +/** Provides access to functions for handling the request and response. */ public interface Context { /** - * Return the matched path as a raw expression. + * Returns the matched path as a raw expression, without any parameter substitution. + * + * @return The matched path as a raw string. */ String matchedPath(); /** - * Sets an attribute on the request. - *

    - * Attributes are available to other handlers in the request lifecycle + * Sets an attribute on the request, accessible to other handlers in the request lifecycle. + * + * @param key The attribute key. + * @param value The attribute value. */ Context attribute(String key, Object value); /** - * Get the specified attribute from the request. + * Gets the attribute with the specified key from the request. + * + * @param The type of the attribute. + * @param key The attribute key. + * @return The attribute value, or null if not found. */ T attribute(String key); /** - * Return a request cookie by name, or null. + * Returns the value of a cookie with the specified name from the request. + * + * @param name The name of the cookie. + * @return The value of the cookie, or null if the cookie is not found. */ String cookie(String name); /** - * Returns a map with all the cookie keys and values on the request. + * Returns a map containing all the cookie names and their corresponding values from the request. + * + * @return A map of cookie names to their values. */ Map cookieMap(); /** - * Sets a cookie with name, value with unlimited age. + * Sets a cookie with the specified name and value, with no expiration date. + * + * @param name The name of the cookie. + * @param value The value of the cookie. */ Context cookie(String name, String value); /** - * Sets a cookie with name, value, and max-age. + * Sets a cookie with the specified name, value, and maximum age in seconds. + * + * @param name The name of the cookie. + * @param value The value of the cookie. + * @param maxAge The maximum age of the cookie in seconds. */ Context cookie(String name, String value, int maxAge); /** - * Sets a Cookie. + * Sets a cookie using the provided `Cookie` object. + * + * @param cookie The cookie object to set. */ Context cookie(Cookie cookie); /** - * Remove a cookie by name. + * Removes a cookie with the specified name. + * + * @param name The name of the cookie to remove. */ Context removeCookie(String name); /** - * Remove a cookie by name and path. + * Removes a cookie with the specified name and path. + * + * @param name The name of the cookie to remove. + * @param path The path of the cookie to remove. */ Context removeCookie(String name, String path); /** - * Redirect to the specified location using 302 status code. + * Redirects the client to the specified location using a 302 (Found) status code. + * + * @param location The URL to redirect to. */ void redirect(String location); /** - * Redirect to the location specifying the response status code. + * Redirects the client to the specified location using the given HTTP status code. + * + * @param location The URL to redirect to. + * @param httpStatusCode The HTTP status code to use for the redirect. */ void redirect(String location, int httpStatusCode); - /** Roles attached to this route */ + /** + * Returns a set of roles associated with the current route. + * + * @return A set of roles. + */ Set routeRoles(); /** - * Return the request body as bytes. + * Returns the request body as a byte array. + * + * @return The request body as a byte array. */ byte[] bodyAsBytes(); /** - * Return the request body as an inputStream. + * Returns the request body as an input stream. + * + * @return The request body as an input stream. */ InputStream bodyAsInputStream(); @@ -105,29 +142,19 @@ public interface Context { */ T bodyAsClass(Class beanType); - /** - * Return the request body as String. - */ + /** Return the request body as String. */ String body(); - /** - * Return the request content length. - */ + /** Return the request content length. */ long contentLength(); - /** - * Return the request content type. - */ + /** Return the request content type. */ String contentType(); - /** - * Set the response content type. - */ + /** Set the response content type. */ Context contentType(String contentType); - /** - * Return all the path parameters as a map. - */ + /** Return all the path parameters as a map. */ Map pathParamMap(); /** @@ -154,140 +181,116 @@ default String queryParam(String name, String defaultValue) { return val != null ? val : defaultValue; } - /** - * Return all the query parameters for the given parameter name. - */ + /** Return all the query parameters for the given parameter name. */ List queryParams(String name); /** * Return all the query parameters as a map. - *

    - * Note this returns the first value for any given key if that key has multiple values. + * + *

    Note this returns the first value for any given key if that key has multiple values. */ Map queryParamMap(); - /** - * Return the request query string, or null. - */ + /** Return the request query string, or null. */ String queryString(); - /** - * Return the first form param value for the specified key or null. - */ + /** Return the first form param value for the specified key or null. */ default String formParam(String key) { return formParam(key, null); } - /** - * Return the first form param value for the specified key or the default value. - */ + /** Return the first form param value for the specified key or the default value. */ default String formParam(String key, String defaultValue) { final List values = formParamMap().get(key); return values == null || values.isEmpty() ? defaultValue : values.get(0); } - /** - * Return the form params for the specified key, or empty list. - */ + /** Return the form params for the specified key, or empty list. */ default List formParams(String key) { final List values = formParamMap().get(key); return values != null ? values : emptyList(); } - /** - * Returns a map with all the form param keys and values. - */ + /** Returns a map with all the form param keys and values. */ Map> formParamMap(); - /** - * Return the underlying JDK {@link HttpExchange} object backing the context - */ + /** Return the underlying JDK {@link HttpExchange} object backing the context */ HttpExchange exchange(); - /** - * Return the request scheme. - */ + /** Return the request scheme. */ String scheme(); - /** - * Return the request url. - */ + /** Return the request url. */ String url(); - /** - * Return the full request url, including query string (if present) - */ + /** Return the full request url, including query string (if present) */ default String fullUrl() { final String url = url(); final String qs = queryString(); return qs == null ? url : url + "?" + qs; } - /** - * Return the request context path. - */ + /** Return the request context path. */ String contextPath(); - /** - * Return the request user agent, or null. - */ + /** Return the request user agent, or null. */ default String userAgent() { return header(Constants.USER_AGENT); } - /** - * Set the status code on the response. - */ + /** Set the status code on the response. */ Context status(int statusCode); - /** - * Return the current response status. - */ + /** Return the current response status. */ int status(); - /** - * Write plain text content to the response. - */ + /** Write plain text content to the response. */ Context text(String content); - /** - * Write html content to the response. - */ + /** Write html content to the response. */ Context html(String content); /** - * Set the response body as JSON for the given bean. + * Set the content type as application/json and write the response. + * + * @param bean the object to serialize and write */ Context json(Object bean); /** - * Write the stream as a JSON stream with new line delimiters - * {@literal application/x-json-stream}. + * Write the stream as a JSON stream with new line delimiters {@literal + * application/x-json-stream}. * * @param stream The stream of beans to write as json */ Context jsonStream(Stream stream); /** - * Write the stream as a JSON stream with new line delimiters - * {@literal application/x-json-stream}. + * Write the stream as a JSON stream with new line delimiters {@literal + * application/x-json-stream}. * * @param iterator The iterator of beans to write as json */ Context jsonStream(Iterator iterator); /** - * Write raw content to the response. + * Writes the given string content directly to the response. + * + * @param content The string content to write. */ Context write(String content); /** - * Write raw bytes to the response. + * Writes the given bytes directly to the response. + * + * @param bytes The byte array to write. */ Context write(byte[] bytes); /** - * Write raw inputStream to the response. + * Writes the content from the given InputStream directly to the response body. + * + * @param is The input stream containing the content to write. */ Context write(InputStream is); @@ -303,16 +306,25 @@ default Context render(String name) { /** * Render a template typically as html with the given model. * - * @param name The template name + * @param name The template name * @param model The model used with the template */ Context render(String name, Map model); /** * Return all the request headers as a map. + * + * @return all the headers as a single value Map */ Map headerMap(); + /** + * Return underlying request headers. + * + * @return the request headers + */ + Headers headers(); + /** * Return the request header. * @@ -323,7 +335,7 @@ default Context render(String name) { /** * Set the response header. * - * @param key The header key + * @param key The header key * @param value The header value */ Context header(String key, String value); @@ -331,163 +343,235 @@ default Context render(String name) { /** * Set the response header. * - * @param key The header key + * @param key The header key * @param value The header value */ Context header(String key, List value); - /** Set the response headers using the provided map. */ + /** Add the response headers using the provided map. */ default Context headers(Map headers) { headers.forEach(this::header); return this; } - /** Set the response headers using the provided map. */ + /** + * Sets the response headers using the provided map. + * + * @param headers A map containing the header names as keys and their corresponding values as + * lists. + * @return The updated context object. + */ Context headerMap(Map> headers); /** - * Return the response header. + * Returns the value of the specified response header. + * + * @param key The name of the header. + * @return The value of the header, or null if not found. */ String responseHeader(String key); /** - * Returns the request host, or null. + * Returns the host name of the request. + * + * @return The host name of the request, or null if not available. */ String host(); /** - * Returns the request IP. + * Returns the IP address of the client making the request. + * + * @return The IP address of the client. */ String ip(); /** - * Returns the request method. + * Returns the HTTP method used in the request (e.g., GET, POST, PUT, DELETE). + * + * @return The HTTP method of the request. */ String method(); /** - * Return the request path. + * Returns the path part of the request URI. + * + * @return The path part of the request URI. */ String path(); /** - * Return the request port. + * Returns the port number used in the request. + * + * @return The port number of the request. */ int port(); /** - * Return the request protocol. + * Returns the protocol used in the request (e.g., HTTP/1.1). + * + * @return The protocol of the request. */ String protocol(); /** * Gets basic-auth credentials from the request, or throws. * - *

    Returns a wrapper object containing the Base64 decoded username - * and password from the Authorization header, or null if basic-auth is not properly configured + *

    Returns a wrapper object containing the Base64 decoded username and password from the + * Authorization header, or null if basic-auth is not properly configured */ BasicAuthCredentials basicAuthCredentials(); - interface Cookie { + /** + * This interface represents a cookie used in HTTP communication. Cookies are small pieces of data + * sent from a server to a web browser and stored on the user's computer. They can be used to + * store information about a user's session, preferences, or other data. + */ + public interface Cookie { /** - * Return an expired cookie given the name. + * Creates and returns a new expired cookie with the given name. This cookie will be sent to the + * browser but will be immediately discarded. It's useful for removing existing cookies. * * @param name The name of the cookie. - * @return The expired cookie + * @return A new expired cookie with the given name. */ static Cookie expired(String name) { return DCookie.expired(name); } /** - * Return a new cookie given the name and value. + * Creates and returns a new cookie with the given name and value. * - * @param name The name of the cookie - * @param value The cookie value - * @return The new cookie + * @param name The name of the cookie. + * @param value The value to store in the cookie. + * @return A new cookie with the given name and value. */ static Cookie of(String name, String value) { return DCookie.of(name, value); } /** - * Return the name. + * Returns the name of this cookie. + * + * @return The name of the cookie. */ String name(); /** - * Return the value. + * Returns the value stored in this cookie. + * + * @return The value of the cookie. */ String value(); /** - * Return the domain. + * Returns the domain for which this cookie is valid. + * + * @return The domain associated with the cookie, or null if not set. */ String domain(); /** - * Set the domain. + * Sets the domain for which this cookie is valid. + * + * @param domain The domain for which the cookie should be valid. + * @return A new cookie instance with the updated domain. */ Cookie domain(String domain); /** - * Return the max age. + * Returns the maximum age (in seconds) of this cookie. An expired cookie (maxAge of 0) will be + * deleted immediately by the browser. + * + * @return The maximum age of the cookie in seconds, or null if not set. */ Duration maxAge(); /** - * Set the max age. + * Sets the maximum age (in seconds) of this cookie. An expired cookie (maxAge of 0) will be + * deleted immediately by the browser. + * + * @param maxAge The maximum age of the cookie in seconds. + * @return A new cookie instance with the updated maxAge. */ Cookie maxAge(Duration maxAge); /** - * Return the cookie expiration. + * Returns the date and time when this cookie expires. + * + * @return The expiration date and time of the cookie, or null if not set. */ ZonedDateTime expires(); /** - * Set when the cookie expires. + * Sets the date and time when this cookie expires. + * + * @param expires The date and time when the cookie should expire. + * @return A new cookie instance with the updated expires value. */ Cookie expires(ZonedDateTime expires); /** - * Return the path. + * Returns the path on the server for which this cookie is valid. Cookies are only sent to the + * browser if the URL path starts with this value. + * + * @return The path associated with the cookie, or null if not set. */ String path(); /** - * Set the path. + * Sets the path on the server for which this cookie is valid. Cookies are only sent to the + * browser if the URL path starts with this value. + * + * @param path The path on the server for which the cookie should be valid. + * @return A new cookie instance with the updated path. */ Cookie path(String path); /** - * Return the secure attribute of the cookie. + * Indicates whether this cookie should only be sent over secure connections (HTTPS). + * + * @return True if the cookie should only be sent over secure connections, false otherwise. */ boolean secure(); /** - * Set the secure attribute of the cookie. + * Sets the secure attribute for this cookie. + * + *

    When enabled, the cookie will only be sent over secure HTTPS connections. + * + * @param secure {@code true} to enable the secure attribute, {@code false} to disable it + * @return this cookie instance with the updated secure attribute */ Cookie secure(boolean secure); /** - * Return the httpOnly attribute of the cookie. + * Checks if the HttpOnly attribute is enabled for this cookie. + * + *

    The HttpOnly attribute ensures that the cookie is inaccessible to JavaScript, helping to + * mitigate cross-site scripting (XSS) attacks. + * + * @return {@code true} if the cookie has the HttpOnly attribute enabled, {@code false} + * otherwise */ boolean httpOnly(); /** - * Set the httpOnly attribute of the cookie. + * Sets the HttpOnly attribute for this cookie. + * + *

    When enabled, the cookie will not be accessible via client-side scripts, providing + * additional security against XSS attacks. + * + * @param httpOnly {@code true} to enable the HttpOnly attribute, {@code false} to disable it + * @return this cookie instance with the updated HttpOnly attribute */ Cookie httpOnly(boolean httpOnly); /** - * Returns content of the cookie as a 'Set-Cookie:' header value specified - * by RFC6265. + * Returns content of the cookie as a 'Set-Cookie:' header value specified by RFC6265. */ @Override String toString(); - } - } diff --git a/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java b/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java index f3680a70..18bcedd8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java @@ -3,18 +3,25 @@ import java.io.IOException; /** - * A handler which is invoked to process HTTP exchanges. Each HTTP exchange is handled by one of - * these handlers. + * A functional interface representing an HTTP request handler. + * + *

    Implementations of this interface are responsible for processing incoming HTTP requests and + * generating appropriate responses. The {@code handle} method provides access to a {@link Context} + * object, which encapsulates the request and response details. + * + * @see Context */ @FunctionalInterface public interface ExchangeHandler { /** - * Handle the given request and generate an appropriate response. See {@link Context} for a - * description of the steps involved in handling an exchange. + * Handles the given HTTP request and generates a response. * - * @param ctx the request context containing the request from the client and used to send the - * response + *

    The {@link Context} object provides access to request information such as headers, + * parameters, and body, as well as methods for constructing and sending the response. + * + * @param ctx The context object containing the request and response details. + * @throws IOException if an I/O error occurs during request processing or response generation. */ void handle(Context ctx) throws IOException; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java b/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java index 5d5b56b4..f55ddbf1 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java @@ -5,7 +5,7 @@ import com.sun.net.httpserver.HttpExchange; /** - * A filter used to pre- and post-process incoming requests. Pre-processing occurs before the + * A filter used to pre/post-process incoming requests. Pre-processing occurs before the * application's exchange handler is invoked, and post-processing occurs after the exchange handler * returns. Filters are organized in chains, and are associated with {@link Context} instances. * diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index f42f5218..98a1f343 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -12,15 +12,14 @@ * Create configure and start Jex. * *

    {@code
    + * final Jex.Server app = Jex.create()
    + *   .routing(routing -> routing
    + *     .get("/", ctx -> ctx.text("hello world"))
    + *     .get("/one", ctx -> ctx.text("one"))
    + *   .port(8080)
    + *   .start();
      *
    - *     final Jex.Server app = Jex.create()
    - *       .routing(routing -> routing
    - *         .get("/", ctx -> ctx.text("hello world"))
    - *         .get("/one", ctx -> ctx.text("one"))
    - *       .port(8080)
    - *       .start();
    - *
    - *     app.shutdown();
    + * app.shutdown();
      *
      * }
    */ @@ -30,15 +29,14 @@ public sealed interface Jex permits DJex { * Create Jex. * *
    {@code
    +   * final Jex.Server app = Jex.create()
    +   *   .routing(routing -> routing
    +   *     .get("/", ctx -> ctx.text("hello world"))
    +   *     .get("/one", ctx -> ctx.text("one"))
    +   *   .port(8080)
    +   *   .start();
        *
    -   *     final Jex.Server app = Jex.create()
    -   *       .routing(routing -> routing
    -   *         .get("/", ctx -> ctx.text("hello world"))
    -   *         .get("/one", ctx -> ctx.text("one"))
    -   *       .port(8080)
    -   *       .start();
    -   *
    -   *     app.shutdown();
    +   * app.shutdown();
        *
        * }
    */ @@ -47,37 +45,59 @@ static Jex create() { } /** - * Set a custom attribute that can be used by an implementation. + * Sets a custom attribute that can be accessed later by the Jex instance or its components. + * + * @param The type of the attribute. + * @param cls The class of the attribute. + * @param instance The instance of the attribute. */ Jex attribute(Class cls, T instance); /** - * Return a custom attribute. + * Returns a custom attribute previously set using {@link #attribute(Class, Object)}. + * + * @param The type of the attribute. + * @param cls The class of the attribute. + * @return The attribute instance, or null if not found. */ T attribute(Class cls); /** - * Add routes and handlers to the routing. + * Adds a new HTTP route and its associated handler to the Jex routing configuration. + * + * @param routes The HTTP service to add. */ Jex routing(Routing.HttpService routes); /** - * Add many routes and handlers to the routing. + * Adds multiple HTTP routes and their associated handlers to the Jex routing configuration. + * + * @param routes A collection of HTTP services to add. */ Jex routing(Collection routes); /** - * Return the Routing to configure. + * Returns the routing configuration object, allowing for further customization. + * + * @return The routing configuration object. */ Routing routing(); - /** Add a static resource route */ + /** + * Adds a static resource route using the provided configuration. + * + * @param config The configuration for the static resource route. + */ default Jex staticResource(StaticContentConfig config) { routing().get(config.httpPath(), config.createHandler()); return this; } - /** Add a static resource route using a consumer */ + /** + * Adds a static resource route using a consumer to configure the {@link StaticContentConfig}. + * + * @param consumer The consumer to configure the static resource route. + */ default Jex staticResource(Consumer consumer) { var builder = StaticResourceHandlerBuilder.builder(); consumer.accept(builder); @@ -86,91 +106,94 @@ default Jex staticResource(Consumer consumer) { } /** - * Set the JsonService. + * Sets the JSON service to use for serialization and deserialization. + * + * @param jsonService The JSON service to use. */ Jex jsonService(JsonService jsonService); /** - * Add Plugin functionality. + * Adds a plugin to the Jex instance, extending its functionality. + * + * @param plugin The plugin to add. */ Jex plugin(JexPlugin plugin); /** - * Configure given the dependency injection scope from avaje-inject. + * Configures the Jex instance using a dependency injection scope from Avaje-Inject. + * + *

    This method allows you to leverage the Avaje-Inject framework to provide dependencies like + * Handlers, StaticResources, and Plugins to the Jex instance. * - * @param beanScope The scope potentially containing Handlers, AccessManager, Plugins etc. + * @param beanScope The Avaje-Inject BeanScope containing the dependencies. + * @return The configured Jex instance. */ Jex configureWith(BeanScope beanScope); /** - * Configure via a lambda taking the JexConfig instance. + * Configures the Jex instance using a functional approach. + * + *

    The provided consumer lambda allows you to customize the Jex configuration, such as setting + * the port, context path, and other options. + * + * @param configure A consumer lambda that accepts a `JexConfig` instance for configuration. + * @return The configured Jex instance. */ Jex configure(Consumer configure); /** - * Set the port to use. + * Sets the port number on which the Jex server will listen for incoming requests. + * + * @param port The port number to use. + * @return The updated Jex instance. */ Jex port(int port); /** - * Set the context path. + * Sets the context path for the Jex application. + * + *

    The context path is the portion of the URL that identifies the application. + * + * @param contextPath The context path to use. + * @return The updated Jex instance. */ Jex context(String contextPath); /** * Explicitly register a template renderer. - *

    - * Note that if not explicitly registered TemplateRender's can be - * automatically registered via ServiceLoader just by including them - * to the class path. * - * @param renderer The template renderer to register + *

    Note that if not explicitly registered TemplateRender's can be automatically registered via + * ServiceLoader just by including them to the class path. + * + * @param renderer The template renderer to register * @param extensions The extensions the renderer is used for */ Jex register(TemplateRender renderer, String... extensions); - /** - * Return the application lifecycle support. - */ + /** Return the application lifecycle support. */ AppLifecycle lifecycle(); - /** - * Return the configuration. - */ + /** Return the configuration. */ JexConfig config(); - /** - * Start the server. - */ + /** Start the server. */ Jex.Server start(); - /** - * The running server. - */ + /** The running server. */ interface Server { /** - * Register a function to execute LAST on shutdown after all the - * normal lifecycle shutdown functions have run. - *

    - * Typically, we desire to shut down logging (e.g. Log4J) last. + * Register a function to execute LAST on shutdown after all the normal lifecycle shutdown + * functions have run. + * + *

    Typically, we desire to shut down logging (e.g. Log4J) last. */ void onShutdown(Runnable onShutdown); - /** - * Shutdown the server. - */ + /** Shutdown the server. */ void shutdown(); - /** - * Return the port the server is using. - */ - default int port() { - throw new IllegalStateException("not supported"); - } - - default void restart() { - throw new IllegalStateException("not supported"); - } + /** The port of the server */ + int port(); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java b/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java index 1f666375..72e55c3f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java @@ -91,6 +91,7 @@ static StaticContentConfig create() { */ StaticContentConfig location(ResourceLocation location); + /** the resource location */ public enum ResourceLocation { CLASS_PATH, FILE diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/GzipCompressor.java b/avaje-jex/src/main/java/io/avaje/jex/compression/GzipCompressor.java index 6c52e9ea..c3945dc4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/compression/GzipCompressor.java +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/GzipCompressor.java @@ -6,7 +6,6 @@ final class GzipCompressor implements Compressor { static final String ENCODING = "gzip"; - private static final String EXTENSION = ".gz"; private final int level; GzipCompressor() { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java index f7f6003c..4511b928 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java @@ -12,15 +12,18 @@ import io.avaje.jex.spi.JsonService; +/** Jackson JsonService */ public final class JacksonJsonService implements JsonService { private final ObjectMapper mapper; + /** Create with defaults for Jackson */ public JacksonJsonService() { - this.mapper = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this.mapper = + new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } + /** Create with a Jackson instance that might have custom configuration. */ public JacksonJsonService(ObjectMapper mapper) { this.mapper = mapper; } @@ -28,7 +31,6 @@ public JacksonJsonService(ObjectMapper mapper) { @Override public T jsonRead(Class clazz, InputStream is) { try { - // read direct return mapper.readValue(is, clazz); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java index 7eae6747..59cac2ef 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java @@ -9,23 +9,17 @@ import java.io.OutputStream; import java.util.Iterator; -/** - * Provides JsonService using avaje-jsonb. - */ +/** Provides JsonService using avaje-jsonb. */ public final class JsonbJsonService implements JsonService { private final Jsonb jsonb; - /** - * Create with defaults for Jsonb. - */ + /** Create with defaults for Jsonb. */ public JsonbJsonService() { this.jsonb = Jsonb.builder().build(); } - /** - * Create with a Jsonb instance that might have custom configuration. - */ + /** Create with a Jsonb instance that might have custom configuration. */ public JsonbJsonService(Jsonb jsonb) { this.jsonb = jsonb; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java b/avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java index f6b4bad8..d9686381 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java @@ -1,5 +1,6 @@ package io.avaje.jex.http; +/** Http Error Status codes */ public enum ErrorCode { REDIRECT(302, "Redirect"), diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index 49d9eae4..700540d8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -398,7 +398,7 @@ public Context render(String name, Map model) { @Override public Map headerMap() { Map map = new LinkedHashMap<>(); - for (Map.Entry> entry : exchange.getRequestHeaders().entrySet()) { + for (var entry : exchange.getRequestHeaders().entrySet()) { final List value = entry.getValue(); if (!value.isEmpty()) { map.put(entry.getKey(), value.getFirst()); @@ -407,6 +407,11 @@ public Map headerMap() { return map; } + @Override + public Headers headers() { + return exchange.getRequestHeaders(); + } + @Override public String header(String key) { return header(exchange.getRequestHeaders(), key); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java index 3b98da0a..76748e26 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java @@ -36,4 +36,9 @@ public void shutdown() { log.log(Level.TRACE, "server http listeners stopped"); lifecycle.status(AppLifecycle.Status.STOPPED); } + + @Override + public int port() { + return server.getAddress().getPort(); + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java index ed0d22d4..60f380f7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java @@ -5,8 +5,7 @@ import java.util.Iterator; /** - * - *

    Service responsible for handling JSON-based request and response bodies. + * Service responsible for handling JSON-based request and response bodies. * * @see {@link JexExtension} for SPI registration details. */ From 8ddb68d5576aa884428ba5edf813783a081da8cc Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 27 Nov 2024 18:46:51 -0500 Subject: [PATCH 077/250] Add `package-info.java`s (#97) * javadocs * Update Jex.java * Update JdkJexServer.java * package info --- .../src/main/java/io/avaje/jex/Routing.java | 23 +++++++------------ .../io/avaje/jex/StaticContentConfig.java | 4 ++-- .../io/avaje/jex/core/json/package-info.java | 2 ++ .../java/io/avaje/jex/http/package-info.java | 2 ++ .../io/avaje/jex/security/package-info.java | 2 ++ .../java/io/avaje/jex/spi/package-info.java | 6 ++++- avaje-jex/src/main/java/module-info.java | 18 +++++++++++++++ 7 files changed, 39 insertions(+), 18 deletions(-) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/core/json/package-info.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/package-info.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/security/package-info.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index 40c7cd39..022761e5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -8,10 +8,11 @@ import io.avaje.jex.security.Role; +/** Routing abstraction. */ public sealed interface Routing permits DefaultRouting { - /** Add the routes provided by the Routing HttpService. */ - Routing add(Routing.HttpService routes); + /** Add the routes provided by the given HttpService. */ + Routing add(Routing.HttpService service); /** Add all the routes provided by the Routing Services. */ Routing addAll(Collection routes); @@ -47,12 +48,12 @@ public sealed interface Routing permits DefaultRouting { Routing withRoles(Role... permittedRoles); /** - * Registers an error handler that handles the given type of exceptions. - * This will replace an existing error handler for the same exception class. + * Registers an error handler that handles the given type of exceptions. This will replace an + * existing error handler for the same exception class. * * @param exceptionClass the type of exception to handle by this handler - * @param handler the error handler - * @param exception type + * @param handler the error handler + * @param exception type * @return updated routing */ Routing error(Class exceptionClass, ExceptionHandler handler); @@ -87,7 +88,7 @@ public sealed interface Routing permits DefaultRouting { /** Add a filter for all requests. */ Routing filter(HttpFilter handler); - /** Add a preprocessing filter for all requests. */ + /** Add a pre-processing filter for all requests. */ default Routing before(Consumer handler) { return filter( @@ -154,21 +155,13 @@ interface Entry { /** The type of route entry. */ enum Type { - /** Http GET. */ GET, - /** Http POST. */ POST, - /** HTTP PUT. */ PUT, - /** HTTP PATCH. */ PATCH, - /** HTTP DELETE. */ DELETE, - /** HTTP HEAD. */ HEAD, - /** HTTP TRACE. */ TRACE, - /** HTTP OPTIONS. */ OPTIONS; } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java b/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java index 72e55c3f..962d3093 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java @@ -31,7 +31,7 @@ static StaticContentConfig create() { /** * Sets the file to serve, or the folder your files are located in. (default: "/public/") * - * @param root the root directory + * @param resource the root directory * @return the updated configuration */ StaticContentConfig resource(String resource); @@ -91,7 +91,7 @@ static StaticContentConfig create() { */ StaticContentConfig location(ResourceLocation location); - /** the resource location */ + /** Resource location */ public enum ResourceLocation { CLASS_PATH, FILE diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/package-info.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/package-info.java new file mode 100644 index 00000000..83defa32 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/package-info.java @@ -0,0 +1,2 @@ +/** Optional JsonServices */ +package io.avaje.jex.core.json; diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/package-info.java b/avaje-jex/src/main/java/io/avaje/jex/http/package-info.java new file mode 100644 index 00000000..714cc482 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/http/package-info.java @@ -0,0 +1,2 @@ +/** Http Exceptions */ +package io.avaje.jex.http; diff --git a/avaje-jex/src/main/java/io/avaje/jex/security/package-info.java b/avaje-jex/src/main/java/io/avaje/jex/security/package-info.java new file mode 100644 index 00000000..bf145990 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/security/package-info.java @@ -0,0 +1,2 @@ +/** Security Classes */ +package io.avaje.jex.security; diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/package-info.java b/avaje-jex/src/main/java/io/avaje/jex/spi/package-info.java index 9a38d0bc..9656b44c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/package-info.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/package-info.java @@ -1,2 +1,6 @@ -/** SPI extension interfaces */ +/** + * SPI extension interfaces + * + * @see {@link io.avaje.jex.spi.JexExtension} + */ package io.avaje.jex.spi; diff --git a/avaje-jex/src/main/java/module-info.java b/avaje-jex/src/main/java/module-info.java index 4ee418a8..a688ce9f 100644 --- a/avaje-jex/src/main/java/module-info.java +++ b/avaje-jex/src/main/java/module-info.java @@ -1,5 +1,23 @@ import io.avaje.jex.spi.JexExtension; +/** + * Defines the Jex HTTP server API, for running a minimal HTTP server. + * + *

    {@code
    + * final Jex.Server app = Jex.create()
    + *   .routing(routing -> routing
    + *     .get("/", ctx -> ctx.text("hello world"))
    + *     .get("/one", ctx -> ctx.text("one"))
    + *   .port(8080)
    + *   .start();
    + *
    + * app.shutdown();
    + *
    + * }
    + * + * @uses JexExtension + * + */ module io.avaje.jex { exports io.avaje.jex; From 4f66693d225312ca251d093421dedd47e4faae1b Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 28 Nov 2024 21:35:49 -0500 Subject: [PATCH 078/250] rewrite routing (#98) --- .../main/java/io/avaje/jex/DJexConfig.java | 12 ++--- .../src/main/java/io/avaje/jex/JexConfig.java | 10 ++-- .../io/avaje/jex/jdk/BaseFilterChain.java | 32 ++++++++++++ .../java/io/avaje/jex/jdk/BaseHandler.java | 33 ------------- .../java/io/avaje/jex/jdk/JdkContext.java | 6 ++- .../main/java/io/avaje/jex/jdk/JdkFilter.java | 28 ----------- .../java/io/avaje/jex/jdk/JdkJexServer.java | 4 +- .../java/io/avaje/jex/jdk/JdkServerStart.java | 13 +++-- ...RoutingFilter.java => RoutingHandler.java} | 49 ++++--------------- .../java/io/avaje/jex/routes/RouteEntry.java | 6 +-- .../main/java/io/avaje/jex/routes/Routes.java | 8 +-- .../io/avaje/jex/routes/RoutesBuilder.java | 13 ++--- .../java/io/avaje/jex/routes/SpiRoutes.java | 9 ++-- 13 files changed, 82 insertions(+), 141 deletions(-) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilterChain.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/jdk/JdkFilter.java rename avaje-jex/src/main/java/io/avaje/jex/jdk/{RoutingFilter.java => RoutingHandler.java} (56%) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index b9f04b46..10da8cfb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -6,7 +6,7 @@ import java.util.concurrent.Executors; import java.util.function.Consumer; -import javax.net.ssl.SSLContext; +import com.sun.net.httpserver.HttpsConfigurator; import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.spi.JsonService; @@ -22,7 +22,7 @@ final class DJexConfig implements JexConfig { private Executor executor; private JsonService jsonService; private final Map renderers = new HashMap<>(); - private SSLContext sslContext; + private HttpsConfigurator httpsConfig; private boolean useJexSpi = true; private final CompressionConfig compression = new CompressionConfig(); @@ -120,13 +120,13 @@ public Map renderers() { } @Override - public SSLContext sslContext() { - return this.sslContext; + public HttpsConfigurator httpsConfig() { + return this.httpsConfig; } @Override - public JexConfig sslContext(SSLContext ssl) { - this.sslContext = ssl; + public JexConfig httpsConfig(HttpsConfigurator ssl) { + this.httpsConfig = ssl; return this; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index cc29dfc5..09fe2418 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -7,6 +7,8 @@ import javax.net.ssl.SSLContext; +import com.sun.net.httpserver.HttpsConfigurator; + import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; @@ -67,11 +69,11 @@ public sealed interface JexConfig permits DJexConfig { /** Return the JsonService. */ JsonService jsonService(); - /** Return the ssl context if https is enabled. */ - SSLContext sslContext(); + /** Return the {@link HttpsConfigurator} if https is enabled. */ + HttpsConfigurator httpsConfig(); - /** Enable https with the provided SSLContext. */ - JexConfig sslContext(SSLContext ssl); + /** Enable https with the provided {@link HttpsConfigurator} */ + JexConfig httpsConfig(HttpsConfigurator https); /** Return the template renderers registered by extension. */ Map renderers(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilterChain.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilterChain.java new file mode 100644 index 00000000..88efe66f --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilterChain.java @@ -0,0 +1,32 @@ +package io.avaje.jex.jdk; + +import java.io.IOException; +import java.util.List; +import java.util.ListIterator; + +import io.avaje.jex.ExchangeHandler; +import io.avaje.jex.HttpFilter; +import io.avaje.jex.HttpFilter.FilterChain; + +final class BaseFilterChain implements FilterChain { + + private final ListIterator iter; + private final ExchangeHandler handler; + private final JdkContext ctx; + + BaseFilterChain(List filters, ExchangeHandler handler, JdkContext ctx) { + this.iter = filters.listIterator(); + this.handler = handler; + this.ctx = ctx; + } + + @Override + public void proceed() throws IOException { + if (!iter.hasNext()) { + handler.handle(ctx); + ctx.setMode(Mode.AFTER); + } else { + iter.next().filter(ctx, this); + } + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java deleted file mode 100644 index cec1cd79..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseHandler.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.avaje.jex.jdk; - -import java.io.IOException; - -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; - -import io.avaje.jex.ExchangeHandler; -import io.avaje.jex.routes.SpiRoutes; - -final class BaseHandler implements HttpHandler { - - private final SpiRoutes routes; - - BaseHandler(SpiRoutes routes) { - this.routes = routes; - } - - void waitForIdle(long maxSeconds) { - routes.waitForIdle(maxSeconds); - } - - @Override - public void handle(HttpExchange exchange) throws IOException { - JdkContext ctx = (JdkContext) exchange.getAttribute("JdkContext"); - ExchangeHandler handlerConsumer = - (ExchangeHandler) exchange.getAttribute("SpiRoutes.Entry.Handler"); - - ctx.setMode(Mode.EXCHANGE); - handlerConsumer.handle(ctx); - ctx.setMode(Mode.AFTER); - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index 700540d8..e0005fc2 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -17,6 +17,7 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Base64; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -46,6 +47,7 @@ public final class JdkContext implements Context { private final CompressionConfig compressionConfig; private final String path; private final Map pathParams; + private final Map attributes = new HashMap<>(); private final Set roles; private final HttpExchange exchange; private Mode mode; @@ -92,14 +94,14 @@ public String matchedPath() { @Override public Context attribute(String key, Object value) { - exchange.setAttribute(key, value); + attributes.put(key, value); return this; } @Override @SuppressWarnings("unchecked") public T attribute(String key) { - return (T) exchange.getAttribute(key); + return (T) attributes.get(key); } private Map parseCookies() { diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkFilter.java deleted file mode 100644 index 152702c7..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkFilter.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.avaje.jex.jdk; - -import java.io.IOException; - -import com.sun.net.httpserver.Filter; -import com.sun.net.httpserver.HttpExchange; - -import io.avaje.jex.HttpFilter; - -public class JdkFilter extends Filter { - - private final HttpFilter handler; - - public JdkFilter(HttpFilter handler) { - this.handler = handler; - } - - @Override - public void doFilter(HttpExchange exchange, Chain chain) throws IOException { - var ctx = (JdkContext) exchange.getAttribute("JdkContext"); - handler.filter(ctx, () -> chain.doFilter(exchange)); - } - - @Override - public String description() { - return "JexFilter"; - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java index 76748e26..32de4f62 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java @@ -13,9 +13,9 @@ final class JdkJexServer implements Jex.Server { private final HttpServer server; private final AppLifecycle lifecycle; - private final BaseHandler handler; + private final RoutingHandler handler; - JdkJexServer(HttpServer server, AppLifecycle lifecycle, BaseHandler handler) { + JdkJexServer(HttpServer server, AppLifecycle lifecycle, RoutingHandler handler) { this.server = server; this.lifecycle = lifecycle; this.handler = handler; diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index e58cec93..115c5bf8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -22,13 +22,13 @@ public final class JdkServerStart { public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceManager) { try { var port = new InetSocketAddress(jex.config().port()); - final var sslContext = jex.config().sslContext(); + final HttpsConfigurator https = jex.config().httpsConfig(); final HttpServer server; final String scheme; - if (sslContext != null) { + if (https != null) { var httpsServer = HttpsServer.create(port, 0); - httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext)); + httpsServer.setHttpsConfigurator(https); server = httpsServer; scheme = "https"; } else { @@ -38,11 +38,10 @@ public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceMana final var manager = new CtxServiceManager(serviceManager, scheme, ""); - var handler = new BaseHandler(routes); - var context = server.createContext("/", handler); - context.getFilters().add(new RoutingFilter(routes, manager, jex.config().compression())); - context.getFilters().addAll(routes.filters()); + final var handler = new RoutingHandler(routes, manager, jex.config().compression()); + server.setExecutor(jex.config().executor()); + server.createContext("/", handler); server.start(); jex.lifecycle().status(AppLifecycle.Status.STARTED); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingHandler.java similarity index 56% rename from avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java rename to avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingHandler.java index 551e248a..ce48e1e3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingHandler.java @@ -1,27 +1,30 @@ package io.avaje.jex.jdk; import java.io.IOException; +import java.util.List; import java.util.Map; import java.util.Set; -import com.sun.net.httpserver.Filter; import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; -import io.avaje.jex.ExchangeHandler; +import io.avaje.jex.HttpFilter; import io.avaje.jex.Routing; import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.http.NotFoundException; import io.avaje.jex.routes.SpiRoutes; -final class RoutingFilter extends Filter { +public final class RoutingHandler implements HttpHandler { private final SpiRoutes routes; private final CtxServiceManager mgr; private final CompressionConfig compressionConfig; + private final List filters; - RoutingFilter(SpiRoutes routes, CtxServiceManager mgr, CompressionConfig compressionConfig) { + RoutingHandler(SpiRoutes routes, CtxServiceManager mgr, CompressionConfig compressionConfig) { this.mgr = mgr; this.routes = routes; + this.filters = routes.filters(); this.compressionConfig = compressionConfig; } @@ -30,26 +33,14 @@ void waitForIdle(long maxSeconds) { } @Override - public void doFilter(HttpExchange exchange, Filter.Chain chain) { + public void handle(HttpExchange exchange) { final String uri = exchange.getRequestURI().getPath(); final Routing.Type routeType = mgr.lookupRoutingType(exchange.getRequestMethod()); final SpiRoutes.Entry route = routes.match(routeType, uri); if (route == null) { var ctx = new JdkContext(mgr, compressionConfig, exchange, uri, Set.of()); - routes.inc(); - try { - ctx.setMode(Mode.BEFORE); - processNoRoute(ctx, uri, routeType); - exchange.setAttribute("JdkContext", ctx); - chain.doFilter(exchange); - handleNoResponse(exchange); - } catch (Exception e) { - handleException(ctx, e); - } finally { - routes.dec(); - exchange.close(); - } + handleException(ctx, new NotFoundException("uri: " + uri)); } else { route.inc(); try { @@ -59,10 +50,7 @@ public void doFilter(HttpExchange exchange, Filter.Chain chain) { mgr, compressionConfig, exchange, route.matchPath(), params, route.roles()); try { ctx.setMode(Mode.BEFORE); - exchange.setAttribute("JdkContext", ctx); - ExchangeHandler handlerConsumer = route::handle; - exchange.setAttribute("SpiRoutes.Entry.Handler", handlerConsumer); - chain.doFilter(exchange); + new BaseFilterChain(filters, route.handler(), ctx).proceed(); handleNoResponse(exchange); } catch (Exception e) { handleException(ctx, e); @@ -83,21 +71,4 @@ private void handleNoResponse(HttpExchange exchange) throws IOException { private void handleException(JdkContext ctx, Exception e) { mgr.handleException(ctx, e); } - - private void processNoRoute(JdkContext ctx, String uri, Routing.Type routeType) { - if (routeType == Routing.Type.HEAD && hasGetHandler(uri)) { - ctx.status(200); - return; - } - throw new NotFoundException("uri: " + uri); - } - - private boolean hasGetHandler(String uri) { - return routes.match(Routing.Type.GET, uri) != null; - } - - @Override - public String description() { - return "Routing Filter"; - } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java index 3a9ee4a3..25a08e49 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java @@ -1,11 +1,9 @@ package io.avaje.jex.routes; -import java.io.IOException; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; -import io.avaje.jex.Context; import io.avaje.jex.ExchangeHandler; import io.avaje.jex.security.Role; @@ -43,8 +41,8 @@ public boolean matches(String requestUri) { } @Override - public void handle(Context ctx) throws IOException { - handler.handle(ctx); + public ExchangeHandler handler() { + return handler; } @Override diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java b/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java index 4d9b68b5..a48e197e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java @@ -7,8 +7,8 @@ import java.util.concurrent.locks.LockSupport; import io.avaje.applog.AppLog; +import io.avaje.jex.HttpFilter; import io.avaje.jex.Routing; -import io.avaje.jex.jdk.JdkFilter; final class Routes implements SpiRoutes { @@ -22,12 +22,12 @@ final class Routes implements SpiRoutes { /** * The filters. */ - private final List filters; + private final List filters; private final AtomicLong noRouteCounter = new AtomicLong(); - Routes(EnumMap typeMap, List filters) { + Routes(EnumMap typeMap, List filters) { this.typeMap = typeMap; this.filters = filters; } @@ -79,7 +79,7 @@ public Entry match(Routing.Type type, String pathInfo) { } @Override - public List filters() { + public List filters() { return filters; } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java index 7d54d775..5e9de0f0 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java @@ -1,26 +1,23 @@ package io.avaje.jex.routes; -import io.avaje.jex.*; -import io.avaje.jex.jdk.JdkFilter; - -import java.util.ArrayList; import java.util.EnumMap; import java.util.List; +import io.avaje.jex.HttpFilter; +import io.avaje.jex.Routing; + public final class RoutesBuilder { private final EnumMap typeMap = new EnumMap<>(Routing.Type.class); private final boolean ignoreTrailingSlashes; - private final List filters = new ArrayList<>(); + private final List filters; public RoutesBuilder(Routing routing, boolean ignoreTrailingSlashes) { this.ignoreTrailingSlashes = ignoreTrailingSlashes; for (var handler : routing.handlers()) { typeMap.computeIfAbsent(handler.getType(), h -> new RouteIndex()).add(convert(handler)); } - for (var handler : routing.filters()) { - filters.add(new JdkFilter(handler)); - } + filters = List.copyOf(routing.filters()); } private SpiRoutes.Entry convert(Routing.Entry handler) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java b/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java index 54068511..86b7c832 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java @@ -6,8 +6,9 @@ import java.util.Set; import io.avaje.jex.Context; +import io.avaje.jex.ExchangeHandler; +import io.avaje.jex.HttpFilter; import io.avaje.jex.Routing; -import io.avaje.jex.jdk.JdkFilter; import io.avaje.jex.security.Role; /** @@ -43,7 +44,7 @@ public sealed interface SpiRoutes permits Routes { /** * Get filters */ - List filters(); + List filters(); /** * A route entry. @@ -56,9 +57,9 @@ interface Entry { boolean matches(String requestUri); /** - * Handle the request. + * Handler for the request. */ - void handle(Context ctx) throws IOException; + ExchangeHandler handler(); /** * Return the path parameter map given the uri. From 17c17045d53eb5e95c712122ab90234352e693d5 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 28 Nov 2024 23:24:03 -0500 Subject: [PATCH 079/250] Tidy Config (#99) - remove unused methods - add socket backlog config option - javadoc --- .../src/main/java/io/avaje/jex/Context.java | 2 +- .../src/main/java/io/avaje/jex/DJex.java | 6 - .../main/java/io/avaje/jex/DJexConfig.java | 22 +--- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 15 +-- .../src/main/java/io/avaje/jex/JexConfig.java | 114 ++++++++++++------ .../java/io/avaje/jex/jdk/JdkServerStart.java | 15 +-- 6 files changed, 91 insertions(+), 83 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index 11f52c32..63ba7ced 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -78,7 +78,7 @@ public interface Context { Context cookie(String name, String value, int maxAge); /** - * Sets a cookie using the provided `Cookie` object. + * Sets a cookie using the provided {@link Cookie} object. * * @param cookie The cookie object to set. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index a5bf11dc..d83eaadf 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -88,12 +88,6 @@ public Jex port(int port) { return this; } - @Override - public Jex context(String contextPath) { - config.contextPath(contextPath); - return this; - } - @Override public Jex register(TemplateRender renderer, String... extensions) { for (String extension : extensions) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 10da8cfb..028ff6af 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -15,8 +15,7 @@ final class DJexConfig implements JexConfig { private int port = 8080; - private String host; - private String contextPath = "/"; + private int socketBacklog = 0; private boolean health = true; private boolean ignoreTrailingSlashes = true; private Executor executor; @@ -33,14 +32,8 @@ public JexConfig port(int port) { } @Override - public JexConfig host(String host) { - this.host = host; - return this; - } - - @Override - public JexConfig contextPath(String contextPath) { - this.contextPath = contextPath; + public JexConfig socketBacklog(int socketBacklog) { + this.socketBacklog = socketBacklog; return this; } @@ -90,13 +83,8 @@ public int port() { } @Override - public String host() { - return host; - } - - @Override - public String contextPath() { - return contextPath; + public int socketBacklog() { + return socketBacklog; } @Override diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 98a1f343..4782d9f5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -134,9 +134,9 @@ default Jex staticResource(Consumer consumer) { * Configures the Jex instance using a functional approach. * *

    The provided consumer lambda allows you to customize the Jex configuration, such as setting - * the port, context path, and other options. + * the port, compression, and other options. * - * @param configure A consumer lambda that accepts a `JexConfig` instance for configuration. + * @param configure A consumer lambda that accepts a {@link JexConfig} instance for configuration. * @return The configured Jex instance. */ Jex configure(Consumer configure); @@ -145,20 +145,9 @@ default Jex staticResource(Consumer consumer) { * Sets the port number on which the Jex server will listen for incoming requests. * * @param port The port number to use. - * @return The updated Jex instance. */ Jex port(int port); - /** - * Sets the context path for the Jex application. - * - *

    The context path is the portion of the URL that identifies the application. - * - * @param contextPath The context path to use. - * @return The updated Jex instance. - */ - Jex context(String contextPath); - /** * Explicitly register a template renderer. * diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index 09fe2418..cff502b3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -2,88 +2,124 @@ import java.util.Map; import java.util.concurrent.Executor; -import java.util.concurrent.Executors; import java.util.function.Consumer; -import javax.net.ssl.SSLContext; - import com.sun.net.httpserver.HttpsConfigurator; import io.avaje.jex.compression.CompressionConfig; +import io.avaje.jex.spi.JexPlugin; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; -/** Jex configuration. */ +/** + * Jex configuration interface. + * + *

    Provides a fluent API for configuring Jex's various settings, including port, host, health + * endpoint, trailing slash handling, JSON service, template renderers, executor service, HTTPS + * configuration, compression, and plugin loading. + */ public sealed interface JexConfig permits DJexConfig { - /** Set the port to use. Defaults to 7001. */ + /** + * Sets the port number on which Jex will listen for incoming requests. + * + * @param port The port number. + */ JexConfig port(int port); - /** Set the host to bind to. */ - JexConfig host(String host); - - /** Set the contextPath. */ - JexConfig contextPath(String contextPath); + /** + * Set the socket backlog. If this value is less than or equal to zero, then a system default + * value is used + * + * @param backlog the socket backlog. If this value is less than or equal to zero, then a system + * default value is used + */ + JexConfig socketBacklog(int backlog); - /** Set to true to include the health endpoint. Defaults to true. */ + /** + * Enables/Disables the default health endpoint. + * + * @param health whether to enable/disable. + */ JexConfig health(boolean health); - /** Set to true to ignore trailing slashes. Defaults to true. */ + /** + * Configures whether trailing slashes in request URIs should be ignored. + * + * @param ignoreTrailingSlashes whether to enable/disable trailing slashes. + */ JexConfig ignoreTrailingSlashes(boolean ignoreTrailingSlashes); - /** Set the JsonService to use. */ + /** + * Sets the JSON service used for (de)serialization. + * + * @param jsonService The json service instance. + */ JexConfig jsonService(JsonService jsonService); /** - * Register a template renderer explicitly. + * Registers a template renderer for a specific file extension. * - * @param extension The extension the renderer applies to. - * @param renderer The template render to use for the given extension. + * @param extension The file extension. + * @param renderer The template renderer implementation. */ JexConfig renderer(String extension, TemplateRender renderer); - /** Set executor for serving requests. */ + /** + * Sets the executor service used to handle incoming requests. + * + * @param executor The executor service. + */ JexConfig executor(Executor executor); /** - * Executor for serving requests. Defaults to a {@link - * Executors#newVirtualThreadPerTaskExecutor()} + * Enable https with the provided {@link HttpsConfigurator} + * + * @param https The HTTPS configuration. */ - Executor executor(); - - /** Return the port to use. */ - int port(); + JexConfig httpsConfig(HttpsConfigurator https); - /** Return the host to bind to. */ - String host(); + /** + * Configures compression settings using a consumer function. + * + * @param consumer The consumer function to configure compression settings. + * @return The updated configuration. + */ + JexConfig compression(Consumer consumer); - /** Return the contextPath to use. */ - String contextPath(); + /** Returns the configured port number. (Defaults to 8080 if not set) */ + int port(); - /** Return true to include the health endpoint. */ + /** Returns whether the health endpoint is enabled. */ boolean health(); - /** Return true to ignore trailing slashes. */ + /** Returns whether trailing slashes in request URIs are ignored. */ boolean ignoreTrailingSlashes(); - /** Return the JsonService. */ + /** Returns the configured JSON service. */ JsonService jsonService(); /** Return the {@link HttpsConfigurator} if https is enabled. */ HttpsConfigurator httpsConfig(); - /** Enable https with the provided {@link HttpsConfigurator} */ - JexConfig httpsConfig(HttpsConfigurator https); + /** Returns the configured compression settings. */ + CompressionConfig compression(); - /** Return the template renderers registered by extension. */ + /** Returns a map of registered template renderers, keyed by file extension. */ Map renderers(); - /** configure compression via consumer */ - JexConfig compression(Consumer consumer); - - /** get compression configuration */ - CompressionConfig compression(); + /** + * Executor for serving requests. Defaults to a {@link + * Executors#newVirtualThreadPerTaskExecutor()} + */ + Executor executor(); - /** whether to disable JexPlugins loaded from ServiceLoader */ + /** + * Disables auto-configuring the current instance with {@link JexPlugin} loaded using the + * ServiceLoader. + */ JexConfig disableSpiPlugins(); + + /** Return the socket backlog. */ + int socketBacklog(); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index 115c5bf8..de307994 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -6,7 +6,6 @@ import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpServer; -import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsServer; import io.avaje.applog.AppLog; @@ -21,26 +20,28 @@ public final class JdkServerStart { public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceManager) { try { - var port = new InetSocketAddress(jex.config().port()); - final HttpsConfigurator https = jex.config().httpsConfig(); + final var config = jex.config(); + final var port = new InetSocketAddress(config.port()); + final var https = config.httpsConfig(); + final var backlog = config.socketBacklog(); final HttpServer server; final String scheme; if (https != null) { - var httpsServer = HttpsServer.create(port, 0); + var httpsServer = HttpsServer.create(port, backlog); httpsServer.setHttpsConfigurator(https); server = httpsServer; scheme = "https"; } else { scheme = "http"; - server = HttpServer.create(port, 0); + server = HttpServer.create(port, backlog); } final var manager = new CtxServiceManager(serviceManager, scheme, ""); - final var handler = new RoutingHandler(routes, manager, jex.config().compression()); + final var handler = new RoutingHandler(routes, manager, config.compression()); - server.setExecutor(jex.config().executor()); + server.setExecutor(config.executor()); server.createContext("/", handler); server.start(); From 61de9365c604562ad78d4ced4011c90c0672cd39 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 28 Nov 2024 23:37:32 -0500 Subject: [PATCH 080/250] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6fd1f2d7..e912a0f0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +![Supported JVM Versions](https://img.shields.io/badge/JVM-21-brightgreen.svg?&logo=openjdk) [![Discord](https://img.shields.io/discord/1074074312421683250?color=%237289da&label=discord)](https://discord.gg/Qcqf9R27BR) [![Build](https://github.com/avaje/avaje-jex/actions/workflows/build.yml/badge.svg)](https://github.com/avaje/avaje-jex/actions/workflows/build.yml) [![JDK EA](https://github.com/avaje/avaje-jex/actions/workflows/jdk-ea.yml/badge.svg)](https://github.com/avaje/avaje-jex/actions/workflows/jdk-ea.yml) @@ -54,7 +55,7 @@ An example would be [@robaho's implementation](https://github.com/robaho/httpser io.github.robaho httpserver - 1.0.9 + 1.0.10 ``` @@ -79,7 +80,7 @@ If you find yourself pining for the JAX-RS style of controllers, you can have av io.avaje - avaje-http-sigma-generator + avaje-http-jex-generator 2.9-RC3 provided true From 7f8e97ab7cb819ba7ed878a6ce069c6c52851a04 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Sat, 30 Nov 2024 09:28:29 +1300 Subject: [PATCH 081/250] Restore contextPath (#101) --- avaje-jex/src/main/java/io/avaje/jex/DJex.java | 6 ++++++ .../src/main/java/io/avaje/jex/DJexConfig.java | 12 ++++++++++++ avaje-jex/src/main/java/io/avaje/jex/Jex.java | 10 ++++++++++ avaje-jex/src/main/java/io/avaje/jex/JexConfig.java | 11 +++++++++++ .../main/java/io/avaje/jex/jdk/JdkServerStart.java | 13 ++++++------- .../java/io/avaje/jex/jdk/ContextLengthTest.java | 2 +- 6 files changed, 46 insertions(+), 8 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index d83eaadf..a5bf11dc 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -88,6 +88,12 @@ public Jex port(int port) { return this; } + @Override + public Jex context(String contextPath) { + config.contextPath(contextPath); + return this; + } + @Override public Jex register(TemplateRender renderer, String... extensions) { for (String extension : extensions) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 028ff6af..4671d8c9 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -15,6 +15,7 @@ final class DJexConfig implements JexConfig { private int port = 8080; + private String contextPath = "/"; private int socketBacklog = 0; private boolean health = true; private boolean ignoreTrailingSlashes = true; @@ -31,6 +32,12 @@ public JexConfig port(int port) { return this; } + @Override + public JexConfig contextPath(String contextPath) { + this.contextPath = contextPath; + return this; + } + @Override public JexConfig socketBacklog(int socketBacklog) { this.socketBacklog = socketBacklog; @@ -82,6 +89,11 @@ public int port() { return port; } + @Override + public String contextPath() { + return contextPath; + } + @Override public int socketBacklog() { return socketBacklog; diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 4782d9f5..8ebdfb72 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -148,6 +148,16 @@ default Jex staticResource(Consumer consumer) { */ Jex port(int port); + /** + * Sets the context path for the Jex application. + * + *

    The context path is the portion of the URL that identifies the application. + * + * @param contextPath The context path to use. + * @return The updated Jex instance. + */ + Jex context(String contextPath); + /** * Explicitly register a template renderer. * diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index cff502b3..11c21d97 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -27,6 +27,13 @@ public sealed interface JexConfig permits DJexConfig { */ JexConfig port(int port); + /** + * Set the contextPath. + * + * @param contextPath The context path which defaults to "/". + */ + JexConfig contextPath(String contextPath); + /** * Set the socket backlog. If this value is less than or equal to zero, then a system default * value is used @@ -90,6 +97,9 @@ public sealed interface JexConfig permits DJexConfig { /** Returns the configured port number. (Defaults to 8080 if not set) */ int port(); + /** Return the contextPath. (Defaults to "/") */ + String contextPath(); + /** Returns whether the health endpoint is enabled. */ boolean health(); @@ -122,4 +132,5 @@ public sealed interface JexConfig permits DJexConfig { /** Return the socket backlog. */ int socketBacklog(); + } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index de307994..d8bbefc2 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.io.UncheckedIOException; -import java.lang.System.Logger.Level; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpServer; @@ -14,6 +13,8 @@ import io.avaje.jex.core.SpiServiceManager; import io.avaje.jex.routes.SpiRoutes; +import static java.lang.System.Logger.Level.INFO; + public final class JdkServerStart { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); @@ -22,6 +23,7 @@ public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceMana try { final var config = jex.config(); final var port = new InetSocketAddress(config.port()); + final var contextPath = config.contextPath(); final var https = config.httpsConfig(); final var backlog = config.socketBacklog(); @@ -37,18 +39,15 @@ public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceMana server = HttpServer.create(port, backlog); } - final var manager = new CtxServiceManager(serviceManager, scheme, ""); - + final var manager = new CtxServiceManager(serviceManager, scheme, contextPath); final var handler = new RoutingHandler(routes, manager, config.compression()); server.setExecutor(config.executor()); - server.createContext("/", handler); + server.createContext(contextPath, handler); server.start(); jex.lifecycle().status(AppLifecycle.Status.STARTED); - log.log( - Level.INFO, - "started com.sun.net.httpserver.HttpServer on port %s://%s".formatted(scheme, port)); + log.log(INFO, "started com.sun.net.httpserver.HttpServer on port %s://%s", scheme, port); return new JdkJexServer(server, jex.lifecycle(), handler); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextLengthTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextLengthTest.java index f817711a..3aa92d11 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextLengthTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextLengthTest.java @@ -90,7 +90,7 @@ void contextPath() { .GET().asString(); assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("contextPath:"); + assertThat(res.body()).isEqualTo("contextPath:/"); } @Test From 5e179b4c1d5380c77ee3969f5e186942c5c2c146 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Sat, 30 Nov 2024 11:39:34 +1300 Subject: [PATCH 082/250] Version 3.0-RC4 --- avaje-jex-freemarker/pom.xml | 6 +++--- avaje-jex-htmx/pom.xml | 4 ++-- avaje-jex-mustache/pom.xml | 6 +++--- avaje-jex-test/pom.xml | 4 ++-- avaje-jex/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 4 ++-- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 7dccbe40..4377fe1b 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC3 + 3.0-RC4 avaje-jex-freemarker @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-RC3 + 3.0-RC4 provided @@ -39,7 +39,7 @@ io.avaje avaje-jex-test - 3.0-RC3 + 3.0-RC4 test diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 2eb460c8..ff6319e0 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC3 + 3.0-RC4 avaje-jex-htmx @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC3 + 3.0-RC4 diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 950c03bb..93b0689c 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC3 + 3.0-RC4 avaje-jex-mustache @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-RC3 + 3.0-RC4 provided @@ -40,7 +40,7 @@ io.avaje avaje-jex-test - 3.0-RC3 + 3.0-RC4 test diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index f008fe43..8f63160a 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC3 + 3.0-RC4 avaje-jex-test @@ -14,7 +14,7 @@ io.avaje avaje-jex - 3.0-RC3 + 3.0-RC4 provided diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 28115d84..e4139c48 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC3 + 3.0-RC4 avaje-jex diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 480770dc..c5ebf079 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC3 + 3.0-RC4 diff --git a/examples/pom.xml b/examples/pom.xml index 6744ee72..010bc923 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC3 + 3.0-RC4 examples diff --git a/pom.xml b/pom.xml index b41733e1..ccbd4496 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.avaje avaje-jex-parent - 3.0-RC3 + 3.0-RC4 pom @@ -22,7 +22,7 @@ 2.18.1 false 21 - 2024-11-27T18:36:08Z + 2024-11-29T22:35:26Z From df7afbb37f1e6d5cf6e419c8562f5e41ebbb0d8d Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 29 Nov 2024 20:24:19 -0500 Subject: [PATCH 083/250] RC4 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e912a0f0..39f854dd 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ An example would be [@robaho's implementation](https://github.com/robaho/httpser io.avaje avaje-jex - 3.0-RC3 + 3.0-RC4 @@ -68,20 +68,20 @@ If you find yourself pining for the JAX-RS style of controllers, you can have av io.avaje avaje-jex - 3.0-RC3 + 3.0-RC4 io.avaje avaje-http-api - 2.9-RC3 + 2.9-RC4 io.avaje avaje-http-jex-generator - 2.9-RC3 + 2.9-RC4 provided true From 818e91963ea28468057a7974f1d1e33e8b716d38 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 30 Nov 2024 10:53:39 -0500 Subject: [PATCH 084/250] re-enable host config (#103) --- .../src/main/java/io/avaje/jex/DJexConfig.java | 12 ++++++++++++ .../src/main/java/io/avaje/jex/JexConfig.java | 13 +++++++++++-- .../main/java/io/avaje/jex/jdk/JdkServerStart.java | 14 ++++++++++---- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 4671d8c9..c6d14b29 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -16,6 +16,7 @@ final class DJexConfig implements JexConfig { private int port = 8080; private String contextPath = "/"; + private String host; private int socketBacklog = 0; private boolean health = true; private boolean ignoreTrailingSlashes = true; @@ -26,6 +27,12 @@ final class DJexConfig implements JexConfig { private boolean useJexSpi = true; private final CompressionConfig compression = new CompressionConfig(); + @Override + public JexConfig host(String host) { + this.host = host; + return this; + } + @Override public JexConfig port(int port) { this.port = port; @@ -84,6 +91,11 @@ public JexConfig executor(Executor factory) { return this; } + @Override + public String host() { + return host; + } + @Override public int port() { return port; diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index 11c21d97..b19e429c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -21,7 +21,14 @@ public sealed interface JexConfig permits DJexConfig { /** - * Sets the port number on which Jex will listen for incoming requests. + * Set the host on which the HttpServer will bind to. + * + * @param host The host. + */ + JexConfig host(String host); + + /** + * Sets the port number on which the HttpServer will listen for incoming requests. * * @param port The port number. */ @@ -97,6 +104,9 @@ public sealed interface JexConfig permits DJexConfig { /** Returns the configured port number. (Defaults to 8080 if not set) */ int port(); + /** Returns the configured host. (Defaults to localhost if not set) */ + String host(); + /** Return the contextPath. (Defaults to "/") */ String contextPath(); @@ -132,5 +142,4 @@ public sealed interface JexConfig permits DJexConfig { /** Return the socket backlog. */ int socketBacklog(); - } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index d8bbefc2..c350e74d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.UncheckedIOException; +import java.net.InetAddress; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpServer; @@ -22,7 +23,9 @@ public final class JdkServerStart { public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceManager) { try { final var config = jex.config(); - final var port = new InetSocketAddress(config.port()); + final var port = config.port(); + final var host = InetAddress.getByName(config.host()); + final var socket = new InetSocketAddress(host, config.port()); final var contextPath = config.contextPath(); final var https = config.httpsConfig(); final var backlog = config.socketBacklog(); @@ -30,13 +33,13 @@ public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceMana final HttpServer server; final String scheme; if (https != null) { - var httpsServer = HttpsServer.create(port, backlog); + var httpsServer = HttpsServer.create(socket, backlog); httpsServer.setHttpsConfigurator(https); server = httpsServer; scheme = "https"; } else { scheme = "http"; - server = HttpServer.create(port, backlog); + server = HttpServer.create(socket, backlog); } final var manager = new CtxServiceManager(serviceManager, scheme, contextPath); @@ -47,7 +50,10 @@ public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceMana server.start(); jex.lifecycle().status(AppLifecycle.Status.STARTED); - log.log(INFO, "started com.sun.net.httpserver.HttpServer on port %s://%s", scheme, port); + log.log( + INFO, + "started com.sun.net.httpserver.HttpServer on port %s://%s:%s" + .formatted(scheme, host.getHostName(), port)); return new JdkJexServer(server, jex.lifecycle(), handler); } catch (IOException e) { throw new UncheckedIOException(e); From c79815f3bf552dd3b176cef04127914409fd1ccc Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Sun, 1 Dec 2024 10:05:25 +1300 Subject: [PATCH 085/250] Restore binding to any local address (rather than loopback address) (#104) --- .../main/java/io/avaje/jex/DJexConfig.java | 15 ++++++---- .../src/main/java/io/avaje/jex/JexConfig.java | 5 +++- .../java/io/avaje/jex/jdk/JdkServerStart.java | 28 ++++++++++--------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index c6d14b29..ee0cb116 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -86,8 +86,8 @@ public Executor executor() { } @Override - public JexConfig executor(Executor factory) { - this.executor = factory; + public JexConfig executor(Executor executor) { + this.executor = executor; return this; } @@ -131,14 +131,19 @@ public Map renderers() { return renderers; } + @Override + public String scheme() { + return httpsConfig == null ? "http" : "https"; + } + @Override public HttpsConfigurator httpsConfig() { - return this.httpsConfig; + return httpsConfig; } @Override - public JexConfig httpsConfig(HttpsConfigurator ssl) { - this.httpsConfig = ssl; + public JexConfig httpsConfig(HttpsConfigurator httpsConfig) { + this.httpsConfig = httpsConfig; return this; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index b19e429c..c36c42e3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -21,7 +21,7 @@ public sealed interface JexConfig permits DJexConfig { /** - * Set the host on which the HttpServer will bind to. + * Set the host on which the HttpServer will bind to. Defaults to any local address. * * @param host The host. */ @@ -122,6 +122,9 @@ public sealed interface JexConfig permits DJexConfig { /** Return the {@link HttpsConfigurator} if https is enabled. */ HttpsConfigurator httpsConfig(); + /** Return the schema as http or https. */ + String scheme(); + /** Returns the configured compression settings. */ CompressionConfig compression(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index c350e74d..9af850fd 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -4,6 +4,7 @@ import java.io.UncheckedIOException; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.UnknownHostException; import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpsServer; @@ -11,6 +12,7 @@ import io.avaje.applog.AppLog; import io.avaje.jex.AppLifecycle; import io.avaje.jex.Jex; +import io.avaje.jex.JexConfig; import io.avaje.jex.core.SpiServiceManager; import io.avaje.jex.routes.SpiRoutes; @@ -23,25 +25,20 @@ public final class JdkServerStart { public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceManager) { try { final var config = jex.config(); - final var port = config.port(); - final var host = InetAddress.getByName(config.host()); - final var socket = new InetSocketAddress(host, config.port()); - final var contextPath = config.contextPath(); + final var socketAddress = createSocketAddress(config); final var https = config.httpsConfig(); - final var backlog = config.socketBacklog(); final HttpServer server; - final String scheme; if (https != null) { - var httpsServer = HttpsServer.create(socket, backlog); + var httpsServer = HttpsServer.create(socketAddress, config.socketBacklog()); httpsServer.setHttpsConfigurator(https); server = httpsServer; - scheme = "https"; } else { - scheme = "http"; - server = HttpServer.create(socket, backlog); + server = HttpServer.create(socketAddress, config.socketBacklog()); } + final var scheme = config.scheme(); + final var contextPath = config.contextPath(); final var manager = new CtxServiceManager(serviceManager, scheme, contextPath); final var handler = new RoutingHandler(routes, manager, config.compression()); @@ -51,12 +48,17 @@ public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceMana jex.lifecycle().status(AppLifecycle.Status.STARTED); log.log( - INFO, - "started com.sun.net.httpserver.HttpServer on port %s://%s:%s" - .formatted(scheme, host.getHostName(), port)); + INFO, + "started com.sun.net.httpserver.HttpServer on port {0}://{1}", + scheme, socketAddress); return new JdkJexServer(server, jex.lifecycle(), handler); } catch (IOException e) { throw new UncheckedIOException(e); } } + + private static InetSocketAddress createSocketAddress(JexConfig config) throws UnknownHostException { + final var inetAddress = config.host() == null ? null : InetAddress.getByName(config.host()); + return new InetSocketAddress(inetAddress, config.port()); + } } From 5ac69cf0a19a3a9426b238130bc9ced7542e3f25 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Sun, 1 Dec 2024 12:21:30 +1300 Subject: [PATCH 086/250] Tidy javadoc and unneeded public modifiers on interfaces (#105) --- avaje-jex/src/main/java/io/avaje/jex/Context.java | 4 ++-- avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 1 - avaje-jex/src/main/java/io/avaje/jex/Routing.java | 2 -- avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java | 3 ++- avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java | 2 +- 8 files changed, 8 insertions(+), 10 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index 63ba7ced..9c05be3c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -227,7 +227,7 @@ default List formParams(String key) { default String fullUrl() { final String url = url(); final String qs = queryString(); - return qs == null ? url : url + "?" + qs; + return qs == null ? url : url + '?' + qs; } /** Return the request context path. */ @@ -426,7 +426,7 @@ default Context headers(Map headers) { * sent from a server to a web browser and stored on the user's computer. They can be used to * store information about a user's session, preferences, or other data. */ - public interface Cookie { + interface Cookie { /** * Creates and returns a new expired cookie with the given name. This cookie will be sent to the diff --git a/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java b/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java index f55ddbf1..8ef2d4e7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java @@ -44,7 +44,7 @@ public interface HttpFilter { * route. */ @FunctionalInterface - public interface FilterChain { + interface FilterChain { /** * Calls the next filter in the chain, or else the user's exchange handler, if this is the final diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 8ebdfb72..9b6a12df 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -101,7 +101,6 @@ default Jex staticResource(StaticContentConfig config) { default Jex staticResource(Consumer consumer) { var builder = StaticResourceHandlerBuilder.builder(); consumer.accept(builder); - return staticResource(builder); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index 022761e5..281ed6f6 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -90,7 +90,6 @@ public sealed interface Routing permits DefaultRouting { /** Add a pre-processing filter for all requests. */ default Routing before(Consumer handler) { - return filter( (ctx, chain) -> { handler.accept(ctx); @@ -100,7 +99,6 @@ default Routing before(Consumer handler) { /** Add a post-processing filter for all requests. */ default Routing after(Consumer handler) { - return filter( (ctx, chain) -> { chain.proceed(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java b/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java index 962d3093..70a9548f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java @@ -6,6 +6,7 @@ /** Builder for a static resource exchange handler. */ public sealed interface StaticContentConfig permits StaticResourceHandlerBuilder { + /** Create and return a new static content configuration. */ static StaticContentConfig create() { return StaticResourceHandlerBuilder.builder(); } @@ -92,7 +93,7 @@ static StaticContentConfig create() { StaticContentConfig location(ResourceLocation location); /** Resource location */ - public enum ResourceLocation { + enum ResourceLocation { CLASS_PATH, FILE } diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java index 035f9581..04926ccb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java @@ -6,7 +6,7 @@ * A plugin that can register things like routes, exception handlers and configure the current Jex * instance. * - * @see {@link JexExtension} for SPI registration details. + * @see JexExtension for SPI registration details. */ @FunctionalInterface public non-sealed interface JexPlugin extends JexExtension { diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java index 60f380f7..b7ef9e1b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java @@ -7,7 +7,7 @@ /** * Service responsible for handling JSON-based request and response bodies. * - * @see {@link JexExtension} for SPI registration details. + * @see JexExtension for SPI registration details. */ public non-sealed interface JsonService extends JexExtension { diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java b/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java index c1cd94c2..4ce2a249 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java @@ -7,7 +7,7 @@ /** * Template rendering typically of html. * - * @see {@link JexExtension} for SPI registration details. + * @see JexExtension for SPI registration details. */ public non-sealed interface TemplateRender extends JexExtension { From 2a64f2669c6bb95d8c80eb7ae0d3abd82efa0014 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Sun, 1 Dec 2024 15:01:43 +1300 Subject: [PATCH 087/250] CompressedOutputStream as final class (#107) --- .../io/avaje/jex/compression/CompressedOutputStream.java | 8 ++------ .../java/io/avaje/jex/compression/GzipCompressor.java | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java index a067c21d..cdbdb9d5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java @@ -12,13 +12,13 @@ * OutputStream implementation that conditionally compresses the output based on configuration and * request headers. */ -public class CompressedOutputStream extends OutputStream { +public final class CompressedOutputStream extends OutputStream { private final int minSizeForCompression; private final CompressionConfig compression; private final Context ctx; - private final OutputStream originStream; + private OutputStream compressedStream; private boolean compressionDecided; @@ -33,9 +33,7 @@ public CompressedOutputStream( private void decideCompression(int length) throws IOException { if (!compressionDecided) { var encoding = ctx.responseHeader(Constants.CONTENT_ENCODING); - if (encoding != null) { - this.compressedStream = findMatchingCompressor(encoding) .orElseThrow( @@ -81,9 +79,7 @@ public void close() throws IOException { } private Optional findMatchingCompressor(String acceptedEncoding) { - if (acceptedEncoding != null) { - return Arrays.stream(acceptedEncoding.split(",")).map(compression::forType).findFirst(); } return Optional.empty(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/GzipCompressor.java b/avaje-jex/src/main/java/io/avaje/jex/compression/GzipCompressor.java index c3945dc4..52778950 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/compression/GzipCompressor.java +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/GzipCompressor.java @@ -29,9 +29,9 @@ public OutputStream compress(OutputStream out) throws IOException { return new LeveledGzipStream(out, level); } - static class LeveledGzipStream extends GZIPOutputStream { + private static final class LeveledGzipStream extends GZIPOutputStream { - public LeveledGzipStream(OutputStream out, int level) throws IOException { + private LeveledGzipStream(OutputStream out, int level) throws IOException { super(out); this.def.setLevel(level); } From d25a6ce5ff5f2696c04918d1f7e3ef042b1838a3 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 2 Dec 2024 08:51:15 +1300 Subject: [PATCH 088/250] Add support for Multiple Handlers for a single route (#106) This is used to support HTMX style applications where a single route might be handled by a handler for "normal full page rendering" and also by a second HTMX specific handler for a "htmx partial render". Typically, the "correct" handler is invoked by detecting something like a http header value (e.g. HX_REQUEST). --- .../src/main/java/io/avaje/jex/Context.java | 3 + .../java/io/avaje/jex/jdk/JdkContext.java | 5 ++ .../io/avaje/jex/routes/MultiHandler.java | 25 ++++++ .../java/io/avaje/jex/routes/RouteEntry.java | 6 ++ .../java/io/avaje/jex/routes/RouteIndex.java | 46 +++++----- .../io/avaje/jex/routes/RouteIndexBuild.java | 85 +++++++++++++++++++ .../io/avaje/jex/routes/RoutesBuilder.java | 4 +- .../java/io/avaje/jex/routes/SpiRoutes.java | 5 +- .../io/avaje/jex/jdk/MultiHandlerTest.java | 78 +++++++++++++++++ .../io/avaje/jex/routes/RouteIndexTest.java | 78 ++++++++++------- 10 files changed, 273 insertions(+), 62 deletions(-) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/routes/MultiHandler.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndexBuild.java create mode 100644 avaje-jex/src/test/java/io/avaje/jex/jdk/MultiHandlerTest.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index 9c05be3c..4b2f2ba9 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -421,6 +421,9 @@ default Context headers(Map headers) { */ BasicAuthCredentials basicAuthCredentials(); + /** Return true if the response has been sent. */ + boolean responseSent(); + /** * This interface represents a cookie used in HTTP communication. Cookies are small pieces of data * sent from a server to a web browser and stored on the user's computer. They can be used to diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index e0005fc2..cfb34839 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -387,6 +387,11 @@ public Context write(InputStream is) { return this; } + @Override + public boolean responseSent() { + return exchange.getResponseCode() != -1; + } + int statusCode() { return statusCode == 0 ? 200 : statusCode; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/MultiHandler.java b/avaje-jex/src/main/java/io/avaje/jex/routes/MultiHandler.java new file mode 100644 index 00000000..a44b7d66 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/MultiHandler.java @@ -0,0 +1,25 @@ +package io.avaje.jex.routes; + +import io.avaje.jex.Context; +import io.avaje.jex.ExchangeHandler; + +import java.io.IOException; + +final class MultiHandler implements ExchangeHandler { + + private final ExchangeHandler[] handlers; + + MultiHandler(ExchangeHandler[] handlers) { + this.handlers = handlers; + } + + @Override + public void handle(Context ctx) throws IOException { + for (ExchangeHandler handler : handlers) { + handler.handle(ctx); + if (ctx.responseSent()) { + break; + } + } + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java index 25a08e49..cd1a9e40 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java @@ -20,6 +20,12 @@ final class RouteEntry implements SpiRoutes.Entry { this.roles = roles; } + @Override + public SpiRoutes.Entry multiHandler(ExchangeHandler[] handlers) { + final var handler = new MultiHandler(handlers); + return new RouteEntry(path, handler, roles); + } + @Override public void inc() { active.incrementAndGet(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java index a26e90c6..c7df775a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java @@ -1,6 +1,5 @@ package io.avaje.jex.routes; -import java.util.ArrayList; import java.util.List; final class RouteIndex { @@ -8,29 +7,27 @@ final class RouteIndex { /** * Partition entries by the number of path segments. */ - private final RouteIndex.Entry[] entries = new RouteIndex.Entry[6]; + private final IndexEntry[] entries; /** * Wildcard/splat based route entries. */ - private final List wildcardEntries = new ArrayList<>(); + private final SpiRoutes.Entry[] wildcardEntries; - RouteIndex() { - for (int i = 0; i < entries.length; i++) { - entries[i] = new RouteIndex.Entry(); - } + RouteIndex(List wildcards, List> pathEntries) { + this.wildcardEntries = wildcards.toArray(new SpiRoutes.Entry[0]); + this.entries = pathEntries.stream() + .map(RouteIndex::toEntry) + .toList() + .toArray(new IndexEntry[0]); } - private int index(int segmentCount) { - return Math.min(segmentCount, 5); + private static IndexEntry toEntry(List routeEntries) { + return new IndexEntry(routeEntries.toArray(new SpiRoutes.Entry[0])); } - void add(SpiRoutes.Entry entry) { - if (entry.multiSlash()) { - wildcardEntries.add(entry); - } else { - entries[index(entry.segmentCount())].add(entry); - } + private int index(int segmentCount) { + return Math.min(segmentCount, 5); } SpiRoutes.Entry match(String pathInfo) { @@ -63,7 +60,7 @@ private int segmentCount(String pathInfo) { long activeRequests() { long total = 0; - for (RouteIndex.Entry entry : entries) { + for (IndexEntry entry : entries) { total += entry.activeRequests(); } for (SpiRoutes.Entry entry : wildcardEntries) { @@ -72,21 +69,16 @@ long activeRequests() { return total; } - private static class Entry { + private static final class IndexEntry { - private final List list = new ArrayList<>(); + private final SpiRoutes.Entry[] pathEntries; - void add(SpiRoutes.Entry entry) { - if (entry.literal()) { - // add literal paths to the beginning - list.add(0, entry); - } else { - list.add(entry); - } + IndexEntry(SpiRoutes.Entry[] pathEntries) { + this.pathEntries = pathEntries; } SpiRoutes.Entry match(String pathInfo) { - for (SpiRoutes.Entry entry : list) { + for (SpiRoutes.Entry entry : pathEntries) { if (entry.matches(pathInfo)) { return entry; } @@ -96,7 +88,7 @@ SpiRoutes.Entry match(String pathInfo) { long activeRequests() { long total = 0; - for (SpiRoutes.Entry entry : list) { + for (SpiRoutes.Entry entry : pathEntries) { total += entry.activeRequests(); } return total; diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndexBuild.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndexBuild.java new file mode 100644 index 00000000..bb7b7674 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndexBuild.java @@ -0,0 +1,85 @@ +package io.avaje.jex.routes; + +import io.avaje.jex.ExchangeHandler; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Build the RouteIndex. + */ +final class RouteIndexBuild { + + /** + * Partition entries by the number of path segments. + */ + private final RouteIndexBuild.Entry[] entries = new RouteIndexBuild.Entry[6]; + + /** + * Wildcard/splat based route entries. + */ + private final List wildcardEntries = new ArrayList<>(); + + RouteIndexBuild() { + for (int i = 0; i < entries.length; i++) { + entries[i] = new RouteIndexBuild.Entry(); + } + } + + private int index(int segmentCount) { + return Math.min(segmentCount, 5); + } + + void add(SpiRoutes.Entry entry) { + if (entry.multiSlash()) { + wildcardEntries.add(entry); + } else { + entries[index(entry.segmentCount())].add(entry); + } + } + + /** + * Build and return the RouteIndex. + */ + RouteIndex build() { + final List> pathEntries = new ArrayList<>(); + for (Entry entry : entries) { + pathEntries.add(entry.build()); + } + return new RouteIndex(wildcardEntries, pathEntries); + } + + private static class Entry { + + private final List list = new ArrayList<>(); + private final Map> pathMap = new HashMap<>(); + + void add(SpiRoutes.Entry entry) { + if (entry.literal()) { + // add literal paths to the beginning + list.addFirst(entry); + } else { + pathMap.computeIfAbsent(entry.matchPath(), k -> new ArrayList<>(2)).add(entry); + } + } + + List build() { + List result = new ArrayList<>(list.size() + pathMap.size()); + result.addAll(list); + pathMap.values().forEach(pathList -> { + if (pathList.size() == 1) { + result.add(pathList.getFirst()); + } else { + ExchangeHandler[] handlers = pathList.stream() + .map(SpiRoutes.Entry::handler) + .toList() + .toArray(new ExchangeHandler[0]); + result.add(pathList.getFirst().multiHandler(handlers)); + } + }); + return result; + } + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java index 5e9de0f0..b05ef4c6 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java @@ -14,9 +14,11 @@ public final class RoutesBuilder { public RoutesBuilder(Routing routing, boolean ignoreTrailingSlashes) { this.ignoreTrailingSlashes = ignoreTrailingSlashes; + final var buildMap = new EnumMap(Routing.Type.class); for (var handler : routing.handlers()) { - typeMap.computeIfAbsent(handler.getType(), h -> new RouteIndex()).add(convert(handler)); + buildMap.computeIfAbsent(handler.getType(), h -> new RouteIndexBuild()).add(convert(handler)); } + buildMap.forEach((key, value) -> typeMap.put(key, value.build())); filters = List.copyOf(routing.filters()); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java b/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java index 86b7c832..7a96f9fb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java @@ -1,11 +1,9 @@ package io.avaje.jex.routes; -import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Set; -import io.avaje.jex.Context; import io.avaje.jex.ExchangeHandler; import io.avaje.jex.HttpFilter; import io.avaje.jex.Routing; @@ -103,6 +101,9 @@ interface Entry { /** Return the authentication roles for the route. */ Set roles(); + + /** Create and return a new Entry with multiple handlers. */ + Entry multiHandler(ExchangeHandler[] handlers); } } diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/MultiHandlerTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/MultiHandlerTest.java new file mode 100644 index 00000000..3ac050e0 --- /dev/null +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/MultiHandlerTest.java @@ -0,0 +1,78 @@ +package io.avaje.jex.jdk; + +import io.avaje.jex.Jex; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import java.net.http.HttpResponse; + +import static org.assertj.core.api.Assertions.assertThat; + +class MultiHandlerTest { + + static TestPair pair = init(); + + static TestPair init() { + Jex app = Jex.create() + .routing(routing -> routing + .get("/hi", ctx4 -> { + if (ctx4.header("Hx-Request") != null) { + ctx4.text("HxResponse"); + } + }) + .get("/hi", ctx -> ctx.text("NormalResponse")) + .get("/hi/{id}", ctx3 -> { + if (ctx3.header("Hx-Request") != null) { + ctx3.text("HxResponse|" + ctx3.pathParam("id")); + } + }) + .get("/hi/{id}", ctx2 -> { + if (ctx2.header("H2-Request") != null) { + ctx2.text("H2Response|" + ctx2.pathParam("id")); + } + }) + .get("/hi/{id}", ctx1 -> ctx1.text("NormalResponse|" + ctx1.pathParam("id"))) + ); + return TestPair.create(app); + } + + @AfterAll + static void end() { + pair.shutdown(); + } + + @Test + void test() { + HttpResponse hres = pair.request().path("hi").GET().asString(); + assertThat(hres.statusCode()).isEqualTo(200); + assertThat(hres.body()).isEqualTo("NormalResponse"); + + HttpResponse hxRes = pair.request() + .header("Hx-Request", "true") + .path("hi") + .GET().asString(); + assertThat(hxRes.statusCode()).isEqualTo(200); + assertThat(hxRes.body()).isEqualTo("HxResponse"); + } + + @Test + void testWithPathParam() { + HttpResponse hres = pair.request().path("hi/42").GET().asString(); + assertThat(hres.statusCode()).isEqualTo(200); + assertThat(hres.body()).isEqualTo("NormalResponse|42"); + + HttpResponse hxRes = pair.request() + .header("Hx-Request", "true") + .path("hi/42") + .GET().asString(); + assertThat(hxRes.statusCode()).isEqualTo(200); + assertThat(hxRes.body()).isEqualTo("HxResponse|42"); + + HttpResponse h2Res = pair.request() + .header("H2-Request", "true") + .path("hi/42") + .GET().asString(); + assertThat(h2Res.statusCode()).isEqualTo(200); + assertThat(h2Res.body()).isEqualTo("H2Response|42"); + } +} diff --git a/avaje-jex/src/test/java/io/avaje/jex/routes/RouteIndexTest.java b/avaje-jex/src/test/java/io/avaje/jex/routes/RouteIndexTest.java index 5db0e53e..674bc734 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/routes/RouteIndexTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/routes/RouteIndexTest.java @@ -5,45 +5,57 @@ import java.util.Set; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import io.avaje.jex.Routing; class RouteIndexTest { - private static final Routing.Entry routingEntry = Mockito.mock(Routing.Entry.class); - @Test void match() { - RouteIndex index = new RouteIndex(); - index.add(entry("/")); - index.add(entry("/a/b/c")); - index.add(entry("/a/b/d")); - index.add(entry("/a/b/d/e")); - index.add(entry("/a/b/d/e/f")); - index.add(entry("/a/b/d/e/f/g")); - index.add(entry("/a/b/d/e/f/g/h")); - index.add(entry("/a/b/d/e/f/g2/h")); + var indexBuild = new RouteIndexBuild(); + indexBuild.add(entry("/")); + indexBuild.add(entry("/a/b/c")); + indexBuild.add(entry("/a/b/d")); + indexBuild.add(entry("/a/b/d/e")); + indexBuild.add(entry("/a/b/d/e/f")); + indexBuild.add(entry("/a/b/d/e/f/g")); + indexBuild.add(entry("/a/b/d/e/f/g/h")); + indexBuild.add(entry("/a/b/d/e/f/g2/h")); + + RouteIndex index = indexBuild.build(); assertThat(index.match("/").matchPath()).isEqualTo("/"); assertThat(index.match("/a/b/d/e/f/g2/h").matchPath()).isEqualTo("/a/b/d/e/f/g2/h"); } + @Test + void matchMulti() { + var indexBuild = new RouteIndexBuild(); + indexBuild.add(entry("/hi/{id}")); + indexBuild.add(entry("/a/b/c")); + indexBuild.add(entry("/hi/{id}")); + indexBuild.add(entry("/b")); + + RouteIndex index = indexBuild.build(); + + SpiRoutes.Entry entry = index.match("/hi/42"); + assertThat(entry).isNotNull(); + } + @Test void match_args() { - RouteIndex index = new RouteIndex(); - index.add(entry("/")); - index.add(entry("/{id}")); - index.add(entry("/{id}/a")); - index.add(entry("/{id}/b")); - index.add(entry("/a/{id}/c")); - index.add(entry("/a/{name}/d")); - index.add(entry("/a/b/d/e")); - index.add(entry("/a/b/d/e/f")); - index.add(entry("/a/b/d/e/f/g")); - index.add(entry("/a/b/d/e/f/g/h")); - index.add(entry("/a/b/d/e/f/g2/h")); + var indexBuild = new RouteIndexBuild(); + indexBuild.add(entry("/")); + indexBuild.add(entry("/{id}")); + indexBuild.add(entry("/{id}/a")); + indexBuild.add(entry("/{id}/b")); + indexBuild.add(entry("/a/{id}/c")); + indexBuild.add(entry("/a/{name}/d")); + indexBuild.add(entry("/a/b/d/e")); + indexBuild.add(entry("/a/b/d/e/f")); + indexBuild.add(entry("/a/b/d/e/f/g")); + indexBuild.add(entry("/a/b/d/e/f/g/h")); + indexBuild.add(entry("/a/b/d/e/f/g2/h")); + var index = indexBuild.build(); assertThat(index.match("/").matchPath()).isEqualTo("/"); assertThat(index.match("/42").matchPath()).isEqualTo("/{id}"); assertThat(index.match("/99").matchPath()).isEqualTo("/{id}"); @@ -54,12 +66,13 @@ void match_args() { @Test void match_splat() { - RouteIndex index = new RouteIndex(); - index.add(entry("/")); - index.add(entry("/{id}")); - index.add(entry("/{id}/a")); - index.add(entry("/{id}/*")); + var indexBuild = new RouteIndexBuild(); + indexBuild.add(entry("/")); + indexBuild.add(entry("/{id}")); + indexBuild.add(entry("/{id}/a")); + indexBuild.add(entry("/{id}/*")); + var index = indexBuild.build(); assertThat(index.match("/").matchPath()).isEqualTo("/"); assertThat(index.match("/42").matchPath()).isEqualTo("/{id}"); assertThat(index.match("/42/a").matchPath()).isEqualTo("/{id}/a"); @@ -68,6 +81,7 @@ void match_splat() { } private SpiRoutes.Entry entry(String path) { - return new RouteEntry(new PathParser(path, true), routingEntry.getHandler(), Set.of()); + return new RouteEntry(new PathParser(path, true), null, Set.of()); } + } From 04ece1124de0d6d7a6ae17ef1f33b7bd58752c7c Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 2 Dec 2024 09:24:10 +1300 Subject: [PATCH 089/250] Add example-robaho project (#108) --- examples/example-robaho/pom.xml | 40 +++++++++++++++++++ .../src/main/java/io/avaje/Main.java | 24 +++++++++++ .../src/main/resources/logback.xml | 18 +++++++++ examples/pom.xml | 1 + 4 files changed, 83 insertions(+) create mode 100644 examples/example-robaho/pom.xml create mode 100644 examples/example-robaho/src/main/java/io/avaje/Main.java create mode 100644 examples/example-robaho/src/main/resources/logback.xml diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml new file mode 100644 index 00000000..972683fe --- /dev/null +++ b/examples/example-robaho/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + io.avaje + examples + 0.1 + + + example-robaho + + + + io.github.robaho + httpserver + 1.0.10 + + + + io.avaje + avaje-jex + 3.0-RC4 + + + + org.slf4j + slf4j-jdk-platform-logging + 2.0.16 + + + + ch.qos.logback + logback-classic + 1.5.12 + + + + diff --git a/examples/example-robaho/src/main/java/io/avaje/Main.java b/examples/example-robaho/src/main/java/io/avaje/Main.java new file mode 100644 index 00000000..d7366d27 --- /dev/null +++ b/examples/example-robaho/src/main/java/io/avaje/Main.java @@ -0,0 +1,24 @@ +package io.avaje; + +import io.avaje.jex.Jex; + +public class Main { + + public static void main(String[] args) { + + // Below system property is NOT required as it will register via service loading + // System.setProperty("com.sun.net.httpserver.HttpServerProvider", "robaho.net.httpserver.DefaultHttpServerProvider"); + + Jex.create() + .routing(routing -> routing + .get("/", ctx -> ctx.text("root")) + .get("/one", ctx -> ctx.text("one")) + .get("/two/{name}", ctx -> { + ctx.text("two Yo " + ctx.pathParam("name")); + }) + .post("one", ctx -> ctx.text("posted"))) + .port(7002) + .start(); + + } +} diff --git a/examples/example-robaho/src/main/resources/logback.xml b/examples/example-robaho/src/main/resources/logback.xml new file mode 100644 index 00000000..4bcf2f92 --- /dev/null +++ b/examples/example-robaho/src/main/resources/logback.xml @@ -0,0 +1,18 @@ + + + + TRACE + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + diff --git a/examples/pom.xml b/examples/pom.xml index 010bc923..a98476dc 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -14,6 +14,7 @@ example-jdk + example-robaho From e0f489f34424b6c88828c05b22c0c6205b8c2c12 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 2 Dec 2024 12:46:21 +1300 Subject: [PATCH 090/250] Expose Context.outputStream() such that outputStream can be used directly (#109) For example, with Jsonb we can obtain the specific type once and then write directly to the outputSteam like: ctx.status(200).contentType("application/json"); var result = HelloDto.rob(); jsonTypeHelloDto.toJson(result, ctx.outputStream()); ... where jsonTypeHelloDto is obtained like: static final Jsonb jsonb = Jsonb.builder().build(); static final JsonType jsonTypeHelloDto = jsonb.type(HelloDto.class); With this approach, it effectively bypasses the underlying io.avaje.jex.spi.JsonService with the view that this can be more flexible and more efficient. --- avaje-jex/pom.xml | 6 +++ .../src/main/java/io/avaje/jex/Context.java | 10 +++++ .../java/io/avaje/jex/jdk/JdkContext.java | 1 + .../test/java/io/avaje/jex/jdk/HelloDto.java | 3 ++ .../test/java/io/avaje/jex/jdk/JsonTest.java | 39 +++++++++++++++++-- 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index e4139c48..cab9c672 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -48,6 +48,12 @@ true + + io.avaje + avaje-jsonb-generator + 2.3 + test + diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index 4b2f2ba9..cd91cad7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -4,6 +4,7 @@ import static java.util.Collections.emptyMap; import java.io.InputStream; +import java.io.OutputStream; import java.time.Duration; import java.time.ZonedDateTime; import java.util.Iterator; @@ -294,6 +295,15 @@ default String userAgent() { */ Context write(InputStream is); + /** + * Return the outputStream to write content. It is expected that + * the {@link #contentType(String)} has been set prior to obtaining + * and writing to the outputStream. + * + * @return The outputStream to write content to. + */ + OutputStream outputStream(); + /** * Render a template typically as html. * diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index cfb34839..c0ffe37d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -477,6 +477,7 @@ public String protocol() { return exchange.getProtocol(); } + @Override public OutputStream outputStream() { var out = mgr.createOutputStream(this); if (compressionConfig.compressionEnabled()) { diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/HelloDto.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/HelloDto.java index e8861f3f..a352e742 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/HelloDto.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/HelloDto.java @@ -1,5 +1,8 @@ package io.avaje.jex.jdk; +import io.avaje.jsonb.Json; + +@Json public class HelloDto { public long id; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java b/avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java index 1ab2dcab..a7fc591a 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java @@ -11,6 +11,8 @@ import java.util.concurrent.locks.LockSupport; import java.util.stream.Stream; +import io.avaje.jsonb.JsonType; +import io.avaje.jsonb.Jsonb; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; @@ -26,12 +28,19 @@ private static AutoCloseIterator createBeanIterator() { return new AutoCloseIterator<>(HELLO_BEANS.iterator()); } - static TestPair pair = init(); + static final TestPair pair = init(); + static final Jsonb jsonb = Jsonb.builder().build(); + static final JsonType jsonTypeHelloDto = jsonb.type(HelloDto.class); static TestPair init() { Jex app = Jex.create() .routing(routing -> routing - .get("/", ctx -> ctx.json(HelloDto.rob()).status(200)) + .get("/", ctx -> ctx.status(200).json(HelloDto.rob())) + .get("/usingOutputStream", ctx -> { + ctx.status(200).contentType("application/json"); + var result = HelloDto.rob(); + jsonTypeHelloDto.toJson(result, ctx.outputStream()); + }) .get("/iterate", ctx -> ctx.jsonStream(ITERATOR)) .get("/stream", ctx -> ctx.jsonStream(HELLO_BEANS.stream())) .post("/", ctx -> ctx.text("bean[" + ctx.bodyAsClass(HelloDto.class) + "]"))); @@ -58,9 +67,33 @@ void get() { .GET().asString(); final HttpHeaders headers = hres.headers(); - assertThat(headers.firstValue("Content-Type").get()).isEqualTo("application/json"); + assertThat(headers.firstValue("Content-Type").orElseThrow()).isEqualTo("application/json"); } + @Test + void usingOutputStream() { + + var bean = pair.request().path("usingOutputStream") + .GET() + .bean(HelloDto.class); + + assertThat(bean.id).isEqualTo(42); + assertThat(bean.name).isEqualTo("rob"); + + final HttpResponse hres = pair.request() + .GET().asString(); + + final HttpHeaders headers = hres.headers(); + assertThat(headers.firstValue("Content-Type").orElseThrow()).isEqualTo("application/json"); + + bean = pair.request().path("usingOutputStream") + .GET() + .bean(HelloDto.class); + assertThat(bean.id).isEqualTo(42); + assertThat(bean.name).isEqualTo("rob"); + } + + @Test void stream_viaIterator() { final Stream beanStream = pair.request() From e1266bd22227f4c7725d78a6dd8ec69d6a42480e Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 2 Dec 2024 13:13:51 +1300 Subject: [PATCH 091/250] Version 3.0-RC5 --- avaje-jex-freemarker/pom.xml | 6 +++--- avaje-jex-htmx/pom.xml | 4 ++-- avaje-jex-mustache/pom.xml | 6 +++--- avaje-jex-test/pom.xml | 4 ++-- avaje-jex/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 4 ++-- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 4377fe1b..64fcdbe2 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC4 + 3.0-RC5 avaje-jex-freemarker @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-RC4 + 3.0-RC5 provided @@ -39,7 +39,7 @@ io.avaje avaje-jex-test - 3.0-RC4 + 3.0-RC5 test diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index ff6319e0..5db71573 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC4 + 3.0-RC5 avaje-jex-htmx @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC4 + 3.0-RC5 diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 93b0689c..3099951a 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC4 + 3.0-RC5 avaje-jex-mustache @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-RC4 + 3.0-RC5 provided @@ -40,7 +40,7 @@ io.avaje avaje-jex-test - 3.0-RC4 + 3.0-RC5 test diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 8f63160a..9956372b 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC4 + 3.0-RC5 avaje-jex-test @@ -14,7 +14,7 @@ io.avaje avaje-jex - 3.0-RC4 + 3.0-RC5 provided diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index cab9c672..e294dd0f 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC4 + 3.0-RC5 avaje-jex diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index c5ebf079..12a0da0e 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC4 + 3.0-RC5 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 972683fe..da5fb336 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC4 + 3.0-RC5 diff --git a/examples/pom.xml b/examples/pom.xml index a98476dc..6b0f0d02 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC4 + 3.0-RC5 examples diff --git a/pom.xml b/pom.xml index ccbd4496..90a98eb2 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.avaje avaje-jex-parent - 3.0-RC4 + 3.0-RC5 pom @@ -22,7 +22,7 @@ 2.18.1 false 21 - 2024-11-29T22:35:26Z + 2024-12-02T00:10:24Z From 72ed10d6e9275d8e31c2a35453b07a2f11a37475 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 2 Dec 2024 14:50:34 +1300 Subject: [PATCH 092/250] Move classes from jdk package into core so that less need to be public (#110) * Make RoutingHandler not public * Move classes from jdk package into core so that less need to be public A few classes like JdkContext needed to be public because they where in the jdk package and moving them to core allows them to be non-public --- avaje-jex/src/main/java/io/avaje/jex/DJex.java | 5 ++--- .../java/io/avaje/jex/{jdk => core}/BaseFilterChain.java | 2 +- .../java/io/avaje/jex/{jdk => core}/BufferedOutStream.java | 2 +- .../main/java/io/avaje/jex/{jdk => core}/CookieParser.java | 2 +- .../main/java/io/avaje/jex/core/CoreServiceManager.java | 7 +++---- .../java/io/avaje/jex/{jdk => core}/CtxServiceManager.java | 5 ++--- .../src/main/java/io/avaje/jex/core/ExceptionManager.java | 1 - .../main/java/io/avaje/jex/{jdk => core}/JdkContext.java | 5 ++--- .../main/java/io/avaje/jex/{jdk => core}/JdkJexServer.java | 2 +- .../java/io/avaje/jex/{jdk => core}/JdkServerStart.java | 6 +++--- .../src/main/java/io/avaje/jex/{jdk => core}/Mode.java | 2 +- .../java/io/avaje/jex/{jdk => core}/RoutingHandler.java | 4 ++-- .../src/main/java/io/avaje/jex/core/SpiServiceManager.java | 2 -- avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java | 2 +- .../java/io/avaje/jex/compression/CompressionTest.java | 2 +- .../java/io/avaje/jex/{jdk => core}/AutoCloseIterator.java | 2 +- .../io/avaje/jex/{jdk => core}/CharacterEncodingTest.java | 2 +- .../io/avaje/jex/{jdk => core}/ContextAttributeTest.java | 2 +- .../io/avaje/jex/{jdk => core}/ContextFormParamTest.java | 2 +- .../java/io/avaje/jex/{jdk => core}/ContextLengthTest.java | 2 +- .../test/java/io/avaje/jex/{jdk => core}/ContextTest.java | 2 +- .../java/io/avaje/jex/{jdk => core}/CookieParserTest.java | 2 +- .../java/io/avaje/jex/{jdk => core}/CookieServerTest.java | 2 +- .../io/avaje/jex/{jdk => core}/ExceptionManagerTest.java | 2 +- .../test/java/io/avaje/jex/{jdk => core}/FilterTest.java | 2 +- .../test/java/io/avaje/jex/{jdk => core}/HeadersTest.java | 2 +- .../io/avaje/jex/{jdk => core}/HealthPluginOffTest.java | 2 +- .../java/io/avaje/jex/{jdk => core}/HealthPluginTest.java | 2 +- .../test/java/io/avaje/jex/{jdk => core}/HelloBean.java | 2 +- .../src/test/java/io/avaje/jex/{jdk => core}/HelloDto.java | 2 +- .../java/io/avaje/jex/{jdk => core}/JdkJexServerTest.java | 2 +- .../src/test/java/io/avaje/jex/{jdk => core}/JsonTest.java | 2 +- .../src/test/java/io/avaje/jex/{jdk => core}/Main.java | 3 +-- .../java/io/avaje/jex/{jdk => core}/MultiHandlerTest.java | 2 +- .../java/io/avaje/jex/{jdk => core}/NestedRoutesTest.java | 2 +- .../java/io/avaje/jex/{jdk => core}/QueryParamTest.java | 4 +--- .../test/java/io/avaje/jex/{jdk => core}/RedirectTest.java | 2 +- .../src/test/java/io/avaje/jex/{jdk => core}/TestPair.java | 2 +- examples/example-jdk-jsonb/README.md | 2 +- 39 files changed, 45 insertions(+), 55 deletions(-) rename avaje-jex/src/main/java/io/avaje/jex/{jdk => core}/BaseFilterChain.java (96%) rename avaje-jex/src/main/java/io/avaje/jex/{jdk => core}/BufferedOutStream.java (98%) rename avaje-jex/src/main/java/io/avaje/jex/{jdk => core}/CookieParser.java (99%) rename avaje-jex/src/main/java/io/avaje/jex/{jdk => core}/CtxServiceManager.java (93%) rename avaje-jex/src/main/java/io/avaje/jex/{jdk => core}/JdkContext.java (99%) rename avaje-jex/src/main/java/io/avaje/jex/{jdk => core}/JdkJexServer.java (97%) rename avaje-jex/src/main/java/io/avaje/jex/{jdk => core}/JdkServerStart.java (92%) rename avaje-jex/src/main/java/io/avaje/jex/{jdk => core}/Mode.java (73%) rename avaje-jex/src/main/java/io/avaje/jex/{jdk => core}/RoutingHandler.java (96%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/AutoCloseIterator.java (95%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/CharacterEncodingTest.java (98%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/ContextAttributeTest.java (98%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/ContextFormParamTest.java (99%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/ContextLengthTest.java (99%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/ContextTest.java (99%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/CookieParserTest.java (98%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/CookieServerTest.java (99%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/ExceptionManagerTest.java (99%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/FilterTest.java (99%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/HeadersTest.java (97%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/HealthPluginOffTest.java (97%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/HealthPluginTest.java (99%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/HelloBean.java (87%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/HelloDto.java (94%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/JdkJexServerTest.java (98%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/JsonTest.java (99%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/Main.java (78%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/MultiHandlerTest.java (98%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/NestedRoutesTest.java (98%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/QueryParamTest.java (98%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/RedirectTest.java (98%) rename avaje-jex/src/test/java/io/avaje/jex/{jdk => core}/TestPair.java (98%) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index a5bf11dc..af6c52d6 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -2,9 +2,8 @@ import io.avaje.inject.BeanScope; import io.avaje.jex.core.CoreServiceLoader; -import io.avaje.jex.core.CoreServiceManager; import io.avaje.jex.core.HealthPlugin; -import io.avaje.jex.jdk.JdkServerStart; +import io.avaje.jex.core.JdkServerStart; import io.avaje.jex.routes.RoutesBuilder; import io.avaje.jex.routes.SpiRoutes; import io.avaje.jex.spi.*; @@ -121,6 +120,6 @@ public Server start() { new RoutesBuilder(routing, config.ignoreTrailingSlashes()) .build(); - return new JdkServerStart().start(this, routes, CoreServiceManager.create(this)); + return new JdkServerStart().start(this, routes); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilterChain.java b/avaje-jex/src/main/java/io/avaje/jex/core/BaseFilterChain.java similarity index 96% rename from avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilterChain.java rename to avaje-jex/src/main/java/io/avaje/jex/core/BaseFilterChain.java index 88efe66f..d023b8cc 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/BaseFilterChain.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BaseFilterChain.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import java.io.IOException; import java.util.List; diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java b/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java similarity index 98% rename from avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java rename to avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java index 47b1c601..3d0482eb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/BufferedOutStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/CookieParser.java b/avaje-jex/src/main/java/io/avaje/jex/core/CookieParser.java similarity index 99% rename from avaje-jex/src/main/java/io/avaje/jex/jdk/CookieParser.java rename to avaje-jex/src/main/java/io/avaje/jex/core/CookieParser.java index aa2e3bdd..861654bf 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/CookieParser.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CookieParser.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import java.util.ArrayList; import java.util.LinkedHashMap; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java index 1ef59b1d..db25a195 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java @@ -20,24 +20,23 @@ import io.avaje.jex.Routing; import io.avaje.jex.core.json.JacksonJsonService; import io.avaje.jex.core.json.JsonbJsonService; -import io.avaje.jex.jdk.JdkContext; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; /** * Core implementation of SpiServiceManager provided to specific implementations like jetty etc. */ -public final class CoreServiceManager implements SpiServiceManager { +final class CoreServiceManager implements SpiServiceManager { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); - public static final String UTF_8 = "UTF-8"; + static final String UTF_8 = "UTF-8"; private final HttpMethodMap methodMap = new HttpMethodMap(); private final JsonService jsonService; private final ExceptionManager exceptionHandler; private final TemplateManager templateManager; - public static SpiServiceManager create(Jex jex) { + static SpiServiceManager create(Jex jex) { return new Builder(jex).build(); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/CtxServiceManager.java similarity index 93% rename from avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java rename to avaje-jex/src/main/java/io/avaje/jex/core/CtxServiceManager.java index ce74f049..1f9666d5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CtxServiceManager.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import java.io.InputStream; import java.io.OutputStream; @@ -9,9 +9,8 @@ import io.avaje.jex.Context; import io.avaje.jex.Routing; -import io.avaje.jex.core.SpiServiceManager; -public final class CtxServiceManager implements SpiServiceManager { +final class CtxServiceManager implements SpiServiceManager { private final String scheme; private final String contextPath; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index fa956fe7..cf2ba174 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -10,7 +10,6 @@ import io.avaje.jex.http.ErrorCode; import io.avaje.jex.http.HttpResponseException; import io.avaje.jex.http.InternalServerErrorException; -import io.avaje.jex.jdk.JdkContext; public final class ExceptionManager { diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java similarity index 99% rename from avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java rename to avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index c0ffe37d..e8e185d0 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import static io.avaje.jex.core.Constants.APPLICATION_JSON; import static io.avaje.jex.core.Constants.APPLICATION_X_JSON_STREAM; @@ -31,13 +31,12 @@ import io.avaje.jex.Context; import io.avaje.jex.compression.CompressedOutputStream; import io.avaje.jex.compression.CompressionConfig; -import io.avaje.jex.core.Constants; import io.avaje.jex.http.ErrorCode; import io.avaje.jex.http.RedirectException; import io.avaje.jex.security.BasicAuthCredentials; import io.avaje.jex.security.Role; -public final class JdkContext implements Context { +final class JdkContext implements Context { private static final String UTF8 = "UTF8"; private static final int SC_MOVED_TEMPORARILY = 302; diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkJexServer.java similarity index 97% rename from avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java rename to avaje-jex/src/main/java/io/avaje/jex/core/JdkJexServer.java index 32de4f62..03f01c9c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkJexServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkJexServer.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import com.sun.net.httpserver.HttpServer; import io.avaje.applog.AppLog; diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkServerStart.java similarity index 92% rename from avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java rename to avaje-jex/src/main/java/io/avaje/jex/core/JdkServerStart.java index 9af850fd..3f9cf92e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkServerStart.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import java.io.IOException; import java.io.UncheckedIOException; @@ -13,7 +13,6 @@ import io.avaje.jex.AppLifecycle; import io.avaje.jex.Jex; import io.avaje.jex.JexConfig; -import io.avaje.jex.core.SpiServiceManager; import io.avaje.jex.routes.SpiRoutes; import static java.lang.System.Logger.Level.INFO; @@ -22,7 +21,8 @@ public final class JdkServerStart { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); - public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceManager) { + public Jex.Server start(Jex jex, SpiRoutes routes) { + SpiServiceManager serviceManager = CoreServiceManager.create(jex); try { final var config = jex.config(); final var socketAddress = createSocketAddress(config); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/Mode.java b/avaje-jex/src/main/java/io/avaje/jex/core/Mode.java similarity index 73% rename from avaje-jex/src/main/java/io/avaje/jex/jdk/Mode.java rename to avaje-jex/src/main/java/io/avaje/jex/core/Mode.java index bd0e6fd5..1b242a40 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/Mode.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/Mode.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; /** status of the request */ enum Mode { diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingHandler.java b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java similarity index 96% rename from avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingHandler.java rename to avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java index ce48e1e3..17f0c447 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import java.io.IOException; import java.util.List; @@ -14,7 +14,7 @@ import io.avaje.jex.http.NotFoundException; import io.avaje.jex.routes.SpiRoutes; -public final class RoutingHandler implements HttpHandler { +final class RoutingHandler implements HttpHandler { private final SpiRoutes routes; private final CtxServiceManager mgr; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java index ab60303b..c244adaf 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java @@ -9,8 +9,6 @@ import io.avaje.jex.Context; import io.avaje.jex.Routing; -import io.avaje.jex.jdk.CtxServiceManager; -import io.avaje.jex.jdk.JdkContext; /** * Core service methods available to Context implementations. diff --git a/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java b/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java index e470ad66..08c9918d 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java @@ -9,7 +9,7 @@ import org.junit.jupiter.api.Test; import io.avaje.jex.StaticContentConfig.ResourceLocation; -import io.avaje.jex.jdk.TestPair; +import io.avaje.jex.core.TestPair; class StaticFileTest { diff --git a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java index ac9ab042..fb6b3c1b 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java @@ -10,7 +10,7 @@ import io.avaje.jex.Jex; import io.avaje.jex.StaticContentConfig.ResourceLocation; import io.avaje.jex.core.Constants; -import io.avaje.jex.jdk.TestPair; +import io.avaje.jex.core.TestPair; class CompressionTest { diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java b/avaje-jex/src/test/java/io/avaje/jex/core/AutoCloseIterator.java similarity index 95% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java rename to avaje-jex/src/test/java/io/avaje/jex/core/AutoCloseIterator.java index 74541670..6df2bd9e 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/AutoCloseIterator.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/AutoCloseIterator.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import java.util.Iterator; import java.util.concurrent.atomic.AtomicBoolean; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/CharacterEncodingTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/CharacterEncodingTest.java similarity index 98% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/CharacterEncodingTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/CharacterEncodingTest.java index 3d0f7dcb..5ed62058 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/CharacterEncodingTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/CharacterEncodingTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.jex.Jex; import org.junit.jupiter.api.AfterAll; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextAttributeTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ContextAttributeTest.java similarity index 98% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/ContextAttributeTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/ContextAttributeTest.java index aad420d9..899c9f02 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextAttributeTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ContextAttributeTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.jex.Jex; import org.junit.jupiter.api.AfterAll; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextFormParamTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ContextFormParamTest.java similarity index 99% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/ContextFormParamTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/ContextFormParamTest.java index 6ca5e2c0..c438ddaa 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextFormParamTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ContextFormParamTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.jex.Jex; import org.junit.jupiter.api.AfterAll; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextLengthTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ContextLengthTest.java similarity index 99% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/ContextLengthTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/ContextLengthTest.java index 3aa92d11..a12b0998 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextLengthTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ContextLengthTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.jex.Jex; import org.junit.jupiter.api.AfterAll; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ContextTest.java similarity index 99% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/ContextTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/ContextTest.java index a1619978..35912af2 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/ContextTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ContextTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.jex.Jex; import org.junit.jupiter.api.AfterAll; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/CookieParserTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/CookieParserTest.java similarity index 98% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/CookieParserTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/CookieParserTest.java index 46649180..a63c57ef 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/CookieParserTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/CookieParserTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import org.junit.jupiter.api.Test; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/CookieServerTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/CookieServerTest.java similarity index 99% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/CookieServerTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/CookieServerTest.java index 20a4610f..b81746c1 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/CookieServerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/CookieServerTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.jex.Context; import io.avaje.jex.Jex; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ExceptionManagerTest.java similarity index 99% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/ExceptionManagerTest.java index e2a2a08c..e555fe6e 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/ExceptionManagerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ExceptionManagerTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.jex.Jex; import io.avaje.jex.http.ErrorCode; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java similarity index 99% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java index 5d7823fc..95826c40 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/FilterTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.jex.Jex; import org.junit.jupiter.api.AfterAll; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/HeadersTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/HeadersTest.java similarity index 97% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/HeadersTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/HeadersTest.java index 263452e9..2fb8e75d 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/HeadersTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/HeadersTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.http.client.HttpClient; import io.avaje.http.client.JacksonBodyAdapter; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/HealthPluginOffTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/HealthPluginOffTest.java similarity index 97% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/HealthPluginOffTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/HealthPluginOffTest.java index 2b1612aa..e8c51345 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/HealthPluginOffTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/HealthPluginOffTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.jex.Jex; import org.junit.jupiter.api.AfterAll; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/HealthPluginTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/HealthPluginTest.java similarity index 99% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/HealthPluginTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/HealthPluginTest.java index 4dcbb4d5..fb0438e4 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/HealthPluginTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/HealthPluginTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.http.client.HttpClient; import io.avaje.http.client.JacksonBodyAdapter; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/HelloBean.java b/avaje-jex/src/test/java/io/avaje/jex/core/HelloBean.java similarity index 87% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/HelloBean.java rename to avaje-jex/src/test/java/io/avaje/jex/core/HelloBean.java index a89d5f57..327263c2 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/HelloBean.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/HelloBean.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; public class HelloBean { diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/HelloDto.java b/avaje-jex/src/test/java/io/avaje/jex/core/HelloDto.java similarity index 94% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/HelloDto.java rename to avaje-jex/src/test/java/io/avaje/jex/core/HelloDto.java index a352e742..5cc0ea98 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/HelloDto.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/HelloDto.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.jsonb.Json; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/JdkJexServerTest.java similarity index 98% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/JdkJexServerTest.java index d13b5401..9910e196 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/JdkJexServerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/JdkJexServerTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import static org.assertj.core.api.Assertions.assertThat; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java similarity index 99% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java index a7fc591a..c3a88646 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/JsonTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import static java.util.Arrays.asList; import static java.util.stream.Collectors.toList; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/Main.java b/avaje-jex/src/test/java/io/avaje/jex/core/Main.java similarity index 78% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/Main.java rename to avaje-jex/src/test/java/io/avaje/jex/core/Main.java index dbdb29b3..62c8da9a 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/Main.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/Main.java @@ -1,7 +1,6 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.jex.Jex; -import io.avaje.jex.core.HealthPlugin; public class Main { diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/MultiHandlerTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/MultiHandlerTest.java similarity index 98% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/MultiHandlerTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/MultiHandlerTest.java index 3ac050e0..a5d627bd 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/MultiHandlerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/MultiHandlerTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.jex.Jex; import org.junit.jupiter.api.AfterAll; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/NestedRoutesTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/NestedRoutesTest.java similarity index 98% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/NestedRoutesTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/NestedRoutesTest.java index 84630d92..b234a350 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/NestedRoutesTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/NestedRoutesTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.jex.Jex; import org.junit.jupiter.api.AfterAll; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/QueryParamTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/QueryParamTest.java similarity index 98% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/QueryParamTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/QueryParamTest.java index 64e7ed13..442d09a7 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/QueryParamTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/QueryParamTest.java @@ -1,12 +1,10 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.jex.Jex; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import java.net.http.HttpResponse; -import java.util.Map; -import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/RedirectTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/RedirectTest.java similarity index 98% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/RedirectTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/RedirectTest.java index 0ad09b81..dc5756ad 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/RedirectTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/RedirectTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.jex.Jex; import org.junit.jupiter.api.AfterAll; diff --git a/avaje-jex/src/test/java/io/avaje/jex/jdk/TestPair.java b/avaje-jex/src/test/java/io/avaje/jex/core/TestPair.java similarity index 98% rename from avaje-jex/src/test/java/io/avaje/jex/jdk/TestPair.java rename to avaje-jex/src/test/java/io/avaje/jex/core/TestPair.java index 3b9171f4..b53288c0 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/jdk/TestPair.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/TestPair.java @@ -1,4 +1,4 @@ -package io.avaje.jex.jdk; +package io.avaje.jex.core; import io.avaje.http.client.HttpClient; import io.avaje.http.client.HttpClientRequest; diff --git a/examples/example-jdk-jsonb/README.md b/examples/example-jdk-jsonb/README.md index fe4f1219..ba42f2ea 100644 --- a/examples/example-jdk-jsonb/README.md +++ b/examples/example-jdk-jsonb/README.md @@ -15,7 +15,7 @@ To Run: ./target/mytest # produces -# Oct 21, 2022 1:39:36 PM io.avaje.jex.jdk.JdkServerStart start +# Oct 21, 2022 1:39:36 PM io.avaje.jex.core.JdkServerStart start # INFO: started server on port 7003 version 2.5-SNAPSHOT ``` From 8615774c09025ad5b65f2b65a99df2d031f4bd37 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 2 Dec 2024 15:12:34 +1300 Subject: [PATCH 093/250] Rename JdkServerStart to BootstrapServer, more classes non-public (#111) * Rename JdkServerStart to BootstrapServer, more classes non-public - Rename JdkServerStart to BootstrapServer - Move all bootstrap logic into BootstrapServer - Make more classes in core non-public * Make BootstrapServer final --- .../src/main/java/io/avaje/jex/DJex.java | 20 ++--------- .../main/java/io/avaje/jex/DJexConfig.java | 3 +- .../src/main/java/io/avaje/jex/JexConfig.java | 3 ++ ...kServerStart.java => BootstrapServer.java} | 35 ++++++++++++++----- .../io/avaje/jex/core/CoreServiceLoader.java | 8 ++--- .../io/avaje/jex/core/CoreServiceManager.java | 3 +- .../io/avaje/jex/core/CtxServiceManager.java | 6 +--- .../java/io/avaje/jex/core/HealthPlugin.java | 2 +- .../java/io/avaje/jex/core/JdkContext.java | 2 +- .../io/avaje/jex/core/TemplateManager.java | 11 +++--- 10 files changed, 47 insertions(+), 46 deletions(-) rename avaje-jex/src/main/java/io/avaje/jex/core/{JdkServerStart.java => BootstrapServer.java} (79%) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index af6c52d6..d13b0696 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -1,11 +1,7 @@ package io.avaje.jex; import io.avaje.inject.BeanScope; -import io.avaje.jex.core.CoreServiceLoader; -import io.avaje.jex.core.HealthPlugin; -import io.avaje.jex.core.JdkServerStart; -import io.avaje.jex.routes.RoutesBuilder; -import io.avaje.jex.routes.SpiRoutes; +import io.avaje.jex.core.BootstrapServer; import io.avaje.jex.spi.*; import java.util.*; @@ -108,18 +104,6 @@ public AppLifecycle lifecycle() { @Override public Server start() { - if (config.health()) { - plugin(new HealthPlugin()); - } - - if (config.useSpiPlugins()) { - CoreServiceLoader.plugins().forEach(p -> p.apply(this)); - } - - final SpiRoutes routes = - new RoutesBuilder(routing, config.ignoreTrailingSlashes()) - .build(); - - return new JdkServerStart().start(this, routes); + return BootstrapServer.start(this); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index ee0cb116..7187256e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -164,7 +164,8 @@ public DJexConfig disableSpiPlugins() { return this; } - boolean useSpiPlugins() { + @Override + public boolean useSpiPlugins() { return useJexSpi; } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index c36c42e3..72d93de8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -145,4 +145,7 @@ public sealed interface JexConfig permits DJexConfig { /** Return the socket backlog. */ int socketBacklog(); + + /** Return true if SPI plugins should be loaded and registered. */ + boolean useSpiPlugins(); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java similarity index 79% rename from avaje-jex/src/main/java/io/avaje/jex/core/JdkServerStart.java rename to avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java index 3f9cf92e..5fe633a9 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java @@ -1,27 +1,44 @@ package io.avaje.jex.core; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; - import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpsServer; - import io.avaje.applog.AppLog; import io.avaje.jex.AppLifecycle; import io.avaje.jex.Jex; import io.avaje.jex.JexConfig; +import io.avaje.jex.routes.RoutesBuilder; import io.avaje.jex.routes.SpiRoutes; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; + import static java.lang.System.Logger.Level.INFO; -public final class JdkServerStart { +public final class BootstrapServer { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); - public Jex.Server start(Jex jex, SpiRoutes routes) { + public static Jex.Server start(Jex jex) { + final var config = jex.config(); + if (config.health()) { + jex.plugin(new HealthPlugin()); + } + + if (config.useSpiPlugins()) { + CoreServiceLoader.plugins().forEach(p -> p.apply(jex)); + } + + final SpiRoutes routes = + new RoutesBuilder(jex.routing(), config.ignoreTrailingSlashes()) + .build(); + + return start(jex, routes); + } + + static Jex.Server start(Jex jex, SpiRoutes routes) { SpiServiceManager serviceManager = CoreServiceManager.create(jex); try { final var config = jex.config(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java index 422be797..53411915 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java @@ -11,7 +11,7 @@ import io.avaje.jex.spi.TemplateRender; /** Core implementation of SpiServiceManager provided to specific implementations like jetty etc. */ -public final class CoreServiceLoader { +final class CoreServiceLoader { private static final CoreServiceLoader INSTANCE = new CoreServiceLoader(); @@ -31,15 +31,15 @@ public final class CoreServiceLoader { jsonService = spiJsonService; } - public static Optional jsonService() { + static Optional jsonService() { return Optional.ofNullable(INSTANCE.jsonService); } - public static List getRenders() { + static List getRenders() { return INSTANCE.renders; } - public static List plugins() { + static List plugins() { return INSTANCE.plugins; } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java index db25a195..b5bbf39e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java @@ -140,7 +140,8 @@ public Map> parseParamMap(String body, String charset) { } } - private static class Builder { + private static final class Builder { + private final Jex jex; Builder(Jex jex) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CtxServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/CtxServiceManager.java index 1f9666d5..20135c06 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CtxServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CtxServiceManager.java @@ -30,11 +30,7 @@ String scheme() { return scheme; } - public String url() { - return scheme + "://"; - } - - public String contextPath() { + String contextPath() { return contextPath; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java b/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java index bc8aca4f..30939756 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java @@ -9,7 +9,7 @@ * Health plugin with liveness and readiness support based on * the application lifecycle support. */ -public final class HealthPlugin implements JexPlugin { +final class HealthPlugin implements JexPlugin { private AppLifecycle lifecycle; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index e8e185d0..9b13aa13 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -485,7 +485,7 @@ public OutputStream outputStream() { return out; } - public void setMode(Mode type) { + void setMode(Mode type) { this.mode = type; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java index 65e1ec44..23043dd1 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java @@ -11,16 +11,15 @@ /** * Render templates typically as html. */ -public final class TemplateManager { +final class TemplateManager { private final Map map = new HashMap<>(); - private final Set> renderTypes = new HashSet<>(); /** * Register all the extension renderer pairs. */ - public void register(Map source) { + void register(Map source) { map.putAll(source); map.values().forEach(templateRender -> renderTypes.add(templateRender.getClass())); } @@ -28,7 +27,7 @@ public void register(Map source) { /** * Auto register via ServiceLoader if it has not already been explicitly registered. */ - public void registerDefault(TemplateRender render) { + void registerDefault(TemplateRender render) { if (!renderTypes.contains(render.getClass())) { for (String extension : render.defaultExtensions()) { map.computeIfAbsent(extension, k->render); @@ -39,7 +38,7 @@ public void registerDefault(TemplateRender render) { /** * Register an extension and renderer. */ - public void register(String extn, TemplateRender renderer) { + void register(String extn, TemplateRender renderer) { map.put(extn, renderer); } @@ -50,7 +49,7 @@ public void register(String extn, TemplateRender renderer) { * @param name The name of the template * @param model The model key value pairs to render use with the template */ - public void render(Context ctx, String name, Map model) { + void render(Context ctx, String name, Map model) { final String extn = extension(name); if (extn == null) { throw new IllegalArgumentException("No extension, not handled yet - " + name); From 6735a4b0b2268377bdfd92ae8a61600f033d88b5 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 2 Dec 2024 16:12:43 +1300 Subject: [PATCH 094/250] Make ExceptionManager non-public (#112) --- .../java/io/avaje/jex/core/ExceptionManager.java | 6 +++--- .../jex/{ => core}/DefaultErrorHandlingTest.java | 13 +++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) rename avaje-jex/src/test/java/io/avaje/jex/{ => core}/DefaultErrorHandlingTest.java (85%) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index cf2ba174..0f6c6009 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -11,7 +11,7 @@ import io.avaje.jex.http.HttpResponseException; import io.avaje.jex.http.InternalServerErrorException; -public final class ExceptionManager { +final class ExceptionManager { private static final String APPLICATION_JSON = "application/json"; @@ -19,12 +19,12 @@ public final class ExceptionManager { private final Map, ExceptionHandler> handlers; - public ExceptionManager(Map, ExceptionHandler> handlers) { + ExceptionManager(Map, ExceptionHandler> handlers) { this.handlers = handlers; } @SuppressWarnings("unchecked") - public ExceptionHandler find(Class exceptionType) { + ExceptionHandler find(Class exceptionType) { Class type = exceptionType; do { final ExceptionHandler handler = handlers.get(type); diff --git a/avaje-jex/src/test/java/io/avaje/jex/DefaultErrorHandlingTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/DefaultErrorHandlingTest.java similarity index 85% rename from avaje-jex/src/test/java/io/avaje/jex/DefaultErrorHandlingTest.java rename to avaje-jex/src/test/java/io/avaje/jex/core/DefaultErrorHandlingTest.java index 7a3f983e..a022e107 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/DefaultErrorHandlingTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/DefaultErrorHandlingTest.java @@ -1,13 +1,15 @@ -package io.avaje.jex; +package io.avaje.jex.core; import static org.assertj.core.api.Assertions.assertThat; import java.nio.file.DirectoryIteratorException; +import io.avaje.jex.Context; +import io.avaje.jex.ExceptionHandler; +import io.avaje.jex.Jex; +import io.avaje.jex.Routing; import org.junit.jupiter.api.Test; -import io.avaje.jex.core.ExceptionManager; - class DefaultErrorHandlingTest { private final ExceptionHandler rt = new RT(); @@ -15,8 +17,7 @@ class DefaultErrorHandlingTest { @Test void exception() { - - Routing router = new DefaultRouting(); + Routing router = Jex.create().routing(); router.error(RuntimeException.class, rt); var handling = new ExceptionManager(router.errorHandlers()); @@ -28,7 +29,7 @@ void exception() { @Test void exception_expect_highestMatch() { - Routing router = new DefaultRouting(); + Routing router = Jex.create().routing(); router.error(RuntimeException.class, rt); router.error(IllegalStateException.class, ise); From be45a30c0a23405ab059a71b80312af469f76d68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Dec 2024 22:45:12 -0500 Subject: [PATCH 095/250] Bump com.fasterxml.jackson.core:jackson-databind from 2.18.1 to 2.18.2 in the dependencies group (#113) * Bump com.fasterxml.jackson.core:jackson-databind Bumps the dependencies group with 1 update: [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson). Updates `com.fasterxml.jackson.core:jackson-databind` from 2.18.1 to 2.18.2 - [Commits](https://github.com/FasterXML/jackson/commits) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] * Update FilterTest.java --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Josiah Noel <32279667+SentryMan@users.noreply.github.com> --- avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java | 2 ++ examples/example-jdk/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java index 95826c40..216afa84 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java @@ -100,11 +100,13 @@ void get_two_expect_extraFilters() { } private void assertNoBeforeAfterTwo(HttpResponse res) { + assertThat(res.statusCode()).isLessThan(300); assertThat(res.headers().firstValue("before-two")).isEmpty(); assertThat(afterTwo.get()).isNull(); } private void assertHasBeforeAfterAll(HttpResponse res) { + assertThat(res.statusCode()).isLessThan(300); assertThat(res.headers().firstValue("before-all")).get().isEqualTo("set"); assertThat(afterAll.get()).isEqualTo("set"); } diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 12a0da0e..144312f5 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -30,7 +30,7 @@ com.fasterxml.jackson.core jackson-databind - 2.18.1 + 2.18.2 diff --git a/pom.xml b/pom.xml index 90a98eb2..07768dde 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ true - 2.18.1 + 2.18.2 false 21 2024-12-02T00:10:24Z From 81e064794603d5917b1d92a491895536968c8e11 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 2 Dec 2024 16:49:32 +1300 Subject: [PATCH 096/250] Improve flaky test FilterTest (with after filter) (#114) The after filter actions can occur after the response has been sent back to the client, so the client thread can execute before the actual after filter action has completed for these tests. Co-authored-by: Josiah Noel <32279667+SentryMan@users.noreply.github.com> --- avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java index 216afa84..a46e284e 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java @@ -74,7 +74,6 @@ void get() { clearAfter(); res = pair.request().path("two").GET().asString(); - LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(10)); assertHasBeforeAfterAll(res); assertNoBeforeAfterTwo(res); } @@ -108,6 +107,7 @@ private void assertNoBeforeAfterTwo(HttpResponse res) { private void assertHasBeforeAfterAll(HttpResponse res) { assertThat(res.statusCode()).isLessThan(300); assertThat(res.headers().firstValue("before-all")).get().isEqualTo("set"); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(2)); assertThat(afterAll.get()).isEqualTo("set"); } } From 7a6ec8294416054c74523c2e2ffb49f95cfe301c Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 2 Dec 2024 16:55:42 +1300 Subject: [PATCH 097/250] remove applog (#115) Co-authored-by: Josiah Noel <32279667+SentryMan@users.noreply.github.com> --- avaje-jex/pom.xml | 6 ------ .../src/main/java/io/avaje/jex/DefaultLifecycle.java | 5 +---- .../src/main/java/io/avaje/jex/core/BootstrapServer.java | 4 ++-- .../main/java/io/avaje/jex/core/CoreServiceManager.java | 3 +-- .../src/main/java/io/avaje/jex/core/ExceptionManager.java | 7 +++---- .../src/main/java/io/avaje/jex/core/JdkJexServer.java | 8 ++++---- avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java | 3 +-- avaje-jex/src/main/java/module-info.java | 1 - 8 files changed, 12 insertions(+), 25 deletions(-) diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index e294dd0f..1e712970 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -14,12 +14,6 @@ - - io.avaje - avaje-applog - 1.0 - - io.avaje avaje-config diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultLifecycle.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultLifecycle.java index 8b0f9478..ce0d6b4a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultLifecycle.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultLifecycle.java @@ -1,9 +1,6 @@ package io.avaje.jex; -import io.avaje.applog.AppLog; - import java.lang.System.Logger.Level; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -13,7 +10,7 @@ final class DefaultLifecycle implements AppLifecycle { - private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); + private static final System.Logger log = System.getLogger("io.avaje.jex"); private final List shutdownRunnable = new ArrayList<>(); private final ReentrantLock lock = new ReentrantLock(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java index 5fe633a9..2ed7fd98 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java @@ -2,7 +2,7 @@ import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpsServer; -import io.avaje.applog.AppLog; + import io.avaje.jex.AppLifecycle; import io.avaje.jex.Jex; import io.avaje.jex.JexConfig; @@ -19,7 +19,7 @@ public final class BootstrapServer { - private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); + private static final System.Logger log = System.getLogger("io.avaje.jex"); public static Jex.Server start(Jex jex) { final var config = jex.config(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java index b5bbf39e..49868cc1 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java @@ -14,7 +14,6 @@ import java.util.Map; import java.util.stream.Stream; -import io.avaje.applog.AppLog; import io.avaje.jex.Context; import io.avaje.jex.Jex; import io.avaje.jex.Routing; @@ -28,7 +27,7 @@ */ final class CoreServiceManager implements SpiServiceManager { - private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); + private static final System.Logger log = System.getLogger("io.avaje.jex"); static final String UTF_8 = "UTF-8"; private final HttpMethodMap methodMap = new HttpMethodMap(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index 0f6c6009..b734e0df 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -1,10 +1,9 @@ package io.avaje.jex.core; -import static java.lang.System.Logger.Level.WARNING; +import static java.lang.System.Logger.Level.ERROR; import java.util.Map; -import io.avaje.applog.AppLog; import io.avaje.jex.Context; import io.avaje.jex.ExceptionHandler; import io.avaje.jex.http.ErrorCode; @@ -15,7 +14,7 @@ final class ExceptionManager { private static final String APPLICATION_JSON = "application/json"; - private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); + private static final System.Logger log = System.getLogger("io.avaje.jex"); private final Map, ExceptionHandler> handlers; @@ -52,7 +51,7 @@ void handle(JdkContext ctx, Exception e) { } private void unhandledException(JdkContext ctx, Exception e) { - log.log(WARNING, "Uncaught exception", e); + log.log(ERROR, "Uncaught exception", e); defaultHandling(ctx, new InternalServerErrorException(ErrorCode.INTERNAL_SERVER_ERROR.message())); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkJexServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkJexServer.java index 03f01c9c..4418792a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkJexServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkJexServer.java @@ -1,15 +1,15 @@ package io.avaje.jex.core; +import java.lang.System.Logger.Level; + import com.sun.net.httpserver.HttpServer; -import io.avaje.applog.AppLog; + import io.avaje.jex.AppLifecycle; import io.avaje.jex.Jex; -import java.lang.System.Logger.Level; - final class JdkJexServer implements Jex.Server { - private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); + private static final System.Logger log = System.getLogger("io.avaje.jex"); private final HttpServer server; private final AppLifecycle lifecycle; diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java b/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java index a48e197e..505b8666 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java @@ -6,13 +6,12 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.LockSupport; -import io.avaje.applog.AppLog; import io.avaje.jex.HttpFilter; import io.avaje.jex.Routing; final class Routes implements SpiRoutes { - private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); + private static final System.Logger log = System.getLogger("io.avaje.jex"); /** * The "real" handlers by http method. diff --git a/avaje-jex/src/main/java/module-info.java b/avaje-jex/src/main/java/module-info.java index a688ce9f..ed6f9a15 100644 --- a/avaje-jex/src/main/java/module-info.java +++ b/avaje-jex/src/main/java/module-info.java @@ -29,7 +29,6 @@ requires transitive java.net.http; requires transitive jdk.httpserver; - requires transitive io.avaje.applog; requires static com.fasterxml.jackson.core; requires static com.fasterxml.jackson.databind; requires static io.avaje.jsonb; From 42494411297e3c522fe0ac25362f45e9350044b0 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 1 Dec 2024 23:15:56 -0500 Subject: [PATCH 098/250] Fuse ServiceManager implementation (#116) * Fuse ServiceManagers * Update ContextUtilTest.java * Update ContextUtilTest.java * Update SpiServiceManager.java --- .../io/avaje/jex/core/BootstrapServer.java | 20 +- .../java/io/avaje/jex/core/Constants.java | 2 +- .../io/avaje/jex/core/CoreServiceManager.java | 208 -------------- .../io/avaje/jex/core/CtxServiceManager.java | 91 ------ .../java/io/avaje/jex/core/JdkContext.java | 6 +- .../io/avaje/jex/core/RoutingHandler.java | 4 +- .../io/avaje/jex/core/SpiServiceManager.java | 262 ++++++++++++++---- .../io/avaje/jex/core/ContextUtilTest.java | 20 +- 8 files changed, 228 insertions(+), 385 deletions(-) delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/core/CtxServiceManager.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java index 2ed7fd98..7ba28e8c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java @@ -32,15 +32,13 @@ public static Jex.Server start(Jex jex) { } final SpiRoutes routes = - new RoutesBuilder(jex.routing(), config.ignoreTrailingSlashes()) - .build(); + new RoutesBuilder(jex.routing(), config.ignoreTrailingSlashes()).build(); return start(jex, routes); } static Jex.Server start(Jex jex, SpiRoutes routes) { - SpiServiceManager serviceManager = CoreServiceManager.create(jex); - try { + try { final var config = jex.config(); final var socketAddress = createSocketAddress(config); final var https = config.httpsConfig(); @@ -56,8 +54,8 @@ static Jex.Server start(Jex jex, SpiRoutes routes) { final var scheme = config.scheme(); final var contextPath = config.contextPath(); - final var manager = new CtxServiceManager(serviceManager, scheme, contextPath); - final var handler = new RoutingHandler(routes, manager, config.compression()); + SpiServiceManager serviceManager = SpiServiceManager.create(jex); + final var handler = new RoutingHandler(routes, serviceManager, config.compression()); server.setExecutor(config.executor()); server.createContext(contextPath, handler); @@ -65,16 +63,18 @@ static Jex.Server start(Jex jex, SpiRoutes routes) { jex.lifecycle().status(AppLifecycle.Status.STARTED); log.log( - INFO, - "started com.sun.net.httpserver.HttpServer on port {0}://{1}", - scheme, socketAddress); + INFO, + "started com.sun.net.httpserver.HttpServer on port {0}://{1}", + scheme, + socketAddress); return new JdkJexServer(server, jex.lifecycle(), handler); } catch (IOException e) { throw new UncheckedIOException(e); } } - private static InetSocketAddress createSocketAddress(JexConfig config) throws UnknownHostException { + private static InetSocketAddress createSocketAddress(JexConfig config) + throws UnknownHostException { final var inetAddress = config.host() == null ? null : InetAddress.getByName(config.host()); return new InetSocketAddress(inetAddress, config.port()); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/Constants.java b/avaje-jex/src/main/java/io/avaje/jex/core/Constants.java index 3d644975..5a90e9bb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/Constants.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/Constants.java @@ -16,7 +16,7 @@ private Constants() {} public static final String HOST = "Host"; public static final String USER_AGENT = "User-Agent"; public static final String ACCEPT_ENCODING = "Accept-Encoding"; - + public static final String TEXT_HTML = "text/html"; public static final String TEXT_PLAIN = "text/plain"; public static final String TEXT_HTML_UTF8 = "text/html;charset=utf-8"; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java deleted file mode 100644 index 49868cc1..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceManager.java +++ /dev/null @@ -1,208 +0,0 @@ -package io.avaje.jex.core; - -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UncheckedIOException; -import java.io.UnsupportedEncodingException; -import java.lang.System.Logger.Level; -import java.net.URLDecoder; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -import io.avaje.jex.Context; -import io.avaje.jex.Jex; -import io.avaje.jex.Routing; -import io.avaje.jex.core.json.JacksonJsonService; -import io.avaje.jex.core.json.JsonbJsonService; -import io.avaje.jex.spi.JsonService; -import io.avaje.jex.spi.TemplateRender; - -/** - * Core implementation of SpiServiceManager provided to specific implementations like jetty etc. - */ -final class CoreServiceManager implements SpiServiceManager { - - private static final System.Logger log = System.getLogger("io.avaje.jex"); - static final String UTF_8 = "UTF-8"; - - private final HttpMethodMap methodMap = new HttpMethodMap(); - private final JsonService jsonService; - private final ExceptionManager exceptionHandler; - private final TemplateManager templateManager; - - static SpiServiceManager create(Jex jex) { - return new Builder(jex).build(); - } - - CoreServiceManager(JsonService jsonService, ExceptionManager manager, TemplateManager templateManager) { - this.jsonService = jsonService; - this.exceptionHandler = manager; - this.templateManager = templateManager; - } - - @Override - public T jsonRead(Class clazz, InputStream is) { - return jsonService.jsonRead(clazz, is); - } - - @Override - public void jsonWrite(Object bean, OutputStream os) { - jsonService.jsonWrite(bean, os); - } - - @Override - public void jsonWriteStream(Stream stream, OutputStream os) { - try (stream) { - jsonService.jsonWriteStream(stream.iterator(), os); - } - } - - @Override - public void jsonWriteStream(Iterator iterator, OutputStream os) { - try { - jsonService.jsonWriteStream(iterator, os); - } finally { - maybeClose(iterator); - } - } - - @Override - public void maybeClose(Object iterator) { - if (iterator instanceof AutoCloseable closeable) { - try { - closeable.close(); - } catch (Exception e) { - throw new RuntimeException("Error closing iterator " + iterator, e); - } - } - } - - @Override - public Routing.Type lookupRoutingType(String method) { - return methodMap.get(method); - } - - @Override - public void handleException(JdkContext ctx, Exception e) { - exceptionHandler.handle(ctx, e); - } - - @Override - public void render(Context ctx, String name, Map model) { - templateManager.render(ctx, name, model); - } - - - @Override - public String requestCharset(Context ctx) { - return parseCharset(ctx.header(Constants.CONTENT_TYPE)); - } - - static String parseCharset(String header) { - if (header != null) { - for (String val : header.split(";")) { - val = val.trim(); - if (val.regionMatches(true, 0, "charset", 0, "charset".length())) { - return val.split("=")[1].trim(); - } - } - } - return UTF_8; - } - - @Override - public Map> formParamMap(Context ctx, String charset) { - return parseParamMap(ctx.body(), charset); - } - - @Override - public Map> parseParamMap(String body, String charset) { - if (body == null || body.isEmpty()) { - return Collections.emptyMap(); - } - try { - Map> map = new LinkedHashMap<>(); - for (String pair : body.split("&")) { - final String[] split1 = pair.split("=", 2); - String key = URLDecoder.decode(split1[0], charset); - String val = split1.length > 1 ? URLDecoder.decode(split1[1], charset) : ""; - map.computeIfAbsent(key, s -> new ArrayList<>()).add(val); - } - return map; - } catch (UnsupportedEncodingException e) { - throw new UncheckedIOException(e); - } - } - - private static final class Builder { - - private final Jex jex; - - Builder(Jex jex) { - this.jex = jex; - } - - SpiServiceManager build() { - return new CoreServiceManager( - initJsonService(), - new ExceptionManager(jex.routing().errorHandlers()), - initTemplateMgr()); - } - - JsonService initJsonService() { - final JsonService jsonService = jex.config().jsonService(); - if (jsonService != null) { - return jsonService; - } - return CoreServiceLoader.jsonService() - .orElseGet(this::defaultJsonService); - } - - /** - * Create a reasonable default JsonService if Jackson or avaje-jsonb are present. - */ - JsonService defaultJsonService() { - if (detectJackson()) { - try { - return new JacksonJsonService(); - } catch (IllegalAccessError errorNotInModulePath) { - // not in module path - log.log(Level.DEBUG, "Not using Jackson due to module path {0}", errorNotInModulePath.getMessage()); - } - } - return detectJsonb() ? new JsonbJsonService() : null; - } - - boolean detectJackson() { - return detectTypeExists("com.fasterxml.jackson.databind.ObjectMapper"); - } - - boolean detectJsonb() { - return detectTypeExists("io.avaje.jsonb.Jsonb"); - } - - private boolean detectTypeExists(String className) { - try { - Class.forName(className); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - - TemplateManager initTemplateMgr() { - TemplateManager mgr = new TemplateManager(); - mgr.register(jex.config().renderers()); - for (TemplateRender render : CoreServiceLoader.getRenders()) { - mgr.registerDefault(render); - } - return mgr; - } - - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CtxServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/CtxServiceManager.java deleted file mode 100644 index 20135c06..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CtxServiceManager.java +++ /dev/null @@ -1,91 +0,0 @@ -package io.avaje.jex.core; - -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -import io.avaje.jex.Context; -import io.avaje.jex.Routing; - -final class CtxServiceManager implements SpiServiceManager { - - private final String scheme; - private final String contextPath; - private final SpiServiceManager delegate; - - CtxServiceManager(SpiServiceManager delegate, String scheme, String contextPath) { - this.delegate = delegate; - this.scheme = scheme; - this.contextPath = contextPath; - } - - OutputStream createOutputStream(JdkContext jdkContext) { - return new BufferedOutStream(jdkContext); - } - - String scheme() { - return scheme; - } - - String contextPath() { - return contextPath; - } - - @Override - public T jsonRead(Class clazz, InputStream is) { - return delegate.jsonRead(clazz, is); - } - - @Override - public void jsonWrite(Object bean, OutputStream os) { - delegate.jsonWrite(bean, os); - } - - @Override - public void jsonWriteStream(Stream stream, OutputStream os) { - delegate.jsonWriteStream(stream, os); - } - - @Override - public void jsonWriteStream(Iterator iterator, OutputStream os) { - delegate.jsonWriteStream(iterator, os); - } - - @Override - public void maybeClose(Object iterator) { - delegate.maybeClose(iterator); - } - - @Override - public Routing.Type lookupRoutingType(String method) { - return delegate.lookupRoutingType(method); - } - - @Override - public void handleException(JdkContext ctx, Exception e) { - delegate.handleException(ctx, e); - } - - @Override - public void render(Context ctx, String name, Map model) { - delegate.render(ctx, name, model); - } - - @Override - public String requestCharset(Context ctx) { - return delegate.requestCharset(ctx); - } - - @Override - public Map> formParamMap(Context ctx, String charset) { - return delegate.formParamMap(ctx, charset); - } - - @Override - public Map> parseParamMap(String body, String charset) { - return delegate.parseParamMap(body, charset); - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index 9b13aa13..3c7d7b49 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -42,7 +42,7 @@ final class JdkContext implements Context { private static final int SC_MOVED_TEMPORARILY = 302; private static final String SET_COOKIE = "Set-Cookie"; private static final String COOKIE = "Cookie"; - private final CtxServiceManager mgr; + private final SpiServiceManager mgr; private final CompressionConfig compressionConfig; private final String path; private final Map pathParams; @@ -57,7 +57,7 @@ final class JdkContext implements Context { private String characterEncoding; JdkContext( - CtxServiceManager mgr, + SpiServiceManager mgr, CompressionConfig compressionConfig, HttpExchange exchange, String path, @@ -73,7 +73,7 @@ final class JdkContext implements Context { /** Create when no route matched. */ JdkContext( - CtxServiceManager mgr, + SpiServiceManager mgr, CompressionConfig compressionConfig, HttpExchange exchange, String path, diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java index 17f0c447..46f36eb8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java @@ -17,11 +17,11 @@ final class RoutingHandler implements HttpHandler { private final SpiRoutes routes; - private final CtxServiceManager mgr; + private final SpiServiceManager mgr; private final CompressionConfig compressionConfig; private final List filters; - RoutingHandler(SpiRoutes routes, CtxServiceManager mgr, CompressionConfig compressionConfig) { + RoutingHandler(SpiRoutes routes, SpiServiceManager mgr, CompressionConfig compressionConfig) { this.mgr = mgr; this.routes = routes; this.filters = routes.filters(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java index c244adaf..886f56d5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java @@ -2,71 +2,215 @@ import java.io.InputStream; import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.io.UnsupportedEncodingException; +import java.lang.System.Logger.Level; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Stream; import io.avaje.jex.Context; +import io.avaje.jex.Jex; import io.avaje.jex.Routing; +import io.avaje.jex.core.json.JacksonJsonService; +import io.avaje.jex.core.json.JsonbJsonService; +import io.avaje.jex.spi.JsonService; +import io.avaje.jex.spi.TemplateRender; -/** - * Core service methods available to Context implementations. - */ -public sealed interface SpiServiceManager permits CoreServiceManager, CtxServiceManager { - - /** - * Read and return the type from json request content. - */ - T jsonRead(Class clazz, InputStream ctx); - - /** - * Write as json to response content. - */ - void jsonWrite(Object bean, OutputStream ctx); - - /** - * Write as json stream to response content. - */ - void jsonWriteStream(Stream stream, OutputStream ctx); - - /** - * Write as json stream to response content. - */ - void jsonWriteStream(Iterator iterator, OutputStream ctx); - - /** - * Maybe close if iterator is a AutoClosable. - */ - void maybeClose(Object iterator); - - /** - * Return the routing type given the http method. - */ - Routing.Type lookupRoutingType(String method); - - /** - * Handle the exception. - */ - void handleException(JdkContext ctx, Exception e); - - /** - * Render using template manager. - */ - void render(Context ctx, String name, Map model); - - /** - * Return the character set of the request. - */ - String requestCharset(Context ctx); - - /** - * Parse and return the body as form parameters. - */ - Map> formParamMap(Context ctx, String charset); - - /** - * Parse and return the content as url encoded parameters. - */ - Map> parseParamMap(String body, String charset); +/** Core service methods available to Context implementations. */ +final class SpiServiceManager { + + private static final System.Logger log = System.getLogger("io.avaje.jex"); + static final String UTF_8 = "UTF-8"; + + private final HttpMethodMap methodMap = new HttpMethodMap(); + private final JsonService jsonService; + private final ExceptionManager exceptionHandler; + private final TemplateManager templateManager; + private final String scheme; + private final String contextPath; + + static SpiServiceManager create(Jex jex) { + return new Builder(jex).build(); + } + + SpiServiceManager( + JsonService jsonService, + ExceptionManager manager, + TemplateManager templateManager, + String scheme, + String contextPath) { + this.jsonService = jsonService; + this.exceptionHandler = manager; + this.templateManager = templateManager; + this.scheme = scheme; + this.contextPath = contextPath; + } + + OutputStream createOutputStream(JdkContext jdkContext) { + return new BufferedOutStream(jdkContext); + } + + public T jsonRead(Class clazz, InputStream is) { + return jsonService.jsonRead(clazz, is); + } + + public void jsonWrite(Object bean, OutputStream os) { + jsonService.jsonWrite(bean, os); + } + + public void jsonWriteStream(Stream stream, OutputStream os) { + try (stream) { + jsonService.jsonWriteStream(stream.iterator(), os); + } + } + + public void jsonWriteStream(Iterator iterator, OutputStream os) { + try { + jsonService.jsonWriteStream(iterator, os); + } finally { + maybeClose(iterator); + } + } + + public void maybeClose(Object iterator) { + if (iterator instanceof AutoCloseable closeable) { + try { + closeable.close(); + } catch (Exception e) { + throw new RuntimeException("Error closing iterator " + iterator, e); + } + } + } + + public Routing.Type lookupRoutingType(String method) { + return methodMap.get(method); + } + + public void handleException(JdkContext ctx, Exception e) { + exceptionHandler.handle(ctx, e); + } + + public void render(Context ctx, String name, Map model) { + templateManager.render(ctx, name, model); + } + + public String requestCharset(Context ctx) { + return parseCharset(ctx.header(Constants.CONTENT_TYPE)); + } + + static String parseCharset(String header) { + if (header != null) { + for (String val : header.split(";")) { + val = val.trim(); + if (val.regionMatches(true, 0, "charset", 0, "charset".length())) { + return val.split("=")[1].trim(); + } + } + } + return UTF_8; + } + + public Map> formParamMap(Context ctx, String charset) { + return parseParamMap(ctx.body(), charset); + } + + public Map> parseParamMap(String body, String charset) { + if (body == null || body.isEmpty()) { + return Collections.emptyMap(); + } + try { + Map> map = new LinkedHashMap<>(); + for (String pair : body.split("&")) { + final String[] split1 = pair.split("=", 2); + String key = URLDecoder.decode(split1[0], charset); + String val = split1.length > 1 ? URLDecoder.decode(split1[1], charset) : ""; + map.computeIfAbsent(key, s -> new ArrayList<>()).add(val); + } + return map; + } catch (UnsupportedEncodingException e) { + throw new UncheckedIOException(e); + } + } + + String scheme() { + return scheme; + } + + String contextPath() { + return contextPath; + } + + private static final class Builder { + + private final Jex jex; + + Builder(Jex jex) { + this.jex = jex; + } + + SpiServiceManager build() { + return new SpiServiceManager( + initJsonService(), + new ExceptionManager(jex.routing().errorHandlers()), + initTemplateMgr(), + jex.config().scheme(), + jex.config().contextPath()); + } + + JsonService initJsonService() { + final JsonService jsonService = jex.config().jsonService(); + if (jsonService != null) { + return jsonService; + } + return CoreServiceLoader.jsonService().orElseGet(this::defaultJsonService); + } + + /** Create a reasonable default JsonService if Jackson or avaje-jsonb are present. */ + JsonService defaultJsonService() { + if (detectJackson()) { + try { + return new JacksonJsonService(); + } catch (IllegalAccessError errorNotInModulePath) { + // not in module path + log.log( + Level.DEBUG, + "Not using Jackson due to module path {0}", + errorNotInModulePath.getMessage()); + } + } + return detectJsonb() ? new JsonbJsonService() : null; + } + + boolean detectJackson() { + return detectTypeExists("com.fasterxml.jackson.databind.ObjectMapper"); + } + + boolean detectJsonb() { + return detectTypeExists("io.avaje.jsonb.Jsonb"); + } + + private boolean detectTypeExists(String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + TemplateManager initTemplateMgr() { + TemplateManager mgr = new TemplateManager(); + mgr.register(jex.config().renderers()); + for (TemplateRender render : CoreServiceLoader.getRenders()) { + mgr.registerDefault(render); + } + return mgr; + } + } } diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java index 79dcb785..95232a94 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java @@ -1,25 +1,23 @@ package io.avaje.jex.core; -import org.junit.jupiter.api.Test; - -import io.avaje.jex.core.CoreServiceManager; - import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; + class ContextUtilTest { @Test void parseCharset_defaults() { - assertThat(CoreServiceManager.parseCharset("")).isEqualTo(CoreServiceManager.UTF_8); - assertThat(CoreServiceManager.parseCharset("junk")).isEqualTo(CoreServiceManager.UTF_8); + assertThat(SpiServiceManager.parseCharset("")).isEqualTo(SpiServiceManager.UTF_8); + assertThat(SpiServiceManager.parseCharset("junk")).isEqualTo(SpiServiceManager.UTF_8); } @Test void parseCharset_caseCheck() { - assertThat(CoreServiceManager.parseCharset("app/foo; charset=ME")).isEqualTo("ME"); - assertThat(CoreServiceManager.parseCharset("app/foo;charset=ME")).isEqualTo("ME"); - assertThat(CoreServiceManager.parseCharset("app/foo;charset = ME ")).isEqualTo("ME"); - assertThat(CoreServiceManager.parseCharset("app/foo;charset = ME;")).isEqualTo("ME"); - assertThat(CoreServiceManager.parseCharset("app/foo;charset = ME;other=junk")).isEqualTo("ME"); + assertThat(SpiServiceManager.parseCharset("app/foo; charset=ME")).isEqualTo("ME"); + assertThat(SpiServiceManager.parseCharset("app/foo;charset=ME")).isEqualTo("ME"); + assertThat(SpiServiceManager.parseCharset("app/foo;charset = ME ")).isEqualTo("ME"); + assertThat(SpiServiceManager.parseCharset("app/foo;charset = ME;")).isEqualTo("ME"); + assertThat(SpiServiceManager.parseCharset("app/foo;charset = ME;other=junk")).isEqualTo("ME"); } } From e19c29e04aa4f0206f74012dfbeaed7bf08bedfb Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 2 Dec 2024 20:39:33 +1300 Subject: [PATCH 099/250] Change RoutesBuilder to use LinkedHashMap (preserve order of added routes?) (#117) Effectively replacing EnumMap with LinkedHashMap as we need to preserve the order of routes that are added. --- avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java index b05ef4c6..5e0c3c61 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java @@ -1,6 +1,7 @@ package io.avaje.jex.routes; import java.util.EnumMap; +import java.util.LinkedHashMap; import java.util.List; import io.avaje.jex.HttpFilter; @@ -14,7 +15,7 @@ public final class RoutesBuilder { public RoutesBuilder(Routing routing, boolean ignoreTrailingSlashes) { this.ignoreTrailingSlashes = ignoreTrailingSlashes; - final var buildMap = new EnumMap(Routing.Type.class); + final var buildMap = new LinkedHashMap(); for (var handler : routing.handlers()) { buildMap.computeIfAbsent(handler.getType(), h -> new RouteIndexBuild()).add(convert(handler)); } From a499fb90b01eae0a2ae22fcb611adac873303264 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 2 Dec 2024 20:55:15 +1300 Subject: [PATCH 100/250] Version 3.0-RC6 --- avaje-jex-freemarker/pom.xml | 6 +++--- avaje-jex-htmx/pom.xml | 4 ++-- avaje-jex-mustache/pom.xml | 6 +++--- avaje-jex-test/pom.xml | 4 ++-- avaje-jex/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 4 ++-- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 64fcdbe2..1336c733 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC5 + 3.0-RC6 avaje-jex-freemarker @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-RC5 + 3.0-RC6 provided @@ -39,7 +39,7 @@ io.avaje avaje-jex-test - 3.0-RC5 + 3.0-RC6 test diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 5db71573..16938d2b 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC5 + 3.0-RC6 avaje-jex-htmx @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC5 + 3.0-RC6 diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 3099951a..e6061bd5 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC5 + 3.0-RC6 avaje-jex-mustache @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-RC5 + 3.0-RC6 provided @@ -40,7 +40,7 @@ io.avaje avaje-jex-test - 3.0-RC5 + 3.0-RC6 test diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 9956372b..5d5124bd 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC5 + 3.0-RC6 avaje-jex-test @@ -14,7 +14,7 @@ io.avaje avaje-jex - 3.0-RC5 + 3.0-RC6 provided diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 1e712970..b2817363 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC5 + 3.0-RC6 avaje-jex diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 144312f5..8a7bbc43 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC5 + 3.0-RC6 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index da5fb336..29ef83b0 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC5 + 3.0-RC6 diff --git a/examples/pom.xml b/examples/pom.xml index 6b0f0d02..28bda8c7 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC5 + 3.0-RC6 examples diff --git a/pom.xml b/pom.xml index 07768dde..44db4c2f 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.avaje avaje-jex-parent - 3.0-RC5 + 3.0-RC6 pom @@ -22,7 +22,7 @@ 2.18.2 false 21 - 2024-12-02T00:10:24Z + 2024-12-02T07:39:59Z From 480e737a374c71d9318c1431e9706e5f00727626 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 2 Dec 2024 21:51:46 +1300 Subject: [PATCH 101/250] Fix path ordering route building issue - RouteIndexBuild.Entry.pathMap as LinkedHashMap (#118) The RouteIndexBuild.Entry.pathMap needs to preserve ordering of the routes. The order that they are added/registered must be preserved. This is the precedence of route matching where the first match wins (literal paths should match first etc). --- .../java/io/avaje/jex/core/BootstrapServer.java | 2 ++ .../main/java/io/avaje/jex/routes/RouteEntry.java | 5 +++++ .../main/java/io/avaje/jex/routes/RouteIndex.java | 13 +++++++++++++ .../java/io/avaje/jex/routes/RouteIndexBuild.java | 7 ++----- .../src/main/java/io/avaje/jex/routes/Routes.java | 6 +++++- 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java index 7ba28e8c..dafba879 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java @@ -15,6 +15,7 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; +import static java.lang.System.Logger.Level.DEBUG; import static java.lang.System.Logger.Level.INFO; public final class BootstrapServer { @@ -67,6 +68,7 @@ static Jex.Server start(Jex jex, SpiRoutes routes) { "started com.sun.net.httpserver.HttpServer on port {0}://{1}", scheme, socketAddress); + log.log(DEBUG, routes); return new JdkJexServer(server, jex.lifecycle(), handler); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java index cd1a9e40..d7e60350 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java @@ -80,4 +80,9 @@ public boolean literal() { public Set roles() { return roles; } + + @Override + public String toString() { + return path.raw(); + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java index c7df775a..71b62acb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java @@ -1,5 +1,6 @@ package io.avaje.jex.routes; +import java.util.Arrays; import java.util.List; final class RouteIndex { @@ -22,6 +23,13 @@ final class RouteIndex { .toArray(new IndexEntry[0]); } + @Override + public String toString() { + return "RouteIndex{" + Arrays.toString(entries) + + ", wildcards=" + Arrays.toString(wildcardEntries) + + '}'; + } + private static IndexEntry toEntry(List routeEntries) { return new IndexEntry(routeEntries.toArray(new SpiRoutes.Entry[0])); } @@ -77,6 +85,11 @@ private static final class IndexEntry { this.pathEntries = pathEntries; } + @Override + public String toString() { + return Arrays.toString(pathEntries); + } + SpiRoutes.Entry match(String pathInfo) { for (SpiRoutes.Entry entry : pathEntries) { if (entry.matches(pathInfo)) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndexBuild.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndexBuild.java index bb7b7674..e13259cf 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndexBuild.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndexBuild.java @@ -2,10 +2,7 @@ import io.avaje.jex.ExchangeHandler; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Build the RouteIndex. @@ -54,7 +51,7 @@ RouteIndex build() { private static class Entry { private final List list = new ArrayList<>(); - private final Map> pathMap = new HashMap<>(); + private final Map> pathMap = new LinkedHashMap<>(); void add(SpiRoutes.Entry entry) { if (entry.literal()) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java b/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java index 505b8666..f8a4a0d4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java @@ -25,12 +25,16 @@ final class Routes implements SpiRoutes { private final AtomicLong noRouteCounter = new AtomicLong(); - Routes(EnumMap typeMap, List filters) { this.typeMap = typeMap; this.filters = filters; } + @Override + public String toString() { + return "Routes{" + typeMap + ", filters=" + filters + '}'; + } + @Override public void inc() { noRouteCounter.incrementAndGet(); From f2d4c27e2fae2b7d9e98d5f6a6aa426da4363421 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 2 Dec 2024 21:54:57 +1300 Subject: [PATCH 102/250] Version 3.0-RC7 --- avaje-jex-freemarker/pom.xml | 6 +++--- avaje-jex-htmx/pom.xml | 4 ++-- avaje-jex-mustache/pom.xml | 6 +++--- avaje-jex-test/pom.xml | 4 ++-- avaje-jex/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 4 ++-- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 1336c733..3d70396a 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC6 + 3.0-RC7 avaje-jex-freemarker @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-RC6 + 3.0-RC7 provided @@ -39,7 +39,7 @@ io.avaje avaje-jex-test - 3.0-RC6 + 3.0-RC7 test diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 16938d2b..1288771e 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC6 + 3.0-RC7 avaje-jex-htmx @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC6 + 3.0-RC7 diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index e6061bd5..32fdff27 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC6 + 3.0-RC7 avaje-jex-mustache @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-RC6 + 3.0-RC7 provided @@ -40,7 +40,7 @@ io.avaje avaje-jex-test - 3.0-RC6 + 3.0-RC7 test diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 5d5124bd..c9e10214 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC6 + 3.0-RC7 avaje-jex-test @@ -14,7 +14,7 @@ io.avaje avaje-jex - 3.0-RC6 + 3.0-RC7 provided diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index b2817363..1b160b84 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC6 + 3.0-RC7 avaje-jex diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 8a7bbc43..cd72444b 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC6 + 3.0-RC7 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 29ef83b0..38e0b622 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC6 + 3.0-RC7 diff --git a/examples/pom.xml b/examples/pom.xml index 28bda8c7..ee2b2358 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC6 + 3.0-RC7 examples diff --git a/pom.xml b/pom.xml index 44db4c2f..06059337 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.avaje avaje-jex-parent - 3.0-RC6 + 3.0-RC7 pom @@ -22,7 +22,7 @@ 2.18.2 false 21 - 2024-12-02T07:39:59Z + 2024-12-02T08:52:38Z From e59545025e809ba6f55c8b2ad22f6b615de6b470 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 2 Dec 2024 11:19:35 -0500 Subject: [PATCH 103/250] RC7 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 39f854dd..20dcd81c 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ An example would be [@robaho's implementation](https://github.com/robaho/httpser io.avaje avaje-jex - 3.0-RC4 + 3.0-RC7 @@ -68,20 +68,20 @@ If you find yourself pining for the JAX-RS style of controllers, you can have av io.avaje avaje-jex - 3.0-RC4 + 3.0-RC7 io.avaje avaje-http-api - 2.9-RC4 + 2.9-RC7 io.avaje avaje-http-jex-generator - 2.9-RC4 + 2.9-RC3 provided true From b25367f262a85c14686c1bcc0f52d15a1b45a848 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:11:34 -0500 Subject: [PATCH 104/250] Change Handler Signatures to handle Checked Exceptions (#119) * Handlers can now handle checked exceptions * handle checked exceptions --- .../main/java/io/avaje/jex/htmx/DHxHandler.java | 16 +++++++--------- .../main/java/io/avaje/jex/ExchangeHandler.java | 2 +- .../src/main/java/io/avaje/jex/HttpFilter.java | 4 ++-- .../java/io/avaje/jex/core/BaseFilterChain.java | 3 +-- .../java/io/avaje/jex/core/RoutingHandler.java | 12 +++++++----- .../java/io/avaje/jex/routes/MultiHandler.java | 4 +--- 6 files changed, 19 insertions(+), 22 deletions(-) diff --git a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandler.java b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandler.java index 2f85fefb..ea3a682c 100644 --- a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandler.java +++ b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandler.java @@ -1,12 +1,13 @@ package io.avaje.jex.htmx; +import static io.avaje.jex.htmx.HxHeaders.HX_REQUEST; +import static io.avaje.jex.htmx.HxHeaders.HX_TARGET; +import static io.avaje.jex.htmx.HxHeaders.HX_TRIGGER; +import static io.avaje.jex.htmx.HxHeaders.HX_TRIGGER_NAME; + import io.avaje.jex.Context; import io.avaje.jex.ExchangeHandler; -import java.io.IOException; - -import static io.avaje.jex.htmx.HxHeaders.*; - final class DHxHandler implements ExchangeHandler { private final ExchangeHandler delegate; @@ -22,17 +23,14 @@ final class DHxHandler implements ExchangeHandler { } @Override - public void handle(Context ctx) throws IOException { + public void handle(Context ctx) throws Exception { if (ctx.header(HX_REQUEST) != null && matched(ctx)) { delegate.handle(ctx); } } private boolean matched(Context ctx) { - if (target != null && notMatched(ctx.header(HX_TARGET), target)) { - return false; - } - if (trigger != null && notMatched(ctx.header(HX_TRIGGER), trigger)) { + if ((target != null && notMatched(ctx.header(HX_TARGET), target)) || (trigger != null && notMatched(ctx.header(HX_TRIGGER), trigger))) { return false; } return triggerName == null || !notMatched(ctx.header(HX_TRIGGER_NAME), triggerName); diff --git a/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java b/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java index 18bcedd8..249b6174 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java @@ -23,5 +23,5 @@ public interface ExchangeHandler { * @param ctx The context object containing the request and response details. * @throws IOException if an I/O error occurs during request processing or response generation. */ - void handle(Context ctx) throws IOException; + void handle(Context ctx) throws Exception; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java b/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java index 8ef2d4e7..987bcb2b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java @@ -37,7 +37,7 @@ public interface HttpFilter { * @param ctx the {@code Context} of the current request * @param chain the {@code FilterChain} which allows the next filter to be invoked */ - void filter(Context ctx, FilterChain chain) throws IOException; + void filter(Context ctx, FilterChain chain) throws Exception; /** * Filter chain that contains all subsequent filters that are configured, as well as the final @@ -54,6 +54,6 @@ interface FilterChain { * * @throws IOException if an I/O error occurs */ - void proceed() throws IOException; + void proceed() throws Exception; } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BaseFilterChain.java b/avaje-jex/src/main/java/io/avaje/jex/core/BaseFilterChain.java index d023b8cc..b84171bf 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BaseFilterChain.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BaseFilterChain.java @@ -1,6 +1,5 @@ package io.avaje.jex.core; -import java.io.IOException; import java.util.List; import java.util.ListIterator; @@ -21,7 +20,7 @@ final class BaseFilterChain implements FilterChain { } @Override - public void proceed() throws IOException { + public void proceed() throws Exception { if (!iter.hasNext()) { handler.handle(ctx); ctx.setMode(Mode.AFTER); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java index 46f36eb8..41fecc0b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java @@ -9,7 +9,6 @@ import com.sun.net.httpserver.HttpHandler; import io.avaje.jex.HttpFilter; -import io.avaje.jex.Routing; import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.http.NotFoundException; import io.avaje.jex.routes.SpiRoutes; @@ -34,13 +33,16 @@ void waitForIdle(long maxSeconds) { @Override public void handle(HttpExchange exchange) { - final String uri = exchange.getRequestURI().getPath(); - final Routing.Type routeType = mgr.lookupRoutingType(exchange.getRequestMethod()); - final SpiRoutes.Entry route = routes.match(routeType, uri); + final var uri = exchange.getRequestURI().getPath(); + final var routeType = mgr.lookupRoutingType(exchange.getRequestMethod()); + final var route = routes.match(routeType, uri); if (route == null) { var ctx = new JdkContext(mgr, compressionConfig, exchange, uri, Set.of()); - handleException(ctx, new NotFoundException("uri: " + uri)); + handleException( + ctx, + new NotFoundException( + "No route matching http method %s, with path %s".formatted(routeType.name(), uri))); } else { route.inc(); try { diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/MultiHandler.java b/avaje-jex/src/main/java/io/avaje/jex/routes/MultiHandler.java index a44b7d66..85b2685d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/MultiHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/MultiHandler.java @@ -3,8 +3,6 @@ import io.avaje.jex.Context; import io.avaje.jex.ExchangeHandler; -import java.io.IOException; - final class MultiHandler implements ExchangeHandler { private final ExchangeHandler[] handlers; @@ -14,7 +12,7 @@ final class MultiHandler implements ExchangeHandler { } @Override - public void handle(Context ctx) throws IOException { + public void handle(Context ctx) throws Exception { for (ExchangeHandler handler : handlers) { handler.handle(ctx); if (ctx.responseSent()) { From 4703315d27306cb4bf03274877115367a2cf7a44 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:15:30 -0500 Subject: [PATCH 105/250] Error example --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 20dcd81c..c4f5c932 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ var app = Jex.create() chain.proceed(); System.out.println("after request"); })) + .error(IllegalStateException.class, (ctx, exception) -> ctx.status(500).text("Handled IllegalStateException|" + exception.getMessage())) .staticResource( b -> b.httpPath("/myResource") From 15547bc6e94943437ee246ad5049466cf072a6f7 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:21:56 -0500 Subject: [PATCH 106/250] Update README.md --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c4f5c932..f9a4b7cd 100644 --- a/README.md +++ b/README.md @@ -7,16 +7,15 @@ [![javadoc](https://javadoc.io/badge2/io.avaje/avaje-jex/javadoc.svg?color=purple)](https://javadoc.io/doc/io.avaje/avaje-jex) # Avaje-Jex - -Lightweight (~120KB) wrapper over the JDK's [`jdk.httpserver`](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html) classes. +Lightweight (~120KB) wrapper over the JDK's built-in [HTTP server](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html). Features: - [Context](https://javadoc.io/doc/io.avaje/avaje-jex/latest/io.avaje.jex/io/avaje/jex/Context.html) abstraction over `HttpExchange` to easily retrieve and send request/response data. - Fluent API - Static resource handling -- Compression SPI (will use gzip by default if the caller accepts it and the response exceeds a certain size) -- Json SPI (will use either Avaje Jsonb or Jackson if available) +- Compression SPI +- Json SPI - Virtual threads enabled by default ```java @@ -29,7 +28,7 @@ var app = Jex.create() System.out.println("before request"); chain.proceed(); System.out.println("after request"); - })) + }) .error(IllegalStateException.class, (ctx, exception) -> ctx.status(500).text("Handled IllegalStateException|" + exception.getMessage())) .staticResource( b -> From 11c900e985711944e3a677e40fa82f16bf093b0b Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 3 Dec 2024 18:32:17 +1300 Subject: [PATCH 107/250] Add DS_Store to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f4aa06b9..b373cb31 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ build/ *.prefs *.factorypath bin/ +.DS_Store From 7ac32a39c2f82bc6292cf129b77b4158a5d431dc Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 3 Dec 2024 09:48:55 -0500 Subject: [PATCH 108/250] RC8 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f9a4b7cd..4a1adb63 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ An example would be [@robaho's implementation](https://github.com/robaho/httpser io.avaje avaje-jex - 3.0-RC7 + 3.0-RC8 @@ -68,20 +68,20 @@ If you find yourself pining for the JAX-RS style of controllers, you can have av io.avaje avaje-jex - 3.0-RC7 + 3.0-RC8 io.avaje avaje-http-api - 2.9-RC7 + 2.9-RC4 io.avaje avaje-http-jex-generator - 2.9-RC3 + 2.9-RC4 provided true From 71e2e00384860c1d6d4bed1a65ec97d869eca3a1 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 4 Dec 2024 00:00:06 -0500 Subject: [PATCH 109/250] fix test failing without jackson (#122) --- avaje-jex-test/pom.xml | 12 ++---------- .../src/main/java/io/avaje/jex/test/TestPair.java | 6 ++---- avaje-jex-test/src/main/java/module-info.java | 4 +--- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index c9e10214..d909169b 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -14,14 +14,14 @@ io.avaje avaje-jex - 3.0-RC7 + ${project.version} provided io.avaje avaje-http-client - 2.8 + 2.9-RC4 @@ -32,18 +32,10 @@ true - - io.avaje - avaje-jsonb - 2.3 - true - - com.fasterxml.jackson.core jackson-databind ${jackson.version} - true diff --git a/avaje-jex-test/src/main/java/io/avaje/jex/test/TestPair.java b/avaje-jex-test/src/main/java/io/avaje/jex/test/TestPair.java index f40f63cc..2a665c5b 100644 --- a/avaje-jex-test/src/main/java/io/avaje/jex/test/TestPair.java +++ b/avaje-jex-test/src/main/java/io/avaje/jex/test/TestPair.java @@ -1,12 +1,11 @@ package io.avaje.jex.test; +import java.util.Random; + import io.avaje.http.client.HttpClient; import io.avaje.http.client.HttpClientRequest; -import io.avaje.http.client.JacksonBodyAdapter; import io.avaje.jex.Jex; -import java.util.Random; - /** * Server and Client pair for a test. */ @@ -51,7 +50,6 @@ public static TestPair create(Jex app) { var url = "http://localhost:" + port; var client = HttpClient.builder() .baseUrl(url) - .bodyAdapter(new JacksonBodyAdapter()) .build(); return new TestPair(port, jexServer, client); diff --git a/avaje-jex-test/src/main/java/module-info.java b/avaje-jex-test/src/main/java/module-info.java index 724d928b..0a2163e6 100644 --- a/avaje-jex-test/src/main/java/module-info.java +++ b/avaje-jex-test/src/main/java/module-info.java @@ -4,10 +4,8 @@ requires transitive io.avaje.jex; requires transitive io.avaje.http.client; - requires static com.fasterxml.jackson.databind; - requires static io.avaje.jsonb; + requires transitive com.fasterxml.jackson.databind; requires static io.avaje.inject.test; - requires static org.apiguardian.api; // stink man !! provides io.avaje.inject.test.Plugin with io.avaje.jex.test.JexInjectPlugin; } From 82aaad125555452124e0ef59d08d39453f005c86 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 4 Dec 2024 01:37:47 -0500 Subject: [PATCH 110/250] Split static resources into it's own module (#121) * split static resources into it's own module * project version * doc * Update README.md --- README.md | 7 +- avaje-jex-freemarker/pom.xml | 6 +- avaje-jex-htmx/pom.xml | 4 +- avaje-jex-mustache/pom.xml | 6 +- avaje-jex-static-content/pom.xml | 27 ++++++++ .../staticcontent}/AbstractStaticHandler.java | 4 +- .../staticcontent}/ClassResourceLoader.java | 2 +- .../staticcontent}/DefaultResourceLoader.java | 2 +- .../StaticClassResourceHandler.java | 6 +- .../staticcontent/StaticContentService.java | 69 +++++++++---------- .../jex/staticcontent}/StaticFileHandler.java | 6 +- .../StaticResourceHandlerBuilder.java | 60 ++++++++-------- .../avaje/jex/staticcontent/package-info.java | 15 ++++ .../src/main/java/module-info.java | 21 ++++++ .../jex/staticcontent}/StaticFileTest.java | 37 +++++----- .../src/test/resources/logback.xml | 19 +++++ .../src/test/resources/public/index.html | 9 +++ .../src/test/resources/public/sus.txt | 1 + avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 21 ------ .../jex/compression/CompressionTest.java | 20 +++--- examples/example-jdk/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 5 +- 26 files changed, 208 insertions(+), 149 deletions(-) create mode 100644 avaje-jex-static-content/pom.xml rename {avaje-jex/src/main/java/io/avaje/jex => avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent}/AbstractStaticHandler.java (95%) rename {avaje-jex/src/main/java/io/avaje/jex => avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent}/ClassResourceLoader.java (97%) rename {avaje-jex/src/main/java/io/avaje/jex => avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent}/DefaultResourceLoader.java (97%) rename {avaje-jex/src/main/java/io/avaje/jex => avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent}/StaticClassResourceHandler.java (96%) rename avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java => avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java (53%) rename {avaje-jex/src/main/java/io/avaje/jex => avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent}/StaticFileHandler.java (94%) rename {avaje-jex/src/main/java/io/avaje/jex => avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent}/StaticResourceHandlerBuilder.java (76%) create mode 100644 avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java create mode 100644 avaje-jex-static-content/src/main/java/module-info.java rename {avaje-jex/src/test/java/io/avaje/jex => avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent}/StaticFileTest.java (72%) create mode 100644 avaje-jex-static-content/src/test/resources/logback.xml create mode 100644 avaje-jex-static-content/src/test/resources/public/index.html create mode 100644 avaje-jex-static-content/src/test/resources/public/sus.txt diff --git a/README.md b/README.md index 4a1adb63..26dbbfe2 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![javadoc](https://javadoc.io/badge2/io.avaje/avaje-jex/javadoc.svg?color=purple)](https://javadoc.io/doc/io.avaje/avaje-jex) # Avaje-Jex -Lightweight (~120KB) wrapper over the JDK's built-in [HTTP server](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html). +Lightweight (~100KB) wrapper over the JDK's built-in [HTTP server](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html). Features: @@ -30,11 +30,6 @@ var app = Jex.create() System.out.println("after request"); }) .error(IllegalStateException.class, (ctx, exception) -> ctx.status(500).text("Handled IllegalStateException|" + exception.getMessage())) - .staticResource( - b -> - b.httpPath("/myResource") - .resource("/public") - .directoryIndex("index.html")) .port(8080) .start(); ``` diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 3d70396a..5fd56c72 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC7 + 3.0-RC8 avaje-jex-freemarker @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-RC7 + ${project.version} provided @@ -39,7 +39,7 @@ io.avaje avaje-jex-test - 3.0-RC7 + ${project.version} test diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 1288771e..5c136663 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC7 + 3.0-RC8 avaje-jex-htmx @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC7 + ${project.version} diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 32fdff27..7df8ec15 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC7 + 3.0-RC8 avaje-jex-mustache @@ -18,7 +18,7 @@ io.avaje avaje-jex - 3.0-RC7 + ${project.version} provided @@ -40,7 +40,7 @@ io.avaje avaje-jex-test - 3.0-RC7 + ${project.version} test diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml new file mode 100644 index 00000000..0458d2f6 --- /dev/null +++ b/avaje-jex-static-content/pom.xml @@ -0,0 +1,27 @@ + + 4.0.0 + + io.avaje + avaje-jex-parent + 3.0-RC8 + + avaje-jex-static-content + + + + io.avaje + avaje-jex + ${project.version} + + + + io.avaje + avaje-jex-test + ${project.version} + test + + + + \ No newline at end of file diff --git a/avaje-jex/src/main/java/io/avaje/jex/AbstractStaticHandler.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/AbstractStaticHandler.java similarity index 95% rename from avaje-jex/src/main/java/io/avaje/jex/AbstractStaticHandler.java rename to avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/AbstractStaticHandler.java index e57f69f9..5776aa90 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/AbstractStaticHandler.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/AbstractStaticHandler.java @@ -1,4 +1,4 @@ -package io.avaje.jex; +package io.avaje.jex.staticcontent; import java.net.FileNameMap; import java.net.URLConnection; @@ -8,6 +8,8 @@ import com.sun.net.httpserver.HttpExchange; +import io.avaje.jex.Context; +import io.avaje.jex.ExchangeHandler; import io.avaje.jex.http.BadRequestException; import io.avaje.jex.http.NotFoundException; diff --git a/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/ClassResourceLoader.java similarity index 97% rename from avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java rename to avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/ClassResourceLoader.java index 10bbb3ae..7d22d9cd 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/ClassResourceLoader.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/ClassResourceLoader.java @@ -1,4 +1,4 @@ -package io.avaje.jex; +package io.avaje.jex.staticcontent; import java.io.InputStream; import java.net.URL; diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultResourceLoader.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/DefaultResourceLoader.java similarity index 97% rename from avaje-jex/src/main/java/io/avaje/jex/DefaultResourceLoader.java rename to avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/DefaultResourceLoader.java index 29dade88..5d46c7ed 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultResourceLoader.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/DefaultResourceLoader.java @@ -1,4 +1,4 @@ -package io.avaje.jex; +package io.avaje.jex.staticcontent; import java.io.InputStream; import java.net.URL; diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticClassResourceHandler.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticClassResourceHandler.java similarity index 96% rename from avaje-jex/src/main/java/io/avaje/jex/StaticClassResourceHandler.java rename to avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticClassResourceHandler.java index 75f46208..6af3a0c3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticClassResourceHandler.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticClassResourceHandler.java @@ -1,11 +1,13 @@ -package io.avaje.jex; +package io.avaje.jex.staticcontent; import java.net.URL; import java.nio.file.Paths; import java.util.Map; import java.util.function.Predicate; -final class StaticClassResourceHandler extends AbstractStaticHandler implements ExchangeHandler { +import io.avaje.jex.Context; + +final class StaticClassResourceHandler extends AbstractStaticHandler { private final URL indexFile; private final URL singleFile; diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java similarity index 53% rename from avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java rename to avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java index 70a9548f..7f56a613 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticContentConfig.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java @@ -1,41 +1,48 @@ -package io.avaje.jex; +package io.avaje.jex.staticcontent; import java.net.URLConnection; import java.util.function.Predicate; +import io.avaje.jex.Context; +import io.avaje.jex.Routing.HttpService; + /** Builder for a static resource exchange handler. */ -public sealed interface StaticContentConfig permits StaticResourceHandlerBuilder { +public sealed interface StaticContentService extends HttpService + permits StaticResourceHandlerBuilder { - /** Create and return a new static content configuration. */ - static StaticContentConfig create() { - return StaticResourceHandlerBuilder.builder(); + /** + * Create and return a new static content configuration. + * + * @param resourceRoot The file to serve, or the directory the files are located in. + */ + static StaticContentService createCP(String resourceRoot) { + return StaticResourceHandlerBuilder.builder(resourceRoot); } - /** Return a new ExchangeHandler that will serve the resources */ - ExchangeHandler createHandler(); - /** - * Sets the HTTP path for the static resource handler. - * - * @param path the HTTP path prefix - * @return the updated configuration + * Create and return a new static class path content configuration with the `/public` directory as + * the root. */ - StaticContentConfig httpPath(String path); + static StaticContentService createCP() { + return StaticResourceHandlerBuilder.builder("/public/"); + } /** - * Gets the current HTTP path. + * Create and return a new static content configuration for a File. * - * @return the current HTTP path + * @param resourceRoot The path of the file to serve, or the directory the files are located in. */ - String httpPath(); + static StaticContentService createFile(String resourceRoot) { + return StaticResourceHandlerBuilder.builder(resourceRoot).file(); + } /** - * Sets the file to serve, or the folder your files are located in. (default: "/public/") + * Sets the HTTP path for the static resource handler. * - * @param resource the root directory + * @param path the HTTP path prefix * @return the updated configuration */ - StaticContentConfig resource(String resource); + StaticContentService httpPath(String path); /** * Sets the index file to be served when a directory is requests. @@ -43,7 +50,7 @@ static StaticContentConfig create() { * @param directoryIndex the index file * @return the updated configuration */ - StaticContentConfig directoryIndex(String directoryIndex); + StaticContentService directoryIndex(String directoryIndex); /** * Sets a custom resource loader for loading class/module path resources. This is normally used @@ -54,7 +61,7 @@ static StaticContentConfig create() { * @param resourceLoader the custom resource loader * @return the updated configuration */ - StaticContentConfig resourceLoader(ClassResourceLoader resourceLoader); + StaticContentService resourceLoader(ClassResourceLoader resourceLoader); /** * Adds a new MIME type mapping to the configuration. (Default: uses {@link @@ -65,7 +72,7 @@ static StaticContentConfig create() { * "application/javascript") * @return the updated configuration */ - StaticContentConfig putMimeTypeMapping(String ext, String mimeType); + StaticContentService putMimeTypeMapping(String ext, String mimeType); /** * Adds a new response header to the configuration. @@ -74,7 +81,7 @@ static StaticContentConfig create() { * @param value the header value * @return the updated configuration */ - StaticContentConfig putResponseHeader(String key, String value); + StaticContentService putResponseHeader(String key, String value); /** * Sets a predicate to filter files based on the request context. @@ -82,19 +89,5 @@ static StaticContentConfig create() { * @param skipFilePredicate the predicate to use * @return the updated configuration */ - StaticContentConfig skipFilePredicate(Predicate skipFilePredicate); - - /** - * Sets the resource location (CLASSPATH or FILE). - * - * @param location the resource location - * @return the updated configuration - */ - StaticContentConfig location(ResourceLocation location); - - /** Resource location */ - enum ResourceLocation { - CLASS_PATH, - FILE - } + StaticContentService skipFilePredicate(Predicate skipFilePredicate); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java similarity index 94% rename from avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java rename to avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java index 22e7ee1e..49ff2325 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java @@ -1,4 +1,4 @@ -package io.avaje.jex; +package io.avaje.jex.staticcontent; import java.io.File; import java.io.FileInputStream; @@ -9,7 +9,9 @@ import com.sun.net.httpserver.HttpExchange; -final class StaticFileHandler extends AbstractStaticHandler implements ExchangeHandler { +import io.avaje.jex.Context; + +final class StaticFileHandler extends AbstractStaticHandler { private final File indexFile; private final File singleFile; diff --git a/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java similarity index 76% rename from avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java rename to avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java index 832ce2f3..d56ef901 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java @@ -1,16 +1,17 @@ -package io.avaje.jex; - -import static io.avaje.jex.StaticContentConfig.ResourceLocation.CLASS_PATH; +package io.avaje.jex.staticcontent; import java.io.File; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.Objects; -import java.util.function.Function; import java.util.function.Predicate; -final class StaticResourceHandlerBuilder implements StaticContentConfig { +import io.avaje.jex.Context; +import io.avaje.jex.ExchangeHandler; +import io.avaje.jex.Routing; + +final class StaticResourceHandlerBuilder implements StaticContentService { private static final String FAILED_TO_LOCATE_FILE = "Failed to locate file: "; private static final String DIRECTORY_INDEX_FAILURE = @@ -19,28 +20,34 @@ final class StaticResourceHandlerBuilder implements StaticContentConfig { private static final ClassResourceLoader DEFAULT_LOADER = new DefaultResourceLoader(); private String path = "/"; - private String root = "/public/"; + private String root; private String directoryIndex = null; private ClassResourceLoader resourceLoader = DEFAULT_LOADER; private final Map mimeTypes = new HashMap<>(); private final Map headers = new HashMap<>(); private Predicate skipFilePredicate = NO_OP_PREDICATE; - private ResourceLocation location = CLASS_PATH; + private boolean isClasspath = true; - private StaticResourceHandlerBuilder() {} + private StaticResourceHandlerBuilder(String root) { + this.root = root; + } - static StaticResourceHandlerBuilder builder() { - return new StaticResourceHandlerBuilder(); + static StaticResourceHandlerBuilder builder(String root) { + return new StaticResourceHandlerBuilder(root); } @Override - public ExchangeHandler createHandler() { + public void add(Routing routing) { + + routing.get(path, createHandler()); + } + + ExchangeHandler createHandler() { path = Objects.requireNonNull(path) .transform(this::prependSlash) .transform(s -> s.endsWith("/*") ? s.substring(0, s.length() - 2) : s); - final var isClasspath = location == CLASS_PATH; root = isClasspath ? root.transform(this::prependSlash) : root; if (isClasspath && "/".equals(root)) { throw new IllegalArgumentException( @@ -52,8 +59,8 @@ public ExchangeHandler createHandler() { "Directory Index file is required when serving directories"); } - if (location == ResourceLocation.FILE) { - return fileLoader(File::new); + if (!isClasspath) { + return fileLoader(); } return classPathHandler(); @@ -65,17 +72,6 @@ public StaticResourceHandlerBuilder httpPath(String path) { return this; } - @Override - public String httpPath() { - return path; - } - - @Override - public StaticResourceHandlerBuilder resource(String directory) { - this.root = directory; - return this; - } - @Override public StaticResourceHandlerBuilder directoryIndex(String directoryIndex) { this.directoryIndex = directoryIndex; @@ -106,9 +102,8 @@ public StaticResourceHandlerBuilder skipFilePredicate(Predicate skipFil return this; } - @Override - public StaticResourceHandlerBuilder location(ResourceLocation location) { - this.location = location; + public StaticResourceHandlerBuilder file() { + this.isClasspath = false; return this; } @@ -120,14 +115,13 @@ private String appendSlash(String s) { return s.endsWith("/") ? s : s + "/"; } - private ExchangeHandler fileLoader(Function fileLoader) { + private StaticFileHandler fileLoader() { String fsRoot; File dirIndex = null; File singleFile = null; if (directoryIndex != null) { try { - dirIndex = - fileLoader.apply(root.transform(this::appendSlash) + directoryIndex).getCanonicalFile(); + dirIndex = new File(root.transform(this::appendSlash) + directoryIndex).getCanonicalFile(); fsRoot = dirIndex.getParentFile().getPath(); } catch (Exception e) { @@ -136,7 +130,7 @@ private ExchangeHandler fileLoader(Function fileLoader) { } } else { try { - singleFile = fileLoader.apply(root).getCanonicalFile(); + singleFile = new File(root).getCanonicalFile(); fsRoot = singleFile.getParentFile().getPath(); } catch (Exception e) { @@ -148,7 +142,7 @@ private ExchangeHandler fileLoader(Function fileLoader) { path, fsRoot, mimeTypes, headers, skipFilePredicate, dirIndex, singleFile); } - private ExchangeHandler classPathHandler() { + private StaticClassResourceHandler classPathHandler() { URL dirIndex = null; URL singleFile = null; if (directoryIndex != null) { diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java new file mode 100644 index 00000000..cadfdb52 --- /dev/null +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java @@ -0,0 +1,15 @@ +/** + * Static Content API - see {@link io.avaje.jex.staticcontent.StaticContentService}. + * + *

    {@code
    + * var staticContent = StaticContentService.createCP("/public").directoryIndex("index.html");
    + * final Jex.Server app = Jex.create()
    + *   .routing(staticContent.createService())
    + *   .port(8080)
    + *   .start();
    + *
    + * app.shutdown();
    + *
    + * }
    + */ +package io.avaje.jex.staticcontent; diff --git a/avaje-jex-static-content/src/main/java/module-info.java b/avaje-jex-static-content/src/main/java/module-info.java new file mode 100644 index 00000000..192a26ab --- /dev/null +++ b/avaje-jex-static-content/src/main/java/module-info.java @@ -0,0 +1,21 @@ +/** + * Defines the Static Content API for serving static resources with Jex - see {@link io.avaje.jex.staticcontent.StaticContentService}. + * + *
    {@code
    + * var staticContent = StaticContentService.createCP("/public").directoryIndex("index.html");
    + * final Jex.Server app = Jex.create()
    + *   .routing(staticContent.createService())
    + *   .port(8080)
    + *   .start();
    + *
    + * app.shutdown();
    + *
    + * }
    + */ +module io.avaje.jex.staticcontent { + + exports io.avaje.jex.staticcontent; + + requires transitive io.avaje.jex; + +} diff --git a/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java similarity index 72% rename from avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java rename to avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java index 08c9918d..d6833cfa 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/StaticFileTest.java +++ b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java @@ -1,4 +1,4 @@ -package io.avaje.jex; +package io.avaje.jex.staticcontent; import static org.assertj.core.api.Assertions.assertThat; @@ -8,8 +8,8 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; -import io.avaje.jex.StaticContentConfig.ResourceLocation; -import io.avaje.jex.core.TestPair; +import io.avaje.jex.Jex; +import io.avaje.jex.test.TestPair; class StaticFileTest { @@ -19,30 +19,27 @@ static TestPair init() { final Jex app = Jex.create() - .staticResource(b -> defaultCP(b.httpPath("/index"))) - .staticResource(b -> defaultFile(b.httpPath("/indexFile"))) - .staticResource(b -> defaultCP(b.httpPath("/indexWild/*"))) - .staticResource(b -> defaultFile(b.httpPath("/indexWildFile/*"))) - .staticResource(b -> defaultCP(b.httpPath("/sus/"))) - .staticResource(b -> defaultFile(b.httpPath("/susFile/*"))) - .staticResource(b -> b.httpPath("/single").resource("/logback.xml")) - .staticResource( - b -> - b.location(ResourceLocation.FILE) - .httpPath("/singleFile") - .resource("src/test/resources/logback.xml")); + .routing(defaultCP().httpPath("/index")) + .routing(defaultFile().httpPath("/indexFile")) + .routing(defaultCP().httpPath("/indexWild/*")) + .routing(defaultFile().httpPath("/indexWildFile/*")) + .routing(defaultCP().httpPath("/sus/")) + .routing(defaultFile().httpPath("/susFile/*")) + .routing(StaticContentService.createCP("/logback.xml").httpPath("/single")) + .routing( + StaticContentService.createFile("src/test/resources/logback.xml") + .httpPath("/singleFile")); return TestPair.create(app); } - private static StaticContentConfig defaultFile(StaticContentConfig b) { - return b.location(ResourceLocation.FILE) - .resource("src/test/resources/public") + private static StaticContentService defaultFile() { + return StaticContentService.createFile("src/test/resources/public") .directoryIndex("index.html"); } - private static StaticContentConfig defaultCP(StaticContentConfig b) { - return b.resource("/public").directoryIndex("index.html"); + private static StaticContentService defaultCP() { + return StaticContentService.createCP("/public").directoryIndex("index.html"); } @AfterAll diff --git a/avaje-jex-static-content/src/test/resources/logback.xml b/avaje-jex-static-content/src/test/resources/logback.xml new file mode 100644 index 00000000..199b4a4f --- /dev/null +++ b/avaje-jex-static-content/src/test/resources/logback.xml @@ -0,0 +1,19 @@ + + + + TRACE + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + diff --git a/avaje-jex-static-content/src/test/resources/public/index.html b/avaje-jex-static-content/src/test/resources/public/index.html new file mode 100644 index 00000000..41abec16 --- /dev/null +++ b/avaje-jex-static-content/src/test/resources/public/index.html @@ -0,0 +1,9 @@ + + + + Index.html + + +

    This ia my first page.

    + + \ No newline at end of file diff --git a/avaje-jex-static-content/src/test/resources/public/sus.txt b/avaje-jex-static-content/src/test/resources/public/sus.txt new file mode 100644 index 00000000..b20ae04e --- /dev/null +++ b/avaje-jex-static-content/src/test/resources/public/sus.txt @@ -0,0 +1 @@ +ඞ \ No newline at end of file diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index d909169b..045c0535 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC7 + 3.0-RC8 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 1b160b84..6d95b552 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC7 + 3.0-RC8 avaje-jex diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 9b6a12df..c008a987 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -83,27 +83,6 @@ static Jex create() { */ Routing routing(); - /** - * Adds a static resource route using the provided configuration. - * - * @param config The configuration for the static resource route. - */ - default Jex staticResource(StaticContentConfig config) { - routing().get(config.httpPath(), config.createHandler()); - return this; - } - - /** - * Adds a static resource route using a consumer to configure the {@link StaticContentConfig}. - * - * @param consumer The consumer to configure the static resource route. - */ - default Jex staticResource(Consumer consumer) { - var builder = StaticResourceHandlerBuilder.builder(); - consumer.accept(builder); - return staticResource(builder); - } - /** * Sets the JSON service to use for serialization and deserialization. * diff --git a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java index fb6b3c1b..d28aeeed 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java @@ -8,7 +8,6 @@ import org.junit.jupiter.api.Test; import io.avaje.jex.Jex; -import io.avaje.jex.StaticContentConfig.ResourceLocation; import io.avaje.jex.core.Constants; import io.avaje.jex.core.TestPair; @@ -20,17 +19,20 @@ static TestPair init() { final Jex app = Jex.create() - .staticResource(b -> b.httpPath("/compress").resource("/64KB.json")) .routing( r -> r.get( - "/forced", - ctx -> ctx.header(Constants.CONTENT_ENCODING, "gzip").text("hi"))) - .staticResource( - b -> - b.location(ResourceLocation.FILE) - .httpPath("/sus") - .resource("src/test/resources/public/sus.txt")); + "/compress", + ctx -> + ctx.write(CompressionTest.class.getResourceAsStream("/64KB.json"))) + .get( + "/sus", + ctx -> + ctx.write( + CompressionTest.class.getResourceAsStream("/public/sus.txt"))) + .get( + "/forced", + ctx -> ctx.header(Constants.CONTENT_ENCODING, "gzip").text("hi"))); return TestPair.create(app); } diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index cd72444b..3edd3c3c 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC7 + 3.0-RC8 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 38e0b622..c79f1280 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC7 + 3.0-RC8 diff --git a/examples/pom.xml b/examples/pom.xml index ee2b2358..9f6890e6 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC7 + 3.0-RC8 examples diff --git a/pom.xml b/pom.xml index 06059337..93f93cd0 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.avaje avaje-jex-parent - 3.0-RC7 + 3.0-RC8 pom @@ -22,7 +22,7 @@ 2.18.2 false 21 - 2024-12-02T08:52:38Z + 2024-12-02T22:09:37Z @@ -31,6 +31,7 @@ avaje-jex-freemarker avaje-jex-mustache avaje-jex-htmx + avaje-jex-static-content From 966690f638e6b69d884e691053635e3f5e4bcb9a Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 4 Dec 2024 21:57:56 +1300 Subject: [PATCH 111/250] Remove a null check that isn't required (#123) --- .../java/io/avaje/jex/staticcontent/AbstractStaticHandler.java | 2 -- .../avaje/jex/staticcontent/StaticResourceHandlerBuilder.java | 3 --- avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java | 2 +- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/AbstractStaticHandler.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/AbstractStaticHandler.java index 5776aa90..6cb66a49 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/AbstractStaticHandler.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/AbstractStaticHandler.java @@ -59,12 +59,10 @@ protected String getExt(String path) { protected String lookupMime(String path) { var lower = path.toLowerCase(); - return Objects.requireNonNullElseGet( MIME_MAP.getContentTypeFor(path), () -> { String ext = getExt(lower); - return mimeTypes.getOrDefault(ext, "application/octet-stream"); }); } diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java index d56ef901..eeaf30f0 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java @@ -38,7 +38,6 @@ static StaticResourceHandlerBuilder builder(String root) { @Override public void add(Routing routing) { - routing.get(path, createHandler()); } @@ -122,7 +121,6 @@ private StaticFileHandler fileLoader() { if (directoryIndex != null) { try { dirIndex = new File(root.transform(this::appendSlash) + directoryIndex).getCanonicalFile(); - fsRoot = dirIndex.getParentFile().getPath(); } catch (Exception e) { throw new IllegalStateException( @@ -131,7 +129,6 @@ private StaticFileHandler fileLoader() { } else { try { singleFile = new File(root).getCanonicalFile(); - fsRoot = singleFile.getParentFile().getPath(); } catch (Exception e) { throw new IllegalStateException(FAILED_TO_LOCATE_FILE + root, e); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index 3c7d7b49..96ff9d3c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -250,7 +250,7 @@ public String pathParam(String name) { @Override public String queryParam(String name) { final List vals = queryParams(name); - return vals == null || vals.isEmpty() ? null : vals.getFirst(); + return vals.isEmpty() ? null : vals.getFirst(); } private Map> queryParams() { From ab99458ee4394f80d260eb57a689b6724f63187c Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 4 Dec 2024 22:16:32 +1300 Subject: [PATCH 112/250] Version 3.0-RC9 --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-htmx/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 4 ++-- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 5fd56c72..37f8ff3c 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC8 + 3.0-RC9 avaje-jex-freemarker diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 5c136663..ee3d97e9 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC8 + 3.0-RC9 avaje-jex-htmx diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 7df8ec15..93812b16 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC8 + 3.0-RC9 avaje-jex-mustache diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index 0458d2f6..195849d5 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC8 + 3.0-RC9 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 045c0535..98a37196 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC8 + 3.0-RC9 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 6d95b552..cec5b87f 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC8 + 3.0-RC9 avaje-jex diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 3edd3c3c..e8cd40d7 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC8 + 3.0-RC9 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index c79f1280..e2f9f527 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC8 + 3.0-RC9 diff --git a/examples/pom.xml b/examples/pom.xml index 9f6890e6..c8b1534c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC8 + 3.0-RC9 examples diff --git a/pom.xml b/pom.xml index 93f93cd0..44f347a2 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.avaje avaje-jex-parent - 3.0-RC8 + 3.0-RC9 pom @@ -22,7 +22,7 @@ 2.18.2 false 21 - 2024-12-02T22:09:37Z + 2024-12-04T08:58:25Z From 2df12eb5e49b26896563b2ad2bc469a7ba314453 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 5 Dec 2024 00:41:01 -0500 Subject: [PATCH 113/250] Void Write Methods (#124) * void write methods * void write methods * Update pom.xml --- .../io/avaje/jex/staticcontent/package-info.java | 4 ++-- .../src/main/java/module-info.java | 4 ++-- .../src/main/java/io/avaje/jex/Context.java | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java index cadfdb52..62be4942 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java @@ -2,9 +2,9 @@ * Static Content API - see {@link io.avaje.jex.staticcontent.StaticContentService}. * *
    {@code
    - * var staticContent = StaticContentService.createCP("/public").directoryIndex("index.html");
    + * var staticContent = StaticContentService.createCP("/public").httpPath("/").directoryIndex("index.html");
      * final Jex.Server app = Jex.create()
    - *   .routing(staticContent.createService())
    + *   .routing(staticContent)
      *   .port(8080)
      *   .start();
      *
    diff --git a/avaje-jex-static-content/src/main/java/module-info.java b/avaje-jex-static-content/src/main/java/module-info.java
    index 192a26ab..144923be 100644
    --- a/avaje-jex-static-content/src/main/java/module-info.java
    +++ b/avaje-jex-static-content/src/main/java/module-info.java
    @@ -2,9 +2,9 @@
      * Defines the Static Content API for serving static resources with Jex - see {@link io.avaje.jex.staticcontent.StaticContentService}.
      *
      * 
    {@code
    - * var staticContent = StaticContentService.createCP("/public").directoryIndex("index.html");
    + * var staticContent = StaticContentService.createCP("/public").httpPath("/").directoryIndex("index.html");
      * final Jex.Server app = Jex.create()
    - *   .routing(staticContent.createService())
    + *   .routing(staticContent)
      *   .port(8080)
      *   .start();
      *
    diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java
    index cd91cad7..8d5703b1 100644
    --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java
    +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java
    @@ -246,17 +246,17 @@ default String userAgent() {
       int status();
     
       /** Write plain text content to the response. */
    -  Context text(String content);
    +  void text(String content);
     
       /** Write html content to the response. */
    -  Context html(String content);
    +  void html(String content);
     
       /**
        * Set the content type as application/json and write the response.
        *
        * @param bean the object to serialize and write
        */
    -  Context json(Object bean);
    +  void json(Object bean);
     
       /**
        * Write the stream as a JSON stream with new line delimiters {@literal
    @@ -264,7 +264,7 @@ default String userAgent() {
        *
        * @param stream The stream of beans to write as json
        */
    -   Context jsonStream(Stream stream);
    +   void jsonStream(Stream stream);
     
       /**
        * Write the stream as a JSON stream with new line delimiters {@literal
    @@ -272,28 +272,28 @@ default String userAgent() {
        *
        * @param iterator The iterator of beans to write as json
        */
    -   Context jsonStream(Iterator iterator);
    +   void jsonStream(Iterator iterator);
     
       /**
        * Writes the given string content directly to the response.
        *
        * @param content The string content to write.
        */
    -  Context write(String content);
    +  void write(String content);
     
       /**
        * Writes the given bytes directly to the response.
        *
        * @param bytes The byte array to write.
        */
    -  Context write(byte[] bytes);
    +  void write(byte[] bytes);
     
       /**
        * Writes the content from the given InputStream directly to the response body.
        *
        * @param is The input stream containing the content to write.
        */
    -  Context write(InputStream is);
    +  void write(InputStream is);
     
       /**
        * Return the outputStream to write content. It is expected that
    
    From 12583a8ee39c48b376f61c415ef2d59d765f7a47 Mon Sep 17 00:00:00 2001
    From: Josiah Noel <32279667+SentryMan@users.noreply.github.com>
    Date: Thu, 5 Dec 2024 00:43:41 -0500
    Subject: [PATCH 114/250] Fix Context void (#125)
    
    * void write methods
    
    * Update pom.xml
    
    * Update JdkContext.java
    ---
     .../java/io/avaje/jex/core/JdkContext.java    | 26 +++++++------------
     1 file changed, 10 insertions(+), 16 deletions(-)
    
    diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java
    index 96ff9d3c..c0de6b0b 100644
    --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java
    +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java
    @@ -326,46 +326,42 @@ public int status() {
       }
     
       @Override
    -  public Context json(Object bean) {
    +  public void json(Object bean) {
         contentType(APPLICATION_JSON);
         mgr.jsonWrite(bean, outputStream());
    -    return this;
       }
     
       @Override
    -  public  Context jsonStream(Stream stream) {
    +  public  void jsonStream(Stream stream) {
         contentType(APPLICATION_X_JSON_STREAM);
         mgr.jsonWriteStream(stream, outputStream());
    -    return this;
       }
     
       @Override
    -  public  Context jsonStream(Iterator iterator) {
    +  public  void jsonStream(Iterator iterator) {
         contentType(APPLICATION_X_JSON_STREAM);
         mgr.jsonWriteStream(iterator, outputStream());
    -    return this;
       }
     
       @Override
    -  public Context text(String content) {
    +  public void text(String content) {
         contentType(TEXT_PLAIN_UTF8);
    -    return write(content);
    +    write(content);
       }
     
       @Override
    -  public Context html(String content) {
    +  public void html(String content) {
         contentType(TEXT_HTML_UTF8);
    -    return write(content);
    +    write(content);
       }
     
       @Override
    -  public Context write(String content) {
    +  public void write(String content) {
         write(content.getBytes(StandardCharsets.UTF_8));
    -    return this;
       }
     
       @Override
    -  public Context write(byte[] bytes) {
    +  public void write(byte[] bytes) {
         try (var os = exchange.getResponseBody()) {
           exchange.sendResponseHeaders(statusCode(), bytes.length == 0 ? -1 : bytes.length);
           os.write(bytes);
    @@ -373,17 +369,15 @@ public Context write(byte[] bytes) {
         } catch (IOException e) {
           throw new UncheckedIOException(e);
         }
    -    return this;
       }
     
       @Override
    -  public Context write(InputStream is) {
    +  public void write(InputStream is) {
         try (is; var os = outputStream()) {
           is.transferTo(os);
         } catch (IOException e) {
           throw new UncheckedIOException(e);
         }
    -    return this;
       }
     
       @Override
    
    From 6344c95a9f01979788dc7a47aa5b0ad03d50a081 Mon Sep 17 00:00:00 2001
    From: Rob Bygrave 
    Date: Fri, 6 Dec 2024 03:23:03 +1300
    Subject: [PATCH 115/250] Add SameSite and Partitioned options to Cookie (#126)
    
    ---
     .../src/main/java/io/avaje/jex/Context.java   | 29 +++++++++-
     .../src/main/java/io/avaje/jex/DCookie.java   | 30 ++++++++++
     .../test/java/io/avaje/jex/CookieTest.java    | 57 +++++++++++++++++--
     3 files changed, 111 insertions(+), 5 deletions(-)
    
    diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java
    index 8d5703b1..aa79ac60 100644
    --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java
    +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java
    @@ -559,7 +559,7 @@ static Cookie of(String name, String value) {
         Cookie secure(boolean secure);
     
         /**
    -     * Checks if the HttpOnly attribute is enabled for this cookie.
    +     * Indicates if the HttpOnly attribute is enabled for this cookie.
          *
          * 

    The HttpOnly attribute ensures that the cookie is inaccessible to JavaScript, helping to * mitigate cross-site scripting (XSS) attacks. @@ -580,11 +580,38 @@ static Cookie of(String name, String value) { */ Cookie httpOnly(boolean httpOnly); + /** + * Indicates if the Partitioned attribute is enabled for this cookie. + */ + boolean partitioned(); + + /** + * Set the Partitioned attribute for this cookie. + */ + Cookie partitioned(boolean partitioned); + + /** + * Return the SameSite setting. + */ + SameSite sameSite(); + + /** + * Set the SameSite setting for this cookie. + */ + Cookie sameSite(SameSite sameSite); + /** * Returns content of the cookie as a 'Set-Cookie:' header value specified by RFC6265. */ @Override String toString(); + + /** + * Cookie SameSite options. + */ + enum SameSite { + Strict, Lax, None + } } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DCookie.java b/avaje-jex/src/main/java/io/avaje/jex/DCookie.java index 0c94a06e..7df6d8ef 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DCookie.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DCookie.java @@ -17,8 +17,10 @@ final class DCookie implements Context.Cookie { private ZonedDateTime expires; private Duration maxAge;// = -1; // ;Max-Age=VALUE ... cookies auto-expire private String path; // ;Path=VALUE ... URLs that see the cookie + private SameSite sameSite; // ;SameSite=Strict|Lax|None private boolean secure; // ;Secure ... e.g. use SSL private boolean httpOnly; + private boolean partitioned; private DCookie(String name, String value) { if (name == null || name.isEmpty()) { @@ -112,6 +114,28 @@ public Context.Cookie httpOnly(boolean httpOnly) { return this; } + @Override + public boolean partitioned() { + return partitioned; + } + + @Override + public Context.Cookie partitioned(boolean partitioned) { + this.partitioned = partitioned; + return this; + } + + @Override + public SameSite sameSite() { + return sameSite; + } + + @Override + public Context.Cookie sameSite(SameSite sameSite) { + this.sameSite = sameSite; + return this; + } + @Override public String toString() { StringBuilder result = new StringBuilder(60); @@ -128,12 +152,18 @@ public String toString() { if (path != null) { result.append(PARAM_SEPARATOR).append("Path=").append(path); } + if (sameSite != null) { + result.append(PARAM_SEPARATOR).append("SameSite=").append(sameSite); + } if (secure) { result.append(PARAM_SEPARATOR).append("Secure"); } if (httpOnly) { result.append(PARAM_SEPARATOR).append("HttpOnly"); } + if (partitioned) { + result.append(PARAM_SEPARATOR).append("Partitioned"); + } return result.toString(); } } diff --git a/avaje-jex/src/test/java/io/avaje/jex/CookieTest.java b/avaje-jex/src/test/java/io/avaje/jex/CookieTest.java index 03f2dff9..2fe830dc 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/CookieTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/CookieTest.java @@ -2,8 +2,11 @@ import io.avaje.jex.Context.Cookie; import org.junit.jupiter.api.Test; +import org.mockito.junit.MockitoJUnitRunner; -import static org.junit.jupiter.api.Assertions.assertEquals; +import java.time.Duration; + +import static org.junit.jupiter.api.Assertions.*; class CookieTest { @@ -12,15 +15,61 @@ void format() { assertEquals("key=val", Cookie.of("key", "val").toString()); assertEquals("key=val; Domain=dom", Cookie.of("key", "val").domain("dom").toString()); assertEquals("key=val; Path=/pt", Cookie.of("key", "val").path("/pt").toString()); - //assertEquals("key=val; Path=/; Max-Age=10", Cookie.of("key", "val").maxAge(10).format()); + assertEquals("key=val; Max-Age=10", Cookie.of("key", "val").maxAge(Duration.ofSeconds(10)).toString()); assertEquals("key=val; Secure", Cookie.of("key", "val").secure(true).toString()); assertEquals("key=val; HttpOnly", Cookie.of("key", "val").httpOnly(true).toString()); + assertEquals("key=val; Partitioned", Cookie.of("key", "val").partitioned(true).toString()); + assertEquals("key=val; SameSite=Strict", Cookie.of("key", "val").sameSite(Cookie.SameSite.Strict).toString()); assertEquals("key=val; Secure; HttpOnly", Cookie.of("key", "val").httpOnly(true).secure(true).toString()); } + @Test + void partitioned() { + var c = Cookie.of("k", "v").secure(true).partitioned(true); + assertTrue(c.partitioned()); + + c.partitioned(false); + assertFalse(c.partitioned()); + + assertEquals("k=v; Secure; Partitioned", Cookie.of("k", "v").secure(true).partitioned(true).toString()); + assertEquals("k=v; Secure", Cookie.of("k", "v").secure(true).partitioned(false).toString()); + + } + @Test + void sameSite() { + + var c = Cookie.of("k", "v"); + assertNull(c.sameSite()); + c.sameSite(Cookie.SameSite.Strict); + assertEquals(Cookie.SameSite.Strict, c.sameSite()); + c.sameSite(Cookie.SameSite.Lax); + assertEquals(Cookie.SameSite.Lax, c.sameSite()); + c.sameSite(Cookie.SameSite.None); + assertEquals(Cookie.SameSite.None, c.sameSite()); + c.sameSite(null); + assertNull(c.sameSite()); + + assertEquals("key=val; SameSite=Strict; Secure", Cookie.of("key", "val").secure(true).sameSite(Cookie.SameSite.Strict).toString()); + assertEquals("key=val; SameSite=Lax; Secure", Cookie.of("key", "val").secure(true).sameSite(Cookie.SameSite.Lax).toString()); + assertEquals("key=val; SameSite=None; Secure", Cookie.of("key", "val").secure(true).sameSite(Cookie.SameSite.None).toString()); + assertEquals("key=val; Secure", Cookie.of("key", "val").secure(true).sameSite(null).toString()); + } + @Test void format_all() { - assertEquals("key=val; Domain=dom; Path=/pt; Secure; HttpOnly", Cookie.of("key", "val") - .domain("dom").path("/pt").secure(true).httpOnly(true).toString()); + var cookie = Cookie.of("key", "val") + .domain("dom") + .path("/pt") + .secure(true) + .httpOnly(true) + .partitioned(true) + .sameSite(Cookie.SameSite.Strict); + + assertTrue(cookie.secure()); + assertTrue(cookie.httpOnly()); + assertTrue(cookie.partitioned()); + + assertEquals("key=val; Domain=dom; Path=/pt; SameSite=Strict; Secure; HttpOnly; Partitioned", + cookie.toString()); } } From d982b14fd1f22e45747f47e387fffbcdd3bb1312 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:49:00 -0500 Subject: [PATCH 116/250] add precompression for static files (#127) --- .../staticcontent/AbstractStaticHandler.java | 34 ++++- .../jex/staticcontent/CachedResource.java | 6 + .../staticcontent/ClassResourceLoader.java | 9 -- .../staticcontent/DefaultResourceLoader.java | 17 --- .../StaticClassResourceHandler.java | 47 ++++-- .../staticcontent/StaticContentService.java | 15 +- .../jex/staticcontent/StaticFileHandler.java | 44 +++++- .../StaticResourceHandlerBuilder.java | 47 ++++-- .../avaje/jex/staticcontent/package-info.java | 2 +- .../src/main/java/module-info.java | 2 +- .../CompressedStaticFileTest.java | 135 ++++++++++++++++++ .../jex/staticcontent/StaticFileTest.java | 16 +-- .../jex/compression/CompressionConfig.java | 2 +- 13 files changed, 307 insertions(+), 69 deletions(-) create mode 100644 avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/CachedResource.java create mode 100644 avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/AbstractStaticHandler.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/AbstractStaticHandler.java index 6cb66a49..818f8351 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/AbstractStaticHandler.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/AbstractStaticHandler.java @@ -1,15 +1,21 @@ package io.avaje.jex.staticcontent; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.FileNameMap; import java.net.URLConnection; import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import com.sun.net.httpserver.HttpExchange; import io.avaje.jex.Context; import io.avaje.jex.ExchangeHandler; +import io.avaje.jex.compression.CompressedOutputStream; +import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.http.BadRequestException; import io.avaje.jex.http.NotFoundException; @@ -17,10 +23,13 @@ abstract sealed class AbstractStaticHandler implements ExchangeHandler permits StaticFileHandler, StaticClassResourceHandler { protected final Map mimeTypes; + protected final CompressionConfig compressionConfig; protected final String filesystemRoot; protected final String urlPrefix; protected final Predicate skipFilePredicate; protected final Map headers; + protected final boolean precompress; + protected final Map compressedFiles = new ConcurrentHashMap<>(); private static final FileNameMap MIME_MAP = URLConnection.getFileNameMap(); protected AbstractStaticHandler( @@ -28,12 +37,16 @@ protected AbstractStaticHandler( String filesystemRoot, Map mimeTypes, Map headers, - Predicate skipFilePredicate) { + Predicate skipFilePredicate, + boolean precompress, + CompressionConfig compressionConfig) { + this.compressionConfig = compressionConfig; this.filesystemRoot = filesystemRoot; this.urlPrefix = urlPrefix; this.skipFilePredicate = skipFilePredicate; this.headers = headers; this.mimeTypes = mimeTypes; + this.precompress = precompress; } protected void throw404(HttpExchange jdkExchange) { @@ -66,4 +79,23 @@ protected String lookupMime(String path) { return mimeTypes.getOrDefault(ext, "application/octet-stream"); }); } + + protected boolean isCached(final String path) { + return precompress && compressedFiles.containsKey(path); + } + + protected void addCachedEntry(Context ctx, String urlPath, InputStream fis) throws IOException { + var baos = new ByteArrayOutputStream(); + fis.transferTo(new CompressedOutputStream(compressionConfig, ctx, baos)); + var bytes = baos.toByteArray(); + var responseHeaders = Map.copyOf(ctx.exchange().getResponseHeaders()); + ctx.write(bytes); + compressedFiles.put(urlPath, new CachedResource(responseHeaders, bytes)); + } + + protected void writeCached(Context ctx, String path) { + var cached = compressedFiles.get(path); + ctx.headerMap(cached.headers()); + ctx.write(cached.bytes()); + } } diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/CachedResource.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/CachedResource.java new file mode 100644 index 00000000..bc5dd374 --- /dev/null +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/CachedResource.java @@ -0,0 +1,6 @@ +package io.avaje.jex.staticcontent; + +import java.util.List; +import java.util.Map; + +record CachedResource(Map> headers, byte[] bytes) {} diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/ClassResourceLoader.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/ClassResourceLoader.java index 7d22d9cd..7f7378b3 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/ClassResourceLoader.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/ClassResourceLoader.java @@ -1,6 +1,5 @@ package io.avaje.jex.staticcontent; -import java.io.InputStream; import java.net.URL; /** @@ -31,12 +30,4 @@ static ClassResourceLoader fromClass(Class clazz) { * @return The URL of the resource. */ URL loadResource(String resourcePath); - - /** - * Loads the specified resource and returns an input stream to read its contents. - * - * @param resourcePath The path to the resource. - * @return An InputStream to read the resource. - */ - InputStream loadResourceAsStream(String resourcePath); } diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/DefaultResourceLoader.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/DefaultResourceLoader.java index 5d46c7ed..57b230b1 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/DefaultResourceLoader.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/DefaultResourceLoader.java @@ -1,6 +1,5 @@ package io.avaje.jex.staticcontent; -import java.io.InputStream; import java.net.URL; import java.util.Objects; import java.util.Optional; @@ -29,20 +28,4 @@ public URL loadResource(String resourcePath) { } return Objects.requireNonNull(url, "Unable to locate resource: " + resourcePath); } - - @Override - public InputStream loadResourceAsStream(String resourcePath) { - var resourceStream = clazz.getResourceAsStream(resourcePath); - if (resourceStream == null) { - // search the module path for top level resource - resourceStream = - Optional.ofNullable(ClassLoader.getSystemResourceAsStream(resourcePath)) - .orElseGet( - () -> - Thread.currentThread() - .getContextClassLoader() - .getResourceAsStream(resourcePath)); - } - return Objects.requireNonNull(resourceStream, "Unable to locate resource: " + resourcePath); - } } diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticClassResourceHandler.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticClassResourceHandler.java index 6af3a0c3..6b066839 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticClassResourceHandler.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticClassResourceHandler.java @@ -6,6 +6,7 @@ import java.util.function.Predicate; import io.avaje.jex.Context; +import io.avaje.jex.compression.CompressionConfig; final class StaticClassResourceHandler extends AbstractStaticHandler { @@ -21,8 +22,17 @@ final class StaticClassResourceHandler extends AbstractStaticHandler { Predicate skipFilePredicate, ClassResourceLoader resourceLoader, URL indexFile, - URL singleFile) { - super(urlPrefix, filesystemRoot, mimeTypes, headers, skipFilePredicate); + URL singleFile, + boolean precompress, + CompressionConfig compressionConfig) { + super( + urlPrefix, + filesystemRoot, + mimeTypes, + headers, + skipFilePredicate, + precompress, + compressionConfig); this.resourceLoader = resourceLoader; this.indexFile = indexFile; @@ -32,7 +42,12 @@ final class StaticClassResourceHandler extends AbstractStaticHandler { @Override public void handle(Context ctx) { if (singleFile != null) { - sendURL(ctx, singleFile.getPath(), singleFile); + final var path = singleFile.getPath(); + if (isCached(path)) { + writeCached(ctx, path); + return; + } + sendURL(ctx, path, singleFile); return; } @@ -43,31 +58,39 @@ public void handle(Context ctx) { final String wholeUrlPath = jdkExchange.getRequestURI().getPath(); if (wholeUrlPath.endsWith("/") || wholeUrlPath.equals(urlPrefix)) { - sendURL(ctx, indexFile.getPath(), indexFile); + final var path = indexFile.getPath(); + if (isCached(path)) { + writeCached(ctx, path); + return; + } + sendURL(ctx, path, indexFile); return; } final String urlPath = wholeUrlPath.substring(urlPrefix.length()); + + if (isCached(urlPath)) { + writeCached(ctx, urlPath); + return; + } + final String normalizedPath = Paths.get(filesystemRoot, urlPath).normalize().toString().replace("\\", "/"); if (!normalizedPath.startsWith(filesystemRoot)) { reportPathTraversal(); } - - try (var fis = resourceLoader.loadResourceAsStream(normalizedPath)) { - ctx.header("Content-Type", lookupMime(normalizedPath)); - ctx.headers(headers); - ctx.write(fis); - } catch (final Exception e) { - throw404(ctx.exchange()); - } + sendURL(ctx, urlPath, resourceLoader.loadResource(normalizedPath)); } private void sendURL(Context ctx, String urlPath, URL path) { try (var fis = path.openStream()) { ctx.header("Content-Type", lookupMime(urlPath)); ctx.headers(headers); + if (precompress) { + addCachedEntry(ctx, urlPath, fis); + return; + } ctx.write(fis); } catch (final Exception e) { throw404(ctx.exchange()); diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java index 7f56a613..0008588d 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java @@ -4,14 +4,14 @@ import java.util.function.Predicate; import io.avaje.jex.Context; -import io.avaje.jex.Routing.HttpService; +import io.avaje.jex.spi.JexPlugin; /** Builder for a static resource exchange handler. */ -public sealed interface StaticContentService extends HttpService +public sealed interface StaticContentService extends JexPlugin permits StaticResourceHandlerBuilder { /** - * Create and return a new static content configuration. + * Create and return a new static class path content configuration. * * @param resourceRoot The file to serve, or the directory the files are located in. */ @@ -52,11 +52,18 @@ static StaticContentService createFile(String resourceRoot) { */ StaticContentService directoryIndex(String directoryIndex); + /** + * Sent resources will be pre-compressed and cached in memory when this is enabled + * + * @return the updated configuration + */ + StaticContentService preCompress(); + /** * Sets a custom resource loader for loading class/module path resources. This is normally used * when running the application on the module path when files cannot be discovered. * - *

    Example usage: {@code config.resourceLoader(ClassResourceLoader.create(getClass())) } + *

    Example usage: {@code service.resourceLoader(ClassResourceLoader.create(getClass())) } * * @param resourceLoader the custom resource loader * @return the updated configuration diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java index 49ff2325..6c2ca991 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java @@ -1,5 +1,6 @@ package io.avaje.jex.staticcontent; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -10,6 +11,8 @@ import com.sun.net.httpserver.HttpExchange; import io.avaje.jex.Context; +import io.avaje.jex.compression.CompressedOutputStream; +import io.avaje.jex.compression.CompressionConfig; final class StaticFileHandler extends AbstractStaticHandler { @@ -23,8 +26,17 @@ final class StaticFileHandler extends AbstractStaticHandler { Map headers, Predicate skipFilePredicate, File welcomeFile, - File singleFile) { - super(urlPrefix, filesystemRoot, mimeTypes, headers, skipFilePredicate); + File singleFile, + boolean precompress, + CompressionConfig compressionConfig) { + super( + urlPrefix, + filesystemRoot, + mimeTypes, + headers, + skipFilePredicate, + precompress, + compressionConfig); this.indexFile = welcomeFile; this.singleFile = singleFile; } @@ -33,7 +45,14 @@ final class StaticFileHandler extends AbstractStaticHandler { public void handle(Context ctx) throws IOException { final var jdkExchange = ctx.exchange(); if (singleFile != null) { - sendFile(ctx, jdkExchange, singleFile.getPath(), singleFile); + + final var path = singleFile.getPath(); + if (isCached(path)) { + writeCached(ctx, path); + return; + } + + sendFile(ctx, jdkExchange, path, singleFile); return; } @@ -43,11 +62,24 @@ public void handle(Context ctx) throws IOException { final String wholeUrlPath = jdkExchange.getRequestURI().getPath(); if (wholeUrlPath.endsWith("/") || wholeUrlPath.equals(urlPrefix)) { - sendFile(ctx, jdkExchange, indexFile.getPath(), indexFile); + + final var path = indexFile.getPath(); + if (isCached(path)) { + writeCached(ctx, path); + return; + } + + sendFile(ctx, jdkExchange, path, indexFile); return; } final String urlPath = wholeUrlPath.substring(urlPrefix.length()); + + if (isCached(urlPath)) { + writeCached(ctx, urlPath); + return; + } + File canonicalFile; try { canonicalFile = new File(filesystemRoot, urlPath).getCanonicalFile(); @@ -72,6 +104,10 @@ private void sendFile(Context ctx, HttpExchange jdkExchange, String urlPath, Fil String mimeType = lookupMime(urlPath); ctx.header("Content-Type", mimeType); ctx.headers(headers); + if (precompress) { + addCachedEntry(ctx, urlPath, fis); + return; + } ctx.write(fis); } catch (FileNotFoundException e) { throw404(jdkExchange); diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java index eeaf30f0..1ff2d334 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java @@ -9,7 +9,8 @@ import io.avaje.jex.Context; import io.avaje.jex.ExchangeHandler; -import io.avaje.jex.Routing; +import io.avaje.jex.Jex; +import io.avaje.jex.compression.CompressionConfig; final class StaticResourceHandlerBuilder implements StaticContentService { @@ -27,6 +28,7 @@ final class StaticResourceHandlerBuilder implements StaticContentService { private final Map headers = new HashMap<>(); private Predicate skipFilePredicate = NO_OP_PREDICATE; private boolean isClasspath = true; + private boolean precompress; private StaticResourceHandlerBuilder(String root) { this.root = root; @@ -37,11 +39,11 @@ static StaticResourceHandlerBuilder builder(String root) { } @Override - public void add(Routing routing) { - routing.get(path, createHandler()); + public void apply(Jex jex) { + jex.routing().get(path, createHandler(jex.config().compression())); } - ExchangeHandler createHandler() { + ExchangeHandler createHandler(CompressionConfig compress) { path = Objects.requireNonNull(path) .transform(this::prependSlash) @@ -59,10 +61,10 @@ ExchangeHandler createHandler() { } if (!isClasspath) { - return fileLoader(); + return fileLoader(compress); } - return classPathHandler(); + return classPathHandler(compress); } @Override @@ -101,7 +103,13 @@ public StaticResourceHandlerBuilder skipFilePredicate(Predicate skipFil return this; } - public StaticResourceHandlerBuilder file() { + @Override + public StaticResourceHandlerBuilder preCompress() { + this.precompress = true; + return this; + } + + StaticResourceHandlerBuilder file() { this.isClasspath = false; return this; } @@ -114,7 +122,7 @@ private String appendSlash(String s) { return s.endsWith("/") ? s : s + "/"; } - private StaticFileHandler fileLoader() { + private StaticFileHandler fileLoader(CompressionConfig compress) { String fsRoot; File dirIndex = null; File singleFile = null; @@ -136,10 +144,18 @@ private StaticFileHandler fileLoader() { } return new StaticFileHandler( - path, fsRoot, mimeTypes, headers, skipFilePredicate, dirIndex, singleFile); + path, + fsRoot, + mimeTypes, + headers, + skipFilePredicate, + dirIndex, + singleFile, + precompress, + compress); } - private StaticClassResourceHandler classPathHandler() { + private StaticClassResourceHandler classPathHandler(CompressionConfig compress) { URL dirIndex = null; URL singleFile = null; if (directoryIndex != null) { @@ -149,6 +165,15 @@ private StaticClassResourceHandler classPathHandler() { } return new StaticClassResourceHandler( - path, root, mimeTypes, headers, skipFilePredicate, resourceLoader, dirIndex, singleFile); + path, + root, + mimeTypes, + headers, + skipFilePredicate, + resourceLoader, + dirIndex, + singleFile, + precompress, + compress); } } diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java index 62be4942..0aa9f708 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java @@ -4,7 +4,7 @@ *

    {@code
      * var staticContent = StaticContentService.createCP("/public").httpPath("/").directoryIndex("index.html");
      * final Jex.Server app = Jex.create()
    - *   .routing(staticContent)
    + *   .plugin(staticContent)
      *   .port(8080)
      *   .start();
      *
    diff --git a/avaje-jex-static-content/src/main/java/module-info.java b/avaje-jex-static-content/src/main/java/module-info.java
    index 144923be..c2ff0def 100644
    --- a/avaje-jex-static-content/src/main/java/module-info.java
    +++ b/avaje-jex-static-content/src/main/java/module-info.java
    @@ -4,7 +4,7 @@
      * 
    {@code
      * var staticContent = StaticContentService.createCP("/public").httpPath("/").directoryIndex("index.html");
      * final Jex.Server app = Jex.create()
    - *   .routing(staticContent)
    + *   .plugin(staticContent)
      *   .port(8080)
      *   .start();
      *
    diff --git a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java
    new file mode 100644
    index 00000000..15a4b263
    --- /dev/null
    +++ b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java
    @@ -0,0 +1,135 @@
    +package io.avaje.jex.staticcontent;
    +
    +import static org.assertj.core.api.Assertions.assertThat;
    +
    +import java.net.http.HttpResponse;
    +import java.time.Duration;
    +
    +import org.junit.jupiter.api.AfterAll;
    +import org.junit.jupiter.api.Test;
    +
    +import io.avaje.jex.Jex;
    +import io.avaje.jex.test.TestPair;
    +
    +class CompressedStaticFileTest {
    +
    +  static TestPair pair = init();
    +
    +  static TestPair init() {
    +
    +    final Jex app =
    +        Jex.create()
    +            .plugin(defaultCP().httpPath("/index"))
    +            .plugin(defaultFile().httpPath("/indexFile"))
    +            .plugin(defaultCP().httpPath("/indexWild/*"))
    +            .plugin(defaultFile().httpPath("/indexWildFile/*"))
    +            .plugin(defaultCP().httpPath("/sus/"))
    +            .plugin(defaultFile().httpPath("/susFile/*"))
    +            .plugin(StaticContentService.createCP("/logback.xml").httpPath("/single"))
    +            .plugin(
    +                StaticContentService.createFile("src/test/resources/logback.xml")
    +                    .httpPath("/singleFile"));
    +
    +    return TestPair.create(app);
    +  }
    +
    +  private static StaticContentService defaultFile() {
    +    return StaticContentService.createFile("src/test/resources/public")
    +        .directoryIndex("index.html")
    +        .preCompress();
    +  }
    +
    +  private static StaticContentService defaultCP() {
    +    return StaticContentService.createCP("/public").directoryIndex("index.html").preCompress();
    +  }
    +
    +  @AfterAll
    +  static void end() {
    +    pair.shutdown();
    +  }
    +
    +  @Test
    +  void testGet() {
    +    pair.request().path("index").GET().asString();
    +    HttpResponse res = pair.request().path("index").GET().asString();
    +    assertThat(res.statusCode()).isEqualTo(200);
    +  }
    +
    +  @Test
    +  void testTraversal() {
    +    pair.request().path("indexWild/../hmm").GET().asString();
    +    HttpResponse res = pair.request().path("indexWild/../hmm").GET().asString();
    +    assertThat(res.statusCode()).isEqualTo(400);
    +  }
    +
    +  @Test
    +  void getIndexWildCP() {
    +    pair.request().path("indexWild/").GET().asString();
    +    HttpResponse res = pair.request().path("indexWild/").GET().asString();
    +    assertThat(res.statusCode()).isEqualTo(200);
    +    assertThat(res.headers().firstValue("Content-Type").orElseThrow()).contains("html");
    +  }
    +
    +  @Test
    +  void getIndex404() {
    +    pair.request().path("index").path("index.html").GET().asString();
    +    HttpResponse res = pair.request().path("index").path("index.html").GET().asString();
    +    assertThat(res.statusCode()).isEqualTo(404);
    +  }
    +
    +  @Test
    +  void getDirContentCP() {
    +    pair.request().requestTimeout(Duration.ofHours(1)).path("sus/sus.txt").GET().asString();
    +    HttpResponse res =
    +        pair.request().requestTimeout(Duration.ofHours(1)).path("sus/sus.txt").GET().asString();
    +    assertThat(res.statusCode()).isEqualTo(200);
    +    assertThat(res.body()).contains("ඞ");
    +  }
    +
    +  @Test
    +  void getSingleFileCP() {
    +    pair.request().path("single").GET().asString();
    +    HttpResponse res = pair.request().path("single").GET().asString();
    +    assertThat(res.statusCode()).isEqualTo(200);
    +    assertThat(res.headers().firstValue("Content-Type").orElseThrow()).contains("xml");
    +  }
    +
    +  @Test
    +  void getIndexFile() {
    +    pair.request().path("indexFile").GET().asString();
    +    HttpResponse res = pair.request().path("indexFile").GET().asString();
    +    assertThat(res.statusCode()).isEqualTo(200);
    +    assertThat(res.headers().firstValue("Content-Type").orElseThrow()).contains("html");
    +  }
    +
    +  @Test
    +  void getDirContentFile() {
    +    pair.request().path("susFile/sus.txt").GET().asString();
    +    HttpResponse res = pair.request().path("susFile/sus.txt").GET().asString();
    +    assertThat(res.statusCode()).isEqualTo(200);
    +    assertThat(res.body()).contains("ඞ");
    +  }
    +
    +  @Test
    +  void getSingleResourceFile() {
    +    pair.request().path("singleFile").GET().asString();
    +    HttpResponse res = pair.request().path("singleFile").GET().asString();
    +    assertThat(res.statusCode()).isEqualTo(200);
    +    assertThat(res.headers().firstValue("Content-Type").orElseThrow()).contains("xml");
    +  }
    +
    +  @Test
    +  void getIndexWildFile() {
    +    pair.request().path("indexWildFile/").GET().asString();
    +    HttpResponse res = pair.request().path("indexWildFile/").GET().asString();
    +    assertThat(res.statusCode()).isEqualTo(200);
    +    assertThat(res.headers().firstValue("Content-Type").orElseThrow()).contains("html");
    +  }
    +
    +  @Test
    +  void testFileTraversal() {
    +    pair.request().path("indexWildFile/../traverse").GET().asString();
    +    HttpResponse res = pair.request().path("indexWildFile/../traverse").GET().asString();
    +    assertThat(res.statusCode()).isEqualTo(400);
    +  }
    +}
    diff --git a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java
    index d6833cfa..2f1cc8bf 100644
    --- a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java
    +++ b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java
    @@ -19,14 +19,14 @@ static TestPair init() {
     
         final Jex app =
             Jex.create()
    -            .routing(defaultCP().httpPath("/index"))
    -            .routing(defaultFile().httpPath("/indexFile"))
    -            .routing(defaultCP().httpPath("/indexWild/*"))
    -            .routing(defaultFile().httpPath("/indexWildFile/*"))
    -            .routing(defaultCP().httpPath("/sus/"))
    -            .routing(defaultFile().httpPath("/susFile/*"))
    -            .routing(StaticContentService.createCP("/logback.xml").httpPath("/single"))
    -            .routing(
    +            .plugin(defaultCP().httpPath("/index"))
    +            .plugin(defaultFile().httpPath("/indexFile"))
    +            .plugin(defaultCP().httpPath("/indexWild/*"))
    +            .plugin(defaultFile().httpPath("/indexWildFile/*"))
    +            .plugin(defaultCP().httpPath("/sus/"))
    +            .plugin(defaultFile().httpPath("/susFile/*"))
    +            .plugin(StaticContentService.createCP("/logback.xml").httpPath("/single"))
    +            .plugin(
                     StaticContentService.createFile("src/test/resources/logback.xml")
                         .httpPath("/singleFile"));
     
    diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java
    index 50293f50..c699026a 100644
    --- a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java
    +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java
    @@ -5,7 +5,7 @@
     import java.util.Set;
     
     /** Configuration class for compression settings. */
    -public class CompressionConfig {
    +public final class CompressionConfig {
     
       private static final int HTTP_PACKET_SIZE = 1500;
     
    
    From 5f7dc7d2b46e6d61429050f81c0127e0f933416e Mon Sep 17 00:00:00 2001
    From: Josiah Noel <32279667+SentryMan@users.noreply.github.com>
    Date: Thu, 5 Dec 2024 19:35:50 -0500
    Subject: [PATCH 117/250] Tidy HttpResponseException (#128)
    
    * Update HttpResponseException.java
    
    * Update HttpResponseException.java
    ---
     .../io/avaje/jex/http/HttpResponseException.java  | 15 +--------------
     1 file changed, 1 insertion(+), 14 deletions(-)
    
    diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java b/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java
    index 613894f2..4127cf90 100644
    --- a/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java
    +++ b/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java
    @@ -1,8 +1,5 @@
     package io.avaje.jex.http;
     
    -import java.util.Collections;
    -import java.util.Map;
    -
     /**
      * Throwing an uncaught {@code HttpResponseException} will interrupt http processing and set the
      * status code and response body with the given message
    @@ -10,23 +7,13 @@
     public class HttpResponseException extends RuntimeException {
     
       private final int status;
    -  private final Map details;
     
    -  public HttpResponseException(int status, String message, Map details) {
    +  public HttpResponseException(int status, String message) {
         super(message);
         this.status = status;
    -    this.details = details;
    -  }
    -
    -  public HttpResponseException(int status, String message) {
    -    this(status, message, Collections.emptyMap());
       }
     
       public int getStatus() {
         return status;
       }
    -
    -  public Map getDetails() {
    -    return details;
    -  }
     }
    
    From f5f288781a05176d84d2cdf05cf8c8b23c5530b9 Mon Sep 17 00:00:00 2001
    From: Josiah Noel <32279667+SentryMan@users.noreply.github.com>
    Date: Fri, 6 Dec 2024 00:14:07 -0500
    Subject: [PATCH 118/250] Remove unused attributes (#130)
    
    * remove unused attributes
    
    * Update Routing.java
    ---
     avaje-jex/src/main/java/io/avaje/jex/DJex.java | 13 -------------
     avaje-jex/src/main/java/io/avaje/jex/Jex.java  | 18 ------------------
     .../src/main/java/io/avaje/jex/Routing.java    | 12 +++++++++++-
     .../io/avaje/jex/core/SpiServiceManager.java   |  7 ++++++-
     4 files changed, 17 insertions(+), 33 deletions(-)
    
    diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java
    index d13b0696..3bf1b325 100644
    --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java
    +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java
    @@ -11,7 +11,6 @@ final class DJex implements Jex {
     
       private final Routing routing = new DefaultRouting();
       private final AppLifecycle lifecycle = new DefaultLifecycle();
    -  private final Map, Object> attributes = new HashMap<>();
       private final DJexConfig config = new DJexConfig();
     
       @Override
    @@ -19,18 +18,6 @@ public DJexConfig config() {
         return config;
       }
     
    -  @Override
    -  public  Jex attribute(Class cls, T instance) {
    -    attributes.put(cls, instance);
    -    return this;
    -  }
    -
    -  @Override
    -  @SuppressWarnings("unchecked")
    -  public  T attribute(Class cls) {
    -    return (T) attributes.get(cls);
    -  }
    -
       @Override
       public Jex routing(Routing.HttpService routes) {
         routing.add(routes);
    diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java
    index c008a987..65feb2d9 100644
    --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java
    +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java
    @@ -44,24 +44,6 @@ static Jex create() {
         return new DJex();
       }
     
    -  /**
    -   * Sets a custom attribute that can be accessed later by the Jex instance or its components.
    -   *
    -   * @param  The type of the attribute.
    -   * @param cls The class of the attribute.
    -   * @param instance The instance of the attribute.
    -   */
    -   Jex attribute(Class cls, T instance);
    -
    -  /**
    -   * Returns a custom attribute previously set using {@link #attribute(Class, Object)}.
    -   *
    -   * @param  The type of the attribute.
    -   * @param cls The class of the attribute.
    -   * @return The attribute instance, or null if not found.
    -   */
    -   T attribute(Class cls);
    -
       /**
        * Adds a new HTTP route and its associated handler to the Jex routing configuration.
        *
    diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java
    index 281ed6f6..34040de4 100644
    --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java
    +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java
    @@ -58,7 +58,17 @@ public sealed interface Routing permits DefaultRouting {
        */
        Routing error(Class exceptionClass, ExceptionHandler handler);
     
    -  /** Add a group of route handlers with a common path prefix. */
    +  /**
    +   * Add a group of route handlers with a common path prefix.
    +   *
    +   * 
    {@code
    +   * routing.path("api", () -> {
    +   *     routing.get("/", ctx -> ctx.text("apiRoot"));
    +   *     routing.get("{id}", ctx -> ctx.text("api-" + ctx.pathParam("id")));
    +   * });
    +   *
    +   * }
    + */ Routing path(String path, Group group); /** Add a HEAD handler. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java index 886f56d5..96866dd1 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java @@ -168,7 +168,12 @@ JsonService initJsonService() { if (jsonService != null) { return jsonService; } - return CoreServiceLoader.jsonService().orElseGet(this::defaultJsonService); + + var json = CoreServiceLoader.jsonService().orElseGet(this::defaultJsonService); + + if (json == null) log.log(Level.WARNING, "No Json library configured"); + + return json; } /** Create a reasonable default JsonService if Jackson or avaje-jsonb are present. */ From cd2363c221659842d1a16856c85b61111a97da9c Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 6 Dec 2024 04:31:37 -0500 Subject: [PATCH 119/250] Add Json body field to HttpResponseException (#129) * add json exception * test --- .../io/avaje/jex/core/ExceptionManager.java | 3 +++ .../avaje/jex/http/BadRequestException.java | 6 ++++- .../avaje/jex/http/HttpResponseException.java | 22 ++++++++++++++++++- .../http/InternalServerErrorException.java | 6 ++++- .../io/avaje/jex/http/NotFoundException.java | 4 ++++ .../avaje/jex/core/ExceptionManagerTest.java | 13 +++++++++++ 6 files changed, 51 insertions(+), 3 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index b734e0df..6d11cdf4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -57,8 +57,11 @@ private void unhandledException(JdkContext ctx, Exception e) { private void defaultHandling(JdkContext ctx, HttpResponseException exception) { ctx.status(exception.getStatus()); + var jsonResponse = exception.jsonResponse(); if (exception.getStatus() == ErrorCode.REDIRECT.status()) { ctx.performRedirect(); + } else if (jsonResponse != null) { + ctx.json(jsonResponse); } else if (useJson(ctx)) { ctx.contentType(APPLICATION_JSON).write(asJsonContent(exception)); } else { diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/BadRequestException.java b/avaje-jex/src/main/java/io/avaje/jex/http/BadRequestException.java index c9cc523a..b15f69be 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/BadRequestException.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/BadRequestException.java @@ -1,9 +1,13 @@ package io.avaje.jex.http; -/** Thrown when unable to find a route/resource */ +/** Thrown when request is invalid */ public class BadRequestException extends HttpResponseException { public BadRequestException(String message) { super(400, message); } + + public BadRequestException(Object jsonResponse) { + super(400, jsonResponse); + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java b/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java index 4127cf90..cd33965c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java @@ -2,18 +2,38 @@ /** * Throwing an uncaught {@code HttpResponseException} will interrupt http processing and set the - * status code and response body with the given message + * status code and response body with the given message or json body */ public class HttpResponseException extends RuntimeException { private final int status; + private final Object jsonResponse; + /** + * @param status the http status to send + * @param message the exception message that will be sent back in the response + */ public HttpResponseException(int status, String message) { super(message); this.status = status; + this.jsonResponse = null; + } + + /** + * @param status the http status to send + * @param jsonResponse the response body that will be sent back as json + */ + public HttpResponseException(int status, Object jsonResponse) { + + this.status = status; + this.jsonResponse = jsonResponse; } public int getStatus() { return status; } + + public Object jsonResponse() { + return jsonResponse; + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorException.java b/avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorException.java index 4ddc7716..c39d2aa0 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorException.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorException.java @@ -1,9 +1,13 @@ package io.avaje.jex.http; -/** Thrown when unable to find a route/resource */ +/** Thrown when server has an internal error */ public class InternalServerErrorException extends HttpResponseException { public InternalServerErrorException(String message) { super(500, message); } + + public InternalServerErrorException(Object jsonResponse) { + super(500, jsonResponse); + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/NotFoundException.java b/avaje-jex/src/main/java/io/avaje/jex/http/NotFoundException.java index 5a0caa5a..2d5f8213 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/NotFoundException.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/NotFoundException.java @@ -6,4 +6,8 @@ public class NotFoundException extends HttpResponseException { public NotFoundException(String message) { super(404, message); } + + public NotFoundException(Object jsonResponse) { + super(404, jsonResponse); + } } diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/ExceptionManagerTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ExceptionManagerTest.java index e555fe6e..3593371f 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/ExceptionManagerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ExceptionManagerTest.java @@ -1,6 +1,7 @@ package io.avaje.jex.core; import io.avaje.jex.Jex; +import io.avaje.jex.http.BadRequestException; import io.avaje.jex.http.ErrorCode; import io.avaje.jex.http.HttpResponseException; import io.avaje.jsonb.JsonException; @@ -9,6 +10,7 @@ import org.junit.jupiter.api.Test; import java.net.http.HttpResponse; +import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @@ -34,6 +36,9 @@ static TestPair init() { .put("/nested", ctx -> { throw new JsonException("hmm"); }) + .patch("/patch", ctx -> { + throw new BadRequestException(Map.of("error","bad request")); + }) .error(NullPointerException.class, (ctx, exception) -> ctx.text("npe")) .error(IllegalStateException.class, (ctx, exception) -> ctx.status(222).text("Handled IllegalStateException|" + exception.getMessage())) .error(JsonException.class, (ctx, exception) -> {throw new IllegalStateException();})); @@ -60,6 +65,14 @@ void post() { assertThat(res.body()).isEqualTo("Handled IllegalStateException|foo"); } + @Test + void patch() { + HttpResponse res = pair.request().path("patch").PATCH().asString(); + assertThat(res.statusCode()).isEqualTo(400); + assertThat(res.body()).isEqualTo("{\"error\":\"bad request\"}"); + assertThat(res.headers().firstValue("Content-Type").get()).contains("application/json"); + } + @Test void expect_fallback_to_fallback() { HttpResponse res = pair.request().path("nested").PUT().asString(); From 717789f12f1bd2f82b1042d749b7524bfd783875 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 6 Dec 2024 04:38:42 -0500 Subject: [PATCH 120/250] change group to be a consumer (#131) Co-authored-by: Rob Bygrave --- .../src/main/java/io/avaje/jex/DefaultRouting.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/Routing.java | 2 +- .../java/io/avaje/jex/core/NestedRoutesTest.java | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index 4237a12e..f3cc1968 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -46,7 +46,7 @@ private String path(String path) { private void addEndpoints(String path, Group group) { path = path.startsWith("/") ? path : "/" + path; pathDeque.addLast(path); - group.addGroup(); + group.addGroup(this); pathDeque.removeLast(); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index 34040de4..cc95b2d2 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -130,7 +130,7 @@ default Routing after(Consumer handler) { interface Group { /** Add the group of entries with a common prefix. */ - void addGroup(); + void addGroup(Routing routing); } /** Adds to the Routing. */ diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/NestedRoutesTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/NestedRoutesTest.java index b234a350..67e92230 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/NestedRoutesTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/NestedRoutesTest.java @@ -16,14 +16,14 @@ static TestPair init() { Jex app = Jex.create() .routing(routing -> routing .get("/", ctx -> ctx.text("hello")) - .path("api", () -> { - routing.get("/", ctx -> ctx.text("apiRoot")); - routing.get("{id}", ctx -> ctx.text("api-" + ctx.pathParam("id"))); + .path("api", g -> { + g.get("/", ctx -> ctx.text("apiRoot")); + g.get("{id}", ctx -> ctx.text("api-" + ctx.pathParam("id"))); }) - .path("extra", () -> { - routing.get("/", ctx -> ctx.text("extraRoot")); - routing.get("{id}", ctx -> ctx.text("extra-id-" + ctx.pathParam("id"))); - routing.get("more/{id}", ctx -> ctx.text("extraMore-" + ctx.pathParam("id"))); + .path("extra", g -> { + g.get("/", ctx -> ctx.text("extraRoot")); + g.get("{id}", ctx -> ctx.text("extra-id-" + ctx.pathParam("id"))); + g.get("more/{id}", ctx -> ctx.text("extraMore-" + ctx.pathParam("id"))); })); return TestPair.create(app); } From 94def23261338ca002699ab169c8bee3eda0c2a8 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 6 Dec 2024 04:39:15 -0500 Subject: [PATCH 121/250] Basic routing methods on `Jex` (#132) * basic routing methods on Jex * Update README.md * Update README.md --- README.md | 9 ++- .../StaticResourceHandlerBuilder.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 62 +++++++++++++++++++ .../src/main/java/io/avaje/jex/Routing.java | 2 +- 4 files changed, 68 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 26dbbfe2..7b41adaf 100644 --- a/README.md +++ b/README.md @@ -20,16 +20,15 @@ Features: ```java var app = Jex.create() - .routing(routing -> routing - .get("/", ctx -> ctx.text("hello")) - .get("/one/{id}", ctx -> ctx.text("one-" + ctx.pathParam("id"))) - .filter( + .get("/", ctx -> ctx.text("hello")) + .get("/one/{id}", ctx -> ctx.text("one-" + ctx.pathParam("id"))) + .filter( (ctx, chain) -> { System.out.println("before request"); chain.proceed(); System.out.println("after request"); }) - .error(IllegalStateException.class, (ctx, exception) -> ctx.status(500).text("Handled IllegalStateException|" + exception.getMessage())) + .error(IllegalStateException.class, (ctx, exception) -> ctx.status(500).text(exception.getMessage())) .port(8080) .start(); ``` diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java index 1ff2d334..3ad13237 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java @@ -40,7 +40,7 @@ static StaticResourceHandlerBuilder builder(String root) { @Override public void apply(Jex jex) { - jex.routing().get(path, createHandler(jex.config().compression())); + jex.get(path, createHandler(jex.config().compression())); } ExchangeHandler createHandler(CompressionConfig compress) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 65feb2d9..34b17b8c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -65,6 +65,68 @@ static Jex create() { */ Routing routing(); + /** Add a GET handler. */ + default Jex get(String path, ExchangeHandler handler) { + routing().get(path, handler); + return this; + } + + /** Add a POST handler. */ + default Jex post(String path, ExchangeHandler handler) { + routing().get(path, handler); + return this; + } + + /** Add a PUT handler. */ + default Jex put(String path, ExchangeHandler handler) { + routing().get(path, handler); + return this; + } + + /** Add a PATCH handler. */ + default Jex patch(String path, ExchangeHandler handler) { + routing().get(path, handler); + return this; + } + + /** Add a DELETE handler. */ + default Jex delete(String path, ExchangeHandler handler) { + routing().get(path, handler); + return this; + } + + /** Add a filter for all requests. */ + default Jex filter(HttpFilter handler) { + routing().filter(handler); + return this; + } + + /** Add a pre-processing filter for all requests. */ + default Jex before(Consumer handler) { + routing().before(handler); + return this; + } + + /** Add a post-processing filter for all requests. */ + default Jex after(Consumer handler) { + routing().after(handler); + return this; + } + + /** + * Registers an exception handler that handles the given type of exceptions. This will replace an + * existing error handler for the same exception class. + * + * @param exceptionClass the type of exception to handle by this handler + * @param handler the error handler + * @param exception type + */ + default Jex error( + Class exceptionClass, ExceptionHandler handler) { + routing().error(exceptionClass, handler); + return this; + } + /** * Sets the JSON service to use for serialization and deserialization. * diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index cc95b2d2..f75ad400 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -48,7 +48,7 @@ public sealed interface Routing permits DefaultRouting { Routing withRoles(Role... permittedRoles); /** - * Registers an error handler that handles the given type of exceptions. This will replace an + * Registers an exception handler that handles the given type of exceptions. This will replace an * existing error handler for the same exception class. * * @param exceptionClass the type of exception to handle by this handler From 670910cb06f88b5b10e10eef38bcb05202e1b0da Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Fri, 6 Dec 2024 23:11:16 +1300 Subject: [PATCH 122/250] Add javadoc for exceptions, rename status() method (#133) --- .../java/io/avaje/jex/core/ExceptionManager.java | 6 +++--- .../java/io/avaje/jex/http/BadRequestException.java | 2 ++ .../io/avaje/jex/http/HttpResponseException.java | 13 +++++++++---- .../jex/http/InternalServerErrorException.java | 2 ++ .../java/io/avaje/jex/http/NotFoundException.java | 2 ++ .../java/io/avaje/jex/http/RedirectException.java | 1 + 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index 6d11cdf4..04c598d5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -56,9 +56,9 @@ private void unhandledException(JdkContext ctx, Exception e) { } private void defaultHandling(JdkContext ctx, HttpResponseException exception) { - ctx.status(exception.getStatus()); + ctx.status(exception.status()); var jsonResponse = exception.jsonResponse(); - if (exception.getStatus() == ErrorCode.REDIRECT.status()) { + if (exception.status() == ErrorCode.REDIRECT.status()) { ctx.performRedirect(); } else if (jsonResponse != null) { ctx.json(jsonResponse); @@ -74,7 +74,7 @@ private String asJsonContent(HttpResponseException e) { + jsonEscape(e.getMessage()) + ", " + "\"status\": " - + e.getStatus() + + e.status() + "}"; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/BadRequestException.java b/avaje-jex/src/main/java/io/avaje/jex/http/BadRequestException.java index b15f69be..c8d688fa 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/BadRequestException.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/BadRequestException.java @@ -3,10 +3,12 @@ /** Thrown when request is invalid */ public class BadRequestException extends HttpResponseException { + /** Create with a message. */ public BadRequestException(String message) { super(400, message); } + /** Create with a response that will sent as JSON. */ public BadRequestException(Object jsonResponse) { super(400, jsonResponse); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java b/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java index cd33965c..45ed8eee 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java @@ -10,7 +10,9 @@ public class HttpResponseException extends RuntimeException { private final Object jsonResponse; /** - * @param status the http status to send + * Create with a status and message. + * + * @param status the http status to send * @param message the exception message that will be sent back in the response */ public HttpResponseException(int status, String message) { @@ -20,19 +22,22 @@ public HttpResponseException(int status, String message) { } /** - * @param status the http status to send + * Create with a status and response that will sent as JSON. + * + * @param status the http status to send * @param jsonResponse the response body that will be sent back as json */ public HttpResponseException(int status, Object jsonResponse) { - this.status = status; this.jsonResponse = jsonResponse; } - public int getStatus() { + /** Return the status code. */ + public int status() { return status; } + /** Return the response body that will sent as JSON. */ public Object jsonResponse() { return jsonResponse; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorException.java b/avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorException.java index c39d2aa0..f3f5f7e9 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorException.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/InternalServerErrorException.java @@ -3,10 +3,12 @@ /** Thrown when server has an internal error */ public class InternalServerErrorException extends HttpResponseException { + /** Create with a message. */ public InternalServerErrorException(String message) { super(500, message); } + /** Create with a status and response that will sent as JSON. */ public InternalServerErrorException(Object jsonResponse) { super(500, jsonResponse); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/NotFoundException.java b/avaje-jex/src/main/java/io/avaje/jex/http/NotFoundException.java index 2d5f8213..f23d6cba 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/NotFoundException.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/NotFoundException.java @@ -3,10 +3,12 @@ /** Thrown when unable to find a route/resource */ public class NotFoundException extends HttpResponseException { + /** Create with a message. */ public NotFoundException(String message) { super(404, message); } + /** Create with a response that will sent as JSON. */ public NotFoundException(Object jsonResponse) { super(404, jsonResponse); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/RedirectException.java b/avaje-jex/src/main/java/io/avaje/jex/http/RedirectException.java index 6bdaaefb..ae46149d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/RedirectException.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/RedirectException.java @@ -3,6 +3,7 @@ /** Thrown when redirecting */ public class RedirectException extends HttpResponseException { + /** Create with a message. */ public RedirectException(String message) { super(302, message); } From b68b4fabfaf3245c38633b2efc34d7ffbc45ec2a Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:07:37 -0500 Subject: [PATCH 123/250] Update role registration (#135) * basic routing methods on Jex * Update README.md * Update README.md * options * update roles * doc --- .../java/io/avaje/jex/DefaultRouting.java | 64 ++++------ avaje-jex/src/main/java/io/avaje/jex/Jex.java | 82 +++++++++--- .../src/main/java/io/avaje/jex/Routing.java | 117 ++++++++++-------- .../main/java/io/avaje/jex/package-info.java | 5 +- avaje-jex/src/main/java/module-info.java | 5 +- 5 files changed, 154 insertions(+), 119 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index f3cc1968..be168e8d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -19,11 +19,6 @@ final class DefaultRouting implements Routing { private final Deque pathDeque = new ArrayDeque<>(); private final Map, ExceptionHandler> exceptionHandlers = new HashMap<>(); - /** - * Last entry that we can add permitted roles to. - */ - private Entry lastEntry; - @Override public List handlers() { return handlers; @@ -76,23 +71,9 @@ public Routing path(String path, Group group) { return this; } - @Override - public Routing withRoles(Set permittedRoles) { - if (lastEntry == null) { - throw new IllegalStateException("Must call withRoles() after adding a route"); - } - lastEntry.withRoles(permittedRoles); - return this; - } - - @Override - public Routing withRoles(Role... permittedRoles) { - return withRoles(Set.of(permittedRoles)); - } - - private void add(Type verb, String path, ExchangeHandler handler) { - lastEntry = new Entry(verb, path(path), handler); - handlers.add(lastEntry); + private void add(Type verb, String path, ExchangeHandler handler, Role... roles) { + var entry = new Entry(verb, path(path), handler, Set.of(roles)); + handlers.add(entry); } // ******************************************************************************************** @@ -100,50 +81,50 @@ private void add(Type verb, String path, ExchangeHandler handler) { // ******************************************************************************************** @Override - public Routing get(String path, ExchangeHandler handler) { - add(Type.GET, path, handler); + public Routing get(String path, ExchangeHandler handler, Role... roles) { + add(Type.GET, path, handler, roles); return this; } @Override - public Routing post(String path, ExchangeHandler handler) { - add(Type.POST, path, handler); + public Routing post(String path, ExchangeHandler handler, Role... roles) { + add(Type.POST, path, handler, roles); return this; } @Override - public Routing put(String path, ExchangeHandler handler) { - add(Type.PUT, path, handler); + public Routing put(String path, ExchangeHandler handler, Role... roles) { + add(Type.PUT, path, handler, roles); return this; } @Override - public Routing patch(String path, ExchangeHandler handler) { - add(Type.PATCH, path, handler); + public Routing patch(String path, ExchangeHandler handler, Role... roles) { + add(Type.PATCH, path, handler, roles); return this; } @Override - public Routing delete(String path, ExchangeHandler handler) { - add(Type.DELETE, path, handler); + public Routing delete(String path, ExchangeHandler handler, Role... roles) { + add(Type.DELETE, path, handler, roles); return this; } @Override - public Routing head(String path, ExchangeHandler handler) { - add(Type.HEAD, path, handler); + public Routing head(String path, ExchangeHandler handler, Role... roles) { + add(Type.HEAD, path, handler, roles); return this; } @Override - public Routing trace(String path, ExchangeHandler handler) { - add(Type.TRACE, path, handler); + public Routing trace(String path, ExchangeHandler handler, Role... roles) { + add(Type.TRACE, path, handler, roles); return this; } @Override - public Routing options(String path, ExchangeHandler handler) { - add(Type.OPTIONS, path, handler); + public Routing options(String path, ExchangeHandler handler, Role... roles) { + add(Type.OPTIONS, path, handler, roles); return this; } @@ -162,15 +143,12 @@ private static final class Entry implements Routing.Entry { private final Type type; private final String path; private final ExchangeHandler handler; - private Set roles = Collections.emptySet(); + private final Set roles; - Entry(Type type, String path, ExchangeHandler handler) { + Entry(Type type, String path, ExchangeHandler handler, Set roles) { this.type = type; this.path = path; this.handler = handler; - } - - void withRoles(Set roles) { this.roles = roles; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 34b17b8c..af0afb6a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -4,6 +4,7 @@ import java.util.function.Consumer; import io.avaje.inject.BeanScope; +import io.avaje.jex.security.Role; import io.avaje.jex.spi.JexPlugin; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; @@ -65,49 +66,91 @@ static Jex create() { */ Routing routing(); - /** Add a GET handler. */ - default Jex get(String path, ExchangeHandler handler) { - routing().get(path, handler); + /** + * Adds a GET handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The handler to invoke when a GET request matches the path. + * @param roles An array of roles that are associated with this endpoint. + */ + default Jex get(String path, ExchangeHandler handler, Role... roles) { + routing().get(path, handler, roles); return this; } - /** Add a POST handler. */ - default Jex post(String path, ExchangeHandler handler) { - routing().get(path, handler); + /** + * Adds a POST handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The handler to invoke when a POST request matches the path. + * @param roles An array of roles that are associated with this endpoint. + */ + default Jex post(String path, ExchangeHandler handler, Role... roles) { + routing().get(path, handler, roles); return this; } - /** Add a PUT handler. */ - default Jex put(String path, ExchangeHandler handler) { - routing().get(path, handler); + /** + * Adds a PUT handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The handler to invoke when a PUT request matches the path. + * @param roles An array of roles that are associated with this endpoint. + */ + default Jex put(String path, ExchangeHandler handler, Role... roles) { + routing().get(path, handler, roles); return this; } - /** Add a PATCH handler. */ - default Jex patch(String path, ExchangeHandler handler) { - routing().get(path, handler); + /** + * Adds a PATCH handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The handler to invoke when a PATCH request matches the path. + * @param roles An array of roles that are associated with this endpoint. + */ + default Jex patch(String path, ExchangeHandler handler, Role... roles) { + routing().get(path, handler, roles); + return this; + } + + /** + * Adds a DELETE handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The handler to invoke when a DELETE request matches the path. + * @param roles An array of roles that are associated with this endpoint. + */ + default Jex delete(String path, ExchangeHandler handler, Role... roles) { + routing().get(path, handler, roles); return this; } - /** Add a DELETE handler. */ - default Jex delete(String path, ExchangeHandler handler) { - routing().get(path, handler); + /** + * Adds an OPTIONS handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The handler to invoke when an OPTIONS request matches the path. + * @param roles An array of roles that are associated with this endpoint. + */ + default Jex options(String path, ExchangeHandler handler, Role... roles) { + routing().options(path, handler, roles); return this; } - /** Add a filter for all requests. */ + /** Add a filter for all matched requests. */ default Jex filter(HttpFilter handler) { routing().filter(handler); return this; } - /** Add a pre-processing filter for all requests. */ + /** Add a pre-processing filter for all matched requests. */ default Jex before(Consumer handler) { routing().before(handler); return this; } - /** Add a post-processing filter for all requests. */ + /** Add a post-processing filter for all matched requests. */ default Jex after(Consumer handler) { routing().after(handler); return this; @@ -121,8 +164,7 @@ default Jex after(Consumer handler) { * @param handler the error handler * @param exception type */ - default Jex error( - Class exceptionClass, ExceptionHandler handler) { + default Jex error(Class exceptionClass, ExceptionHandler handler) { routing().error(exceptionClass, handler); return this; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index f75ad400..1e54dff3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -17,36 +17,6 @@ public sealed interface Routing permits DefaultRouting { /** Add all the routes provided by the Routing Services. */ Routing addAll(Collection routes); - /** - * Specify permittedRoles for the last added handler. - * - *
    {@code
    -   * routing
    -   * .get("/customers", getHandler).withRoles(readRoles)
    -   * .post("/customers", postHandler).withRoles(writeRoles)
    -   * ...
    -   *
    -   * }
    - * - * @param permittedRoles The permitted roles required for the last handler - */ - Routing withRoles(Set permittedRoles); - - /** - * Specify permittedRoles for the last added handler using varargs. - * - *
    {@code
    -   * routing
    -   * .get("/customers", getHandler).withRoles(ADMIN, USER)
    -   * .post("/customers", postHandler).withRoles(ADMIN)
    -   * ...
    -   *
    -   * }
    - * - * @param permittedRoles The permitted roles required for the last handler - */ - Routing withRoles(Role... permittedRoles); - /** * Registers an exception handler that handles the given type of exceptions. This will replace an * existing error handler for the same exception class. @@ -54,7 +24,6 @@ public sealed interface Routing permits DefaultRouting { * @param exceptionClass the type of exception to handle by this handler * @param handler the error handler * @param exception type - * @return updated routing */ Routing error(Class exceptionClass, ExceptionHandler handler); @@ -71,34 +40,82 @@ public sealed interface Routing permits DefaultRouting { */ Routing path(String path, Group group); - /** Add a HEAD handler. */ - Routing head(String path, ExchangeHandler handler); + /** + * Adds a HEAD handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The handler to invoke when a HEAD request matches the path. + * @param roles roles that are associated with this endpoint. + */ + Routing head(String path, ExchangeHandler handler, Role... roles); - /** Add a GET handler. */ - Routing get(String path, ExchangeHandler handler); + /** + * Adds a GET handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The handler to invoke when a GET request matches the path. + * @param roles roles that are associated with this endpoint. + */ + Routing get(String path, ExchangeHandler handler, Role... roles); - /** Add a POST handler. */ - Routing post(String path, ExchangeHandler handler); + /** + * Adds a POST handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The handler to invoke when a POST request matches the path. + * @param roles roles that are associated with this endpoint. + */ + Routing post(String path, ExchangeHandler handler, Role... roles); - /** Add a PUT handler. */ - Routing put(String path, ExchangeHandler handler); + /** + * Adds a PUT handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The handler to invoke when a PUT request matches the path. + * @param roles roles that are associated with this endpoint. + */ + Routing put(String path, ExchangeHandler handler, Role... roles); - /** Add a PATCH handler. */ - Routing patch(String path, ExchangeHandler handler); + /** + * Adds a PATCH handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The handler to invoke when a PATCH request matches the path. + * @param roles roles that are associated with this endpoint. + */ + Routing patch(String path, ExchangeHandler handler, Role... roles); - /** Add a DELETE handler. */ - Routing delete(String path, ExchangeHandler handler); + /** + * Adds a DELETE handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The handler to invoke when a DELETE request matches the path. + * @param roles roles that are associated with this endpoint. + */ + Routing delete(String path, ExchangeHandler handler, Role... roles); - /** Add a TRACE handler. */ - Routing trace(String path, ExchangeHandler handler); + /** + * Adds a TRACE handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The handler to invoke when a TRACE request matches the path. + * @param roles roles that are associated with this endpoint. + */ + Routing trace(String path, ExchangeHandler handler, Role... roles); - /** Add an OPTIONS handler. */ - Routing options(String path, ExchangeHandler handler); + /** + * Adds an OPTIONS handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The handler to invoke when an OPTIONS request matches the path. + * @param roles roles that are associated with this endpoint. + */ + Routing options(String path, ExchangeHandler handler, Role... roles); - /** Add a filter for all requests. */ + /** Add a filter for all matched requests. */ Routing filter(HttpFilter handler); - /** Add a pre-processing filter for all requests. */ + /** Add a pre-processing filter for all matched requests. */ default Routing before(Consumer handler) { return filter( (ctx, chain) -> { @@ -107,7 +124,7 @@ default Routing before(Consumer handler) { }); } - /** Add a post-processing filter for all requests. */ + /** Add a post-processing filter for all matched requests. */ default Routing after(Consumer handler) { return filter( (ctx, chain) -> { diff --git a/avaje-jex/src/main/java/io/avaje/jex/package-info.java b/avaje-jex/src/main/java/io/avaje/jex/package-info.java index 2d8b21c2..fd9e77f4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/package-info.java +++ b/avaje-jex/src/main/java/io/avaje/jex/package-info.java @@ -3,9 +3,8 @@ * *
    {@code
      * final Jex.Server app = Jex.create()
    - *   .routing(routing -> routing
    - *     .get("/", ctx -> ctx.text("hello world"))
    - *     .get("/one", ctx -> ctx.text("one"))
    + *   .get("/", ctx -> ctx.text("hello world"))
    + *   .get("/one", ctx -> ctx.text("one"))
      *   .port(8080)
      *   .start();
      *
    diff --git a/avaje-jex/src/main/java/module-info.java b/avaje-jex/src/main/java/module-info.java
    index ed6f9a15..52e36574 100644
    --- a/avaje-jex/src/main/java/module-info.java
    +++ b/avaje-jex/src/main/java/module-info.java
    @@ -5,9 +5,8 @@
      *
      * 
    {@code
      * final Jex.Server app = Jex.create()
    - *   .routing(routing -> routing
    - *     .get("/", ctx -> ctx.text("hello world"))
    - *     .get("/one", ctx -> ctx.text("one"))
    + *   .get("/", ctx -> ctx.text("hello world"))
    + *   .get("/one", ctx -> ctx.text("one"))
      *   .port(8080)
      *   .start();
      *
    
    From 20c450400e18bfe7f44c00a6872126e992d7d756 Mon Sep 17 00:00:00 2001
    From: Rob Bygrave 
    Date: Sat, 7 Dec 2024 05:08:40 +1300
    Subject: [PATCH 124/250] Rename StaticContent and add Builder interface (#134)
    
    ---
     .../jex/staticcontent/StaticContent.java      | 127 ++++++++++++++++++
     .../staticcontent/StaticContentService.java   | 100 --------------
     .../StaticResourceHandlerBuilder.java         |   7 +-
     .../avaje/jex/staticcontent/package-info.java |   2 +-
     .../src/main/java/module-info.java            |   2 +-
     .../CompressedStaticFileTest.java             |  28 ++--
     .../jex/staticcontent/StaticFileTest.java     |  26 ++--
     avaje-jex/src/main/java/io/avaje/jex/Jex.java |   1 +
     8 files changed, 164 insertions(+), 129 deletions(-)
     create mode 100644 avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java
     delete mode 100644 avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java
    
    diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java
    new file mode 100644
    index 00000000..d3baf45f
    --- /dev/null
    +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java
    @@ -0,0 +1,127 @@
    +package io.avaje.jex.staticcontent;
    +
    +import java.net.URLConnection;
    +import java.util.function.Predicate;
    +
    +import io.avaje.jex.Context;
    +import io.avaje.jex.spi.JexPlugin;
    +
    +/**
    + * Static content resource handler.
    + * 
    {@code
    + *
    + *  var staticContent = StaticContent.createFile("src/test/resources/public")
    + *     .directoryIndex("index.html")
    + *     .preCompress()
    + *     .build()
    + *
    + *  Jex.create()
    + *    .plugin(staticContent)
    + *    .port(8080)
    + *    .start();
    + *
    + * }
    + */ +public sealed interface StaticContent extends JexPlugin + permits StaticResourceHandlerBuilder { + + /** + * Create and return a new static content class path configuration. + * + * @param resourceRoot The file to serve, or the directory the files are located in. + */ + static Builder createCP(String resourceRoot) { + return StaticResourceHandlerBuilder.builder(resourceRoot); + } + + /** + * Create and return a new static content class path configuration with the + * `/public` directory as the root. + */ + static Builder createCP() { + return StaticResourceHandlerBuilder.builder("/public/"); + } + + /** + * Create and return a new static content configuration for a File. + * + * @param resourceRoot The path of the file to serve, or the directory the files are located in. + */ + static Builder createFile(String resourceRoot) { + return StaticResourceHandlerBuilder.builder(resourceRoot).file(); + } + + /** + * Builder for StaticContent. + */ + sealed interface Builder + permits StaticResourceHandlerBuilder { + + /** + * Sets the HTTP path for the static resource handler. + * + * @param path the HTTP path prefix + * @return the updated configuration + */ + Builder httpPath(String path); + + /** + * Sets the index file to be served when a directory is requests. + * + * @param directoryIndex the index file + * @return the updated configuration + */ + Builder directoryIndex(String directoryIndex); + + /** + * Sent resources will be pre-compressed and cached in memory when this is enabled + * + * @return the updated configuration + */ + Builder preCompress(); + + /** + * Sets a custom resource loader for loading class/module path resources. This is normally used + * when running the application on the module path when files cannot be discovered. + * + *

    Example usage: {@code service.resourceLoader(ClassResourceLoader.create(getClass())) } + * + * @param resourceLoader the custom resource loader + * @return the updated configuration + */ + Builder resourceLoader(ClassResourceLoader resourceLoader); + + /** + * Adds a new MIME type mapping to the configuration. (Default: uses {@link + * URLConnection#getFileNameMap()} + * + * @param ext the file extension (e.g., "html", "css", "js") + * @param mimeType the corresponding MIME type (e.g., "text/html", "text/css", + * "application/javascript") + * @return the updated configuration + */ + Builder putMimeTypeMapping(String ext, String mimeType); + + /** + * Adds a new response header to the configuration. + * + * @param key the header name + * @param value the header value + * @return the updated configuration + */ + Builder putResponseHeader(String key, String value); + + /** + * Sets a predicate to filter files based on the request context. + * + * @param skipFilePredicate the predicate to use + * @return the updated configuration + */ + Builder skipFilePredicate(Predicate skipFilePredicate); + + /** + * Build and return the StaticContent. + */ + StaticContent build(); + } +} diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java deleted file mode 100644 index 0008588d..00000000 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContentService.java +++ /dev/null @@ -1,100 +0,0 @@ -package io.avaje.jex.staticcontent; - -import java.net.URLConnection; -import java.util.function.Predicate; - -import io.avaje.jex.Context; -import io.avaje.jex.spi.JexPlugin; - -/** Builder for a static resource exchange handler. */ -public sealed interface StaticContentService extends JexPlugin - permits StaticResourceHandlerBuilder { - - /** - * Create and return a new static class path content configuration. - * - * @param resourceRoot The file to serve, or the directory the files are located in. - */ - static StaticContentService createCP(String resourceRoot) { - return StaticResourceHandlerBuilder.builder(resourceRoot); - } - - /** - * Create and return a new static class path content configuration with the `/public` directory as - * the root. - */ - static StaticContentService createCP() { - return StaticResourceHandlerBuilder.builder("/public/"); - } - - /** - * Create and return a new static content configuration for a File. - * - * @param resourceRoot The path of the file to serve, or the directory the files are located in. - */ - static StaticContentService createFile(String resourceRoot) { - return StaticResourceHandlerBuilder.builder(resourceRoot).file(); - } - - /** - * Sets the HTTP path for the static resource handler. - * - * @param path the HTTP path prefix - * @return the updated configuration - */ - StaticContentService httpPath(String path); - - /** - * Sets the index file to be served when a directory is requests. - * - * @param directoryIndex the index file - * @return the updated configuration - */ - StaticContentService directoryIndex(String directoryIndex); - - /** - * Sent resources will be pre-compressed and cached in memory when this is enabled - * - * @return the updated configuration - */ - StaticContentService preCompress(); - - /** - * Sets a custom resource loader for loading class/module path resources. This is normally used - * when running the application on the module path when files cannot be discovered. - * - *

    Example usage: {@code service.resourceLoader(ClassResourceLoader.create(getClass())) } - * - * @param resourceLoader the custom resource loader - * @return the updated configuration - */ - StaticContentService resourceLoader(ClassResourceLoader resourceLoader); - - /** - * Adds a new MIME type mapping to the configuration. (Default: uses {@link - * URLConnection#getFileNameMap()} - * - * @param ext the file extension (e.g., "html", "css", "js") - * @param mimeType the corresponding MIME type (e.g., "text/html", "text/css", - * "application/javascript") - * @return the updated configuration - */ - StaticContentService putMimeTypeMapping(String ext, String mimeType); - - /** - * Adds a new response header to the configuration. - * - * @param key the header name - * @param value the header value - * @return the updated configuration - */ - StaticContentService putResponseHeader(String key, String value); - - /** - * Sets a predicate to filter files based on the request context. - * - * @param skipFilePredicate the predicate to use - * @return the updated configuration - */ - StaticContentService skipFilePredicate(Predicate skipFilePredicate); -} diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java index 3ad13237..2546758b 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java @@ -12,7 +12,7 @@ import io.avaje.jex.Jex; import io.avaje.jex.compression.CompressionConfig; -final class StaticResourceHandlerBuilder implements StaticContentService { +final class StaticResourceHandlerBuilder implements StaticContent.Builder, StaticContent { private static final String FAILED_TO_LOCATE_FILE = "Failed to locate file: "; private static final String DIRECTORY_INDEX_FAILURE = @@ -43,6 +43,11 @@ public void apply(Jex jex) { jex.get(path, createHandler(jex.config().compression())); } + @Override + public StaticContent build() { + return this; + } + ExchangeHandler createHandler(CompressionConfig compress) { path = Objects.requireNonNull(path) diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java index 0aa9f708..16ad428f 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/package-info.java @@ -1,5 +1,5 @@ /** - * Static Content API - see {@link io.avaje.jex.staticcontent.StaticContentService}. + * Static Content API - see {@link io.avaje.jex.staticcontent.StaticContent}. * *

    {@code
      * var staticContent = StaticContentService.createCP("/public").httpPath("/").directoryIndex("index.html");
    diff --git a/avaje-jex-static-content/src/main/java/module-info.java b/avaje-jex-static-content/src/main/java/module-info.java
    index c2ff0def..e50bd8ed 100644
    --- a/avaje-jex-static-content/src/main/java/module-info.java
    +++ b/avaje-jex-static-content/src/main/java/module-info.java
    @@ -1,5 +1,5 @@
     /**
    - * Defines the Static Content API for serving static resources with Jex - see {@link io.avaje.jex.staticcontent.StaticContentService}.
    + * Defines the Static Content API for serving static resources with Jex - see {@link StaticContent}.
      *
      * 
    {@code
      * var staticContent = StaticContentService.createCP("/public").httpPath("/").directoryIndex("index.html");
    diff --git a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java
    index 15a4b263..86ab80b7 100644
    --- a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java
    +++ b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java
    @@ -19,28 +19,30 @@ static TestPair init() {
     
         final Jex app =
             Jex.create()
    -            .plugin(defaultCP().httpPath("/index"))
    -            .plugin(defaultFile().httpPath("/indexFile"))
    -            .plugin(defaultCP().httpPath("/indexWild/*"))
    -            .plugin(defaultFile().httpPath("/indexWildFile/*"))
    -            .plugin(defaultCP().httpPath("/sus/"))
    -            .plugin(defaultFile().httpPath("/susFile/*"))
    -            .plugin(StaticContentService.createCP("/logback.xml").httpPath("/single"))
    +            .plugin(defaultCP().httpPath("/index").build())
    +            .plugin(defaultFile().httpPath("/indexFile").build())
    +            .plugin(defaultCP().httpPath("/indexWild/*").build())
    +            .plugin(defaultFile().httpPath("/indexWildFile/*").build())
    +            .plugin(defaultCP().httpPath("/sus/").build())
    +            .plugin(defaultFile().httpPath("/susFile/*").build())
    +            .plugin(StaticContent.createCP("/logback.xml").httpPath("/single").build())
                 .plugin(
    -                StaticContentService.createFile("src/test/resources/logback.xml")
    -                    .httpPath("/singleFile"));
    +                StaticContent.createFile("src/test/resources/logback.xml")
    +                    .httpPath("/singleFile").build());
     
         return TestPair.create(app);
       }
     
    -  private static StaticContentService defaultFile() {
    -    return StaticContentService.createFile("src/test/resources/public")
    +  private static StaticContent.Builder defaultFile() {
    +    return StaticContent.createFile("src/test/resources/public")
             .directoryIndex("index.html")
             .preCompress();
       }
     
    -  private static StaticContentService defaultCP() {
    -    return StaticContentService.createCP("/public").directoryIndex("index.html").preCompress();
    +  private static StaticContent.Builder defaultCP() {
    +    return StaticContent.createCP("/public")
    +      .directoryIndex("index.html")
    +      .preCompress();
       }
     
       @AfterAll
    diff --git a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java
    index 2f1cc8bf..f7290eb7 100644
    --- a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java
    +++ b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java
    @@ -19,27 +19,27 @@ static TestPair init() {
     
         final Jex app =
             Jex.create()
    -            .plugin(defaultCP().httpPath("/index"))
    -            .plugin(defaultFile().httpPath("/indexFile"))
    -            .plugin(defaultCP().httpPath("/indexWild/*"))
    -            .plugin(defaultFile().httpPath("/indexWildFile/*"))
    -            .plugin(defaultCP().httpPath("/sus/"))
    -            .plugin(defaultFile().httpPath("/susFile/*"))
    -            .plugin(StaticContentService.createCP("/logback.xml").httpPath("/single"))
    +            .plugin(defaultCP().httpPath("/index").build())
    +            .plugin(defaultFile().httpPath("/indexFile").build())
    +            .plugin(defaultCP().httpPath("/indexWild/*").build())
    +            .plugin(defaultFile().httpPath("/indexWildFile/*").build())
    +            .plugin(defaultCP().httpPath("/sus/").build())
    +            .plugin(defaultFile().httpPath("/susFile/*").build())
    +            .plugin(StaticContent.createCP("/logback.xml").httpPath("/single").build())
                 .plugin(
    -                StaticContentService.createFile("src/test/resources/logback.xml")
    -                    .httpPath("/singleFile"));
    +                StaticContent.createFile("src/test/resources/logback.xml")
    +                    .httpPath("/singleFile").build());
     
         return TestPair.create(app);
       }
     
    -  private static StaticContentService defaultFile() {
    -    return StaticContentService.createFile("src/test/resources/public")
    +  private static StaticContent.Builder defaultFile() {
    +    return StaticContent.createFile("src/test/resources/public")
             .directoryIndex("index.html");
       }
     
    -  private static StaticContentService defaultCP() {
    -    return StaticContentService.createCP("/public").directoryIndex("index.html");
    +  private static StaticContent.Builder defaultCP() {
    +    return StaticContent.createCP("/public").directoryIndex("index.html");
       }
     
       @AfterAll
    diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java
    index af0afb6a..d768e0d7 100644
    --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java
    +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java
    @@ -30,6 +30,7 @@ public sealed interface Jex permits DJex {
        * Create Jex.
        *
        * 
    {@code
    +   *
        * final Jex.Server app = Jex.create()
        *   .routing(routing -> routing
        *     .get("/", ctx -> ctx.text("hello world"))
    
    From f331fd47ae5413a50b796c66698c3ecb32c4359e Mon Sep 17 00:00:00 2001
    From: Josiah Noel <32279667+SentryMan@users.noreply.github.com>
    Date: Fri, 6 Dec 2024 12:06:53 -0500
    Subject: [PATCH 125/250] Update README.md
    
    ---
     README.md | 34 ++++++++++++++++++----------------
     1 file changed, 18 insertions(+), 16 deletions(-)
    
    diff --git a/README.md b/README.md
    index 7b41adaf..a496fc9d 100644
    --- a/README.md
    +++ b/README.md
    @@ -19,18 +19,20 @@ Features:
     - Virtual threads enabled by default
     
     ```java
    -var app = Jex.create()
    -  .get("/", ctx -> ctx.text("hello"))
    -  .get("/one/{id}", ctx -> ctx.text("one-" + ctx.pathParam("id")))
    -  .filter(
    -        (ctx, chain) -> {
    -          System.out.println("before request");
    -          chain.proceed();
    -          System.out.println("after request");
    -        })
    -  .error(IllegalStateException.class, (ctx, exception) -> ctx.status(500).text(exception.getMessage()))
    -  .port(8080)
    -  .start();
    +    Jex.create()
    +        .get("/", ctx -> ctx.text("hello"))
    +        .get("/one/{id}", ctx -> ctx.text("one-" + ctx.pathParam("id")))
    +        .filter(
    +            (ctx, chain) -> {
    +              System.out.println("before request");
    +              chain.proceed();
    +              System.out.println("after request");
    +            })
    +        .error(
    +            IllegalStateException.class,
    +            (ctx, exception) -> ctx.status(500).text(exception.getMessage()))
    +        .port(8080)
    +        .start();
     ```
     
     ## Alternate `HttpServer` Implementations
    @@ -43,7 +45,7 @@ An example would be [@robaho's implementation](https://github.com/robaho/httpser
     
       io.avaje
       avaje-jex
    -  3.0-RC8
    +  ${jex.version}
     
     
     
    @@ -62,20 +64,20 @@ If you find yourself pining for the JAX-RS style of controllers, you can have av
     
       io.avaje
       avaje-jex
    -  3.0-RC8
    +  ${jex.version}
     
     
     
       io.avaje
       avaje-http-api
    -  2.9-RC4
    +  ${avaje.http.version}
     
     
     
     
       io.avaje
       avaje-http-jex-generator
    -  2.9-RC4
    +  ${avaje.http.version}
       provided
       true
     
    
    From 38e1616c72cbfe8a2631d287695cfcf7054f271c Mon Sep 17 00:00:00 2001
    From: Josiah Noel <32279667+SentryMan@users.noreply.github.com>
    Date: Fri, 6 Dec 2024 12:19:59 -0500
    Subject: [PATCH 126/250] rename path (#136)
    
    ---
     .../java/io/avaje/jex/DefaultRouting.java     |  7 +++----
     avaje-jex/src/main/java/io/avaje/jex/Jex.java | 21 +++++++++++++++++++
     .../src/main/java/io/avaje/jex/Routing.java   | 20 +++++++-----------
     .../io/avaje/jex/core/NestedRoutesTest.java   |  4 ++--
     4 files changed, 34 insertions(+), 18 deletions(-)
    
    diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java
    index be168e8d..f2cb1561 100644
    --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java
    +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java
    @@ -3,7 +3,6 @@
     import java.util.ArrayDeque;
     import java.util.ArrayList;
     import java.util.Collection;
    -import java.util.Collections;
     import java.util.Deque;
     import java.util.HashMap;
     import java.util.List;
    @@ -38,10 +37,10 @@ private String path(String path) {
         return String.join("", pathDeque) + ((path.startsWith("/") || path.isEmpty()) ? path : "/" + path);
       }
     
    -  private void addEndpoints(String path, Group group) {
    +  private void addEndpoints(String path, HttpService group) {
         path = path.startsWith("/") ? path : "/" + path;
         pathDeque.addLast(path);
    -    group.addGroup(this);
    +    group.add(this);
         pathDeque.removeLast();
       }
     
    @@ -66,7 +65,7 @@ public  Routing error(Class type, ExceptionHandler ha
       }
     
       @Override
    -  public Routing path(String path, Group group) {
    +  public Routing group(String path, HttpService group) {
         addEndpoints(path, group);
         return this;
       }
    diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java
    index d768e0d7..a7d2a870 100644
    --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java
    +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java
    @@ -4,6 +4,7 @@
     import java.util.function.Consumer;
     
     import io.avaje.inject.BeanScope;
    +import io.avaje.jex.Routing.HttpService;
     import io.avaje.jex.security.Role;
     import io.avaje.jex.spi.JexPlugin;
     import io.avaje.jex.spi.JsonService;
    @@ -170,6 +171,26 @@ default  Jex error(Class exceptionClass, ExceptionHandle
         return this;
       }
     
    +  /**
    +   * Add a group of route handlers with a common path prefix.
    +   *
    +   * 
    {@code
    +   * routing.path("api", g -> {
    +   *     g.get("/", ctx -> ctx.text("apiRoot"));
    +   *     g.get("{id}", ctx -> ctx.text("api-" + ctx.pathParam("id")));
    +   * });
    +   *
    +   * }
    + * + * @param path the common path prefix + * @param group the function to register the rout handlers + * + */ + default Jex group(String path, HttpService group) { + routing().group(path, group); + return this; + } + /** * Sets the JSON service to use for serialization and deserialization. * diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index 1e54dff3..ae9aaa20 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -31,14 +31,18 @@ public sealed interface Routing permits DefaultRouting { * Add a group of route handlers with a common path prefix. * *
    {@code
    -   * routing.path("api", () -> {
    -   *     routing.get("/", ctx -> ctx.text("apiRoot"));
    -   *     routing.get("{id}", ctx -> ctx.text("api-" + ctx.pathParam("id")));
    +   * routing.path("api", g -> {
    +   *     g.get("/", ctx -> ctx.text("apiRoot"));
    +   *     g.get("{id}", ctx -> ctx.text("api-" + ctx.pathParam("id")));
        * });
        *
        * }
    + * + * @param path the common path prefix + * @param group the function to register the rout handlers + * */ - Routing path(String path, Group group); + Routing group(String path, HttpService group); /** * Adds a HEAD handler to the route configuration. @@ -142,14 +146,6 @@ default Routing after(Consumer handler) { /** Return all the registered Exception Handlers. */ Map, ExceptionHandler> errorHandlers(); - /** A group of routing entries prefixed by a common path. */ - @FunctionalInterface - interface Group { - - /** Add the group of entries with a common prefix. */ - void addGroup(Routing routing); - } - /** Adds to the Routing. */ @FunctionalInterface interface HttpService { diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/NestedRoutesTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/NestedRoutesTest.java index 67e92230..ef183a8c 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/NestedRoutesTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/NestedRoutesTest.java @@ -16,11 +16,11 @@ static TestPair init() { Jex app = Jex.create() .routing(routing -> routing .get("/", ctx -> ctx.text("hello")) - .path("api", g -> { + .group("api", g -> { g.get("/", ctx -> ctx.text("apiRoot")); g.get("{id}", ctx -> ctx.text("api-" + ctx.pathParam("id"))); }) - .path("extra", g -> { + .group("extra", g -> { g.get("/", ctx -> ctx.text("extraRoot")); g.get("{id}", ctx -> ctx.text("extra-id-" + ctx.pathParam("id"))); g.get("more/{id}", ctx -> ctx.text("extraMore-" + ctx.pathParam("id"))); From de7a535e797745e4a4c8ffe2114e16b53cb05d9a Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:31:01 -0500 Subject: [PATCH 127/250] make jsonWriteStream a default method (#137) it honestly feels kind of niche --- avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java index b7ef9e1b..db9119e6 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java @@ -42,5 +42,8 @@ public non-sealed interface JsonService extends JexExtension { * @param iterator the stream of objects to be serialized * @param os the output stream to write the JSON-Stream data to */ - void jsonWriteStream(Iterator iterator, OutputStream os); + default void jsonWriteStream(Iterator iterator, OutputStream os) { + + throw new UnsupportedOperationException("Not Implemented"); + } } From c0695f4ac6732f858285c9fe3fe2db56bd01ac5c Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:11:42 -0500 Subject: [PATCH 128/250] Support generics in json (#138) * Support generics in json - rename jsonService methods - use java.lang.reflect.Type for deserialization * test --- .../src/main/java/io/avaje/jex/Context.java | 12 ++++- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 6 +-- .../java/io/avaje/jex/core/JdkContext.java | 11 +++-- .../io/avaje/jex/core/SpiServiceManager.java | 17 +++---- .../jex/core/json/JacksonJsonService.java | 10 ++-- .../avaje/jex/core/json/JsonbJsonService.java | 9 ++-- .../main/java/io/avaje/jex/spi/JexPlugin.java | 2 +- .../java/io/avaje/jex/spi/JsonService.java | 29 ++++++------ .../java/io/avaje/jex/spi/TemplateRender.java | 2 +- .../test/java/io/avaje/jex/core/JsonTest.java | 47 ++++++++++++++----- 10 files changed, 92 insertions(+), 53 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index aa79ac60..cd990ae3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -5,6 +5,7 @@ import java.io.InputStream; import java.io.OutputStream; +import java.lang.reflect.Type; import java.time.Duration; import java.time.ZonedDateTime; import java.util.Iterator; @@ -141,7 +142,16 @@ public interface Context { * * @param beanType The bean type */ - T bodyAsClass(Class beanType); + default T bodyAsClass(Class beanType) { + return bodyAsType(beanType); + } + + /*** + * Return the request body as bean. + * + * @param beanType The bean type + */ + T bodyAsType(Type beanType); /** Return the request body as String. */ String body(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index a7d2a870..952b3a8f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -88,7 +88,7 @@ default Jex get(String path, ExchangeHandler handler, Role... roles) { * @param roles An array of roles that are associated with this endpoint. */ default Jex post(String path, ExchangeHandler handler, Role... roles) { - routing().get(path, handler, roles); + routing().post(path, handler, roles); return this; } @@ -112,7 +112,7 @@ default Jex put(String path, ExchangeHandler handler, Role... roles) { * @param roles An array of roles that are associated with this endpoint. */ default Jex patch(String path, ExchangeHandler handler, Role... roles) { - routing().get(path, handler, roles); + routing().patch(path, handler, roles); return this; } @@ -124,7 +124,7 @@ default Jex patch(String path, ExchangeHandler handler, Role... roles) { * @param roles An array of roles that are associated with this endpoint. */ default Jex delete(String path, ExchangeHandler handler, Role... roles) { - routing().get(path, handler, roles); + routing().delete(path, handler, roles); return this; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index c0de6b0b..03065e43 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -11,6 +11,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.UncheckedIOException; +import java.lang.reflect.Type; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.charset.Charset; @@ -180,8 +181,8 @@ public void performRedirect() { } @Override - public T bodyAsClass(Class beanType) { - return mgr.jsonRead(beanType, bodyAsInputStream()); + public T bodyAsType(Type beanType) { + return mgr.fromJson(beanType, bodyAsInputStream()); } @Override @@ -328,19 +329,19 @@ public int status() { @Override public void json(Object bean) { contentType(APPLICATION_JSON); - mgr.jsonWrite(bean, outputStream()); + mgr.toJson(bean, outputStream()); } @Override public void jsonStream(Stream stream) { contentType(APPLICATION_X_JSON_STREAM); - mgr.jsonWriteStream(stream, outputStream()); + mgr.toJsonStream(stream, outputStream()); } @Override public void jsonStream(Iterator iterator) { contentType(APPLICATION_X_JSON_STREAM); - mgr.jsonWriteStream(iterator, outputStream()); + mgr.toJsonStream(iterator, outputStream()); } @Override diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java index 96866dd1..1111f34c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java @@ -5,6 +5,7 @@ import java.io.UncheckedIOException; import java.io.UnsupportedEncodingException; import java.lang.System.Logger.Level; +import java.lang.reflect.Type; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collections; @@ -56,23 +57,23 @@ OutputStream createOutputStream(JdkContext jdkContext) { return new BufferedOutStream(jdkContext); } - public T jsonRead(Class clazz, InputStream is) { - return jsonService.jsonRead(clazz, is); + public T fromJson(Type type, InputStream is) { + return jsonService.fromJson(type, is); } - public void jsonWrite(Object bean, OutputStream os) { - jsonService.jsonWrite(bean, os); + public void toJson(Object bean, OutputStream os) { + jsonService.toJson(bean, os); } - public void jsonWriteStream(Stream stream, OutputStream os) { + public void toJsonStream(Stream stream, OutputStream os) { try (stream) { - jsonService.jsonWriteStream(stream.iterator(), os); + jsonService.toJsonStream(stream.iterator(), os); } } - public void jsonWriteStream(Iterator iterator, OutputStream os) { + public void toJsonStream(Iterator iterator, OutputStream os) { try { - jsonService.jsonWriteStream(iterator, os); + jsonService.toJsonStream(iterator, os); } finally { maybeClose(iterator); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java index 4511b928..886f394d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java @@ -4,6 +4,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.UncheckedIOException; +import java.lang.reflect.Type; import java.util.Iterator; import com.fasterxml.jackson.core.JsonGenerator; @@ -29,16 +30,17 @@ public JacksonJsonService(ObjectMapper mapper) { } @Override - public T jsonRead(Class clazz, InputStream is) { + public T fromJson(Type type, InputStream is) { try { - return mapper.readValue(is, clazz); + var javaType = mapper.getTypeFactory().constructType(type); + return mapper.readValue(is, javaType); } catch (IOException e) { throw new UncheckedIOException(e); } } @Override - public void jsonWrite(Object bean, OutputStream os) { + public void toJson(Object bean, OutputStream os) { try { try (JsonGenerator generator = mapper.createGenerator(os)) { // only flush to underlying OutputStream on success @@ -56,7 +58,7 @@ public void jsonWrite(Object bean, OutputStream os) { } @Override - public void jsonWriteStream(Iterator iterator, OutputStream os) { + public void toJsonStream(Iterator iterator, OutputStream os) { final JsonGenerator generator; try { generator = mapper.createGenerator(os); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java index 59cac2ef..89bd1d8e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java @@ -7,6 +7,7 @@ import java.io.InputStream; import java.io.OutputStream; +import java.lang.reflect.Type; import java.util.Iterator; /** Provides JsonService using avaje-jsonb. */ @@ -25,17 +26,17 @@ public JsonbJsonService(Jsonb jsonb) { } @Override - public T jsonRead(Class clazz, InputStream is) { - return jsonb.type(clazz).fromJson(is); + public T fromJson(Type clazz, InputStream is) { + return jsonb.type(clazz).fromJson(is); } @Override - public void jsonWrite(Object bean, OutputStream os) { + public void toJson(Object bean, OutputStream os) { jsonb.toJson(bean, os); } @Override - public void jsonWriteStream(Iterator iterator, OutputStream os) { + public void toJsonStream(Iterator iterator, OutputStream os) { try (JsonWriter writer = jsonb.writer(os)) { writer.pretty(false); if (iterator.hasNext()) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java index 04926ccb..746f9814 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JexPlugin.java @@ -6,7 +6,7 @@ * A plugin that can register things like routes, exception handlers and configure the current Jex * instance. * - * @see JexExtension for SPI registration details. + * @see JexExtension SPI registration details. */ @FunctionalInterface public non-sealed interface JexPlugin extends JexExtension { diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java index db9119e6..5fe1b2f5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java @@ -2,15 +2,27 @@ import java.io.InputStream; import java.io.OutputStream; +import java.lang.reflect.Type; import java.util.Iterator; /** * Service responsible for handling JSON-based request and response bodies. * - * @see JexExtension for SPI registration details. + * @see JexExtension SPI registration details. */ public non-sealed interface JsonService extends JexExtension { + /** + * **Writes a Java Object as JSON to an OutputStream** + * + *

    Serializes a Java object into JSON format and writes the resulting JSON to the specified + * output stream. + * + * @param bean the Java object to be serialized + * @param os the output stream to write the JSON data to + */ + void toJson(Object bean, OutputStream os); + /** * **Reads JSON from an InputStream** * @@ -21,18 +33,7 @@ public non-sealed interface JsonService extends JexExtension { * @param is the input stream containing the JSON data * @return the deserialized object */ - T jsonRead(Class type, InputStream is); - - /** - * **Writes a Java Object as JSON to an OutputStream** - * - *

    Serializes a Java object into JSON format and writes the resulting JSON to the specified - * output stream. - * - * @param bean the Java object to be serialized - * @param os the output stream to write the JSON data to - */ - void jsonWrite(Object bean, OutputStream os); + T fromJson(Type type, InputStream is); /** * Serializes a stream of Java objects into a JSON-Stream format, using the {@code x-json-stream} @@ -42,7 +43,7 @@ public non-sealed interface JsonService extends JexExtension { * @param iterator the stream of objects to be serialized * @param os the output stream to write the JSON-Stream data to */ - default void jsonWriteStream(Iterator iterator, OutputStream os) { + default void toJsonStream(Iterator iterator, OutputStream os) { throw new UnsupportedOperationException("Not Implemented"); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java b/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java index 4ce2a249..99304bbb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java @@ -7,7 +7,7 @@ /** * Template rendering typically of html. * - * @see JexExtension for SPI registration details. + * @see JexExtension SPI registration details. */ public non-sealed interface TemplateRender extends JexExtension { diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java index c3a88646..92a849ec 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java @@ -11,14 +11,17 @@ import java.util.concurrent.locks.LockSupport; import java.util.stream.Stream; +import io.avaje.jsonb.Json; import io.avaje.jsonb.JsonType; import io.avaje.jsonb.Jsonb; +import io.avaje.jsonb.Types; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import io.avaje.jex.Jex; -class JsonTest { +public class JsonTest { static List HELLO_BEANS = asList(HelloDto.rob(), HelloDto.fi()); @@ -32,18 +35,30 @@ private static AutoCloseIterator createBeanIterator() { static final Jsonb jsonb = Jsonb.builder().build(); static final JsonType jsonTypeHelloDto = jsonb.type(HelloDto.class); + @Json + public record Generic(T value){} + static TestPair init() { - Jex app = Jex.create() - .routing(routing -> routing - .get("/", ctx -> ctx.status(200).json(HelloDto.rob())) - .get("/usingOutputStream", ctx -> { - ctx.status(200).contentType("application/json"); - var result = HelloDto.rob(); - jsonTypeHelloDto.toJson(result, ctx.outputStream()); - }) - .get("/iterate", ctx -> ctx.jsonStream(ITERATOR)) - .get("/stream", ctx -> ctx.jsonStream(HELLO_BEANS.stream())) - .post("/", ctx -> ctx.text("bean[" + ctx.bodyAsClass(HelloDto.class) + "]"))); + Jex app = + Jex.create() + .get("/", ctx -> ctx.status(200).json(HelloDto.rob())) + .post( + "/generic", + ctx -> { + var type = Types.newParameterizedType(Generic.class, String.class); + String value = ctx.>bodyAsType(type).value; + ctx.text(value); + }) + .get( + "/usingOutputStream", + ctx -> { + ctx.status(200).contentType("application/json"); + var result = HelloDto.rob(); + jsonTypeHelloDto.toJson(result, ctx.outputStream()); + }) + .get("/iterate", ctx -> ctx.jsonStream(ITERATOR)) + .get("/stream", ctx -> ctx.jsonStream(HELLO_BEANS.stream())) + .post("/", ctx -> ctx.text("bean[" + ctx.bodyAsClass(HelloDto.class) + "]")); return TestPair.create(app); } @@ -70,6 +85,14 @@ void get() { assertThat(headers.firstValue("Content-Type").orElseThrow()).isEqualTo("application/json"); } + @Test + void generic() { + var generic = new Generic<>("stringy"); + var bean = pair.request().path("generic").body(generic).POST().asString().body(); + + assertThat(bean).isEqualTo(generic.value); + } + @Test void usingOutputStream() { From 2286ad876f8576d28d14a9979e82235efdd7951a Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 6 Dec 2024 16:06:46 -0500 Subject: [PATCH 129/250] Sort JexConfig --- .../src/main/java/io/avaje/jex/JexConfig.java | 144 +++++++++--------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index 72d93de8..eefe8f9b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -20,71 +20,68 @@ */ public sealed interface JexConfig permits DJexConfig { - /** - * Set the host on which the HttpServer will bind to. Defaults to any local address. - * - * @param host The host. - */ - JexConfig host(String host); + /** Returns the configured compression settings. */ + CompressionConfig compression(); /** - * Sets the port number on which the HttpServer will listen for incoming requests. + * Configures compression settings using a consumer function. * - * @param port The port number. + * @param consumer The consumer function to configure compression settings. + * @return The updated configuration. */ - JexConfig port(int port); + JexConfig compression(Consumer consumer); + + /** Return the contextPath. (Defaults to "/") */ + String contextPath(); /** - * Set the contextPath. + * Set the contextPath passed to the underlying HttpServer. (defaults to "/") * - * @param contextPath The context path which defaults to "/". + * @param contextPath The context path */ JexConfig contextPath(String contextPath); /** - * Set the socket backlog. If this value is less than or equal to zero, then a system default - * value is used - * - * @param backlog the socket backlog. If this value is less than or equal to zero, then a system - * default value is used + * Disables auto-configuring the current instance with {@link JexPlugin} loaded using the + * ServiceLoader. */ - JexConfig socketBacklog(int backlog); + JexConfig disableSpiPlugins(); /** - * Enables/Disables the default health endpoint. - * - * @param health whether to enable/disable. + * Executor for serving requests. Defaults to a {@link + * Executors#newVirtualThreadPerTaskExecutor()} */ - JexConfig health(boolean health); + Executor executor(); /** - * Configures whether trailing slashes in request URIs should be ignored. + * Sets the executor service used to handle incoming requests. * - * @param ignoreTrailingSlashes whether to enable/disable trailing slashes. + * @param executor The executor service. */ - JexConfig ignoreTrailingSlashes(boolean ignoreTrailingSlashes); + JexConfig executor(Executor executor); - /** - * Sets the JSON service used for (de)serialization. - * - * @param jsonService The json service instance. - */ - JexConfig jsonService(JsonService jsonService); + /** Returns whether the health endpoint is enabled. */ + boolean health(); /** - * Registers a template renderer for a specific file extension. + * Enables/Disables the default health endpoint. * - * @param extension The file extension. - * @param renderer The template renderer implementation. + * @param health whether to enable/disable. */ - JexConfig renderer(String extension, TemplateRender renderer); + JexConfig health(boolean health); + + /** Returns the configured host. (Defaults to localhost if not set) */ + String host(); /** - * Sets the executor service used to handle incoming requests. + * Set the host on which the HttpServer will bind to. Defaults to any local address. * - * @param executor The executor service. + * @param host The host. */ - JexConfig executor(Executor executor); + JexConfig host(String host); + + /** Return the {@link HttpsConfigurator} if https is enabled. */ + HttpsConfigurator httpsConfig(); /** * Enable https with the provided {@link HttpsConfigurator} @@ -93,59 +90,62 @@ public sealed interface JexConfig permits DJexConfig { */ JexConfig httpsConfig(HttpsConfigurator https); + /** Returns whether trailing slashes in request URIs are ignored. */ + boolean ignoreTrailingSlashes(); + /** - * Configures compression settings using a consumer function. + * Configures whether trailing slashes in request URIs should be ignored. * - * @param consumer The consumer function to configure compression settings. - * @return The updated configuration. + * @param ignoreTrailingSlashes whether to enable/disable trailing slashes. */ - JexConfig compression(Consumer consumer); - - /** Returns the configured port number. (Defaults to 8080 if not set) */ - int port(); - - /** Returns the configured host. (Defaults to localhost if not set) */ - String host(); - - /** Return the contextPath. (Defaults to "/") */ - String contextPath(); - - /** Returns whether the health endpoint is enabled. */ - boolean health(); - - /** Returns whether trailing slashes in request URIs are ignored. */ - boolean ignoreTrailingSlashes(); + JexConfig ignoreTrailingSlashes(boolean ignoreTrailingSlashes); /** Returns the configured JSON service. */ JsonService jsonService(); - /** Return the {@link HttpsConfigurator} if https is enabled. */ - HttpsConfigurator httpsConfig(); - - /** Return the schema as http or https. */ - String scheme(); - - /** Returns the configured compression settings. */ - CompressionConfig compression(); + /** + * Sets the JSON service used for (de)serialization. + * + * @param jsonService The json service instance. + */ + JexConfig jsonService(JsonService jsonService); - /** Returns a map of registered template renderers, keyed by file extension. */ - Map renderers(); + /** Returns the configured port number. (Defaults to 8080 if not set) */ + int port(); /** - * Executor for serving requests. Defaults to a {@link - * Executors#newVirtualThreadPerTaskExecutor()} + * Sets the port number on which the HttpServer will listen for incoming requests. + * + * @param port The port number. */ - Executor executor(); + JexConfig port(int port); /** - * Disables auto-configuring the current instance with {@link JexPlugin} loaded using the - * ServiceLoader. + * Registers a template renderer for a specific file extension. + * + * @param extension The file extension. + * @param renderer The template renderer implementation. */ - JexConfig disableSpiPlugins(); + JexConfig renderer(String extension, TemplateRender renderer); + + /** Returns a map of registered template renderers, keyed by file extension. */ + Map renderers(); + + /** Return the schema as http or https. */ + String scheme(); /** Return the socket backlog. */ int socketBacklog(); + /** + * Set the socket backlog. If this value is less than or equal to zero, then a system default + * value is used + * + * @param backlog the socket backlog. If this value is less than or equal to zero, then a system + * default value is used + */ + JexConfig socketBacklog(int backlog); + /** Return true if SPI plugins should be loaded and registered. */ boolean useSpiPlugins(); } From 2f7cefbc0b2df045338b3aef44ee095391b5001a Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 6 Dec 2024 16:21:05 -0500 Subject: [PATCH 130/250] remove encoding decision based on response header (#140) --- .../avaje/jex/compression/CompressedOutputStream.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java index cdbdb9d5..244738d9 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java @@ -32,17 +32,6 @@ public CompressedOutputStream( private void decideCompression(int length) throws IOException { if (!compressionDecided) { - var encoding = ctx.responseHeader(Constants.CONTENT_ENCODING); - if (encoding != null) { - this.compressedStream = - findMatchingCompressor(encoding) - .orElseThrow( - () -> - new IllegalStateException( - "No compressor found for Content-Encoding:" + encoding)) - .compress(originStream); - } - boolean compressionAllowed = compressedStream == null && compression.allowsForCompression(ctx.contentType()); From d94407b328913ca3bb7d42b73105597a875f66bf Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 7 Dec 2024 11:09:31 -0500 Subject: [PATCH 131/250] add more avaje config options (#141) --- avaje-jex/src/main/java/io/avaje/jex/BootJexState.java | 6 +++--- avaje-jex/src/main/java/io/avaje/jex/JexConfig.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java b/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java index 43e15905..97a5496a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java +++ b/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java @@ -20,9 +20,9 @@ State create(BeanScope beanScope) { jex.configureWith(beanScope); JexConfig config = jex.config(); - int port = config.port(); - config.port(Config.getInt("server.port", port)); - + config.port(Config.getInt("server.port", config.port())); + config.contextPath(Config.get("server.context.path", config.contextPath())); + config.host(Config.get("server.context.host", config.host())); jex.lifecycle().onShutdown(beanScope::close); return new State(jex.start()); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index eefe8f9b..ea5bc43a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -2,6 +2,7 @@ import java.util.Map; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.function.Consumer; import com.sun.net.httpserver.HttpsConfigurator; @@ -48,8 +49,7 @@ public sealed interface JexConfig permits DJexConfig { JexConfig disableSpiPlugins(); /** - * Executor for serving requests. Defaults to a {@link - * Executors#newVirtualThreadPerTaskExecutor()} + * Executor for serving requests. Defaults to a {@link Executors#newVirtualThreadPerTaskExecutor()} */ Executor executor(); @@ -70,7 +70,7 @@ public sealed interface JexConfig permits DJexConfig { */ JexConfig health(boolean health); - /** Returns the configured host. (Defaults to localhost if not set) */ + /** Returns the configured host. */ String host(); /** From a5458110a80ed9327bc6f601f50efb4fcb81e173 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Sun, 8 Dec 2024 11:26:29 +1300 Subject: [PATCH 132/250] Restore json fromJson() using Class (#142) --- .../src/main/java/io/avaje/jex/Context.java | 4 +-- .../java/io/avaje/jex/core/JdkContext.java | 7 ++++- .../io/avaje/jex/core/SpiServiceManager.java | 26 +++++++++++-------- .../jex/core/json/JacksonJsonService.java | 12 +++++++-- .../avaje/jex/core/json/JsonbJsonService.java | 5 ++++ .../java/io/avaje/jex/spi/JsonService.java | 17 +++++++++--- 6 files changed, 50 insertions(+), 21 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index cd990ae3..9ad84781 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -142,9 +142,7 @@ public interface Context { * * @param beanType The bean type */ - default T bodyAsClass(Class beanType) { - return bodyAsType(beanType); - } + T bodyAsClass(Class beanType); /*** * Return the request body as bean. diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index 03065e43..6b41a595 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -180,9 +180,14 @@ public void performRedirect() { } } + @Override + public T bodyAsClass(Class beanType) { + return mgr.fromJson(beanType, bodyAsInputStream()); + } + @Override public T bodyAsType(Type beanType) { - return mgr.fromJson(beanType, bodyAsInputStream()); + return mgr.fromJson(beanType, bodyAsInputStream()); } @Override diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java index 1111f34c..a29857bb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java @@ -57,21 +57,25 @@ OutputStream createOutputStream(JdkContext jdkContext) { return new BufferedOutStream(jdkContext); } - public T fromJson(Type type, InputStream is) { + T fromJson(Class type, InputStream is) { return jsonService.fromJson(type, is); } - public void toJson(Object bean, OutputStream os) { + T fromJson(Type type, InputStream is) { + return jsonService.fromJson(type, is); + } + + void toJson(Object bean, OutputStream os) { jsonService.toJson(bean, os); } - public void toJsonStream(Stream stream, OutputStream os) { + void toJsonStream(Stream stream, OutputStream os) { try (stream) { jsonService.toJsonStream(stream.iterator(), os); } } - public void toJsonStream(Iterator iterator, OutputStream os) { + void toJsonStream(Iterator iterator, OutputStream os) { try { jsonService.toJsonStream(iterator, os); } finally { @@ -79,7 +83,7 @@ public void toJsonStream(Iterator iterator, OutputStream os) { } } - public void maybeClose(Object iterator) { + void maybeClose(Object iterator) { if (iterator instanceof AutoCloseable closeable) { try { closeable.close(); @@ -89,19 +93,19 @@ public void maybeClose(Object iterator) { } } - public Routing.Type lookupRoutingType(String method) { + Routing.Type lookupRoutingType(String method) { return methodMap.get(method); } - public void handleException(JdkContext ctx, Exception e) { + void handleException(JdkContext ctx, Exception e) { exceptionHandler.handle(ctx, e); } - public void render(Context ctx, String name, Map model) { + void render(Context ctx, String name, Map model) { templateManager.render(ctx, name, model); } - public String requestCharset(Context ctx) { + String requestCharset(Context ctx) { return parseCharset(ctx.header(Constants.CONTENT_TYPE)); } @@ -117,11 +121,11 @@ static String parseCharset(String header) { return UTF_8; } - public Map> formParamMap(Context ctx, String charset) { + Map> formParamMap(Context ctx, String charset) { return parseParamMap(ctx.body(), charset); } - public Map> parseParamMap(String body, String charset) { + Map> parseParamMap(String body, String charset) { if (body == null || body.isEmpty()) { return Collections.emptyMap(); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java index 886f394d..81a67586 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java @@ -20,8 +20,7 @@ public final class JacksonJsonService implements JsonService { /** Create with defaults for Jackson */ public JacksonJsonService() { - this.mapper = - new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this.mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } /** Create with a Jackson instance that might have custom configuration. */ @@ -29,6 +28,15 @@ public JacksonJsonService(ObjectMapper mapper) { this.mapper = mapper; } + @Override + public T fromJson(Class type, InputStream is) { + try { + return mapper.readValue(is, type); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + @Override public T fromJson(Type type, InputStream is) { try { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java index 89bd1d8e..a3709203 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java @@ -25,6 +25,11 @@ public JsonbJsonService(Jsonb jsonb) { this.jsonb = jsonb; } + @Override + public T fromJson(Class clazz, InputStream is) { + return jsonb.type(clazz).fromJson(is); + } + @Override public T fromJson(Type clazz, InputStream is) { return jsonb.type(clazz).fromJson(is); diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java index 5fe1b2f5..0eac7be7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java @@ -33,6 +33,18 @@ public non-sealed interface JsonService extends JexExtension { * @param is the input stream containing the JSON data * @return the deserialized object */ + T fromJson(Class type, InputStream is); + + /** + * **Reads JSON from an InputStream** + * + *

    Reads a JSON-formatted input stream and deserializes it into a Java object of the specified + * type. + * + * @param type the Type object of the desired type + * @param is the input stream containing the JSON data + * @return the deserialized object + */ T fromJson(Type type, InputStream is); /** @@ -43,8 +55,5 @@ public non-sealed interface JsonService extends JexExtension { * @param iterator the stream of objects to be serialized * @param os the output stream to write the JSON-Stream data to */ - default void toJsonStream(Iterator iterator, OutputStream os) { - - throw new UnsupportedOperationException("Not Implemented"); - } + void toJsonStream(Iterator iterator, OutputStream os); } From 2a9493598b3289bd2558599efef8e255fd18495c Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:25:39 -0500 Subject: [PATCH 133/250] Optimize Jackson Service (#139) * optimize jackson service * Update README.md * optimize jackson service * Update README.md * Update JacksonJsonService.java * final var + whitespace --------- Co-authored-by: Rob Bygrave --- README.md | 2 +- .../io/avaje/jex/core/json/JacksonJsonService.java | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a496fc9d..842baf45 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![javadoc](https://javadoc.io/badge2/io.avaje/avaje-jex/javadoc.svg?color=purple)](https://javadoc.io/doc/io.avaje/avaje-jex) # Avaje-Jex -Lightweight (~100KB) wrapper over the JDK's built-in [HTTP server](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html). +Lightweight (~105KB) wrapper over the JDK's built-in [HTTP server](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html). Features: diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java index 81a67586..c9551950 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java @@ -6,9 +6,12 @@ import java.io.UncheckedIOException; import java.lang.reflect.Type; import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import io.avaje.jex.spi.JsonService; @@ -17,10 +20,12 @@ public final class JacksonJsonService implements JsonService { private final ObjectMapper mapper; + private final Map javaTypes = new ConcurrentHashMap<>(); /** Create with defaults for Jackson */ public JacksonJsonService() { - this.mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this.mapper = + new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } /** Create with a Jackson instance that might have custom configuration. */ @@ -40,7 +45,10 @@ public T fromJson(Class type, InputStream is) { @Override public T fromJson(Type type, InputStream is) { try { - var javaType = mapper.getTypeFactory().constructType(type); + final var javaType = + javaTypes.computeIfAbsent( + type.getTypeName(), k -> mapper.getTypeFactory().constructType(type)); + return mapper.readValue(is, javaType); } catch (IOException e) { throw new UncheckedIOException(e); From 4ce37ff70eae6ecc245803660ccabe1724814280 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 9 Dec 2024 07:30:08 +1300 Subject: [PATCH 134/250] Version 3.0-RC10 --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-htmx/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 4 ++-- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 37f8ff3c..5d5fb047 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC9 + 3.0-RC10 avaje-jex-freemarker diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index ee3d97e9..b936d06f 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC9 + 3.0-RC10 avaje-jex-htmx diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 93812b16..f1e28768 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC9 + 3.0-RC10 avaje-jex-mustache diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index 195849d5..f6cd0144 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC9 + 3.0-RC10 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 98a37196..7baac1da 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC9 + 3.0-RC10 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index cec5b87f..a2f7c1b0 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC9 + 3.0-RC10 avaje-jex diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index e8cd40d7..7f3c0a4b 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC9 + 3.0-RC10 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index e2f9f527..6ea00903 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC9 + 3.0-RC10 diff --git a/examples/pom.xml b/examples/pom.xml index c8b1534c..aa93e84b 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC9 + 3.0-RC10 examples diff --git a/pom.xml b/pom.xml index 44f347a2..40c7324f 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.avaje avaje-jex-parent - 3.0-RC9 + 3.0-RC10 pom @@ -22,7 +22,7 @@ 2.18.2 false 21 - 2024-12-04T08:58:25Z + 2024-12-08T18:26:16Z From c7bdc7732e31f97c1f803866fafcac9609053b92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 03:37:13 +0000 Subject: [PATCH 135/250] Bump org.graalvm.buildtools:native-maven-plugin Bumps the dependencies group with 1 update: [org.graalvm.buildtools:native-maven-plugin](https://github.com/graalvm/native-build-tools). Updates `org.graalvm.buildtools:native-maven-plugin` from 0.10.3 to 0.10.4 - [Release notes](https://github.com/graalvm/native-build-tools/releases) - [Commits](https://github.com/graalvm/native-build-tools/compare/0.10.3...0.10.4) --- updated-dependencies: - dependency-name: org.graalvm.buildtools:native-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- examples/example-jdk-jsonb/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index dc43bde6..f3912dea 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -19,7 +19,7 @@ 17 - 0.10.3 + 0.10.4 From d817b5bb32890fc2756592696131c82c121946cb Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 9 Dec 2024 18:30:56 -0500 Subject: [PATCH 136/250] Default to jsonb if both jackson and jsonb are present (#144) * default to jsonb if both jackson and jsonb are present * fix test --- .../java/io/avaje/jex/core/SpiServiceManager.java | 14 +++----------- .../src/test/java/io/avaje/jex/core/HelloBean.java | 3 +++ .../src/test/java/io/avaje/jex/core/JsonTest.java | 2 ++ 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java index a29857bb..9568c30a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java @@ -183,18 +183,10 @@ JsonService initJsonService() { /** Create a reasonable default JsonService if Jackson or avaje-jsonb are present. */ JsonService defaultJsonService() { - if (detectJackson()) { - try { - return new JacksonJsonService(); - } catch (IllegalAccessError errorNotInModulePath) { - // not in module path - log.log( - Level.DEBUG, - "Not using Jackson due to module path {0}", - errorNotInModulePath.getMessage()); - } + if (detectJsonb()) { + return new JsonbJsonService(); } - return detectJsonb() ? new JsonbJsonService() : null; + return detectJackson() ? new JacksonJsonService() : null; } boolean detectJackson() { diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/HelloBean.java b/avaje-jex/src/test/java/io/avaje/jex/core/HelloBean.java index 327263c2..1b1a640c 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/HelloBean.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/HelloBean.java @@ -1,5 +1,8 @@ package io.avaje.jex.core; +import io.avaje.jsonb.Json; + +@Json public class HelloBean { public int id; diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java index 92a849ec..99c20ede 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; import io.avaje.jex.Jex; +import io.avaje.jex.core.json.JacksonJsonService; public class JsonTest { @@ -41,6 +42,7 @@ public record Generic(T value){} static TestPair init() { Jex app = Jex.create() + .jsonService(new JacksonJsonService()) .get("/", ctx -> ctx.status(200).json(HelloDto.rob())) .post( "/generic", From 9a791c277f5b7bcdbe27a0a09e7dd6c2a9a355fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 04:06:47 +0000 Subject: [PATCH 137/250] Bump the dependencies group with 3 updates Bumps the dependencies group with 3 updates: [io.avaje:avaje-jsonb](https://github.com/avaje/avaje-jsonb), io.avaje:avaje-jsonb-generator and [io.github.robaho:httpserver](https://github.com/robaho/httpserver). Updates `io.avaje:avaje-jsonb` from 2.3 to 2.4 - [Release notes](https://github.com/avaje/avaje-jsonb/releases) - [Commits](https://github.com/avaje/avaje-jsonb/compare/2.3...2.4) Updates `io.avaje:avaje-jsonb-generator` from 2.3 to 2.4 Updates `io.github.robaho:httpserver` from 1.0.10 to 1.0.11 - [Commits](https://github.com/robaho/httpserver/compare/1.0.10...1.0.11) --- updated-dependencies: - dependency-name: io.avaje:avaje-jsonb dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-jsonb-generator dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.github.robaho:httpserver dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- avaje-jex/pom.xml | 4 ++-- examples/example-jdk-jsonb/pom.xml | 4 ++-- examples/example-robaho/pom.xml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index a2f7c1b0..b53be371 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -38,14 +38,14 @@ io.avaje avaje-jsonb - 2.3 + 2.4 true io.avaje avaje-jsonb-generator - 2.3 + 2.4 test diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index f3912dea..91c6d39e 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -33,7 +33,7 @@ io.avaje avaje-jsonb - 2.3 + 2.4 @@ -53,7 +53,7 @@ io.avaje avaje-jsonb-generator - 2.3 + 2.4 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 6ea00903..a68efc17 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -15,7 +15,7 @@ io.github.robaho httpserver - 1.0.10 + 1.0.11 From ed5f5228ef7a7e711ad423fe83e7ef3bf3c7ce38 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:12:42 -0500 Subject: [PATCH 138/250] Make parent pom useful (#146) * make parent pom useful manage the avaje dependencies with the parent pom * Update pom.xml * fix json --- avaje-jex-freemarker/pom.xml | 10 +- avaje-jex-htmx/pom.xml | 5 +- avaje-jex-mustache/pom.xml | 10 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 12 +- avaje-jex/pom.xml | 24 +- .../avaje/jex/core/json/JsonbJsonService.java | 10 +- .../avaje/jex/core/ExceptionManagerTest.java | 18 +- .../test/java/io/avaje/jex/core/JsonTest.java | 9 +- examples/example-jdk/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 301 ++++++++++++++++-- 13 files changed, 338 insertions(+), 69 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 5d5fb047..9d917387 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC10 + 3.0-RC11 avaje-jex-freemarker @@ -18,7 +18,6 @@ io.avaje avaje-jex - ${project.version} provided @@ -28,6 +27,12 @@ ${freemarker.version} + + io.avaje + avaje-spi-service + provided + + com.fasterxml.jackson.core @@ -39,7 +44,6 @@ io.avaje avaje-jex-test - ${project.version} test diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index b936d06f..2e13303f 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,25 +6,22 @@ io.avaje avaje-jex-parent - 3.0-RC10 + 3.0-RC11 avaje-jex-htmx - 2.8 io.avaje avaje-htmx-api - ${avaje-htmx-api.version} io.avaje avaje-jex - ${project.version} diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index f1e28768..374c4431 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC10 + 3.0-RC11 avaje-jex-mustache @@ -22,6 +22,13 @@ provided + + io.avaje + avaje-spi-service + 2.8 + provided + + com.github.spullara.mustache.java compiler @@ -43,7 +50,6 @@ ${project.version} test - diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index f6cd0144..b4e56196 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC10 + 3.0-RC11 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 7baac1da..656525d2 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC10 + 3.0-RC11 avaje-jex-test @@ -14,21 +14,24 @@ io.avaje avaje-jex - ${project.version} provided + + io.avaje + junit + 1.5 + + io.avaje avaje-http-client - 2.9-RC4 io.avaje avaje-inject-test - 11.0 true @@ -37,7 +40,6 @@ jackson-databind ${jackson.version} - diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index b53be371..b6b6b7b3 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC10 + 3.0-RC11 avaje-jex @@ -17,17 +17,21 @@ io.avaje avaje-config - 4.0 true io.avaje avaje-inject - 11.0 true + + io.avaje + avaje-spi-service + provided + + com.fasterxml.jackson.core jackson-databind @@ -38,14 +42,24 @@ io.avaje avaje-jsonb - 2.4 true io.avaje avaje-jsonb-generator - 2.4 + test + + + + io.avaje + junit + 1.5 + test + + + io.avaje + avaje-http-client test diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java index a3709203..cebb9520 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java @@ -1,15 +1,15 @@ package io.avaje.jex.core.json; -import io.avaje.jex.spi.JsonService; -import io.avaje.jsonb.JsonType; -import io.avaje.jsonb.JsonWriter; -import io.avaje.jsonb.Jsonb; - import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Type; import java.util.Iterator; +import io.avaje.jex.spi.JsonService; +import io.avaje.json.JsonWriter; +import io.avaje.jsonb.JsonType; +import io.avaje.jsonb.Jsonb; + /** Provides JsonService using avaje-jsonb. */ public final class JsonbJsonService implements JsonService { diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/ExceptionManagerTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ExceptionManagerTest.java index 3593371f..01b1af61 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/ExceptionManagerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ExceptionManagerTest.java @@ -1,18 +1,18 @@ package io.avaje.jex.core; -import io.avaje.jex.Jex; -import io.avaje.jex.http.BadRequestException; -import io.avaje.jex.http.ErrorCode; -import io.avaje.jex.http.HttpResponseException; -import io.avaje.jsonb.JsonException; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.net.http.HttpResponse; import java.util.Map; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.avaje.jex.Jex; +import io.avaje.jex.http.BadRequestException; +import io.avaje.jex.http.ErrorCode; +import io.avaje.jex.http.HttpResponseException; +import io.avaje.json.JsonException; class ExceptionManagerTest { diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java index 99c20ede..17fcf78b 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java @@ -11,16 +11,15 @@ import java.util.concurrent.locks.LockSupport; import java.util.stream.Stream; -import io.avaje.jsonb.Json; -import io.avaje.jsonb.JsonType; -import io.avaje.jsonb.Jsonb; -import io.avaje.jsonb.Types; - import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import io.avaje.jex.Jex; import io.avaje.jex.core.json.JacksonJsonService; +import io.avaje.jsonb.Json; +import io.avaje.jsonb.JsonType; +import io.avaje.jsonb.Jsonb; +import io.avaje.jsonb.Types; public class JsonTest { diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 7f3c0a4b..7eec60a6 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC10 + 3.0-RC11 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index a68efc17..a858dd75 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC10 + 3.0-RC11 diff --git a/examples/pom.xml b/examples/pom.xml index aa93e84b..d5adae83 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC10 + 3.0-RC11 examples diff --git a/pom.xml b/pom.xml index 40c7324f..379fd173 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,7 @@ - + 4.0.0 org.avaje @@ -9,7 +11,7 @@ io.avaje avaje-jex-parent - 3.0-RC10 + 3.0-RC11 pom @@ -22,7 +24,18 @@ 2.18.2 false 21 - 2024-12-08T18:26:16Z + 2024-12-16T18:42:05Z + + 4.0 + 11.1-RC1 + 3.0-RC5 + 2.9-RC4 + 9.4 + 2.4 + 1.1 + 2.6 + 1.36 + 15.8.0 @@ -34,31 +47,220 @@ avaje-jex-static-content - - - - - io.avaje - junit - 1.5 - test - - - - io.avaje - avaje-http-client - 2.8 - test - + + + + io.ebean + ebean-bom + ${ebean.version} + import + pom + + + io.avaje + avaje-logback-encoder + 0.4 + + + io.avaje + avaje-config + ${avaje.config.version} + + + io.avaje + avaje-jsonb + ${avaje.jsonb.version} + + + io.avaje + avaje-http-api + ${avaje.http.version} + + + io.avaje + avaje-htmx-api + ${avaje.http.version} + + + io.avaje + avaje-http-client + ${avaje.http.version} + + + io.avaje + avaje-inject + ${avaje.inject.version} + + + io.avaje + avaje-inject-test + ${avaje.inject.version} + + + io.avaje + avaje-metrics + ${avaje.metrics.version} + + + io.avaje + avaje-validator-constraints + ${avaje.validator.version} + + + io.avaje + avaje-validator + ${avaje.validator.version} + + + io.avaje + avaje-http-client-generator + ${avaje.http.version} + + + io.avaje + avaje-http-jex-generator + ${avaje.http.version} + + + io.avaje + avaje-inject-generator + ${avaje.inject.version} + + + io.avaje + avaje-jsonb-generator + ${avaje.jsonb.version} + + + io.avaje + avaje-validator-generator + ${avaje.validator.version} + + + io.avaje + avaje-prisms + ${avaje.prisms.version} + true + + + io.avaje + avaje-record-builder + ${avaje.record.builder.version} + + + io.avaje + avaje-spi-service + ${avaje.spi.version} + + + io.avaje + avaje-jex + ${project.version} + + + io.avaje + avaje-jex-test + ${project.version} + + + io.avaje + avaje-jex-freemarker + ${project.version} + + + io.avaje + avaje-jex-mustache + ${project.version} + + + io.avaje + avaje-jex-htmx + ${project.version} + + + io.avaje + avaje-jex-static-content + ${project.version} + + + - - io.avaje - avaje-spi-service - 2.8 - provided - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -AbuildPlugin=false + + + + + io.avaje + avaje-inject-maven-plugin + ${avaje.inject.version} + + + process-sources + + provides + + + + + + + + + + io.avaje + openapi-maven-plugin + 1.0 + + + main + process-classes + + openapi + + + + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + + + + false + + + + + + io.avaje + avaje-provides-maven-plugin + + + + disable-apt-validation + add-module-spi + + + + + + + @@ -73,5 +275,50 @@ examples + + jdk24Plus + + [24,) + + + + + io.avaje + avaje-provides-maven-plugin + + + + + + create-modules + + + + maven-dependency-plugin + + + copy-modules + package + + copy-dependencies + + + + ${project.build.directory}/modules + runtime + + + + + + org.apache.maven.plugins + maven-jar-plugin + + ${project.build.directory}/modules + + + + + From be3c4d0be7ea11826ac4729377a9f93d13e5b747 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:15:09 +0000 Subject: [PATCH 139/250] Bump the dependencies group with 7 updates Bumps the dependencies group with 7 updates: | Package | From | To | | --- | --- | --- | | io.avaje:avaje-http-api | `2.9-RC4` | `2.9-RC6` | | io.avaje:avaje-htmx-api | `2.9-RC4` | `2.9-RC6` | | [io.avaje:avaje-http-client](https://github.com/avaje/avaje-http-client) | `2.9-RC4` | `2.9-RC6` | | io.avaje:avaje-http-client-generator | `2.9-RC4` | `2.9-RC6` | | io.avaje:avaje-http-jex-generator | `2.9-RC4` | `2.9-RC6` | | [io.avaje:avaje-record-builder](https://github.com/avaje/avaje-record-builder) | `1.1` | `1.2` | | io.avaje:avaje-spi-service | `2.6` | `2.8` | Updates `io.avaje:avaje-http-api` from 2.9-RC4 to 2.9-RC6 Updates `io.avaje:avaje-htmx-api` from 2.9-RC4 to 2.9-RC6 Updates `io.avaje:avaje-http-client` from 2.9-RC4 to 2.9-RC6 - [Release notes](https://github.com/avaje/avaje-http-client/releases) - [Commits](https://github.com/avaje/avaje-http-client/commits) Updates `io.avaje:avaje-http-client-generator` from 2.9-RC4 to 2.9-RC6 Updates `io.avaje:avaje-http-jex-generator` from 2.9-RC4 to 2.9-RC6 Updates `io.avaje:avaje-htmx-api` from 2.9-RC4 to 2.9-RC6 Updates `io.avaje:avaje-http-client` from 2.9-RC4 to 2.9-RC6 - [Release notes](https://github.com/avaje/avaje-http-client/releases) - [Commits](https://github.com/avaje/avaje-http-client/commits) Updates `io.avaje:avaje-http-client-generator` from 2.9-RC4 to 2.9-RC6 Updates `io.avaje:avaje-http-jex-generator` from 2.9-RC4 to 2.9-RC6 Updates `io.avaje:avaje-record-builder` from 1.1 to 1.2 - [Release notes](https://github.com/avaje/avaje-record-builder/releases) - [Commits](https://github.com/avaje/avaje-record-builder/commits/1.2) Updates `io.avaje:avaje-spi-service` from 2.6 to 2.8 --- updated-dependencies: - dependency-name: io.avaje:avaje-http-api dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-htmx-api dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client dependency-type: direct:development dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-jex-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-htmx-api dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client dependency-type: direct:development dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-jex-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-record-builder dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-spi-service dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 379fd173..36400316 100644 --- a/pom.xml +++ b/pom.xml @@ -29,11 +29,11 @@ 4.0 11.1-RC1 3.0-RC5 - 2.9-RC4 + 2.9-RC6 9.4 2.4 - 1.1 - 2.6 + 1.2 + 2.8 1.36 15.8.0 From 8356301ec48571247df37b3b103d32bf85683224 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:23:09 -0500 Subject: [PATCH 140/250] JDK 23 annotation processor change --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 36400316..e117e004 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ 2.18.2 false 21 + full 2024-12-16T18:42:05Z 4.0 From f828f7834561c111213cef2aa9524b88c4ccd9df Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:34:32 -0500 Subject: [PATCH 141/250] Link docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 842baf45..e29cfbdb 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Maven Central](https://img.shields.io/maven-central/v/io.avaje/avaje-jex.svg?label=Maven%20Central)](https://mvnrepository.com/artifact/io.avaje/avaje-jex) [![javadoc](https://javadoc.io/badge2/io.avaje/avaje-jex/javadoc.svg?color=purple)](https://javadoc.io/doc/io.avaje/avaje-jex) -# Avaje-Jex +# [Avaje-Jex](https://avaje.io/jex/) Lightweight (~105KB) wrapper over the JDK's built-in [HTTP server](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html). Features: From f6febb7f1b2dec45f3094f3ba8656e6c4e416354 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:56:14 -0500 Subject: [PATCH 142/250] Fix Jex version and autorun `Ebean` (#148) * fix jex version and autorun ebean * Update pom.xml --- pom.xml | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index e117e004..e53ed5bd 100644 --- a/pom.xml +++ b/pom.xml @@ -156,32 +156,32 @@ io.avaje avaje-jex - ${project.version} + 3.0-RC11 io.avaje avaje-jex-test - ${project.version} + 3.0-RC11 io.avaje avaje-jex-freemarker - ${project.version} + 3.0-RC11 io.avaje avaje-jex-mustache - ${project.version} + 3.0-RC11 io.avaje avaje-jex-htmx - ${project.version} + 3.0-RC11 io.avaje avaje-jex-static-content - ${project.version} + 3.0-RC11 @@ -259,6 +259,19 @@ + + io.ebean + ebean-maven-plugin + ${ebean.version} + + + + enhance + testEnhance + + + + @@ -290,6 +303,22 @@ + + ebean + + + target/classes/META-INF/ebean-generated-info.mf + + + + + + io.ebean + ebean-maven-plugin + + + + create-modules From 3cbf264a2f3021c3e77e8dc5ba107f0b2c4bd0ef Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 18 Dec 2024 11:50:04 +1300 Subject: [PATCH 143/250] Version 3.0-RC12 --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-htmx/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 16 ++++++++-------- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 9d917387..7ddfe19e 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC11 + 3.0-RC12 avaje-jex-freemarker diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 2e13303f..e131aa27 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC11 + 3.0-RC12 avaje-jex-htmx diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 374c4431..7dc0369e 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC11 + 3.0-RC12 avaje-jex-mustache diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index b4e56196..0575c526 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC11 + 3.0-RC12 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 656525d2..d2041b0e 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC11 + 3.0-RC12 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index b6b6b7b3..a8ab6d36 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC11 + 3.0-RC12 avaje-jex diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 7eec60a6..eb4104e6 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC11 + 3.0-RC12 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index a858dd75..ce75f473 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC11 + 3.0-RC12 diff --git a/examples/pom.xml b/examples/pom.xml index d5adae83..1c47cb1c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC11 + 3.0-RC12 examples diff --git a/pom.xml b/pom.xml index e53ed5bd..8fbc6d02 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ io.avaje avaje-jex-parent - 3.0-RC11 + 3.0-RC12 pom @@ -25,7 +25,7 @@ false 21 full - 2024-12-16T18:42:05Z + 2024-12-17T22:48:35Z 4.0 11.1-RC1 @@ -156,32 +156,32 @@ io.avaje avaje-jex - 3.0-RC11 + 3.0-RC12 io.avaje avaje-jex-test - 3.0-RC11 + 3.0-RC12 io.avaje avaje-jex-freemarker - 3.0-RC11 + 3.0-RC12 io.avaje avaje-jex-mustache - 3.0-RC11 + 3.0-RC12 io.avaje avaje-jex-htmx - 3.0-RC11 + 3.0-RC12 io.avaje avaje-jex-static-content - 3.0-RC11 + 3.0-RC12 From 103ed2eaba4fcb35004c214b17a8362e3011f938 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 17 Dec 2024 18:14:00 -0500 Subject: [PATCH 144/250] plugin provides --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 8fbc6d02..7a06bfc7 100644 --- a/pom.xml +++ b/pom.xml @@ -250,6 +250,7 @@ io.avaje avaje-provides-maven-plugin + 2.0 From 911e72823b263a3c532dce10b6166e4534613d61 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 17 Dec 2024 18:24:15 -0500 Subject: [PATCH 145/250] provides plugin version --- pom.xml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 7a06bfc7..485669b9 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ io.avaje avaje-logback-encoder - 0.4 + 0.5 io.avaje @@ -189,7 +189,6 @@ - org.apache.maven.plugins maven-compiler-plugin @@ -229,7 +228,6 @@ - org.apache.maven.plugins maven-shade-plugin @@ -342,7 +340,6 @@ - org.apache.maven.plugins maven-jar-plugin ${project.build.directory}/modules From d4f477362bc48e914601197686504e5d15cf1b22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 23:59:46 +0000 Subject: [PATCH 146/250] Bump the dependencies group with 6 updates Bumps the dependencies group with 6 updates: | Package | From | To | | --- | --- | --- | | io.avaje:avaje-http-api | `2.9-RC6` | `2.9-RC7` | | io.avaje:avaje-htmx-api | `2.9-RC6` | `2.9-RC7` | | [io.avaje:avaje-http-client](https://github.com/avaje/avaje-http-client) | `2.9-RC6` | `2.9-RC7` | | io.avaje:avaje-http-client-generator | `2.9-RC6` | `2.9-RC7` | | io.avaje:avaje-http-jex-generator | `2.9-RC6` | `2.9-RC7` | | io.avaje:avaje-spi-service | `2.8` | `2.9` | Updates `io.avaje:avaje-http-api` from 2.9-RC6 to 2.9-RC7 Updates `io.avaje:avaje-htmx-api` from 2.9-RC6 to 2.9-RC7 Updates `io.avaje:avaje-http-client` from 2.9-RC6 to 2.9-RC7 - [Release notes](https://github.com/avaje/avaje-http-client/releases) - [Commits](https://github.com/avaje/avaje-http-client/commits) Updates `io.avaje:avaje-http-client-generator` from 2.9-RC6 to 2.9-RC7 Updates `io.avaje:avaje-http-jex-generator` from 2.9-RC6 to 2.9-RC7 Updates `io.avaje:avaje-htmx-api` from 2.9-RC6 to 2.9-RC7 Updates `io.avaje:avaje-http-client` from 2.9-RC6 to 2.9-RC7 - [Release notes](https://github.com/avaje/avaje-http-client/releases) - [Commits](https://github.com/avaje/avaje-http-client/commits) Updates `io.avaje:avaje-http-client-generator` from 2.9-RC6 to 2.9-RC7 Updates `io.avaje:avaje-http-jex-generator` from 2.9-RC6 to 2.9-RC7 Updates `io.avaje:avaje-spi-service` from 2.8 to 2.9 --- updated-dependencies: - dependency-name: io.avaje:avaje-http-api dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-htmx-api dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client dependency-type: direct:development dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-jex-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-htmx-api dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client dependency-type: direct:development dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-jex-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-spi-service dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- avaje-jex-mustache/pom.xml | 2 +- pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 7dc0369e..9a72abb5 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -25,7 +25,7 @@ io.avaje avaje-spi-service - 2.8 + 2.9 provided diff --git a/pom.xml b/pom.xml index 485669b9..d0e97498 100644 --- a/pom.xml +++ b/pom.xml @@ -30,11 +30,11 @@ 4.0 11.1-RC1 3.0-RC5 - 2.9-RC6 + 2.9-RC7 9.4 2.4 1.2 - 2.8 + 2.9 1.36 15.8.0 From c64d8011c5b7f7bfbeb305881ccac9d348f52818 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 17 Dec 2024 19:01:25 -0500 Subject: [PATCH 147/250] Provides 2.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d0e97498..8221ae25 100644 --- a/pom.xml +++ b/pom.xml @@ -248,7 +248,7 @@ io.avaje avaje-provides-maven-plugin - 2.0 + 2.1 From c47a89bcdd7eb1d7db9f52214a437960c0de009e Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 18 Dec 2024 23:41:15 -0500 Subject: [PATCH 148/250] Fix contextPath having no effect on routes (#150) --- .../src/main/java/io/avaje/jex/DJex.java | 2 +- .../main/java/io/avaje/jex/DJexConfig.java | 5 ++- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 2 +- .../io/avaje/jex/core/BootstrapServer.java | 2 +- .../io/avaje/jex/routes/RoutesBuilder.java | 10 +++-- .../test/java/io/avaje/jex/CtxPathTest.java | 40 +++++++++++++++++++ 6 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 avaje-jex/src/test/java/io/avaje/jex/CtxPathTest.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index 3bf1b325..724da287 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -71,7 +71,7 @@ public Jex port(int port) { } @Override - public Jex context(String contextPath) { + public Jex contextPath(String contextPath) { config.contextPath(contextPath); return this; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 7187256e..dbb14662 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -41,7 +41,10 @@ public JexConfig port(int port) { @Override public JexConfig contextPath(String contextPath) { - this.contextPath = contextPath; + this.contextPath = + contextPath + .transform(s -> s.startsWith("/") ? s : "/" + s) + .transform(s -> s.endsWith("/") ? s.substring(0, s.lastIndexOf("/")) : s); return this; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 952b3a8f..6c3ee5bb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -242,7 +242,7 @@ default Jex group(String path, HttpService group) { * @param contextPath The context path to use. * @return The updated Jex instance. */ - Jex context(String contextPath); + Jex contextPath(String contextPath); /** * Explicitly register a template renderer. diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java index dafba879..92bb47a3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java @@ -33,7 +33,7 @@ public static Jex.Server start(Jex jex) { } final SpiRoutes routes = - new RoutesBuilder(jex.routing(), config.ignoreTrailingSlashes()).build(); + new RoutesBuilder(jex.routing(), config).build(); return start(jex, routes); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java index 5e0c3c61..1880f11d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java @@ -5,6 +5,7 @@ import java.util.List; import io.avaje.jex.HttpFilter; +import io.avaje.jex.JexConfig; import io.avaje.jex.Routing; public final class RoutesBuilder { @@ -12,10 +13,12 @@ public final class RoutesBuilder { private final EnumMap typeMap = new EnumMap<>(Routing.Type.class); private final boolean ignoreTrailingSlashes; private final List filters; + private final String contextPath; - public RoutesBuilder(Routing routing, boolean ignoreTrailingSlashes) { - this.ignoreTrailingSlashes = ignoreTrailingSlashes; + public RoutesBuilder(Routing routing, JexConfig config) { + this.ignoreTrailingSlashes = config.ignoreTrailingSlashes(); final var buildMap = new LinkedHashMap(); + this.contextPath = config.contextPath().transform(s -> "/".equals(s) ? "" : s); for (var handler : routing.handlers()) { buildMap.computeIfAbsent(handler.getType(), h -> new RouteIndexBuild()).add(convert(handler)); } @@ -24,7 +27,8 @@ public RoutesBuilder(Routing routing, boolean ignoreTrailingSlashes) { } private SpiRoutes.Entry convert(Routing.Entry handler) { - final PathParser pathParser = new PathParser(handler.getPath(), ignoreTrailingSlashes); + final PathParser pathParser = + new PathParser(contextPath + handler.getPath(), ignoreTrailingSlashes); return new RouteEntry(pathParser, handler.getHandler(), handler.getRoles()); } diff --git a/avaje-jex/src/test/java/io/avaje/jex/CtxPathTest.java b/avaje-jex/src/test/java/io/avaje/jex/CtxPathTest.java new file mode 100644 index 00000000..c74a6314 --- /dev/null +++ b/avaje-jex/src/test/java/io/avaje/jex/CtxPathTest.java @@ -0,0 +1,40 @@ +package io.avaje.jex; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.http.HttpResponse; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.avaje.jex.core.TestPair; + +class CtxPathTest { + + static final TestPair pair = init(); + + static TestPair init() { + final Jex app = Jex.create().contextPath("/ctx/").get("/", ctx -> ctx.text("ctx")); + + return TestPair.create(app); + } + + @AfterAll + static void end() { + pair.shutdown(); + } + + @Test + void get() { + HttpResponse res = pair.request().path("ctx").GET().asString(); + + assertThat(res.body()).isEqualTo("ctx"); + } + + @Test + void getRoot404() { + HttpResponse res = pair.request().GET().asString(); + + assertThat(res.statusCode()).isEqualTo(404); + } +} From fb9b8b73b8637bde10d9eb64323bacf934fc707e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 03:49:00 +0000 Subject: [PATCH 149/250] Bump the dependencies group with 3 updates Bumps the dependencies group with 3 updates: io.ebean:ebean-bom, [io.ebean:ebean-maven-plugin](https://github.com/ebean-orm-tools/ebean-maven-plugin) and [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback). Updates `io.ebean:ebean-bom` from 15.8.0 to 15.8.1 Updates `io.ebean:ebean-maven-plugin` from 15.8.0 to 15.8.1 - [Release notes](https://github.com/ebean-orm-tools/ebean-maven-plugin/releases) - [Changelog](https://github.com/ebean-orm-tools/ebean-maven-plugin/blob/master/release.properties) - [Commits](https://github.com/ebean-orm-tools/ebean-maven-plugin/commits) Updates `io.ebean:ebean-maven-plugin` from 15.8.0 to 15.8.1 - [Release notes](https://github.com/ebean-orm-tools/ebean-maven-plugin/releases) - [Changelog](https://github.com/ebean-orm-tools/ebean-maven-plugin/blob/master/release.properties) - [Commits](https://github.com/ebean-orm-tools/ebean-maven-plugin/commits) Updates `ch.qos.logback:logback-classic` from 1.5.12 to 1.5.14 - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.12...v_1.5.14) --- updated-dependencies: - dependency-name: io.ebean:ebean-bom dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: io.ebean:ebean-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: io.ebean:ebean-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- examples/example-jdk/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index eb4104e6..b016dc9e 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -42,7 +42,7 @@ ch.qos.logback logback-classic - 1.5.12 + 1.5.14 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index ce75f473..613c7c9f 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -33,7 +33,7 @@ ch.qos.logback logback-classic - 1.5.12 + 1.5.14 diff --git a/pom.xml b/pom.xml index 8221ae25..7307b2e1 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ 1.2 2.9 1.36 - 15.8.0 + 15.8.1 From 15ffcd92afe4f6f962434b241fcd3b26be22f026 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:53:04 -0500 Subject: [PATCH 150/250] add robaho http server --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index 7307b2e1..67cfff5a 100644 --- a/pom.xml +++ b/pom.xml @@ -183,6 +183,11 @@ avaje-jex-static-content 3.0-RC12 + + io.github.robaho + httpserver + 1.0.11 + From ff52189f0dca389bae47acc2e86103c63a9de46c Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 22 Dec 2024 00:51:14 -0500 Subject: [PATCH 151/250] make apts provided scope (#152) the parent now sets the default maven scope to provided. This can help prevent accidentally using them with compile scope --- pom.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pom.xml b/pom.xml index 67cfff5a..15a5b0ae 100644 --- a/pom.xml +++ b/pom.xml @@ -116,42 +116,57 @@ io.avaje avaje-http-client-generator ${avaje.http.version} + provided + true io.avaje avaje-http-jex-generator ${avaje.http.version} + provided + true io.avaje avaje-inject-generator ${avaje.inject.version} + provided + true io.avaje avaje-jsonb-generator ${avaje.jsonb.version} + provided + true io.avaje avaje-validator-generator ${avaje.validator.version} + provided + true io.avaje avaje-prisms ${avaje.prisms.version} + provided true io.avaje avaje-record-builder ${avaje.record.builder.version} + provided + true io.avaje avaje-spi-service ${avaje.spi.version} + provided + true io.avaje From ebe7393474a15e00684e62832b231a3b5555cd07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 03:26:04 +0000 Subject: [PATCH 152/250] Bump the dependencies group with 2 updates Bumps the dependencies group with 2 updates: org.freemarker:freemarker and [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback). Updates `org.freemarker:freemarker` from 2.3.33 to 2.3.34 Updates `ch.qos.logback:logback-classic` from 1.5.14 to 1.5.15 - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.14...v_1.5.15) --- updated-dependencies: - dependency-name: org.freemarker:freemarker dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- avaje-jex-freemarker/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 7ddfe19e..3d00a31a 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -10,7 +10,7 @@ avaje-jex-freemarker - 2.3.33 + 2.3.34 diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index b016dc9e..542d6b99 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -42,7 +42,7 @@ ch.qos.logback logback-classic - 1.5.14 + 1.5.15 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 613c7c9f..1cd349e6 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -33,7 +33,7 @@ ch.qos.logback logback-classic - 1.5.14 + 1.5.15 From c044eb4f4f2e162a8d55c695fcd43b7096b16683 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 03:56:50 +0000 Subject: [PATCH 153/250] Bump the dependencies group with 4 updates Bumps the dependencies group with 4 updates: [io.avaje:avaje-inject](https://github.com/avaje/avaje-inject), io.avaje:avaje-inject-test, io.avaje:avaje-inject-generator and io.avaje:avaje-inject-maven-plugin. Updates `io.avaje:avaje-inject` from 11.1-RC1 to 11.1-RC2 - [Release notes](https://github.com/avaje/avaje-inject/releases) - [Commits](https://github.com/avaje/avaje-inject/commits) Updates `io.avaje:avaje-inject-test` from 11.1-RC1 to 11.1-RC2 Updates `io.avaje:avaje-inject-generator` from 11.1-RC1 to 11.1-RC2 Updates `io.avaje:avaje-inject-maven-plugin` from 11.1-RC1 to 11.1-RC2 Updates `io.avaje:avaje-inject-test` from 11.1-RC1 to 11.1-RC2 Updates `io.avaje:avaje-inject-generator` from 11.1-RC1 to 11.1-RC2 Updates `io.avaje:avaje-inject-maven-plugin` from 11.1-RC1 to 11.1-RC2 --- updated-dependencies: - dependency-name: io.avaje:avaje-inject dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-test dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-maven-plugin dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-test dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-maven-plugin dependency-type: direct:production dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 15a5b0ae..2e883117 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ 2024-12-17T22:48:35Z 4.0 - 11.1-RC1 + 11.1-RC2 3.0-RC5 2.9-RC7 9.4 From 69ae622f90682398d5424e25913edc52c36fa34d Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 5 Jan 2025 19:09:38 -0500 Subject: [PATCH 154/250] support custom compressors (#156) --- .../jex/compression/CompressedOutputStream.java | 8 +++++++- .../io/avaje/jex/compression/CompressionConfig.java | 12 +++++++++++- .../io/avaje/jex/compression/CompressionTest.java | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java index 244738d9..ce07b41e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; +import java.util.Objects; import java.util.Optional; import io.avaje.jex.Context; @@ -69,7 +70,12 @@ public void close() throws IOException { private Optional findMatchingCompressor(String acceptedEncoding) { if (acceptedEncoding != null) { - return Arrays.stream(acceptedEncoding.split(",")).map(compression::forType).findFirst(); + return Arrays.stream(acceptedEncoding.split(",")) + .map(e -> e.trim().split(";")[0]) + .map(e -> "*".equals(e) ? "gzip" : e.toLowerCase()) + .map(compression::forType) + .filter(Objects::nonNull) + .findFirst(); } return Optional.empty(); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java index c699026a..c01d46a5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java @@ -28,6 +28,16 @@ public final class CompressionConfig { private final Set allowedExcludedTypes = Set.of("image/svg+xml"); + /** + * Adds a custom compressor for a given encoding type. + * + * @param compressor The compressor to use. + */ + public CompressionConfig customCompressor(Compressor compressor) { + compressors.put(compressor.encoding(), compressor); + return this; + } + /** * Sets the default GZIP compression level. * @@ -99,6 +109,6 @@ public boolean allowsForCompression(String contentType) { * @return The compressor for the given Content-Encoding value, or null if not found. */ Compressor forType(String encoding) { - return compressors.get(encoding.toLowerCase()); + return compressors.get(encoding); } } diff --git a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java index d28aeeed..3c7854c1 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java @@ -45,7 +45,7 @@ static void end() { @Test void testCompression() { HttpResponse res = - pair.request().header(Constants.ACCEPT_ENCODING, "gzip").path("compress").GET().asString(); + pair.request().header(Constants.ACCEPT_ENCODING, "deflate, gzip;q=1.0, *;q=0.5").path("compress").GET().asString(); assertThat(res.statusCode()).isEqualTo(200); assertThat(res.headers().firstValue(Constants.CONTENT_ENCODING)).contains("gzip"); } From a90e1af0ba1ff37d6c4a9236b6c6eb6955cea22d Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:48:57 -0500 Subject: [PATCH 155/250] [static-content] add roles (#155) Now supports adding security roles for each resource --- .../avaje/jex/staticcontent/StaticContent.java | 8 +++++--- .../jex/staticcontent/StaticFileHandler.java | 2 -- .../StaticResourceHandlerBuilder.java | 7 +++++-- .../src/main/java/module-info.java | 2 +- .../staticcontent/CompressedStaticFileTest.java | 16 ++++++++-------- .../avaje/jex/staticcontent/StaticFileTest.java | 16 ++++++++-------- 6 files changed, 27 insertions(+), 24 deletions(-) diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java index d3baf45f..2a9e81ab 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java @@ -4,6 +4,7 @@ import java.util.function.Predicate; import io.avaje.jex.Context; +import io.avaje.jex.security.Role; import io.avaje.jex.spi.JexPlugin; /** @@ -58,15 +59,16 @@ sealed interface Builder permits StaticResourceHandlerBuilder { /** - * Sets the HTTP path for the static resource handler. + * Sets the HTTP route for the static resource handler. * * @param path the HTTP path prefix + * @param roles the security roles for the route * @return the updated configuration */ - Builder httpPath(String path); + Builder route(String path, Role... roles); /** - * Sets the index file to be served when a directory is requests. + * Sets the index file to be served when a directory is requested. * * @param directoryIndex the index file * @return the updated configuration diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java index 6c2ca991..da4564ba 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java @@ -1,6 +1,5 @@ package io.avaje.jex.staticcontent; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -11,7 +10,6 @@ import com.sun.net.httpserver.HttpExchange; import io.avaje.jex.Context; -import io.avaje.jex.compression.CompressedOutputStream; import io.avaje.jex.compression.CompressionConfig; final class StaticFileHandler extends AbstractStaticHandler { diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java index 2546758b..c21daaf9 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java @@ -11,6 +11,7 @@ import io.avaje.jex.ExchangeHandler; import io.avaje.jex.Jex; import io.avaje.jex.compression.CompressionConfig; +import io.avaje.jex.security.Role; final class StaticResourceHandlerBuilder implements StaticContent.Builder, StaticContent { @@ -29,6 +30,7 @@ final class StaticResourceHandlerBuilder implements StaticContent.Builder, Stati private Predicate skipFilePredicate = NO_OP_PREDICATE; private boolean isClasspath = true; private boolean precompress; + private Role[] roles = {}; private StaticResourceHandlerBuilder(String root) { this.root = root; @@ -40,7 +42,7 @@ static StaticResourceHandlerBuilder builder(String root) { @Override public void apply(Jex jex) { - jex.get(path, createHandler(jex.config().compression())); + jex.get(path, createHandler(jex.config().compression()), roles); } @Override @@ -73,8 +75,9 @@ ExchangeHandler createHandler(CompressionConfig compress) { } @Override - public StaticResourceHandlerBuilder httpPath(String path) { + public StaticResourceHandlerBuilder route(String path, Role... roles) { this.path = path.endsWith("/") ? path + "*" : path; + this.roles = roles; return this; } diff --git a/avaje-jex-static-content/src/main/java/module-info.java b/avaje-jex-static-content/src/main/java/module-info.java index e50bd8ed..54966043 100644 --- a/avaje-jex-static-content/src/main/java/module-info.java +++ b/avaje-jex-static-content/src/main/java/module-info.java @@ -1,5 +1,5 @@ /** - * Defines the Static Content API for serving static resources with Jex - see {@link StaticContent}. + * Defines the Static Content API for serving static resources with Jex - see {@link io.avaje.jex.staticcontent.StaticContent}. * *

    {@code
      * var staticContent = StaticContentService.createCP("/public").httpPath("/").directoryIndex("index.html");
    diff --git a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java
    index 86ab80b7..ae712260 100644
    --- a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java
    +++ b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java
    @@ -19,16 +19,16 @@ static TestPair init() {
     
         final Jex app =
             Jex.create()
    -            .plugin(defaultCP().httpPath("/index").build())
    -            .plugin(defaultFile().httpPath("/indexFile").build())
    -            .plugin(defaultCP().httpPath("/indexWild/*").build())
    -            .plugin(defaultFile().httpPath("/indexWildFile/*").build())
    -            .plugin(defaultCP().httpPath("/sus/").build())
    -            .plugin(defaultFile().httpPath("/susFile/*").build())
    -            .plugin(StaticContent.createCP("/logback.xml").httpPath("/single").build())
    +            .plugin(defaultCP().route("/index").build())
    +            .plugin(defaultFile().route("/indexFile").build())
    +            .plugin(defaultCP().route("/indexWild/*").build())
    +            .plugin(defaultFile().route("/indexWildFile/*").build())
    +            .plugin(defaultCP().route("/sus/").build())
    +            .plugin(defaultFile().route("/susFile/*").build())
    +            .plugin(StaticContent.createCP("/logback.xml").route("/single").build())
                 .plugin(
                     StaticContent.createFile("src/test/resources/logback.xml")
    -                    .httpPath("/singleFile").build());
    +                    .route("/singleFile").build());
     
         return TestPair.create(app);
       }
    diff --git a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java
    index f7290eb7..752828ba 100644
    --- a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java
    +++ b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java
    @@ -19,16 +19,16 @@ static TestPair init() {
     
         final Jex app =
             Jex.create()
    -            .plugin(defaultCP().httpPath("/index").build())
    -            .plugin(defaultFile().httpPath("/indexFile").build())
    -            .plugin(defaultCP().httpPath("/indexWild/*").build())
    -            .plugin(defaultFile().httpPath("/indexWildFile/*").build())
    -            .plugin(defaultCP().httpPath("/sus/").build())
    -            .plugin(defaultFile().httpPath("/susFile/*").build())
    -            .plugin(StaticContent.createCP("/logback.xml").httpPath("/single").build())
    +            .plugin(defaultCP().route("/index").build())
    +            .plugin(defaultFile().route("/indexFile").build())
    +            .plugin(defaultCP().route("/indexWild/*").build())
    +            .plugin(defaultFile().route("/indexWildFile/*").build())
    +            .plugin(defaultCP().route("/sus/").build())
    +            .plugin(defaultFile().route("/susFile/*").build())
    +            .plugin(StaticContent.createCP("/logback.xml").route("/single").build())
                 .plugin(
                     StaticContent.createFile("src/test/resources/logback.xml")
    -                    .httpPath("/singleFile").build());
    +                    .route("/singleFile").build());
     
         return TestPair.create(app);
       }
    
    From 7edcdcca72d8c1439edd233bf19b14971a352549 Mon Sep 17 00:00:00 2001
    From: Rob Bygrave 
    Date: Mon, 6 Jan 2025 15:56:21 +1300
    Subject: [PATCH 156/250] Rename method CompressionConfig.compressor() (#157)
    
    ---
     .../java/io/avaje/jex/compression/CompressionConfig.java    | 6 +++---
     1 file changed, 3 insertions(+), 3 deletions(-)
    
    diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java
    index c01d46a5..b1f318b2 100644
    --- a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java
    +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressionConfig.java
    @@ -4,7 +4,7 @@
     import java.util.Map;
     import java.util.Set;
     
    -/** Configuration class for compression settings. */
    +/** Configuration for compression settings. */
     public final class CompressionConfig {
     
       private static final int HTTP_PACKET_SIZE = 1500;
    @@ -29,11 +29,11 @@ public final class CompressionConfig {
       private final Set allowedExcludedTypes = Set.of("image/svg+xml");
     
       /**
    -   * Adds a custom compressor for a given encoding type.
    +   * Adds a compressor for a given encoding type.
        *
        * @param compressor The compressor to use.
        */
    -  public CompressionConfig customCompressor(Compressor compressor) {
    +  public CompressionConfig compressor(Compressor compressor) {
         compressors.put(compressor.encoding(), compressor);
         return this;
       }
    
    From af41e868d80ecf973878b70a28d92e610a3f3c61 Mon Sep 17 00:00:00 2001
    From: Rob Bygrave 
    Date: Mon, 6 Jan 2025 15:58:21 +1300
    Subject: [PATCH 157/250] Version 3.0-RC13
    
    ---
     avaje-jex-freemarker/pom.xml     |  2 +-
     avaje-jex-htmx/pom.xml           |  2 +-
     avaje-jex-mustache/pom.xml       |  2 +-
     avaje-jex-static-content/pom.xml |  2 +-
     avaje-jex-test/pom.xml           |  2 +-
     avaje-jex/pom.xml                |  2 +-
     examples/example-jdk/pom.xml     |  2 +-
     examples/example-robaho/pom.xml  |  2 +-
     examples/pom.xml                 |  2 +-
     pom.xml                          | 16 ++++++++--------
     10 files changed, 17 insertions(+), 17 deletions(-)
    
    diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml
    index 3d00a31a..5a6f2c96 100644
    --- a/avaje-jex-freemarker/pom.xml
    +++ b/avaje-jex-freemarker/pom.xml
    @@ -4,7 +4,7 @@
       
         avaje-jex-parent
         io.avaje
    -    3.0-RC12
    +    3.0-RC13
       
     
       avaje-jex-freemarker
    diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml
    index e131aa27..b530d26d 100644
    --- a/avaje-jex-htmx/pom.xml
    +++ b/avaje-jex-htmx/pom.xml
    @@ -6,7 +6,7 @@
       
         io.avaje
         avaje-jex-parent
    -    3.0-RC12
    +    3.0-RC13
       
     
       avaje-jex-htmx
    diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml
    index 9a72abb5..b11d810c 100644
    --- a/avaje-jex-mustache/pom.xml
    +++ b/avaje-jex-mustache/pom.xml
    @@ -4,7 +4,7 @@
       
         avaje-jex-parent
         io.avaje
    -    3.0-RC12
    +    3.0-RC13
       
     
       avaje-jex-mustache
    diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml
    index 0575c526..6e8f2e7a 100644
    --- a/avaje-jex-static-content/pom.xml
    +++ b/avaje-jex-static-content/pom.xml
    @@ -5,7 +5,7 @@
       
         io.avaje
         avaje-jex-parent
    -    3.0-RC12
    +    3.0-RC13
       
       avaje-jex-static-content
       
    diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml
    index d2041b0e..15cfd3ac 100644
    --- a/avaje-jex-test/pom.xml
    +++ b/avaje-jex-test/pom.xml
    @@ -4,7 +4,7 @@
       
         avaje-jex-parent
         io.avaje
    -    3.0-RC12
    +    3.0-RC13
       
     
       avaje-jex-test
    diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml
    index a8ab6d36..ac832e41 100644
    --- a/avaje-jex/pom.xml
    +++ b/avaje-jex/pom.xml
    @@ -4,7 +4,7 @@
       
         io.avaje
         avaje-jex-parent
    -    3.0-RC12
    +    3.0-RC13
       
     
       avaje-jex
    diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml
    index 542d6b99..8e1deae2 100644
    --- a/examples/example-jdk/pom.xml
    +++ b/examples/example-jdk/pom.xml
    @@ -24,7 +24,7 @@
         
           io.avaje
           avaje-jex
    -      3.0-RC12
    +      3.0-RC13
         
     
         
    diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml
    index 1cd349e6..8b15a464 100644
    --- a/examples/example-robaho/pom.xml
    +++ b/examples/example-robaho/pom.xml
    @@ -21,7 +21,7 @@
         
           io.avaje
           avaje-jex
    -      3.0-RC12
    +      3.0-RC13
         
     
         
    diff --git a/examples/pom.xml b/examples/pom.xml
    index 1c47cb1c..9262e6e4 100644
    --- a/examples/pom.xml
    +++ b/examples/pom.xml
    @@ -5,7 +5,7 @@
       
         avaje-jex-parent
         io.avaje
    -    3.0-RC12
    +    3.0-RC13
       
     
       examples
    diff --git a/pom.xml b/pom.xml
    index 2e883117..f0ac7656 100644
    --- a/pom.xml
    +++ b/pom.xml
    @@ -11,7 +11,7 @@
     
       io.avaje
       avaje-jex-parent
    -  3.0-RC12
    +  3.0-RC13
       pom
     
       
    @@ -25,7 +25,7 @@
         false
         21
         full
    -    2024-12-17T22:48:35Z
    +    2025-01-06T02:57:10Z
     
         4.0
         11.1-RC2
    @@ -171,32 +171,32 @@
           
             io.avaje
             avaje-jex
    -        3.0-RC12
    +        3.0-RC13
           
           
             io.avaje
             avaje-jex-test
    -        3.0-RC12
    +        3.0-RC13
           
           
             io.avaje
             avaje-jex-freemarker
    -        3.0-RC12
    +        3.0-RC13
           
           
             io.avaje
             avaje-jex-mustache
    -        3.0-RC12
    +        3.0-RC13
           
           
             io.avaje
             avaje-jex-htmx
    -        3.0-RC12
    +        3.0-RC13
           
           
             io.avaje
             avaje-jex-static-content
    -        3.0-RC12
    +        3.0-RC13
           
           
             io.github.robaho
    
    From 08cba43299d1ae8f9455490469aeeb86bea4632a Mon Sep 17 00:00:00 2001
    From: Josiah Noel <32279667+SentryMan@users.noreply.github.com>
    Date: Sun, 5 Jan 2025 22:34:24 -0500
    Subject: [PATCH 158/250] Fix compression error log (#158)
    
    * fix compression error log
    
    fixes an issue where the outputstream is flushed after sending a compressed response
    
    * some docs
    
    * Update CompressionTest.java
    ---
     .../src/main/java/io/avaje/jex/Context.java   |  8 +++++---
     .../io/avaje/jex/core/BufferedOutStream.java  |  4 +++-
     .../jex/compression/CompressionTest.java      | 20 ++++++++++++++++---
     pom.xml                                       |  7 +++++++
     4 files changed, 32 insertions(+), 7 deletions(-)
    
    diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java
    index 9ad84781..aac84e45 100644
    --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java
    +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java
    @@ -432,10 +432,12 @@ default Context headers(Map headers) {
       String protocol();
     
       /**
    -   * Gets basic-auth credentials from the request, or throws.
    +   * Gets basic-auth credentials from the request.
        *
    -   * 

    Returns a wrapper object containing the Base64 decoded username and password from the - * Authorization header, or null if basic-auth is not properly configured + * @return The Base64 decoded username and password from the + * Authorization header, or null if no header is sent + * + * @throws IllegalStateException if the Authorization header is malformed */ BasicAuthCredentials basicAuthCredentials(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java b/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java index 3d0482eb..2948d254 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java @@ -60,7 +60,9 @@ private void initialiseChunked() throws IOException { @Override public void close() throws IOException { if (stream != null) { - stream.flush(); + if (!context.responseSent()) { + stream.flush(); + } stream.close(); } else { context.write(buffer.toByteArray()); diff --git a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java index 3c7854c1..4091e88b 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java @@ -2,7 +2,10 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.io.IOException; import java.net.http.HttpResponse; +import java.util.Arrays; +import java.util.zip.GZIPInputStream; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; @@ -43,11 +46,22 @@ static void end() { } @Test - void testCompression() { - HttpResponse res = - pair.request().header(Constants.ACCEPT_ENCODING, "deflate, gzip;q=1.0, *;q=0.5").path("compress").GET().asString(); + void testCompression() throws IOException { + var res = + pair.request() + .header(Constants.ACCEPT_ENCODING, "deflate, gzip;q=1.0, *;q=0.5") + .path("compress") + .GET() + .asInputStream(); assertThat(res.statusCode()).isEqualTo(200); assertThat(res.headers().firstValue(Constants.CONTENT_ENCODING)).contains("gzip"); + + var expected = CompressionTest.class.getResourceAsStream("/64KB.json").readAllBytes(); + + final var gzipInputStream = new GZIPInputStream(res.body()); + var decompressed = gzipInputStream.readAllBytes(); + gzipInputStream.close(); + assertThat(decompressed).isEqualTo(expected); } @Test diff --git a/pom.xml b/pom.xml index f0ac7656..a4b88f85 100644 --- a/pom.xml +++ b/pom.xml @@ -168,6 +168,13 @@ provided true + + io.ebean + ${ebean.version} + querybean-generator + provided + true + io.avaje avaje-jex From 8f2419415bfb07d8ecf1d91e51530ab5df134889 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 03:37:16 +0000 Subject: [PATCH 159/250] Bump the dependencies group with 2 updates Bumps the dependencies group with 2 updates: [io.github.robaho:httpserver](https://github.com/robaho/httpserver) and [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback). Updates `io.github.robaho:httpserver` from 1.0.11 to 1.0.17 - [Commits](https://github.com/robaho/httpserver/commits) Updates `ch.qos.logback:logback-classic` from 1.5.15 to 1.5.16 - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.15...v_1.5.16) --- updated-dependencies: - dependency-name: io.github.robaho:httpserver dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- examples/example-jdk/pom.xml | 2 +- examples/example-robaho/pom.xml | 4 ++-- pom.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 8e1deae2..3cc8bb54 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -42,7 +42,7 @@ ch.qos.logback logback-classic - 1.5.15 + 1.5.16 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 8b15a464..276ec8b1 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -15,7 +15,7 @@ io.github.robaho httpserver - 1.0.11 + 1.0.17 @@ -33,7 +33,7 @@ ch.qos.logback logback-classic - 1.5.15 + 1.5.16 diff --git a/pom.xml b/pom.xml index a4b88f85..5ab51405 100644 --- a/pom.xml +++ b/pom.xml @@ -208,7 +208,7 @@ io.github.robaho httpserver - 1.0.11 + 1.0.17 From 2777683f365b36ccd5bf19884fe2075ed33f055a Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:28:00 -0500 Subject: [PATCH 160/250] [boot] autoConfigure ssl if present (#160) --- avaje-jex/src/main/java/io/avaje/jex/DJex.java | 4 +++- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 2 +- .../src/test/java/io/avaje/jex/core/HealthPluginOffTest.java | 2 +- examples/example-robaho/src/main/java/io/avaje/Main.java | 3 --- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index 724da287..36abb7ea 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -3,6 +3,7 @@ import io.avaje.inject.BeanScope; import io.avaje.jex.core.BootstrapServer; import io.avaje.jex.spi.*; +import com.sun.net.httpserver.HttpsConfigurator; import java.util.*; import java.util.function.Consumer; @@ -55,11 +56,12 @@ public Jex configureWith(BeanScope beanScope) { } routing.addAll(beanScope.list(Routing.HttpService.class)); beanScope.getOptional(JsonService.class).ifPresent(this::jsonService); + beanScope.getOptional(HttpsConfigurator.class).ifPresent(config()::httpsConfig); return this; } @Override - public Jex configure(Consumer configure) { + public Jex config(Consumer configure) { configure.accept(config); return this; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 6c3ee5bb..e4530b1c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -225,7 +225,7 @@ default Jex group(String path, HttpService group) { * @param configure A consumer lambda that accepts a {@link JexConfig} instance for configuration. * @return The configured Jex instance. */ - Jex configure(Consumer configure); + Jex config(Consumer configure); /** * Sets the port number on which the Jex server will listen for incoming requests. diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/HealthPluginOffTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/HealthPluginOffTest.java index e8c51345..790b9a5c 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/HealthPluginOffTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/HealthPluginOffTest.java @@ -14,7 +14,7 @@ class HealthPluginOffTest { static TestPair init() { final Jex app = Jex.create() - .configure(config -> config.health(false)) + .config(config -> config.health(false)) .routing(routing -> routing .get("/", ctx -> ctx.text("hello")) ); diff --git a/examples/example-robaho/src/main/java/io/avaje/Main.java b/examples/example-robaho/src/main/java/io/avaje/Main.java index d7366d27..32b594b8 100644 --- a/examples/example-robaho/src/main/java/io/avaje/Main.java +++ b/examples/example-robaho/src/main/java/io/avaje/Main.java @@ -6,9 +6,6 @@ public class Main { public static void main(String[] args) { - // Below system property is NOT required as it will register via service loading - // System.setProperty("com.sun.net.httpserver.HttpServerProvider", "robaho.net.httpserver.DefaultHttpServerProvider"); - Jex.create() .routing(routing -> routing .get("/", ctx -> ctx.text("root")) From 33fcf8d1b6cf010cd908d395945829f77cd6aab4 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:02:51 -0500 Subject: [PATCH 161/250] [boot] autoConfigure ssl if present (#160) From ecdcd14c1d664a03ae080ad88afa679fbd820b49 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 9 Jan 2025 15:05:32 -0500 Subject: [PATCH 162/250] remove unnecessary flush (#161) --- .../src/main/java/io/avaje/jex/core/BufferedOutStream.java | 3 --- avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java | 1 - 2 files changed, 4 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java b/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java index 2948d254..d3b5bae9 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java @@ -60,9 +60,6 @@ private void initialiseChunked() throws IOException { @Override public void close() throws IOException { if (stream != null) { - if (!context.responseSent()) { - stream.flush(); - } stream.close(); } else { context.write(buffer.toByteArray()); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index 6b41a595..f448ece1 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -371,7 +371,6 @@ public void write(byte[] bytes) { try (var os = exchange.getResponseBody()) { exchange.sendResponseHeaders(statusCode(), bytes.length == 0 ? -1 : bytes.length); os.write(bytes); - os.flush(); } catch (IOException e) { throw new UncheckedIOException(e); } From 4d50e9bb458cb8a9ae41f65ca5e18493ef1694d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 03:53:07 +0000 Subject: [PATCH 163/250] Bump the dependencies group with 6 updates Bumps the dependencies group with 6 updates: | Package | From | To | | --- | --- | --- | | io.avaje:avaje-logback-encoder | `0.5` | `0.9` | | [io.avaje:avaje-inject](https://github.com/avaje/avaje-inject) | `11.1-RC2` | `11.1` | | io.avaje:avaje-inject-test | `11.1-RC2` | `11.1` | | io.avaje:avaje-inject-generator | `11.1-RC2` | `11.1` | | io.avaje:avaje-inject-maven-plugin | `11.1-RC2` | `11.1` | | [io.github.robaho:httpserver](https://github.com/robaho/httpserver) | `1.0.17` | `1.0.19` | Updates `io.avaje:avaje-logback-encoder` from 0.5 to 0.9 Updates `io.avaje:avaje-inject` from 11.1-RC2 to 11.1 - [Release notes](https://github.com/avaje/avaje-inject/releases) - [Commits](https://github.com/avaje/avaje-inject/commits/11.1) Updates `io.avaje:avaje-inject-test` from 11.1-RC2 to 11.1 Updates `io.avaje:avaje-inject-generator` from 11.1-RC2 to 11.1 Updates `io.avaje:avaje-inject-maven-plugin` from 11.1-RC2 to 11.1 Updates `io.avaje:avaje-inject-test` from 11.1-RC2 to 11.1 Updates `io.avaje:avaje-inject-generator` from 11.1-RC2 to 11.1 Updates `io.github.robaho:httpserver` from 1.0.17 to 1.0.19 - [Commits](https://github.com/robaho/httpserver/compare/1.0.17...1.0.19) Updates `io.avaje:avaje-inject-maven-plugin` from 11.1-RC2 to 11.1 --- updated-dependencies: - dependency-name: io.avaje:avaje-logback-encoder dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-inject dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-test dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-maven-plugin dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-test dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.github.robaho:httpserver dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-maven-plugin dependency-type: direct:production dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- examples/example-robaho/pom.xml | 2 +- pom.xml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 276ec8b1..10aea4cc 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -15,7 +15,7 @@ io.github.robaho httpserver - 1.0.17 + 1.0.19 diff --git a/pom.xml b/pom.xml index 5ab51405..ac279ac8 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ 2025-01-06T02:57:10Z 4.0 - 11.1-RC2 + 11.1 3.0-RC5 2.9-RC7 9.4 @@ -60,7 +60,7 @@ io.avaje avaje-logback-encoder - 0.5 + 0.9 io.avaje @@ -208,7 +208,7 @@ io.github.robaho httpserver - 1.0.17 + 1.0.19 From c7ab15a5d36be3e2ebc7a3af8c208ebda7327c99 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 14 Jan 2025 03:18:44 -0500 Subject: [PATCH 164/250] make response buffer configurable (#163) --- .../main/java/io/avaje/jex/DJexConfig.java | 24 +++++++++++++++ .../src/main/java/io/avaje/jex/JexConfig.java | 30 ++++++++++++++++++- .../io/avaje/jex/core/BufferedOutStream.java | 23 +++++++------- .../io/avaje/jex/core/SpiServiceManager.java | 14 +++++++-- 4 files changed, 77 insertions(+), 14 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index dbb14662..50b2f093 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -26,6 +26,8 @@ final class DJexConfig implements JexConfig { private HttpsConfigurator httpsConfig; private boolean useJexSpi = true; private final CompressionConfig compression = new CompressionConfig(); + private int bufferInitial = 256; + private long bufferMax = 1024L; @Override public JexConfig host(String host) { @@ -171,4 +173,26 @@ public DJexConfig disableSpiPlugins() { public boolean useSpiPlugins() { return useJexSpi; } + + @Override + public long maxStreamBufferSize() { + return bufferMax; + } + + @Override + public int initialStreamBufferSize() { + return bufferInitial; + } + + @Override + public JexConfig initialStreamBufferSize(int initialSize) { + bufferInitial = initialSize; + return this; + } + + @Override + public JexConfig maxStreamBufferSize(long maxSize) { + bufferMax = maxSize; + return this; + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index ea5bc43a..4b2265d8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -49,7 +49,8 @@ public sealed interface JexConfig permits DJexConfig { JexConfig disableSpiPlugins(); /** - * Executor for serving requests. Defaults to a {@link Executors#newVirtualThreadPerTaskExecutor()} + * Executor for serving requests. Defaults to a {@link + * Executors#newVirtualThreadPerTaskExecutor()} */ Executor executor(); @@ -100,6 +101,19 @@ public sealed interface JexConfig permits DJexConfig { */ JexConfig ignoreTrailingSlashes(boolean ignoreTrailingSlashes); + /** The initial size of the response buffer */ + int initialStreamBufferSize(); + + /** + * Set the initial size of the response stream buffer. If exceeded, the buffer will expand until + * it reaches the maximum configured size + * + *

    Defaults to 256 + * + * @param initialSize The initial size of the response buffer + */ + JexConfig initialStreamBufferSize(int initialSize); + /** Returns the configured JSON service. */ JsonService jsonService(); @@ -110,6 +124,20 @@ public sealed interface JexConfig permits DJexConfig { */ JexConfig jsonService(JsonService jsonService); + /** the maximum size of the response stream buffer. */ + long maxStreamBufferSize(); + + /** + * Set the maximum size of the response stream buffer. If the response data exceeds this size, + * then it will be written to the client using chunked transfer encoding. Otherwise, the response + * will be sent using a Content-Length header with the exact size of the response data. + * + *

    Defaults to 1024 + * + * @param maxSize The maximum size of the response + */ + JexConfig maxStreamBufferSize(long maxSize); + /** Returns the configured port number. (Defaults to 8080 if not set) */ int port(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java b/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java index d3b5bae9..48bbe9fd 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java @@ -8,18 +8,17 @@ final class BufferedOutStream extends OutputStream { - private static final long MAX = Long.getLong("jex.outputBuffer.max", 1024); - private static final int INITIAL = - Integer.getInteger("jex.outputBuffer.initial", 256); - + private final long max; private final JdkContext context; private ByteArrayOutputStream buffer; private OutputStream stream; private long count; - BufferedOutStream(JdkContext context) { + BufferedOutStream(JdkContext context, int initial, long max) { + this.context = context; - this.buffer = new ByteArrayOutputStream(INITIAL); + this.max = max; + this.buffer = new ByteArrayOutputStream(initial); } @Override @@ -27,10 +26,12 @@ public void write(int b) throws IOException { if (stream != null) { stream.write(b); } else { - buffer.write(b); - if (count++ > MAX) { + if (count++ > max) { initialiseChunked(); + stream.write(b); + return; } + buffer.write(b); } } @@ -40,10 +41,12 @@ public void write(byte[] b, int off, int len) throws IOException { stream.write(b, off, len); } else { count += len; - buffer.write(b, off, len); - if (count > MAX) { + if (count > max) { initialiseChunked(); + stream.write(b, off, len); + return; } + buffer.write(b, off, len); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java index 9568c30a..b706053f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java @@ -35,6 +35,8 @@ final class SpiServiceManager { private final TemplateManager templateManager; private final String scheme; private final String contextPath; + private final int bufferInitial; + private final long bufferMax; static SpiServiceManager create(Jex jex) { return new Builder(jex).build(); @@ -45,16 +47,20 @@ static SpiServiceManager create(Jex jex) { ExceptionManager manager, TemplateManager templateManager, String scheme, - String contextPath) { + String contextPath, + long bufferMax, + int bufferInitial) { this.jsonService = jsonService; this.exceptionHandler = manager; this.templateManager = templateManager; this.scheme = scheme; this.contextPath = contextPath; + this.bufferInitial = bufferInitial; + this.bufferMax = bufferMax; } OutputStream createOutputStream(JdkContext jdkContext) { - return new BufferedOutStream(jdkContext); + return new BufferedOutStream(jdkContext, bufferInitial, bufferMax); } T fromJson(Class type, InputStream is) { @@ -165,7 +171,9 @@ SpiServiceManager build() { new ExceptionManager(jex.routing().errorHandlers()), initTemplateMgr(), jex.config().scheme(), - jex.config().contextPath()); + jex.config().contextPath(), + jex.config().maxStreamBufferSize(), + jex.config().initialStreamBufferSize()); } JsonService initJsonService() { From 03c4c40790d360bd34bf449e9b500bf643ebffb7 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 15 Jan 2025 07:52:22 +1300 Subject: [PATCH 165/250] Version 3.0-RC14 --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-htmx/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 16 ++++++++-------- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 5a6f2c96..4d2de645 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC13 + 3.0-RC14 avaje-jex-freemarker diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index b530d26d..aedc00df 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC13 + 3.0-RC14 avaje-jex-htmx diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index b11d810c..bbdf0b60 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC13 + 3.0-RC14 avaje-jex-mustache diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index 6e8f2e7a..938558e5 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC13 + 3.0-RC14 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 15cfd3ac..e20139ab 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC13 + 3.0-RC14 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index ac832e41..05ff1ff2 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC13 + 3.0-RC14 avaje-jex diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 3cc8bb54..a687fdf0 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC13 + 3.0-RC14 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 10aea4cc..19812141 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC13 + 3.0-RC14 diff --git a/examples/pom.xml b/examples/pom.xml index 9262e6e4..ea2a2964 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC13 + 3.0-RC14 examples diff --git a/pom.xml b/pom.xml index ac279ac8..2898e839 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ io.avaje avaje-jex-parent - 3.0-RC13 + 3.0-RC14 pom @@ -25,7 +25,7 @@ false 21 full - 2025-01-06T02:57:10Z + 2025-01-14T18:49:50Z 4.0 11.1 @@ -178,32 +178,32 @@ io.avaje avaje-jex - 3.0-RC13 + 3.0-RC14 io.avaje avaje-jex-test - 3.0-RC13 + 3.0-RC14 io.avaje avaje-jex-freemarker - 3.0-RC13 + 3.0-RC14 io.avaje avaje-jex-mustache - 3.0-RC13 + 3.0-RC14 io.avaje avaje-jex-htmx - 3.0-RC13 + 3.0-RC14 io.avaje avaje-jex-static-content - 3.0-RC13 + 3.0-RC14 io.github.robaho From abe8b7d98282ba746cf732021c2146c80342a9a1 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 17 Jan 2025 14:24:56 -0500 Subject: [PATCH 166/250] add sslSession method (#165) --- avaje-jex/src/main/java/io/avaje/jex/Context.java | 10 ++++++++++ .../src/main/java/io/avaje/jex/core/JdkContext.java | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index aac84e45..a5cd5917 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -13,6 +13,9 @@ import java.util.Map; import java.util.Set; import java.util.stream.Stream; + +import javax.net.ssl.SSLSession; + import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; @@ -444,6 +447,13 @@ default Context headers(Map headers) { /** Return true if the response has been sent. */ boolean responseSent(); + /** + * Get the {@link SSLSession} for this exchange. + * + * @return the {@code SSLSession} + */ + SSLSession sslSession(); + /** * This interface represents a cookie used in HTTP communication. Cookies are small pieces of data * sent from a server to a web browser and stored on the user's computer. They can be used to diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index f448ece1..e26bccb6 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -26,8 +26,11 @@ import java.util.Set; import java.util.stream.Stream; +import javax.net.ssl.SSLSession; + import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpsExchange; import io.avaje.jex.Context; import io.avaje.jex.compression.CompressedOutputStream; @@ -519,4 +522,10 @@ private static BasicAuthCredentials getBasicAuthCredentials(String authorization return new BasicAuthCredentials(credentials[0], credentials[1]); } + + @Override + public SSLSession sslSession() { + + return exchange instanceof HttpsExchange ex ? ex.getSSLSession() : null; + } } From 4484bffe8d677e19ad9b85a9ba4b6be0cd4f20b9 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 17 Jan 2025 15:24:01 -0500 Subject: [PATCH 167/250] Sort Context members (#166) --- .../src/main/java/io/avaje/jex/Context.java | 586 +++++++++--------- .../java/io/avaje/jex/core/JdkContext.java | 450 +++++++------- 2 files changed, 519 insertions(+), 517 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index a5cd5917..bec468cb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -26,20 +26,6 @@ /** Provides access to functions for handling the request and response. */ public interface Context { - /** - * Returns the matched path as a raw expression, without any parameter substitution. - * - * @return The matched path as a raw string. - */ - String matchedPath(); - - /** - * Sets an attribute on the request, accessible to other handlers in the request lifecycle. - * - * @param key The attribute key. - * @param value The attribute value. - */ - Context attribute(String key, Object value); /** * Gets the attribute with the specified key from the request. @@ -51,80 +37,25 @@ public interface Context { T attribute(String key); /** - * Returns the value of a cookie with the specified name from the request. - * - * @param name The name of the cookie. - * @return The value of the cookie, or null if the cookie is not found. - */ - String cookie(String name); - - /** - * Returns a map containing all the cookie names and their corresponding values from the request. - * - * @return A map of cookie names to their values. - */ - Map cookieMap(); - - /** - * Sets a cookie with the specified name and value, with no expiration date. - * - * @param name The name of the cookie. - * @param value The value of the cookie. - */ - Context cookie(String name, String value); - - /** - * Sets a cookie with the specified name, value, and maximum age in seconds. - * - * @param name The name of the cookie. - * @param value The value of the cookie. - * @param maxAge The maximum age of the cookie in seconds. - */ - Context cookie(String name, String value, int maxAge); - - /** - * Sets a cookie using the provided {@link Cookie} object. - * - * @param cookie The cookie object to set. - */ - Context cookie(Cookie cookie); - - /** - * Removes a cookie with the specified name. - * - * @param name The name of the cookie to remove. - */ - Context removeCookie(String name); - - /** - * Removes a cookie with the specified name and path. + * Sets an attribute on the request, accessible to other handlers in the request lifecycle. * - * @param name The name of the cookie to remove. - * @param path The path of the cookie to remove. + * @param key The attribute key. + * @param value The attribute value. */ - Context removeCookie(String name, String path); + Context attribute(String key, Object value); /** - * Redirects the client to the specified location using a 302 (Found) status code. + * Gets basic-auth credentials from the request. * - * @param location The URL to redirect to. - */ - void redirect(String location); - - /** - * Redirects the client to the specified location using the given HTTP status code. + * @return The Base64 decoded username and password from the + * Authorization header, or null if no header is sent * - * @param location The URL to redirect to. - * @param httpStatusCode The HTTP status code to use for the redirect. + * @throws IllegalStateException if the Authorization header is malformed */ - void redirect(String location, int httpStatusCode); + BasicAuthCredentials basicAuthCredentials(); - /** - * Returns a set of roles associated with the current route. - * - * @return A set of roles. - */ - Set routeRoles(); + /** Return the request body as String. */ + String body(); /** * Returns the request body as a byte array. @@ -133,13 +64,6 @@ public interface Context { */ byte[] bodyAsBytes(); - /** - * Returns the request body as an input stream. - * - * @return The request body as an input stream. - */ - InputStream bodyAsInputStream(); - /*** * Return the request body as bean. * @@ -147,6 +71,13 @@ public interface Context { */ T bodyAsClass(Class beanType); + /** + * Returns the request body as an input stream. + * + * @return The request body as an input stream. + */ + InputStream bodyAsInputStream(); + /*** * Return the request body as bean. * @@ -154,9 +85,6 @@ public interface Context { */ T bodyAsType(Type beanType); - /** Return the request body as String. */ - String body(); - /** Return the request content length. */ long contentLength(); @@ -166,45 +94,50 @@ public interface Context { /** Set the response content type. */ Context contentType(String contentType); - /** Return all the path parameters as a map. */ - Map pathParamMap(); + /** Return the request context path. */ + String contextPath(); /** - * Return the path parameter. + * Sets a cookie using the provided {@link Cookie} object. * - * @param name The path parameter name. + * @param cookie The cookie object to set. */ - String pathParam(String name); + Context cookie(Cookie cookie); /** - * Return the first query parameter value. + * Returns the value of a cookie with the specified name from the request. * - * @param name The query parameter name + * @param name The name of the cookie. + * @return The value of the cookie, or null if the cookie is not found. */ - String queryParam(String name); + String cookie(String name); /** - * Return the first query parameter value or the default value if it does not exist. + * Sets a cookie with the specified name and value, with no expiration date. * - * @param name The query parameter name + * @param name The name of the cookie. + * @param value The value of the cookie. */ - default String queryParam(String name, String defaultValue) { - String val = queryParam(name); - return val != null ? val : defaultValue; - } + Context cookie(String name, String value); - /** Return all the query parameters for the given parameter name. */ - List queryParams(String name); + /** + * Sets a cookie with the specified name, value, and maximum age in seconds. + * + * @param name The name of the cookie. + * @param value The value of the cookie. + * @param maxAge The maximum age of the cookie in seconds. + */ + Context cookie(String name, String value, int maxAge); /** - * Return all the query parameters as a map. + * Returns a map containing all the cookie names and their corresponding values from the request. * - *

    Note this returns the first value for any given key if that key has multiple values. + * @return A map of cookie names to their values. */ - Map queryParamMap(); + Map cookieMap(); - /** Return the request query string, or null. */ - String queryString(); + /** Return the underlying JDK {@link HttpExchange} object backing the context */ + HttpExchange exchange(); /** Return the first form param value for the specified key or null. */ default String formParam(String key) { @@ -217,24 +150,15 @@ default String formParam(String key, String defaultValue) { return values == null || values.isEmpty() ? defaultValue : values.get(0); } + /** Returns a map with all the form param keys and values. */ + Map> formParamMap(); + /** Return the form params for the specified key, or empty list. */ default List formParams(String key) { final List values = formParamMap().get(key); return values != null ? values : emptyList(); } - /** Returns a map with all the form param keys and values. */ - Map> formParamMap(); - - /** Return the underlying JDK {@link HttpExchange} object backing the context */ - HttpExchange exchange(); - - /** Return the request scheme. */ - String scheme(); - - /** Return the request url. */ - String url(); - /** Return the full request url, including query string (if present) */ default String fullUrl() { final String url = url(); @@ -242,40 +166,81 @@ default String fullUrl() { return qs == null ? url : url + '?' + qs; } - /** Return the request context path. */ - String contextPath(); + /** + * Return the request header. + * + * @param key The header key + */ + String header(String key); - /** Return the request user agent, or null. */ - default String userAgent() { - return header(Constants.USER_AGENT); - } + /** + * Set the response header. + * + * @param key The header key + * @param value The header value + */ + Context header(String key, List value); - /** Set the status code on the response. */ - Context status(int statusCode); + /** + * Set the response header. + * + * @param key The header key + * @param value The header value + */ + Context header(String key, String value); - /** Return the current response status. */ - int status(); + /** + * Return all the request headers as a map. + * + * @return all the headers as a single value Map + */ + Map headerMap(); - /** Write plain text content to the response. */ - void text(String content); + /** + * Sets the response headers using the provided map. + * + * @param headers A map containing the header names as keys and their corresponding values as + * lists. + * @return The updated context object. + */ + Context headerMap(Map> headers); + + /** + * Return underlying request headers. + * + * @return the request headers + */ + Headers headers(); + + /** Add the response headers using the provided map. */ + default Context headers(Map headers) { + headers.forEach(this::header); + return this; + } + + /** + * Returns the host name of the request. + * + * @return The host name of the request, or null if not available. + */ + String host(); /** Write html content to the response. */ void html(String content); /** - * Set the content type as application/json and write the response. + * Returns the IP address of the client making the request. * - * @param bean the object to serialize and write + * @return The IP address of the client. */ - void json(Object bean); + String ip(); /** - * Write the stream as a JSON stream with new line delimiters {@literal - * application/x-json-stream}. + * Set the content type as application/json and write the response. * - * @param stream The stream of beans to write as json + * @param bean the object to serialize and write */ - void jsonStream(Stream stream); + void json(Object bean); /** * Write the stream as a JSON stream with new line delimiters {@literal @@ -286,25 +251,26 @@ default String userAgent() { void jsonStream(Iterator iterator); /** - * Writes the given string content directly to the response. + * Write the stream as a JSON stream with new line delimiters {@literal + * application/x-json-stream}. * - * @param content The string content to write. + * @param stream The stream of beans to write as json */ - void write(String content); + void jsonStream(Stream stream); /** - * Writes the given bytes directly to the response. + * Returns the matched path as a raw expression, without any parameter substitution. * - * @param bytes The byte array to write. + * @return The matched path as a raw string. */ - void write(byte[] bytes); + String matchedPath(); /** - * Writes the content from the given InputStream directly to the response body. + * Returns the HTTP method used in the request (e.g., GET, POST, PUT, DELETE). * - * @param is The input stream containing the content to write. + * @return The HTTP method of the request. */ - void write(InputStream is); + String method(); /** * Return the outputStream to write content. It is expected that @@ -316,73 +282,112 @@ default String userAgent() { OutputStream outputStream(); /** - * Render a template typically as html. + * Returns the path part of the request URI. * - * @param name The template name + * @return The path part of the request URI. */ - default Context render(String name) { - return render(name, emptyMap()); + String path(); + + /** + * Return the path parameter. + * + * @param name The path parameter name. + */ + String pathParam(String name); + + /** Return all the path parameters as a map. */ + Map pathParamMap(); + + /** + * Returns the port number used in the request. + * + * @return The port number of the request. + */ + int port(); + + /** + * Returns the protocol used in the request (e.g., HTTP/1.1). + * + * @return The protocol of the request. + */ + String protocol(); + + /** + * Return the first query parameter value. + * + * @param name The query parameter name + */ + String queryParam(String name); + + /** + * Return the first query parameter value or the default value if it does not exist. + * + * @param name The query parameter name + */ + default String queryParam(String name, String defaultValue) { + String val = queryParam(name); + return val != null ? val : defaultValue; } /** - * Render a template typically as html with the given model. + * Return all the query parameters as a map. * - * @param name The template name - * @param model The model used with the template + *

    Note this returns the first value for any given key if that key has multiple values. */ - Context render(String name, Map model); + Map queryParamMap(); + + /** Return all the query parameters for the given parameter name. */ + List queryParams(String name); + + /** Return the request query string, or null. */ + String queryString(); /** - * Return all the request headers as a map. + * Redirects the client to the specified location using a 302 (Found) status code. * - * @return all the headers as a single value Map + * @param location The URL to redirect to. */ - Map headerMap(); + void redirect(String location); /** - * Return underlying request headers. + * Redirects the client to the specified location using the given HTTP status code. * - * @return the request headers + * @param location The URL to redirect to. + * @param httpStatusCode The HTTP status code to use for the redirect. */ - Headers headers(); + void redirect(String location, int httpStatusCode); /** - * Return the request header. + * Removes a cookie with the specified name. * - * @param key The header key + * @param name The name of the cookie to remove. */ - String header(String key); + Context removeCookie(String name); /** - * Set the response header. + * Removes a cookie with the specified name and path. * - * @param key The header key - * @param value The header value + * @param name The name of the cookie to remove. + * @param path The path of the cookie to remove. */ - Context header(String key, String value); + Context removeCookie(String name, String path); /** - * Set the response header. + * Render a template typically as html. * - * @param key The header key - * @param value The header value + * @param name The template name */ - Context header(String key, List value); - - /** Add the response headers using the provided map. */ - default Context headers(Map headers) { - headers.forEach(this::header); - return this; + default Context render(String name) { + return render(name, emptyMap()); } /** - * Sets the response headers using the provided map. + * Render a template typically as html with the given model. * - * @param headers A map containing the header names as keys and their corresponding values as - * lists. - * @return The updated context object. + * @param name The template name + * @param model The model used with the template */ - Context headerMap(Map> headers); + Context render(String name, Map model); /** * Returns the value of the specified response header. @@ -392,67 +397,64 @@ default Context headers(Map headers) { */ String responseHeader(String key); - /** - * Returns the host name of the request. - * - * @return The host name of the request, or null if not available. - */ - String host(); + /** Return true if the response has been sent. */ + boolean responseSent(); /** - * Returns the IP address of the client making the request. + * Returns a set of roles associated with the current route. * - * @return The IP address of the client. + * @return A set of roles. */ - String ip(); + Set routeRoles(); - /** - * Returns the HTTP method used in the request (e.g., GET, POST, PUT, DELETE). - * - * @return The HTTP method of the request. - */ - String method(); + /** Return the request scheme. */ + String scheme(); /** - * Returns the path part of the request URI. + * Get the {@link SSLSession} for this exchange. * - * @return The path part of the request URI. + * @return the {@code SSLSession} */ - String path(); + SSLSession sslSession(); - /** - * Returns the port number used in the request. - * - * @return The port number of the request. - */ - int port(); + /** Return the current response status. */ + int status(); + + /** Set the status code on the response. */ + Context status(int statusCode); + + /** Write plain text content to the response. */ + void text(String content); + + /** Return the request url. */ + String url(); + + /** Return the request user agent, or null. */ + default String userAgent() { + return header(Constants.USER_AGENT); + } /** - * Returns the protocol used in the request (e.g., HTTP/1.1). + * Writes the given bytes directly to the response. * - * @return The protocol of the request. + * @param bytes The byte array to write. */ - String protocol(); + void write(byte[] bytes); /** - * Gets basic-auth credentials from the request. - * - * @return The Base64 decoded username and password from the - * Authorization header, or null if no header is sent + * Writes the content from the given InputStream directly to the response body. * - * @throws IllegalStateException if the Authorization header is malformed + * @param is The input stream containing the content to write. */ - BasicAuthCredentials basicAuthCredentials(); - - /** Return true if the response has been sent. */ - boolean responseSent(); + void write(InputStream is); /** - * Get the {@link SSLSession} for this exchange. + * Writes the given string content directly to the response. * - * @return the {@code SSLSession} + * @param content The string content to write. */ - SSLSession sslSession(); + void write(String content); + /** * This interface represents a cookie used in HTTP communication. Cookies are small pieces of data @@ -461,6 +463,13 @@ default Context headers(Map headers) { */ interface Cookie { + /** + * Cookie SameSite options. + */ + enum SameSite { + Strict, Lax, None + } + /** * Creates and returns a new expired cookie with the given name. This cookie will be sent to the * browser but will be immediately discarded. It's useful for removing existing cookies. @@ -484,33 +493,56 @@ static Cookie of(String name, String value) { } /** - * Returns the name of this cookie. + * Returns the domain for which this cookie is valid. * - * @return The name of the cookie. + * @return The domain associated with the cookie, or null if not set. */ - String name(); + String domain(); /** - * Returns the value stored in this cookie. + * Sets the domain for which this cookie is valid. * - * @return The value of the cookie. + * @param domain The domain for which the cookie should be valid. + * @return A new cookie instance with the updated domain. */ - String value(); + Cookie domain(String domain); /** - * Returns the domain for which this cookie is valid. + * Returns the date and time when this cookie expires. * - * @return The domain associated with the cookie, or null if not set. + * @return The expiration date and time of the cookie, or null if not set. */ - String domain(); + ZonedDateTime expires(); /** - * Sets the domain for which this cookie is valid. + * Sets the date and time when this cookie expires. * - * @param domain The domain for which the cookie should be valid. - * @return A new cookie instance with the updated domain. + * @param expires The date and time when the cookie should expire. + * @return A new cookie instance with the updated expires value. */ - Cookie domain(String domain); + Cookie expires(ZonedDateTime expires); + + /** + * Indicates if the HttpOnly attribute is enabled for this cookie. + * + *

    The HttpOnly attribute ensures that the cookie is inaccessible to JavaScript, helping to + * mitigate cross-site scripting (XSS) attacks. + * + * @return {@code true} if the cookie has the HttpOnly attribute enabled, {@code false} + * otherwise + */ + boolean httpOnly(); + + /** + * Sets the HttpOnly attribute for this cookie. + * + *

    When enabled, the cookie will not be accessible via client-side scripts, providing + * additional security against XSS attacks. + * + * @param httpOnly {@code true} to enable the HttpOnly attribute, {@code false} to disable it + * @return this cookie instance with the updated HttpOnly attribute + */ + Cookie httpOnly(boolean httpOnly); /** * Returns the maximum age (in seconds) of this cookie. An expired cookie (maxAge of 0) will be @@ -530,19 +562,21 @@ static Cookie of(String name, String value) { Cookie maxAge(Duration maxAge); /** - * Returns the date and time when this cookie expires. + * Returns the name of this cookie. * - * @return The expiration date and time of the cookie, or null if not set. + * @return The name of the cookie. */ - ZonedDateTime expires(); + String name(); /** - * Sets the date and time when this cookie expires. - * - * @param expires The date and time when the cookie should expire. - * @return A new cookie instance with the updated expires value. + * Indicates if the Partitioned attribute is enabled for this cookie. */ - Cookie expires(ZonedDateTime expires); + boolean partitioned(); + + /** + * Set the Partitioned attribute for this cookie. + */ + Cookie partitioned(boolean partitioned); /** * Returns the path on the server for which this cookie is valid. Cookies are only sent to the @@ -561,6 +595,16 @@ static Cookie of(String name, String value) { */ Cookie path(String path); + /** + * Return the SameSite setting. + */ + SameSite sameSite(); + + /** + * Set the SameSite setting for this cookie. + */ + Cookie sameSite(SameSite sameSite); + /** * Indicates whether this cookie should only be sent over secure connections (HTTPS). * @@ -578,48 +622,6 @@ static Cookie of(String name, String value) { */ Cookie secure(boolean secure); - /** - * Indicates if the HttpOnly attribute is enabled for this cookie. - * - *

    The HttpOnly attribute ensures that the cookie is inaccessible to JavaScript, helping to - * mitigate cross-site scripting (XSS) attacks. - * - * @return {@code true} if the cookie has the HttpOnly attribute enabled, {@code false} - * otherwise - */ - boolean httpOnly(); - - /** - * Sets the HttpOnly attribute for this cookie. - * - *

    When enabled, the cookie will not be accessible via client-side scripts, providing - * additional security against XSS attacks. - * - * @param httpOnly {@code true} to enable the HttpOnly attribute, {@code false} to disable it - * @return this cookie instance with the updated HttpOnly attribute - */ - Cookie httpOnly(boolean httpOnly); - - /** - * Indicates if the Partitioned attribute is enabled for this cookie. - */ - boolean partitioned(); - - /** - * Set the Partitioned attribute for this cookie. - */ - Cookie partitioned(boolean partitioned); - - /** - * Return the SameSite setting. - */ - SameSite sameSite(); - - /** - * Set the SameSite setting for this cookie. - */ - Cookie sameSite(SameSite sameSite); - /** * Returns content of the cookie as a 'Set-Cookie:' header value specified by RFC6265. @@ -628,10 +630,10 @@ static Cookie of(String name, String value) { String toString(); /** - * Cookie SameSite options. + * Returns the value stored in this cookie. + * + * @return The value of the cookie. */ - enum SameSite { - Strict, Lax, None - } + String value(); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index e26bccb6..f98f7a2b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -58,6 +58,7 @@ final class JdkContext implements Context { private Map> queryParams; private Map cookieMap; private int statusCode; + private String characterEncoding; JdkContext( @@ -91,8 +92,9 @@ final class JdkContext implements Context { } @Override - public String matchedPath() { - return path; + @SuppressWarnings("unchecked") + public T attribute(String key) { + return (T) attributes.get(key); } @Override @@ -102,82 +104,36 @@ public Context attribute(String key, Object value) { } @Override - @SuppressWarnings("unchecked") - public T attribute(String key) { - return (T) attributes.get(key); - } - - private Map parseCookies() { - final String cookieHeader = header(exchange.getRequestHeaders(), COOKIE); - if (cookieHeader == null || cookieHeader.isEmpty()) { - return emptyMap(); - } - return CookieParser.parse(cookieHeader); + public BasicAuthCredentials basicAuthCredentials() { + return getBasicAuthCredentials(header("Authorization")); } - @Override - public Map cookieMap() { - if (cookieMap == null) { - cookieMap = parseCookies(); + private static BasicAuthCredentials getBasicAuthCredentials(String authorizationHeader) { + if (authorizationHeader == null || !authorizationHeader.startsWith("Basic ")) { + return null; } - return cookieMap; - } - @Override - public String cookie(String name) { - return cookieMap().get(name); - } - - @Override - public Context cookie(Cookie cookie) { - header(SET_COOKIE, cookie.toString()); - return this; - } - - @Override - public Context cookie(String name, String value) { - header(SET_COOKIE, Cookie.of(name, value).toString()); - return this; - } - - @Override - public Context cookie(String name, String value, int maxAge) { - header(SET_COOKIE, Cookie.of(name, value).maxAge(Duration.ofSeconds(maxAge)).toString()); - return this; - } + String base64Credentials = authorizationHeader.substring("Basic ".length()); + byte[] decodedCredentials = Base64.getDecoder().decode(base64Credentials); + String credentialsString = new String(decodedCredentials); - @Override - public Context removeCookie(String name) { - header(SET_COOKIE, Cookie.expired(name).path("/").toString()); - return this; - } + String[] credentials = credentialsString.split(":", 2); + if (credentials.length != 2) { + throw new IllegalStateException("Invalid Basic Auth header"); + } - @Override - public Context removeCookie(String name, String path) { - header(SET_COOKIE, Cookie.expired(name).path(path).toString()); - return this; + return new BasicAuthCredentials(credentials[0], credentials[1]); } @Override - public void redirect(String location) { - redirect(location, SC_MOVED_TEMPORARILY); + public String body() { + return new String(bodyAsBytes(), Charset.forName(characterEncoding())); } @Override - public void redirect(String location, int statusCode) { - header(Constants.LOCATION, location); - status(statusCode); - if (mode != Mode.EXCHANGE) { - throw new RedirectException(ErrorCode.REDIRECT.message()); - } else { - performRedirect(); - } - } - - public void performRedirect() { + public byte[] bodyAsBytes() { try { - exchange.sendResponseHeaders(statusCode(), -1); - exchange.getResponseBody().close(); + return exchange.getRequestBody().readAllBytes(); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -189,22 +145,13 @@ public T bodyAsClass(Class beanType) { } @Override - public T bodyAsType(Type beanType) { - return mgr.fromJson(beanType, bodyAsInputStream()); - } - - @Override - public byte[] bodyAsBytes() { - try { - return exchange.getRequestBody().readAllBytes(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + public InputStream bodyAsInputStream() { + return exchange.getRequestBody(); } @Override - public InputStream bodyAsInputStream() { - return exchange.getRequestBody(); + public T bodyAsType(Type beanType) { + return mgr.fromJson(beanType, bodyAsInputStream()); } private String characterEncoding() { @@ -214,11 +161,6 @@ private String characterEncoding() { return characterEncoding; } - @Override - public String body() { - return new String(bodyAsBytes(), Charset.forName(characterEncoding())); - } - @Override public long contentLength() { final String len = header(Constants.CONTENT_LENGTH); @@ -230,16 +172,6 @@ public String contentType() { return header(exchange.getRequestHeaders(), Constants.CONTENT_TYPE); } - @Override - public String responseHeader(String key) { - return header(exchange.getResponseHeaders(), key); - } - - private String header(Headers headers, String name) { - final List values = headers.get(name); - return (values == null || values.isEmpty()) ? null : values.getFirst(); - } - @Override public Context contentType(String contentType) { exchange.getResponseHeaders().set(Constants.CONTENT_TYPE, contentType); @@ -247,53 +179,44 @@ public Context contentType(String contentType) { } @Override - public Map pathParamMap() { - return pathParams; + public String contextPath() { + return mgr.contextPath(); } @Override - public String pathParam(String name) { - return pathParams.get(name); + public Context cookie(Cookie cookie) { + header(SET_COOKIE, cookie.toString()); + return this; } @Override - public String queryParam(String name) { - final List vals = queryParams(name); - return vals.isEmpty() ? null : vals.getFirst(); + public String cookie(String name) { + return cookieMap().get(name); } - private Map> queryParams() { - if (queryParams == null) { - queryParams = mgr.parseParamMap(queryString(), UTF8); - } - return queryParams; + @Override + public Context cookie(String name, String value) { + header(SET_COOKIE, Cookie.of(name, value).toString()); + return this; } @Override - public List queryParams(String name) { - final List vals = queryParams().get(name); - return vals == null ? emptyList() : vals; + public Context cookie(String name, String value, int maxAge) { + header(SET_COOKIE, Cookie.of(name, value).maxAge(Duration.ofSeconds(maxAge)).toString()); + return this; } @Override - public Map queryParamMap() { - final Map> map = queryParams(); - if (map.isEmpty()) { - return emptyMap(); - } - final Map single = new LinkedHashMap<>(); - for (Map.Entry> entry : map.entrySet()) { - final List value = entry.getValue(); - if (value != null && !value.isEmpty()) { - single.put(entry.getKey(), value.getFirst()); - } + public Map cookieMap() { + if (cookieMap == null) { + cookieMap = parseCookies(); } - return single; + return cookieMap; } @Override - public String queryString() { - return exchange.getRequestURI().getQuery(); + public HttpExchange exchange() { + return exchange; } @Override @@ -304,34 +227,74 @@ public Map> formParamMap() { return formParams; } - private Map> initFormParamMap() { - return mgr.formParamMap(this, characterEncoding()); + private String header(Headers headers, String name) { + final List values = headers.get(name); + return (values == null || values.isEmpty()) ? null : values.getFirst(); } @Override - public String scheme() { - return mgr.scheme(); + public String header(String key) { + return header(exchange.getRequestHeaders(), key); } @Override - public String url() { - return scheme() + "://" + host() + path; + public Context header(String key, List value) { + exchange.getResponseHeaders().put(key, value); + return this; } @Override - public String contextPath() { - return mgr.contextPath(); + public Context header(String key, String value) { + exchange.getResponseHeaders().add(key, value); + return this; } @Override - public Context status(int statusCode) { - this.statusCode = statusCode; + public Map headerMap() { + Map map = new LinkedHashMap<>(); + for (var entry : exchange.getRequestHeaders().entrySet()) { + final List value = entry.getValue(); + if (!value.isEmpty()) { + map.put(entry.getKey(), value.getFirst()); + } + } + return map; + } + + @Override + public Context headerMap(Map> map) { + exchange.getResponseHeaders().putAll(map); return this; } @Override - public int status() { - return statusCode; + public Headers headers() { + return exchange.getRequestHeaders(); + } + + @Override + public String host() { + return header(Constants.HOST); + } + + @Override + public void html(String content) { + contentType(TEXT_HTML_UTF8); + write(content); + } + + private Map> initFormParamMap() { + return mgr.formParamMap(this, characterEncoding()); + } + + @Override + public String ip() { + final InetSocketAddress remote = exchange.getRemoteAddress(); + if (remote == null) { + return ""; + } + InetAddress address = remote.getAddress(); + return address == null ? remote.getHostString() : address.getHostAddress(); } @Override @@ -340,6 +303,12 @@ public void json(Object bean) { mgr.toJson(bean, outputStream()); } + @Override + public void jsonStream(Iterator iterator) { + contentType(APPLICATION_X_JSON_STREAM); + mgr.toJsonStream(iterator, outputStream()); + } + @Override public void jsonStream(Stream stream) { contentType(APPLICATION_X_JSON_STREAM); @@ -347,144 +316,158 @@ public void jsonStream(Stream stream) { } @Override - public void jsonStream(Iterator iterator) { - contentType(APPLICATION_X_JSON_STREAM); - mgr.toJsonStream(iterator, outputStream()); + public String matchedPath() { + return path; } @Override - public void text(String content) { - contentType(TEXT_PLAIN_UTF8); - write(content); + public String method() { + return exchange.getRequestMethod(); } @Override - public void html(String content) { - contentType(TEXT_HTML_UTF8); - write(content); + public OutputStream outputStream() { + var out = mgr.createOutputStream(this); + if (compressionConfig.compressionEnabled()) { + return new CompressedOutputStream(compressionConfig, this, out); + } + return out; + } + + private Map parseCookies() { + final String cookieHeader = header(exchange.getRequestHeaders(), COOKIE); + if (cookieHeader == null || cookieHeader.isEmpty()) { + return emptyMap(); + } + return CookieParser.parse(cookieHeader); } @Override - public void write(String content) { - write(content.getBytes(StandardCharsets.UTF_8)); + public String path() { + return path; } @Override - public void write(byte[] bytes) { - try (var os = exchange.getResponseBody()) { - exchange.sendResponseHeaders(statusCode(), bytes.length == 0 ? -1 : bytes.length); - os.write(bytes); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + public String pathParam(String name) { + return pathParams.get(name); } @Override - public void write(InputStream is) { - try (is; var os = outputStream()) { - is.transferTo(os); + public Map pathParamMap() { + return pathParams; + } + + public void performRedirect() { + try { + exchange.sendResponseHeaders(statusCode(), -1); + exchange.getResponseBody().close(); } catch (IOException e) { throw new UncheckedIOException(e); } } @Override - public boolean responseSent() { - return exchange.getResponseCode() != -1; + public int port() { + return exchange.getLocalAddress().getPort(); } - int statusCode() { - return statusCode == 0 ? 200 : statusCode; + @Override + public String protocol() { + return exchange.getProtocol(); } @Override - public Context render(String name, Map model) { - mgr.render(this, name, model); - return this; + public String queryParam(String name) { + final List vals = queryParams(name); + return vals.isEmpty() ? null : vals.getFirst(); } @Override - public Map headerMap() { - Map map = new LinkedHashMap<>(); - for (var entry : exchange.getRequestHeaders().entrySet()) { + public Map queryParamMap() { + final Map> map = queryParams(); + if (map.isEmpty()) { + return emptyMap(); + } + final Map single = new LinkedHashMap<>(); + for (Map.Entry> entry : map.entrySet()) { final List value = entry.getValue(); - if (!value.isEmpty()) { - map.put(entry.getKey(), value.getFirst()); + if (value != null && !value.isEmpty()) { + single.put(entry.getKey(), value.getFirst()); } } - return map; + return single; } - @Override - public Headers headers() { - return exchange.getRequestHeaders(); + private Map> queryParams() { + if (queryParams == null) { + queryParams = mgr.parseParamMap(queryString(), UTF8); + } + return queryParams; } @Override - public String header(String key) { - return header(exchange.getRequestHeaders(), key); + public List queryParams(String name) { + final List vals = queryParams().get(name); + return vals == null ? emptyList() : vals; } @Override - public Context header(String key, String value) { - exchange.getResponseHeaders().add(key, value); - return this; + public String queryString() { + return exchange.getRequestURI().getQuery(); } @Override - public Context header(String key, List value) { - exchange.getResponseHeaders().put(key, value); - return this; + public void redirect(String location) { + redirect(location, SC_MOVED_TEMPORARILY); } @Override - public Context headerMap(Map> map) { - exchange.getResponseHeaders().putAll(map); - return this; + public void redirect(String location, int statusCode) { + header(Constants.LOCATION, location); + status(statusCode); + if (mode != Mode.EXCHANGE) { + throw new RedirectException(ErrorCode.REDIRECT.message()); + } else { + performRedirect(); + } } @Override - public String host() { - return header(Constants.HOST); + public Context removeCookie(String name) { + header(SET_COOKIE, Cookie.expired(name).path("/").toString()); + return this; } @Override - public String ip() { - final InetSocketAddress remote = exchange.getRemoteAddress(); - if (remote == null) { - return ""; - } - InetAddress address = remote.getAddress(); - return address == null ? remote.getHostString() : address.getHostAddress(); + public Context removeCookie(String name, String path) { + header(SET_COOKIE, Cookie.expired(name).path(path).toString()); + return this; } @Override - public String method() { - return exchange.getRequestMethod(); + public Context render(String name, Map model) { + mgr.render(this, name, model); + return this; } @Override - public String path() { - return path; + public String responseHeader(String key) { + return header(exchange.getResponseHeaders(), key); } @Override - public int port() { - return exchange.getLocalAddress().getPort(); + public boolean responseSent() { + return exchange.getResponseCode() != -1; } @Override - public String protocol() { - return exchange.getProtocol(); + public Set routeRoles() { + return roles; } @Override - public OutputStream outputStream() { - var out = mgr.createOutputStream(this); - if (compressionConfig.compressionEnabled()) { - return new CompressedOutputStream(compressionConfig, this, out); - } - return out; + public String scheme() { + return mgr.scheme(); } void setMode(Mode type) { @@ -492,40 +475,57 @@ void setMode(Mode type) { } @Override - public HttpExchange exchange() { - return exchange; + public SSLSession sslSession() { + return exchange instanceof HttpsExchange ex ? ex.getSSLSession() : null; } @Override - public Set routeRoles() { - return roles; + public int status() { + return statusCode; } @Override - public BasicAuthCredentials basicAuthCredentials() { - return getBasicAuthCredentials(header("Authorization")); + public Context status(int statusCode) { + this.statusCode = statusCode; + return this; } - private static BasicAuthCredentials getBasicAuthCredentials(String authorizationHeader) { - if (authorizationHeader == null || !authorizationHeader.startsWith("Basic ")) { - return null; - } + int statusCode() { + return statusCode == 0 ? 200 : statusCode; + } - String base64Credentials = authorizationHeader.substring("Basic ".length()); - byte[] decodedCredentials = Base64.getDecoder().decode(base64Credentials); - String credentialsString = new String(decodedCredentials); + @Override + public void text(String content) { + contentType(TEXT_PLAIN_UTF8); + write(content); + } - String[] credentials = credentialsString.split(":", 2); - if (credentials.length != 2) { - throw new IllegalStateException("Invalid Basic Auth header"); - } + @Override + public String url() { + return scheme() + "://" + host() + path; + } - return new BasicAuthCredentials(credentials[0], credentials[1]); + @Override + public void write(byte[] bytes) { + try (var os = exchange.getResponseBody()) { + exchange.sendResponseHeaders(statusCode(), bytes.length == 0 ? -1 : bytes.length); + os.write(bytes); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } @Override - public SSLSession sslSession() { + public void write(InputStream is) { + try (is; var os = outputStream()) { + is.transferTo(os); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } - return exchange instanceof HttpsExchange ex ? ex.getSSLSession() : null; + @Override + public void write(String content) { + write(content.getBytes(StandardCharsets.UTF_8)); } } From d98530240b8ced5b2ca5bdc2ca69b9baba052a85 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 17 Jan 2025 19:23:25 -0500 Subject: [PATCH 168/250] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e29cfbdb..0f79fc99 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![javadoc](https://javadoc.io/badge2/io.avaje/avaje-jex/javadoc.svg?color=purple)](https://javadoc.io/doc/io.avaje/avaje-jex) # [Avaje-Jex](https://avaje.io/jex/) -Lightweight (~105KB) wrapper over the JDK's built-in [HTTP server](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html). +Lightweight (~110KB) wrapper over the JDK's built-in [HTTP server](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html). Features: @@ -51,7 +51,7 @@ An example would be [@robaho's implementation](https://github.com/robaho/httpser io.github.robaho httpserver - 1.0.10 + 1.0.21 ``` From 8596f18fd9e13c3271c87b458c502492b41f82bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 03:23:08 +0000 Subject: [PATCH 169/250] Bump the dependencies group with 7 updates Bumps the dependencies group with 7 updates: | Package | From | To | | --- | --- | --- | | io.avaje:avaje-logback-encoder | `0.9` | `0.10` | | io.avaje:avaje-http-api | `2.9-RC7` | `2.9-RC8` | | io.avaje:avaje-htmx-api | `2.9-RC7` | `2.9-RC8` | | [io.avaje:avaje-http-client](https://github.com/avaje/avaje-http-client) | `2.9-RC7` | `2.9-RC8` | | io.avaje:avaje-http-client-generator | `2.9-RC7` | `2.9-RC8` | | io.avaje:avaje-http-jex-generator | `2.9-RC7` | `2.9-RC8` | | [io.github.robaho:httpserver](https://github.com/robaho/httpserver) | `1.0.19` | `1.0.21` | Updates `io.avaje:avaje-logback-encoder` from 0.9 to 0.10 Updates `io.avaje:avaje-http-api` from 2.9-RC7 to 2.9-RC8 Updates `io.avaje:avaje-htmx-api` from 2.9-RC7 to 2.9-RC8 Updates `io.avaje:avaje-http-client` from 2.9-RC7 to 2.9-RC8 - [Release notes](https://github.com/avaje/avaje-http-client/releases) - [Commits](https://github.com/avaje/avaje-http-client/commits) Updates `io.avaje:avaje-http-client-generator` from 2.9-RC7 to 2.9-RC8 Updates `io.avaje:avaje-http-jex-generator` from 2.9-RC7 to 2.9-RC8 Updates `io.avaje:avaje-htmx-api` from 2.9-RC7 to 2.9-RC8 Updates `io.avaje:avaje-http-client` from 2.9-RC7 to 2.9-RC8 - [Release notes](https://github.com/avaje/avaje-http-client/releases) - [Commits](https://github.com/avaje/avaje-http-client/commits) Updates `io.avaje:avaje-http-client-generator` from 2.9-RC7 to 2.9-RC8 Updates `io.avaje:avaje-http-jex-generator` from 2.9-RC7 to 2.9-RC8 Updates `io.github.robaho:httpserver` from 1.0.19 to 1.0.21 - [Commits](https://github.com/robaho/httpserver/compare/1.0.19...1.0.21) --- updated-dependencies: - dependency-name: io.avaje:avaje-logback-encoder dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-http-api dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-htmx-api dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client dependency-type: direct:development dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-jex-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-htmx-api dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client dependency-type: direct:development dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-jex-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.github.robaho:httpserver dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- examples/example-robaho/pom.xml | 2 +- pom.xml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 19812141..379f930b 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -15,7 +15,7 @@ io.github.robaho httpserver - 1.0.19 + 1.0.21 diff --git a/pom.xml b/pom.xml index 2898e839..57988f79 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ 4.0 11.1 3.0-RC5 - 2.9-RC7 + 2.9-RC8 9.4 2.4 1.2 @@ -60,7 +60,7 @@ io.avaje avaje-logback-encoder - 0.9 + 0.10 io.avaje @@ -208,7 +208,7 @@ io.github.robaho httpserver - 1.0.19 + 1.0.21 From a08a8a822a7cde5cc759caac365a5e3524cee11d Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 22 Jan 2025 00:15:51 -0500 Subject: [PATCH 170/250] Tidy internals (#169) * tidy internals - rename SPIServiceManager to ServiceManager - move compression to serviceManager - Use Enum `valueof` for HttpMethod mapping * Update Routing.java --- .../io/avaje/jex/core/BootstrapServer.java | 4 +-- .../io/avaje/jex/core/CoreServiceLoader.java | 2 +- .../java/io/avaje/jex/core/HttpMethodMap.java | 21 ------------- .../java/io/avaje/jex/core/JdkContext.java | 19 +++--------- .../io/avaje/jex/core/RoutingHandler.java | 12 +++---- ...erviceManager.java => ServiceManager.java} | 31 +++++++++++++------ .../io/avaje/jex/core/ContextUtilTest.java | 16 +++++----- 7 files changed, 42 insertions(+), 63 deletions(-) delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/core/HttpMethodMap.java rename avaje-jex/src/main/java/io/avaje/jex/core/{SpiServiceManager.java => ServiceManager.java} (87%) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java index 92bb47a3..09343d25 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java @@ -55,8 +55,8 @@ static Jex.Server start(Jex jex, SpiRoutes routes) { final var scheme = config.scheme(); final var contextPath = config.contextPath(); - SpiServiceManager serviceManager = SpiServiceManager.create(jex); - final var handler = new RoutingHandler(routes, serviceManager, config.compression()); + ServiceManager serviceManager = ServiceManager.create(jex); + final var handler = new RoutingHandler(routes, serviceManager); server.setExecutor(config.executor()); server.createContext(contextPath, handler); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java index 53411915..ebe2bba7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/CoreServiceLoader.java @@ -10,7 +10,7 @@ import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; -/** Core implementation of SpiServiceManager provided to specific implementations like jetty etc. */ +/** Loads SPI Services. */ final class CoreServiceLoader { private static final CoreServiceLoader INSTANCE = new CoreServiceLoader(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/HttpMethodMap.java b/avaje-jex/src/main/java/io/avaje/jex/core/HttpMethodMap.java deleted file mode 100644 index 5f76650c..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/core/HttpMethodMap.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.avaje.jex.core; - -import java.util.HashMap; -import java.util.Map; - -import io.avaje.jex.Routing; - -final class HttpMethodMap { - - private final Map map = new HashMap<>(); - - HttpMethodMap() { - for (Routing.Type value : Routing.Type.values()) { - map.put(value.name(), value); - } - } - - Routing.Type get(String method) { - return map.get(method); - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index f98f7a2b..483880b5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -33,8 +33,6 @@ import com.sun.net.httpserver.HttpsExchange; import io.avaje.jex.Context; -import io.avaje.jex.compression.CompressedOutputStream; -import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.http.ErrorCode; import io.avaje.jex.http.RedirectException; import io.avaje.jex.security.BasicAuthCredentials; @@ -46,8 +44,7 @@ final class JdkContext implements Context { private static final int SC_MOVED_TEMPORARILY = 302; private static final String SET_COOKIE = "Set-Cookie"; private static final String COOKIE = "Cookie"; - private final SpiServiceManager mgr; - private final CompressionConfig compressionConfig; + private final ServiceManager mgr; private final String path; private final Map pathParams; private final Map attributes = new HashMap<>(); @@ -62,14 +59,12 @@ final class JdkContext implements Context { private String characterEncoding; JdkContext( - SpiServiceManager mgr, - CompressionConfig compressionConfig, + ServiceManager mgr, HttpExchange exchange, String path, Map pathParams, Set roles) { this.mgr = mgr; - this.compressionConfig = compressionConfig; this.roles = roles; this.exchange = exchange; this.path = path; @@ -78,13 +73,11 @@ final class JdkContext implements Context { /** Create when no route matched. */ JdkContext( - SpiServiceManager mgr, - CompressionConfig compressionConfig, + ServiceManager mgr, HttpExchange exchange, String path, Set roles) { this.mgr = mgr; - this.compressionConfig = compressionConfig; this.roles = roles; this.exchange = exchange; this.path = path; @@ -327,11 +320,7 @@ public String method() { @Override public OutputStream outputStream() { - var out = mgr.createOutputStream(this); - if (compressionConfig.compressionEnabled()) { - return new CompressedOutputStream(compressionConfig, this, out); - } - return out; + return mgr.createOutputStream(this); } private Map parseCookies() { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java index 41fecc0b..da39ed16 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java @@ -16,15 +16,13 @@ final class RoutingHandler implements HttpHandler { private final SpiRoutes routes; - private final SpiServiceManager mgr; - private final CompressionConfig compressionConfig; + private final ServiceManager mgr; private final List filters; - RoutingHandler(SpiRoutes routes, SpiServiceManager mgr, CompressionConfig compressionConfig) { + RoutingHandler(SpiRoutes routes, ServiceManager mgr) { this.mgr = mgr; this.routes = routes; this.filters = routes.filters(); - this.compressionConfig = compressionConfig; } void waitForIdle(long maxSeconds) { @@ -38,7 +36,7 @@ public void handle(HttpExchange exchange) { final var route = routes.match(routeType, uri); if (route == null) { - var ctx = new JdkContext(mgr, compressionConfig, exchange, uri, Set.of()); + var ctx = new JdkContext(mgr, exchange, uri, Set.of()); handleException( ctx, new NotFoundException( @@ -47,9 +45,7 @@ public void handle(HttpExchange exchange) { route.inc(); try { final Map params = route.pathParams(uri); - JdkContext ctx = - new JdkContext( - mgr, compressionConfig, exchange, route.matchPath(), params, route.roles()); + JdkContext ctx = new JdkContext(mgr, exchange, route.matchPath(), params, route.roles()); try { ctx.setMode(Mode.BEFORE); new BaseFilterChain(filters, route.handler(), ctx).proceed(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java similarity index 87% rename from avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java rename to avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java index b706053f..1179bb3c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/SpiServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java @@ -18,18 +18,20 @@ import io.avaje.jex.Context; import io.avaje.jex.Jex; import io.avaje.jex.Routing; +import io.avaje.jex.compression.CompressedOutputStream; +import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.core.json.JacksonJsonService; import io.avaje.jex.core.json.JsonbJsonService; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; /** Core service methods available to Context implementations. */ -final class SpiServiceManager { +final class ServiceManager { private static final System.Logger log = System.getLogger("io.avaje.jex"); - static final String UTF_8 = "UTF-8"; + private static final String UTF_8 = "UTF-8"; - private final HttpMethodMap methodMap = new HttpMethodMap(); + private final CompressionConfig compressionConfig; private final JsonService jsonService; private final ExceptionManager exceptionHandler; private final TemplateManager templateManager; @@ -38,11 +40,12 @@ final class SpiServiceManager { private final int bufferInitial; private final long bufferMax; - static SpiServiceManager create(Jex jex) { + static ServiceManager create(Jex jex) { return new Builder(jex).build(); } - SpiServiceManager( + ServiceManager( + CompressionConfig compressionConfig, JsonService jsonService, ExceptionManager manager, TemplateManager templateManager, @@ -50,6 +53,7 @@ static SpiServiceManager create(Jex jex) { String contextPath, long bufferMax, int bufferInitial) { + this.compressionConfig = compressionConfig; this.jsonService = jsonService; this.exceptionHandler = manager; this.templateManager = templateManager; @@ -60,7 +64,11 @@ static SpiServiceManager create(Jex jex) { } OutputStream createOutputStream(JdkContext jdkContext) { - return new BufferedOutStream(jdkContext, bufferInitial, bufferMax); + var out = new BufferedOutStream(jdkContext, bufferInitial, bufferMax); + if (compressionConfig.compressionEnabled()) { + return new CompressedOutputStream(compressionConfig, jdkContext, out); + } + return out; } T fromJson(Class type, InputStream is) { @@ -100,7 +108,11 @@ void maybeClose(Object iterator) { } Routing.Type lookupRoutingType(String method) { - return methodMap.get(method); + try { + return Routing.Type.valueOf(method); + } catch (Exception e) { + return null; + } } void handleException(JdkContext ctx, Exception e) { @@ -165,8 +177,9 @@ private static final class Builder { this.jex = jex; } - SpiServiceManager build() { - return new SpiServiceManager( + ServiceManager build() { + return new ServiceManager( + jex.config().compression(), initJsonService(), new ExceptionManager(jex.routing().errorHandlers()), initTemplateMgr(), diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java index 95232a94..efb95ab2 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java @@ -2,22 +2,24 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.nio.charset.StandardCharsets; + import org.junit.jupiter.api.Test; class ContextUtilTest { @Test void parseCharset_defaults() { - assertThat(SpiServiceManager.parseCharset("")).isEqualTo(SpiServiceManager.UTF_8); - assertThat(SpiServiceManager.parseCharset("junk")).isEqualTo(SpiServiceManager.UTF_8); + assertThat(ServiceManager.parseCharset("")).isEqualTo(StandardCharsets.UTF_8.name()); + assertThat(ServiceManager.parseCharset("junk")).isEqualTo(StandardCharsets.UTF_8.name()); } @Test void parseCharset_caseCheck() { - assertThat(SpiServiceManager.parseCharset("app/foo; charset=ME")).isEqualTo("ME"); - assertThat(SpiServiceManager.parseCharset("app/foo;charset=ME")).isEqualTo("ME"); - assertThat(SpiServiceManager.parseCharset("app/foo;charset = ME ")).isEqualTo("ME"); - assertThat(SpiServiceManager.parseCharset("app/foo;charset = ME;")).isEqualTo("ME"); - assertThat(SpiServiceManager.parseCharset("app/foo;charset = ME;other=junk")).isEqualTo("ME"); + assertThat(ServiceManager.parseCharset("app/foo; charset=ME")).isEqualTo("ME"); + assertThat(ServiceManager.parseCharset("app/foo;charset=ME")).isEqualTo("ME"); + assertThat(ServiceManager.parseCharset("app/foo;charset = ME ")).isEqualTo("ME"); + assertThat(ServiceManager.parseCharset("app/foo;charset = ME;")).isEqualTo("ME"); + assertThat(ServiceManager.parseCharset("app/foo;charset = ME;other=junk")).isEqualTo("ME"); } } From a12b1caa2f907b77c362505c57916a51c7706410 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 26 Jan 2025 01:04:45 -0500 Subject: [PATCH 171/250] Improve startup message (#164) * filter out empty routes from debug message * Update BootstrapServer.java * Update README.md --- .../java/io/avaje/jex/core/BootstrapServer.java | 3 ++- .../java/io/avaje/jex/routes/RouteIndex.java | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java index 09343d25..cf32b387 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java @@ -65,7 +65,8 @@ static Jex.Server start(Jex jex, SpiRoutes routes) { jex.lifecycle().status(AppLifecycle.Status.STARTED); log.log( INFO, - "started com.sun.net.httpserver.HttpServer on port {0}://{1}", + "Avaje Jex started {0} on port {1}://{2}", + server.getClass(), scheme, socketAddress); log.log(DEBUG, routes); diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java index 71b62acb..c75eb3a6 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java @@ -1,7 +1,10 @@ package io.avaje.jex.routes; +import static java.util.stream.Collectors.joining; + import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; final class RouteIndex { @@ -25,9 +28,17 @@ final class RouteIndex { @Override public String toString() { - return "RouteIndex{" + Arrays.toString(entries) + - ", wildcards=" + Arrays.toString(wildcardEntries) + - '}'; + + return "RouteIndex{" + + Stream.concat( + Arrays.stream(entries) + .filter(i -> i.pathEntries.length > 0) + .map(i -> i.pathEntries) + .flatMap(Arrays::stream) + .map(Object::toString), + Arrays.stream(wildcardEntries).map(Object::toString)) + .sorted().collect(joining(", ")) + + '}'; } private static IndexEntry toEntry(List routeEntries) { From a70d02468f2492ab70b09a599b619ab2c2a25d44 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 26 Jan 2025 01:19:49 -0500 Subject: [PATCH 172/250] Handle Errors before After Handlers (#170) * Error Handlers handle Throwable It's possible that problems like IllegalAccessError may appear, causing socket hangups if unhandled * remove unneeded generic * Bump the dependencies group with 7 updates Bumps the dependencies group with 7 updates: | Package | From | To | | --- | --- | --- | | io.avaje:avaje-logback-encoder | `0.9` | `0.10` | | io.avaje:avaje-http-api | `2.9-RC7` | `2.9-RC8` | | io.avaje:avaje-htmx-api | `2.9-RC7` | `2.9-RC8` | | [io.avaje:avaje-http-client](https://github.com/avaje/avaje-http-client) | `2.9-RC7` | `2.9-RC8` | | io.avaje:avaje-http-client-generator | `2.9-RC7` | `2.9-RC8` | | io.avaje:avaje-http-jex-generator | `2.9-RC7` | `2.9-RC8` | | [io.github.robaho:httpserver](https://github.com/robaho/httpserver) | `1.0.19` | `1.0.21` | Updates `io.avaje:avaje-logback-encoder` from 0.9 to 0.10 Updates `io.avaje:avaje-http-api` from 2.9-RC7 to 2.9-RC8 Updates `io.avaje:avaje-htmx-api` from 2.9-RC7 to 2.9-RC8 Updates `io.avaje:avaje-http-client` from 2.9-RC7 to 2.9-RC8 - [Release notes](https://github.com/avaje/avaje-http-client/releases) - [Commits](https://github.com/avaje/avaje-http-client/commits) Updates `io.avaje:avaje-http-client-generator` from 2.9-RC7 to 2.9-RC8 Updates `io.avaje:avaje-http-jex-generator` from 2.9-RC7 to 2.9-RC8 Updates `io.avaje:avaje-htmx-api` from 2.9-RC7 to 2.9-RC8 Updates `io.avaje:avaje-http-client` from 2.9-RC7 to 2.9-RC8 - [Release notes](https://github.com/avaje/avaje-http-client/releases) - [Commits](https://github.com/avaje/avaje-http-client/commits) Updates `io.avaje:avaje-http-client-generator` from 2.9-RC7 to 2.9-RC8 Updates `io.avaje:avaje-http-jex-generator` from 2.9-RC7 to 2.9-RC8 Updates `io.github.robaho:httpserver` from 1.0.19 to 1.0.21 - [Commits](https://github.com/robaho/httpserver/compare/1.0.19...1.0.21) --- updated-dependencies: - dependency-name: io.avaje:avaje-logback-encoder dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-http-api dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-htmx-api dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client dependency-type: direct:development dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-jex-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-htmx-api dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client dependency-type: direct:development dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-jex-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.github.robaho:httpserver dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] * Error Handlers handle Throwable It's possible that problems like IllegalAccessError may appear, causing socket hangups if unhandled * Tidy internals (#169) * tidy internals - rename SPIServiceManager to ServiceManager - move compression to serviceManager - Use Enum `valueof` for HttpMethod mapping * Update Routing.java * handler exceptions before after filters Now Exceptions during handler processing will not skip after handlers - * Update ExceptionHandler.java * iterator * Update ExceptionManager.java * Tidy BaseFilterChain --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Rob Bygrave --- .../main/java/io/avaje/jex/HttpFilter.java | 8 ++---- .../io/avaje/jex/core/BaseFilterChain.java | 27 ++++++++++++------- .../io/avaje/jex/core/ExceptionManager.java | 6 ++--- .../io/avaje/jex/core/RoutingHandler.java | 11 +++----- .../io/avaje/jex/core/ServiceManager.java | 4 +-- 5 files changed, 27 insertions(+), 29 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java b/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java index 987bcb2b..c1059a0c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java @@ -1,7 +1,5 @@ package io.avaje.jex; -import java.io.IOException; - import com.sun.net.httpserver.HttpExchange; /** @@ -37,7 +35,7 @@ public interface HttpFilter { * @param ctx the {@code Context} of the current request * @param chain the {@code FilterChain} which allows the next filter to be invoked */ - void filter(Context ctx, FilterChain chain) throws Exception; + void filter(Context ctx, FilterChain chain); /** * Filter chain that contains all subsequent filters that are configured, as well as the final @@ -51,9 +49,7 @@ interface FilterChain { * filter in the chain. The {@link HttpFilter} may decide to terminate the chain, by not calling * this method. In this case, the filter must send the response to the request, because * the application's {@linkplain HttpExchange exchange} handler will not be invoked. - * - * @throws IOException if an I/O error occurs */ - void proceed() throws Exception; + void proceed(); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BaseFilterChain.java b/avaje-jex/src/main/java/io/avaje/jex/core/BaseFilterChain.java index b84171bf..586905c1 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BaseFilterChain.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BaseFilterChain.java @@ -1,7 +1,6 @@ package io.avaje.jex.core; -import java.util.List; -import java.util.ListIterator; +import java.util.Iterator; import io.avaje.jex.ExchangeHandler; import io.avaje.jex.HttpFilter; @@ -9,23 +8,31 @@ final class BaseFilterChain implements FilterChain { - private final ListIterator iter; + private final Iterator filters; private final ExchangeHandler handler; private final JdkContext ctx; + private final ServiceManager mgr; - BaseFilterChain(List filters, ExchangeHandler handler, JdkContext ctx) { - this.iter = filters.listIterator(); + BaseFilterChain(Iterator filters, ExchangeHandler handler, JdkContext ctx, ServiceManager mgr) { + this.filters = filters; this.handler = handler; this.ctx = ctx; + this.mgr = mgr; } @Override - public void proceed() throws Exception { - if (!iter.hasNext()) { - handler.handle(ctx); - ctx.setMode(Mode.AFTER); + public void proceed() { + if (filters.hasNext()) { + filters.next().filter(ctx, this); } else { - iter.next().filter(ctx, this); + try { + if (!ctx.responseSent()) { + handler.handle(ctx); + } + } catch (Exception t) { + mgr.handleException(ctx, t); + } } + ctx.setMode(Mode.AFTER); } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index 04c598d5..cb6034c9 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -23,10 +23,10 @@ final class ExceptionManager { } @SuppressWarnings("unchecked") - ExceptionHandler find(Class exceptionType) { + ExceptionHandler find(Class exceptionType) { Class type = exceptionType; do { - final ExceptionHandler handler = handlers.get(type); + final var handler = handlers.get(type); if (handler != null) { return (ExceptionHandler) handler; } @@ -36,7 +36,7 @@ ExceptionHandler find(Class exceptionType) { } void handle(JdkContext ctx, Exception e) { - final ExceptionHandler handler = find(e.getClass()); + final var handler = find(e.getClass()); if (handler != null) { try { handler.handle(ctx, e); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java index da39ed16..3bb8e03b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java @@ -9,7 +9,6 @@ import com.sun.net.httpserver.HttpHandler; import io.avaje.jex.HttpFilter; -import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.http.NotFoundException; import io.avaje.jex.routes.SpiRoutes; @@ -37,7 +36,7 @@ public void handle(HttpExchange exchange) { if (route == null) { var ctx = new JdkContext(mgr, exchange, uri, Set.of()); - handleException( + mgr.handleException( ctx, new NotFoundException( "No route matching http method %s, with path %s".formatted(routeType.name(), uri))); @@ -48,10 +47,10 @@ public void handle(HttpExchange exchange) { JdkContext ctx = new JdkContext(mgr, exchange, route.matchPath(), params, route.roles()); try { ctx.setMode(Mode.BEFORE); - new BaseFilterChain(filters, route.handler(), ctx).proceed(); + new BaseFilterChain(filters.iterator(), route.handler(), ctx, mgr).proceed(); handleNoResponse(exchange); } catch (Exception e) { - handleException(ctx, e); + mgr.handleException(ctx, e); } } finally { route.dec(); @@ -65,8 +64,4 @@ private void handleNoResponse(HttpExchange exchange) throws IOException { exchange.sendResponseHeaders(204, -1); } } - - private void handleException(JdkContext ctx, Exception e) { - mgr.handleException(ctx, e); - } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java index 1179bb3c..a04c7e9f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java @@ -115,8 +115,8 @@ Routing.Type lookupRoutingType(String method) { } } - void handleException(JdkContext ctx, Exception e) { - exceptionHandler.handle(ctx, e); + void handleException(JdkContext ctx, Exception t) { + exceptionHandler.handle(ctx, t); } void render(Context ctx, String name, Map model) { From cdc8f7ba30fabe6ca2add0965c6548a5ab8d6a45 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 26 Jan 2025 01:22:34 -0500 Subject: [PATCH 173/250] Simplify BootJex (#171) Co-authored-by: Rob Bygrave --- .../src/main/java/io/avaje/jex/AvajeJex.java | 58 +++++++++++++++++++ .../src/main/java/io/avaje/jex/BootJex.java | 41 ------------- .../main/java/io/avaje/jex/BootJexState.java | 42 -------------- 3 files changed, 58 insertions(+), 83 deletions(-) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/AvajeJex.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/BootJex.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/BootJexState.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/AvajeJex.java b/avaje-jex/src/main/java/io/avaje/jex/AvajeJex.java new file mode 100644 index 00000000..93904513 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/AvajeJex.java @@ -0,0 +1,58 @@ +package io.avaje.jex; + +import io.avaje.config.Config; +import io.avaje.inject.BeanScope; +import io.avaje.jex.Jex.Server; + +/** + * Start Jex using avaje-inject, avaje-http, avaje-config. + * + *

    - avaje-http generates the adapter for the {@code @Controller} + * + *

    - avaje-inject generates dependency injection wiring + * + *

    - avaje-config reads external configuration. + */ +public interface AvajeJex { + + /** + * Start Jex server using {@code @Controller} with avaje-inject, avaje-http, avaje-config. + * + *

    {@code
    +   * public static void main(String[] args) {
    +   *
    +   *   AvajeJex.start();
    +   * }
    +   * }
    + * + * @return The running server. + */ + static Server start() { + return start(BeanScope.builder().build()); + } + + /** + * Start Jex server using {@code @Controller} with avaje-inject, avaje-http, avaje-config. + * + *
    {@code
    +   * public static void main(String[] args) {
    +   *
    +   *   AvajeJex.start();
    +   * }
    +   * }
    + * + * @param beanScope the beanscope used to configure Jex + * @return The running server. + */ + static Server start(BeanScope beanScope) { + Jex jex = beanScope.getOptional(Jex.class).orElse(Jex.create()); + jex.configureWith(beanScope); + + JexConfig config = jex.config(); + config.port(Config.getInt("server.port", config.port())); + config.contextPath(Config.get("server.context.path", config.contextPath())); + config.host(Config.get("server.context.host", config.host())); + + return jex.start(); + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/BootJex.java b/avaje-jex/src/main/java/io/avaje/jex/BootJex.java deleted file mode 100644 index 2b78ab69..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/BootJex.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.avaje.jex; - -import io.avaje.inject.BeanScope; - -/** - * Start Jex using {@code @Controller} and avaje-inject, avaje-http, avaje-config. - *

    - * - avaje-http generates the adapter for the {@code @Controller} - * - avaje-inject generates dependency injection wiring - * - avaje-config reads external configuration (application.properties|yaml, application-test.properties|yaml). - */ -public interface BootJex { - - /** - * Start Jex server using {@code @Controller} with avaje-inject, avaje-http, avaje-config. - * - *

    {@code
    -   * public static void main(String[] args) {
    -   *
    -   *   BootJex.start();
    -   * }
    -   * }
    - */ - static void start() { - BootJexState.start(BeanScope.builder().build()); - } - - /** - * Start Jex server using {@code @Controller} with avaje-inject, avaje-http, avaje-config. - * - *
    {@code
    -   * public static void main(String[] args) {
    -   *
    -   *   BootJex.start();
    -   * }
    -   * }
    - */ - static void start(BeanScope beanScope) { - BootJexState.start(beanScope); - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java b/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java deleted file mode 100644 index 97a5496a..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/BootJexState.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.avaje.jex; - -import io.avaje.config.Config; -import io.avaje.inject.BeanScope; - -final class BootJexState { - - private static State state; - - static void start(BeanScope beanScope) { - state = new BootJexState().create(beanScope); - } - - static void stop() { - state.stop(); - } - - State create(BeanScope beanScope) { - Jex jex = beanScope.getOptional(Jex.class).orElse(Jex.create()); - jex.configureWith(beanScope); - - JexConfig config = jex.config(); - config.port(Config.getInt("server.port", config.port())); - config.contextPath(Config.get("server.context.path", config.contextPath())); - config.host(Config.get("server.context.host", config.host())); - jex.lifecycle().onShutdown(beanScope::close); - return new State(jex.start()); - } - - private static final class State { - - private final Jex.Server server; - - State(Jex.Server server) { - this.server = server; - } - - void stop() { - server.shutdown(); - } - } -} From 5586334d8a97f9e2cf0396b0f0d6641e08c83711 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Sun, 26 Jan 2025 22:42:24 +1300 Subject: [PATCH 174/250] Version 3.0-RC15 --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-htmx/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 16 ++++++++-------- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 4d2de645..d0227efe 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC14 + 3.0-RC15 avaje-jex-freemarker diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index aedc00df..8ecdd1d1 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC14 + 3.0-RC15 avaje-jex-htmx diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index bbdf0b60..35436680 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC14 + 3.0-RC15 avaje-jex-mustache diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index 938558e5..bd5d8b58 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC14 + 3.0-RC15 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index e20139ab..dd2748d0 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC14 + 3.0-RC15 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 05ff1ff2..0267d128 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC14 + 3.0-RC15 avaje-jex diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index a687fdf0..db0ac810 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC14 + 3.0-RC15 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 379f930b..05b38ef0 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC14 + 3.0-RC15 diff --git a/examples/pom.xml b/examples/pom.xml index ea2a2964..6e57ce72 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC14 + 3.0-RC15 examples diff --git a/pom.xml b/pom.xml index 57988f79..32780662 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ io.avaje avaje-jex-parent - 3.0-RC14 + 3.0-RC15 pom @@ -25,7 +25,7 @@ false 21 full - 2025-01-14T18:49:50Z + 2025-01-26T09:41:55Z 4.0 11.1 @@ -178,32 +178,32 @@ io.avaje avaje-jex - 3.0-RC14 + 3.0-RC15 io.avaje avaje-jex-test - 3.0-RC14 + 3.0-RC15 io.avaje avaje-jex-freemarker - 3.0-RC14 + 3.0-RC15 io.avaje avaje-jex-mustache - 3.0-RC14 + 3.0-RC15 io.avaje avaje-jex-htmx - 3.0-RC14 + 3.0-RC15 io.avaje avaje-jex-static-content - 3.0-RC14 + 3.0-RC15 io.github.robaho From 306ac104ac76f38ef9a2e7a7670562c1887d8dd4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 03:47:55 +0000 Subject: [PATCH 175/250] Bump the dependencies group with 3 updates Bumps the dependencies group with 3 updates: io.avaje:avaje-validator-constraints, [io.avaje:avaje-validator](https://github.com/avaje/avaje-validator) and io.avaje:avaje-validator-generator. Updates `io.avaje:avaje-validator-constraints` from 2.4 to 2.5 Updates `io.avaje:avaje-validator` from 2.4 to 2.5 - [Release notes](https://github.com/avaje/avaje-validator/releases) - [Commits](https://github.com/avaje/avaje-validator/compare/2.4...2.5) Updates `io.avaje:avaje-validator-generator` from 2.4 to 2.5 Updates `io.avaje:avaje-validator` from 2.4 to 2.5 - [Release notes](https://github.com/avaje/avaje-validator/releases) - [Commits](https://github.com/avaje/avaje-validator/compare/2.4...2.5) Updates `io.avaje:avaje-validator-generator` from 2.4 to 2.5 --- updated-dependencies: - dependency-name: io.avaje:avaje-validator-constraints dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 32780662..3267502d 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ 3.0-RC5 2.9-RC8 9.4 - 2.4 + 2.5 1.2 2.9 1.36 From fa4b0255079644f3b33acc00046ec216caf8c4b7 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 27 Jan 2025 00:28:15 -0500 Subject: [PATCH 176/250] Fix Jsonb Example (#174) * fix test * fix jsonb test * redo katie test --- avaje-jex-test/pom.xml | 1 - .../test/java/io/avaje/jex/core/JsonTest.java | 9 +- examples/example-http-generation/README.md | 31 +++++ examples/example-http-generation/pom.xml | 84 ++++++++++++++ .../src/main/java/main/Main.java | 4 +- .../src/main/java/module-info.java | 14 +++ .../foo/myapp/config/JexConfiguration.java | 7 +- .../org/foo/myapp/service/HelloService.java | 0 .../main/java/org/foo/myapp/web/HelloApi.java | 4 +- .../org/foo/myapp/web/HelloController.java | 0 .../main/java/org/foo/myapp/web/HiApi.java | 6 +- .../java/org/foo/myapp/web/HiController.java | 6 +- .../src/main/resources/application.properties | 0 .../web/HelloClientInterfaceInMainTest.java | 9 +- .../web/HelloClientInterfaceInTestTest.java | 0 .../HelloClientInterfaceViaImportTest.java | 17 +-- .../foo/myapp/web/HelloControllerTest.java | 6 +- .../org/foo/myapp/web/MyTestHelloApi.java | 0 .../java/org/foo/myapp/web/TestConfig.java | 2 +- examples/example-jdk-jsonb/pom.xml | 85 ++++++-------- .../src/main/java/module-info.java | 6 +- .../src/main/java/org/example/Main.java | 10 +- examples/example-katie/pom.xml | 106 ------------------ .../src/main/java/module-info.java | 16 --- .../src/main/resources/logback.xml | 19 ---- .../src/test/resources/logback-test.xml | 19 ---- examples/pom.xml | 14 +-- 27 files changed, 204 insertions(+), 271 deletions(-) create mode 100644 examples/example-http-generation/README.md create mode 100644 examples/example-http-generation/pom.xml rename examples/{example-katie => example-http-generation}/src/main/java/main/Main.java (62%) create mode 100644 examples/example-http-generation/src/main/java/module-info.java rename examples/{example-katie => example-http-generation}/src/main/java/org/foo/myapp/config/JexConfiguration.java (74%) rename examples/{example-katie => example-http-generation}/src/main/java/org/foo/myapp/service/HelloService.java (100%) rename examples/{example-katie => example-http-generation}/src/main/java/org/foo/myapp/web/HelloApi.java (84%) rename examples/{example-katie => example-http-generation}/src/main/java/org/foo/myapp/web/HelloController.java (100%) rename examples/{example-katie => example-http-generation}/src/main/java/org/foo/myapp/web/HiApi.java (71%) rename examples/{example-katie => example-http-generation}/src/main/java/org/foo/myapp/web/HiController.java (65%) rename examples/{example-katie => example-http-generation}/src/main/resources/application.properties (100%) rename examples/{example-katie => example-http-generation}/src/test/java/org/foo/myapp/web/HelloClientInterfaceInMainTest.java (88%) rename examples/{example-katie => example-http-generation}/src/test/java/org/foo/myapp/web/HelloClientInterfaceInTestTest.java (100%) rename examples/{example-katie => example-http-generation}/src/test/java/org/foo/myapp/web/HelloClientInterfaceViaImportTest.java (88%) rename examples/{example-katie => example-http-generation}/src/test/java/org/foo/myapp/web/HelloControllerTest.java (78%) rename examples/{example-katie => example-http-generation}/src/test/java/org/foo/myapp/web/MyTestHelloApi.java (100%) rename examples/{example-katie => example-http-generation}/src/test/java/org/foo/myapp/web/TestConfig.java (90%) delete mode 100644 examples/example-katie/pom.xml delete mode 100644 examples/example-katie/src/main/java/module-info.java delete mode 100644 examples/example-katie/src/main/resources/logback.xml delete mode 100644 examples/example-katie/src/test/resources/logback-test.xml diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index dd2748d0..8d1bd041 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -14,7 +14,6 @@ io.avaje avaje-jex - provided diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java index 17fcf78b..99c20ede 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java @@ -11,15 +11,16 @@ import java.util.concurrent.locks.LockSupport; import java.util.stream.Stream; +import io.avaje.jsonb.Json; +import io.avaje.jsonb.JsonType; +import io.avaje.jsonb.Jsonb; +import io.avaje.jsonb.Types; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import io.avaje.jex.Jex; import io.avaje.jex.core.json.JacksonJsonService; -import io.avaje.jsonb.Json; -import io.avaje.jsonb.JsonType; -import io.avaje.jsonb.Jsonb; -import io.avaje.jsonb.Types; public class JsonTest { diff --git a/examples/example-http-generation/README.md b/examples/example-http-generation/README.md new file mode 100644 index 00000000..895c8db7 --- /dev/null +++ b/examples/example-http-generation/README.md @@ -0,0 +1,31 @@ +# example-http-generation + +- Minimal example of Jex web routing with underlying JDK Http server +- Uses avaje-jsonb for json marshalling (via source code generation) +- Uses avaje-http to create JAX-RS style routing (via source code generation) +- Uses avaje-inject for DI (via source code generation) +- Uses avaje-config for configuration + +## Graalvm native-image + +To Build +```sh +mvn clean package -Pnative -DskipTests +``` + +To Run: +```sh +./target/mytest + +# produces +# Oct 21, 2022 1:39:36 PM io.avaje.jex.core.JdkServerStart start +# INFO: started server on port 7003 version 2.5-SNAPSHOT +``` + +To Play: +```sh +curl localhost:7003/foo/44 + +# produces +# {"id":44,"name":"Rob"} +``` diff --git a/examples/example-http-generation/pom.xml b/examples/example-http-generation/pom.xml new file mode 100644 index 00000000..370329b1 --- /dev/null +++ b/examples/example-http-generation/pom.xml @@ -0,0 +1,84 @@ + + + + avaje-jex-parent + io.avaje + 3.0-RC15 + + 4.0.0 + + example-http-generation + + + full + 21 + + + + + + io.avaje + avaje-config + + + + + io.avaje + avaje-inject + + + io.avaje + avaje-http-api + + + io.avaje + avaje-http-client + + + + io.avaje + avaje-jex + + + io.avaje + avaje-jsonb + + + + io.avaje + avaje-inject-test + + + + io.avaje + avaje-jex-test + test + + + + + + io.avaje + avaje-inject-generator + provided + + + io.avaje + avaje-jsonb-generator + provided + + + io.avaje + avaje-http-client-generator + provided + + + io.avaje + avaje-http-jex-generator + provided + + + + diff --git a/examples/example-katie/src/main/java/main/Main.java b/examples/example-http-generation/src/main/java/main/Main.java similarity index 62% rename from examples/example-katie/src/main/java/main/Main.java rename to examples/example-http-generation/src/main/java/main/Main.java index 59953da8..c0d52f5f 100644 --- a/examples/example-katie/src/main/java/main/Main.java +++ b/examples/example-http-generation/src/main/java/main/Main.java @@ -1,12 +1,12 @@ package main; -import io.avaje.jex.BootJex; +import io.avaje.jex.AvajeJex; public class Main { public static void main(String[] args) { - BootJex.start(); + AvajeJex.start(); } } diff --git a/examples/example-http-generation/src/main/java/module-info.java b/examples/example-http-generation/src/main/java/module-info.java new file mode 100644 index 00000000..bd9e29ea --- /dev/null +++ b/examples/example-http-generation/src/main/java/module-info.java @@ -0,0 +1,14 @@ +open module example.http.generation { + + requires io.avaje.jsonb; + requires io.avaje.http.api; + requires io.avaje.jex; + requires io.avaje.inject; + + requires io.avaje.http.client; + + provides io.avaje.inject.spi.InjectExtension with org.foo.myapp.MyappModule; + provides io.avaje.jsonb.spi.JsonbExtension with org.foo.myapp.web.jsonb.GeneratedJsonComponent; + provides io.avaje.http.client.HttpClient.GeneratedComponent with org.foo.myapp.web.httpclient.GeneratedHttpComponent; + +} diff --git a/examples/example-katie/src/main/java/org/foo/myapp/config/JexConfiguration.java b/examples/example-http-generation/src/main/java/org/foo/myapp/config/JexConfiguration.java similarity index 74% rename from examples/example-katie/src/main/java/org/foo/myapp/config/JexConfiguration.java rename to examples/example-http-generation/src/main/java/org/foo/myapp/config/JexConfiguration.java index 8c57ffb9..1c99a12b 100644 --- a/examples/example-katie/src/main/java/org/foo/myapp/config/JexConfiguration.java +++ b/examples/example-http-generation/src/main/java/org/foo/myapp/config/JexConfiguration.java @@ -3,15 +3,14 @@ import io.avaje.inject.Bean; import io.avaje.inject.Factory; import io.avaje.inject.PostConstruct; -import io.avaje.jex.Jex; +import io.avaje.jex.spi.JexPlugin; @Factory public class JexConfiguration { @Bean - Jex buildJex() { - return Jex.create() - .port(8002); + JexPlugin buildJex() { + return j -> j.port(8002); } @PostConstruct diff --git a/examples/example-katie/src/main/java/org/foo/myapp/service/HelloService.java b/examples/example-http-generation/src/main/java/org/foo/myapp/service/HelloService.java similarity index 100% rename from examples/example-katie/src/main/java/org/foo/myapp/service/HelloService.java rename to examples/example-http-generation/src/main/java/org/foo/myapp/service/HelloService.java diff --git a/examples/example-katie/src/main/java/org/foo/myapp/web/HelloApi.java b/examples/example-http-generation/src/main/java/org/foo/myapp/web/HelloApi.java similarity index 84% rename from examples/example-katie/src/main/java/org/foo/myapp/web/HelloApi.java rename to examples/example-http-generation/src/main/java/org/foo/myapp/web/HelloApi.java index 4af1d330..a0e4480d 100644 --- a/examples/example-katie/src/main/java/org/foo/myapp/web/HelloApi.java +++ b/examples/example-http-generation/src/main/java/org/foo/myapp/web/HelloApi.java @@ -2,11 +2,9 @@ import io.avaje.http.api.Client; import io.avaje.http.api.Get; -import io.avaje.http.api.Path; import io.avaje.http.api.Produces; -@Client -@Path("/hello") +@Client("/hello") public interface HelloApi { @Produces("text/plain") diff --git a/examples/example-katie/src/main/java/org/foo/myapp/web/HelloController.java b/examples/example-http-generation/src/main/java/org/foo/myapp/web/HelloController.java similarity index 100% rename from examples/example-katie/src/main/java/org/foo/myapp/web/HelloController.java rename to examples/example-http-generation/src/main/java/org/foo/myapp/web/HelloController.java diff --git a/examples/example-katie/src/main/java/org/foo/myapp/web/HiApi.java b/examples/example-http-generation/src/main/java/org/foo/myapp/web/HiApi.java similarity index 71% rename from examples/example-katie/src/main/java/org/foo/myapp/web/HiApi.java rename to examples/example-http-generation/src/main/java/org/foo/myapp/web/HiApi.java index 6fb87418..47067954 100644 --- a/examples/example-katie/src/main/java/org/foo/myapp/web/HiApi.java +++ b/examples/example-http-generation/src/main/java/org/foo/myapp/web/HiApi.java @@ -14,9 +14,5 @@ public interface HiApi { Hello hello(); @Json - class Hello { - public int id2; - public String msg; - public String other; - } + record Hello(int id2, String msg, String other) {} } diff --git a/examples/example-katie/src/main/java/org/foo/myapp/web/HiController.java b/examples/example-http-generation/src/main/java/org/foo/myapp/web/HiController.java similarity index 65% rename from examples/example-katie/src/main/java/org/foo/myapp/web/HiController.java rename to examples/example-http-generation/src/main/java/org/foo/myapp/web/HiController.java index 85a9a0db..1cde58c9 100644 --- a/examples/example-katie/src/main/java/org/foo/myapp/web/HiController.java +++ b/examples/example-http-generation/src/main/java/org/foo/myapp/web/HiController.java @@ -12,10 +12,6 @@ public String hi() { @Override public Hello hello() { - Hello hello = new Hello(); - hello.id2 = 42; - hello.msg = "hello"; - hello.other = "other"; - return hello; + return new Hello(42, "hello", "other"); } } diff --git a/examples/example-katie/src/main/resources/application.properties b/examples/example-http-generation/src/main/resources/application.properties similarity index 100% rename from examples/example-katie/src/main/resources/application.properties rename to examples/example-http-generation/src/main/resources/application.properties diff --git a/examples/example-katie/src/test/java/org/foo/myapp/web/HelloClientInterfaceInMainTest.java b/examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceInMainTest.java similarity index 88% rename from examples/example-katie/src/test/java/org/foo/myapp/web/HelloClientInterfaceInMainTest.java rename to examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceInMainTest.java index 929e622a..92161d41 100644 --- a/examples/example-katie/src/test/java/org/foo/myapp/web/HelloClientInterfaceInMainTest.java +++ b/examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceInMainTest.java @@ -1,14 +1,17 @@ package org.foo.myapp.web; -import io.avaje.inject.test.InjectTest; -import jakarta.inject.Inject; +import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; +import io.avaje.http.api.Client; +import io.avaje.inject.test.InjectTest; +import jakarta.inject.Inject; /** * A `@Client` interface lives in src/main - not usually expected. */ +@Client.Import(types =HelloApi.class) @InjectTest class HelloClientInterfaceInMainTest { diff --git a/examples/example-katie/src/test/java/org/foo/myapp/web/HelloClientInterfaceInTestTest.java b/examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceInTestTest.java similarity index 100% rename from examples/example-katie/src/test/java/org/foo/myapp/web/HelloClientInterfaceInTestTest.java rename to examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceInTestTest.java diff --git a/examples/example-katie/src/test/java/org/foo/myapp/web/HelloClientInterfaceViaImportTest.java b/examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceViaImportTest.java similarity index 88% rename from examples/example-katie/src/test/java/org/foo/myapp/web/HelloClientInterfaceViaImportTest.java rename to examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceViaImportTest.java index a850f1ed..78917a0f 100644 --- a/examples/example-katie/src/test/java/org/foo/myapp/web/HelloClientInterfaceViaImportTest.java +++ b/examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceViaImportTest.java @@ -1,14 +1,15 @@ package org.foo.myapp.web; -import io.avaje.http.api.Client; -import io.avaje.http.client.HttpClientContext; -import io.avaje.inject.test.InjectTest; -import jakarta.inject.Inject; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.net.http.HttpResponse; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; + +import io.avaje.http.api.Client; +import io.avaje.http.client.HttpClient; +import io.avaje.inject.test.InjectTest; +import jakarta.inject.Inject; /** * HiApi is a 'server' interface. @@ -22,7 +23,7 @@ class HelloClientInterfaceViaImportTest { @Inject static HiApi client; - @Inject static HttpClientContext rawClient; + @Inject static HttpClient rawClient; @Test void hello() { @@ -37,7 +38,7 @@ void hello() { @Test void hello2() { var hello = client.hello(); - assertThat(hello.msg).isEqualTo("hello"); + assertThat(hello.msg()).isEqualTo("hello"); } @Test diff --git a/examples/example-katie/src/test/java/org/foo/myapp/web/HelloControllerTest.java b/examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloControllerTest.java similarity index 78% rename from examples/example-katie/src/test/java/org/foo/myapp/web/HelloControllerTest.java rename to examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloControllerTest.java index 03f74180..5344d044 100644 --- a/examples/example-katie/src/test/java/org/foo/myapp/web/HelloControllerTest.java +++ b/examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloControllerTest.java @@ -1,6 +1,6 @@ package org.foo.myapp.web; -import io.avaje.http.client.HttpClientContext; +import io.avaje.http.client.HttpClient; import io.avaje.inject.test.InjectTest; import jakarta.inject.Inject; import org.junit.jupiter.api.Test; @@ -10,12 +10,12 @@ import static org.assertj.core.api.Assertions.assertThat; /** - * Using a raw HttpClientContext - not bad. + * Using a raw HttpClient - not bad. */ @InjectTest class HelloControllerTest { - @Inject HttpClientContext client; + @Inject HttpClient client; @Test void hello() { diff --git a/examples/example-katie/src/test/java/org/foo/myapp/web/MyTestHelloApi.java b/examples/example-http-generation/src/test/java/org/foo/myapp/web/MyTestHelloApi.java similarity index 100% rename from examples/example-katie/src/test/java/org/foo/myapp/web/MyTestHelloApi.java rename to examples/example-http-generation/src/test/java/org/foo/myapp/web/MyTestHelloApi.java diff --git a/examples/example-katie/src/test/java/org/foo/myapp/web/TestConfig.java b/examples/example-http-generation/src/test/java/org/foo/myapp/web/TestConfig.java similarity index 90% rename from examples/example-katie/src/test/java/org/foo/myapp/web/TestConfig.java rename to examples/example-http-generation/src/test/java/org/foo/myapp/web/TestConfig.java index ebd8c58b..e7f79971 100644 --- a/examples/example-katie/src/test/java/org/foo/myapp/web/TestConfig.java +++ b/examples/example-http-generation/src/test/java/org/foo/myapp/web/TestConfig.java @@ -1,6 +1,6 @@ package org.foo.myapp.web; -import io.avaje.http.client.HttpClientContext; +import io.avaje.http.client.HttpClient; import io.avaje.http.client.JsonbBodyAdapter; import io.avaje.inject.Bean; import io.avaje.inject.Factory; diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index 91c6d39e..1aafaf50 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -4,10 +4,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - org.avaje - java11-oss - 4.5 - + avaje-jex-parent + io.avaje + 3.0-RC15 org.example @@ -15,63 +14,47 @@ 1 - - - - 17 - 0.10.4 + 21 + + io.avaje + avaje-config + io.avaje avaje-jex - 2.6-SNAPSHOT - io.avaje avaje-jsonb - 2.4 + + + + + io.avaje + avaje-inject-generator + provided + + + io.avaje + avaje-jsonb-generator + provided + + + io.avaje + avaje-http-client-generator + provided + + + io.avaje + avaje-http-jex-generator + provided - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.13.0 - - 17 - - - - - - io.avaje - avaje-jsonb-generator - 2.4 - - - - - - - - - - - - - - - - - - native @@ -80,7 +63,7 @@ org.graalvm.buildtools native-maven-plugin - ${native.maven.plugin.version} + 0.10.4 true @@ -108,9 +91,9 @@ --no-fallback --allow-incomplete-classpath -H:ReflectionConfigurationFiles=graalvm-meta/reflection.json - - - + + + diff --git a/examples/example-jdk-jsonb/src/main/java/module-info.java b/examples/example-jdk-jsonb/src/main/java/module-info.java index b3538a38..eaf03ab9 100644 --- a/examples/example-jdk-jsonb/src/main/java/module-info.java +++ b/examples/example-jdk-jsonb/src/main/java/module-info.java @@ -1,10 +1,8 @@ -import io.avaje.jsonb.Jsonb; +import io.avaje.jsonb.spi.JsonbExtension; module example.jdkTwo { requires io.avaje.jex; requires io.avaje.jsonb; - - provides Jsonb.GeneratedComponent with org.example.jsonb.GeneratedJsonComponent; - + provides JsonbExtension with org.example.jsonb.GeneratedJsonComponent; } diff --git a/examples/example-jdk-jsonb/src/main/java/org/example/Main.java b/examples/example-jdk-jsonb/src/main/java/org/example/Main.java index a67d1392..15fd557e 100644 --- a/examples/example-jdk-jsonb/src/main/java/org/example/Main.java +++ b/examples/example-jdk-jsonb/src/main/java/org/example/Main.java @@ -1,13 +1,13 @@ package org.example; +import java.util.Map; +import java.util.Set; + import io.avaje.jex.Context; import io.avaje.jex.Jex; -import io.avaje.jex.core.JsonbJsonService; +import io.avaje.jex.core.json.JsonbJsonService; import io.avaje.jsonb.Jsonb; -import java.util.Map; -import java.util.Set; - public class Main { private static final System.Logger log = System.getLogger("org.example"); @@ -17,7 +17,7 @@ public static void main(String[] args) { Jsonb jsonb = Jsonb.builder().build(); Jex.create() - .configure(config -> config.jsonService(new JsonbJsonService(jsonb))) + .config(config -> config.jsonService(new JsonbJsonService(jsonb))) //.attribute(Executor.class, Executors.newVirtualThreadPerTaskExecutor()) .routing(routing -> routing .get("/", ctx -> ctx.text("hello world")) diff --git a/examples/example-katie/pom.xml b/examples/example-katie/pom.xml deleted file mode 100644 index 49689368..00000000 --- a/examples/example-katie/pom.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - - examples - io.avaje - 0.1 - - 4.0.0 - - example-katie - - - 18 - 18 - - - - - - org.avaje - logback - 1.0 - - - - - - - - - - io.avaje - avaje-jex-jetty - 2.6-SNAPSHOT - - - - io.avaje.kate - avaje-kate - 0.9.7 - - - - - io.avaje - avaje-http-client - 2.0 - - - - io.avaje - avaje-jex-test - 2.6-SNAPSHOT - test - - - - io.avaje.kate - avaje-kate-test - 0.9.7 - test - - - - - - - - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven-surefire-plugin.version} - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.10.1 - - - - io.avaje.kate - avaje-kate-apt - 0.9.7 - - - - - - - - - - - - - diff --git a/examples/example-katie/src/main/java/module-info.java b/examples/example-katie/src/main/java/module-info.java deleted file mode 100644 index 586ea628..00000000 --- a/examples/example-katie/src/main/java/module-info.java +++ /dev/null @@ -1,16 +0,0 @@ -import io.avaje.jsonb.Jsonb; - -open module example.katie { - - requires io.avaje.kate; -// requires io.avaje.jsonb; -// requires io.avaje.http.api; -// requires io.avaje.jex; -// requires io.avaje.jex.jetty; -// requires io.avaje.inject; - - requires io.avaje.http.client; - - provides io.avaje.inject.spi.Module with org.foo.myapp.MyappModule; - provides Jsonb.GeneratedComponent with org.foo.myapp.web.jsonb.GeneratedJsonComponent; -} diff --git a/examples/example-katie/src/main/resources/logback.xml b/examples/example-katie/src/main/resources/logback.xml deleted file mode 100644 index 9e1c33b7..00000000 --- a/examples/example-katie/src/main/resources/logback.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - TRACE - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - - diff --git a/examples/example-katie/src/test/resources/logback-test.xml b/examples/example-katie/src/test/resources/logback-test.xml deleted file mode 100644 index ebd125dd..00000000 --- a/examples/example-katie/src/test/resources/logback-test.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - TRACE - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - - diff --git a/examples/pom.xml b/examples/pom.xml index 6e57ce72..e66fbd83 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -13,20 +13,10 @@ pom + example-http-generation example-jdk + example-jdk-jsonb example-robaho - - - - jdk17plus - - [17,20] - - - example-jdk-jsonb - - - From b3866bb29ee5e1745cc51015d24aca9be04483eb Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 27 Jan 2025 03:37:57 -0500 Subject: [PATCH 177/250] Support Jetty SPI Impl (#173) * Support Jetty Impl * port doc * Update pom.xml * Revert "Update pom.xml" This reverts commit d66c767cf10cd49d18be777bacc1dce0a80a272c. --- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 2 + .../src/main/java/io/avaje/jex/JexConfig.java | 5 ++- .../io/avaje/jex/core/BootstrapServer.java | 11 ++++- .../java/io/avaje/jex/core/JdkContext.java | 2 +- examples/example-jetty/pom.xml | 40 +++++++++++++++++++ .../src/main/java/io/avaje/Main.java | 24 +++++++++++ examples/pom.xml | 1 + 7 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 examples/example-jetty/pom.xml create mode 100644 examples/example-jetty/src/main/java/io/avaje/Main.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index e4530b1c..1bb00762 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -230,6 +230,8 @@ default Jex group(String path, HttpService group) { /** * Sets the port number on which the Jex server will listen for incoming requests. * + *

    The default value is 8080. If The port is set to 0, the server will randomly choose an available port. + * * @param port The port number to use. */ Jex port(int port); diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index 4b2265d8..0a1e8ec5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -142,7 +142,10 @@ public sealed interface JexConfig permits DJexConfig { int port(); /** - * Sets the port number on which the HttpServer will listen for incoming requests. + * Sets the port number on which the HttpServer will listen for incoming requests. * + * + *

    The default value is 8080. If The port is set to 0, the server will randomly choose an + * available port. * * @param port The port number. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java index cf32b387..a52dc780 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java @@ -58,7 +58,14 @@ static Jex.Server start(Jex jex, SpiRoutes routes) { ServiceManager serviceManager = ServiceManager.create(jex); final var handler = new RoutingHandler(routes, serviceManager); - server.setExecutor(config.executor()); + final var serverClass = server.getClass(); + + // jetty's server does not support setExecutor with virtual threads (VT) + // as it has it's own impl that will auto-use VTs + if (!serverClass.getName().contains("jetty")) { + server.setExecutor(config.executor()); + } + server.createContext(contextPath, handler); server.start(); @@ -66,7 +73,7 @@ static Jex.Server start(Jex jex, SpiRoutes routes) { log.log( INFO, "Avaje Jex started {0} on port {1}://{2}", - server.getClass(), + serverClass, scheme, socketAddress); log.log(DEBUG, routes); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index 483880b5..3eed10f2 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -446,7 +446,7 @@ public String responseHeader(String key) { @Override public boolean responseSent() { - return exchange.getResponseCode() != -1; + return exchange.getResponseCode() > 1; } @Override diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml new file mode 100644 index 00000000..ee8de9bf --- /dev/null +++ b/examples/example-jetty/pom.xml @@ -0,0 +1,40 @@ + + 4.0.0 + + io.avaje + examples + 0.1 + + example-jetty + + + org.eclipse.jetty + jetty-server + 12.0.16 + + + + org.eclipse.jetty + jetty-http-spi + 12.0.16 + + + io.avaje + avaje-jex + 3.0-RC15 + + + + org.slf4j + slf4j-jdk-platform-logging + 2.0.16 + + + + ch.qos.logback + logback-classic + 1.5.16 + + + + \ No newline at end of file diff --git a/examples/example-jetty/src/main/java/io/avaje/Main.java b/examples/example-jetty/src/main/java/io/avaje/Main.java new file mode 100644 index 00000000..a0de5700 --- /dev/null +++ b/examples/example-jetty/src/main/java/io/avaje/Main.java @@ -0,0 +1,24 @@ +package io.avaje; + +import io.avaje.jex.Jex; + +public class Main { + + public static void main(String[] args) { + + Jex.create() + .get("/", ctx -> ctx.text("" + Thread.currentThread().isVirtual())) + .get("/one", ctx -> { + System.out.println("one"); + + ctx.text("one");}) + .get( + "/two/{name}", + ctx -> { + ctx.text("two Yo " + ctx.pathParam("name")); + }) + .post("one", ctx -> ctx.text("posted")) + .port(7002) + .start(); + } +} diff --git a/examples/pom.xml b/examples/pom.xml index e66fbd83..4ffd98e7 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -17,6 +17,7 @@ example-jdk example-jdk-jsonb example-robaho + example-jetty From e84324a0a8a54cc567dbd426cae18faf279de145 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 28 Jan 2025 07:14:55 +1300 Subject: [PATCH 178/250] Version 3.0-RC16 --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-htmx/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- examples/example-http-generation/pom.xml | 2 +- examples/example-jdk-jsonb/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-jetty/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 16 ++++++++-------- 13 files changed, 20 insertions(+), 20 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index d0227efe..4ec2f100 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC15 + 3.0-RC16 avaje-jex-freemarker diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 8ecdd1d1..7f354584 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC15 + 3.0-RC16 avaje-jex-htmx diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 35436680..763b1f20 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC15 + 3.0-RC16 avaje-jex-mustache diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index bd5d8b58..bc6138ec 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC15 + 3.0-RC16 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 8d1bd041..68028c86 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC15 + 3.0-RC16 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 0267d128..850cfd2b 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC15 + 3.0-RC16 avaje-jex diff --git a/examples/example-http-generation/pom.xml b/examples/example-http-generation/pom.xml index 370329b1..7119cbe3 100644 --- a/examples/example-http-generation/pom.xml +++ b/examples/example-http-generation/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC15 + 3.0-RC16 4.0.0 diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index 1aafaf50..42a50a82 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -6,7 +6,7 @@ avaje-jex-parent io.avaje - 3.0-RC15 + 3.0-RC16 org.example diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index db0ac810..707b451d 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC15 + 3.0-RC16 diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index ee8de9bf..1a559622 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC15 + 3.0-RC16 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 05b38ef0..f682da74 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC15 + 3.0-RC16 diff --git a/examples/pom.xml b/examples/pom.xml index 4ffd98e7..9fdb4100 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC15 + 3.0-RC16 examples diff --git a/pom.xml b/pom.xml index 3267502d..951ffaf5 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ io.avaje avaje-jex-parent - 3.0-RC15 + 3.0-RC16 pom @@ -25,7 +25,7 @@ false 21 full - 2025-01-26T09:41:55Z + 2025-01-27T08:47:34Z 4.0 11.1 @@ -178,32 +178,32 @@ io.avaje avaje-jex - 3.0-RC15 + 3.0-RC16 io.avaje avaje-jex-test - 3.0-RC15 + 3.0-RC16 io.avaje avaje-jex-freemarker - 3.0-RC15 + 3.0-RC16 io.avaje avaje-jex-mustache - 3.0-RC15 + 3.0-RC16 io.avaje avaje-jex-htmx - 3.0-RC15 + 3.0-RC16 io.avaje avaje-jex-static-content - 3.0-RC15 + 3.0-RC16 io.github.robaho From eda6b8dbb173c83964ce0bc014ed544c4c24b181 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 21:07:14 +0000 Subject: [PATCH 179/250] Bump the dependencies group with 7 updates Bumps the dependencies group with 7 updates: | Package | From | To | | --- | --- | --- | | [io.avaje:avaje-jsonb](https://github.com/avaje/avaje-jsonb) | `3.0-RC5` | `3.0-RC6` | | io.avaje:avaje-jsonb-generator | `3.0-RC5` | `3.0-RC6` | | io.avaje:avaje-http-api | `2.9-RC8` | `3.0-RC1` | | io.avaje:avaje-htmx-api | `2.9-RC8` | `3.0-RC1` | | [io.avaje:avaje-http-client](https://github.com/avaje/avaje-http-client) | `2.9-RC8` | `3.0-RC1` | | io.avaje:avaje-http-client-generator | `2.9-RC8` | `3.0-RC1` | | io.avaje:avaje-http-jex-generator | `2.9-RC8` | `3.0-RC1` | Updates `io.avaje:avaje-jsonb` from 3.0-RC5 to 3.0-RC6 - [Release notes](https://github.com/avaje/avaje-jsonb/releases) - [Commits](https://github.com/avaje/avaje-jsonb/commits) Updates `io.avaje:avaje-jsonb-generator` from 3.0-RC5 to 3.0-RC6 Updates `io.avaje:avaje-http-api` from 2.9-RC8 to 3.0-RC1 Updates `io.avaje:avaje-htmx-api` from 2.9-RC8 to 3.0-RC1 Updates `io.avaje:avaje-http-client` from 2.9-RC8 to 3.0-RC1 - [Release notes](https://github.com/avaje/avaje-http-client/releases) - [Commits](https://github.com/avaje/avaje-http-client/commits) Updates `io.avaje:avaje-http-client-generator` from 2.9-RC8 to 3.0-RC1 Updates `io.avaje:avaje-http-jex-generator` from 2.9-RC8 to 3.0-RC1 Updates `io.avaje:avaje-htmx-api` from 2.9-RC8 to 3.0-RC1 Updates `io.avaje:avaje-http-client` from 2.9-RC8 to 3.0-RC1 - [Release notes](https://github.com/avaje/avaje-http-client/releases) - [Commits](https://github.com/avaje/avaje-http-client/commits) Updates `io.avaje:avaje-http-client-generator` from 2.9-RC8 to 3.0-RC1 Updates `io.avaje:avaje-http-jex-generator` from 2.9-RC8 to 3.0-RC1 Updates `io.avaje:avaje-jsonb-generator` from 3.0-RC5 to 3.0-RC6 --- updated-dependencies: - dependency-name: io.avaje:avaje-jsonb dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-jsonb-generator dependency-type: direct:development dependency-group: dependencies - dependency-name: io.avaje:avaje-http-api dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-htmx-api dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client dependency-type: direct:development dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-jex-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-htmx-api dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client dependency-type: direct:development dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-http-jex-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-jsonb-generator dependency-type: direct:development dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 951ffaf5..575f9a9e 100644 --- a/pom.xml +++ b/pom.xml @@ -29,8 +29,8 @@ 4.0 11.1 - 3.0-RC5 - 2.9-RC8 + 3.0-RC6 + 3.0-RC1 9.4 2.5 1.2 From 17499d93cf8a396e5d92838e32db8d1cefff7062 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 27 Jan 2025 17:29:24 -0500 Subject: [PATCH 180/250] bump inject version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 575f9a9e..8b4dfc6c 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ 2025-01-27T08:47:34Z 4.0 - 11.1 + 11.2-RC2 3.0-RC6 3.0-RC1 9.4 From 780d48053b0969b47aff5bd791e57f4e51ea92a4 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 30 Jan 2025 13:38:19 -0500 Subject: [PATCH 181/250] reproducible only when deploying (#176) --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8b4dfc6c..bea23808 100644 --- a/pom.xml +++ b/pom.xml @@ -25,8 +25,6 @@ false 21 full - 2025-01-27T08:47:34Z - 4.0 11.2-RC2 3.0-RC6 @@ -305,6 +303,9 @@ central + + 2025-01-27T08:47:34Z + default From 5c66c0de76ee0a6cd3a9083925b93d1ab4bf7fba Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 31 Jan 2025 01:14:07 -0500 Subject: [PATCH 182/250] Add jetty to README (#177) --- README.md | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0f79fc99..18d76021 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Supported JVM Versions](https://img.shields.io/badge/JVM-21-brightgreen.svg?&logo=openjdk) +![Supported JVM Versions](https://img.shields.io/badge/JVM-21+-brightgreen.svg?&logo=openjdk) [![Discord](https://img.shields.io/discord/1074074312421683250?color=%237289da&label=discord)](https://discord.gg/Qcqf9R27BR) [![Build](https://github.com/avaje/avaje-jex/actions/workflows/build.yml/badge.svg)](https://github.com/avaje/avaje-jex/actions/workflows/build.yml) [![JDK EA](https://github.com/avaje/avaje-jex/actions/workflows/jdk-ea.yml/badge.svg)](https://github.com/avaje/avaje-jex/actions/workflows/jdk-ea.yml) @@ -39,8 +39,11 @@ Features: The JDK provides an SPI to swap the underlying `HttpServer`, so you can easily use jex with alternate implementations by adding them as a dependency. -An example would be [@robaho's implementation](https://github.com/robaho/httpserver?tab=readme-ov-file#performance) where performance seems to be increased by 10x over the default in certain benchmarks. +### Robaho +[@robaho's implementation](https://github.com/robaho/httpserver?tab=readme-ov-file#performance) is an ultra-lightweight implementation that seems to increase performance by 10x over the default in certain benchmarks. + +[![Maven Central](https://img.shields.io/maven-central/v/io.github.robaho/httpserver.svg?label=robaho.version)](https://mvnrepository.com/artifact/io.github.robaho/httpserver) ```xml io.avaje @@ -51,7 +54,32 @@ An example would be [@robaho's implementation](https://github.com/robaho/httpser io.github.robaho httpserver - 1.0.21 + ${robaho.version} + +``` + +### Eclipse Jetty + +Jetty is a classic embedded server that needs no introduction. + +[![Maven Central](https://img.shields.io/maven-central/v/org.eclipse.jetty/jetty-http-spi.svg?label=jetty.version)](https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-http-spi) +```xml + + io.avaje + avaje-jex + ${jex.version} + + + + org.eclipse.jetty + jetty-server + ${jetty.version} + + + + org.eclipse.jetty + jetty-http-spi + ${jetty.version} ``` @@ -60,6 +88,8 @@ An example would be [@robaho's implementation](https://github.com/robaho/httpser If you find yourself pining for the JAX-RS style of controllers, you can have avaje http generate jex adapters for your annotated classes. ### Add dependencies + +[![Avaje-HTTP](https://img.shields.io/maven-central/v/io.avaje/avaje-http-api.svg?label=avaje.http.version)](https://mvnrepository.com/artifact/io.avaje/avaje-jex) ```xml io.avaje From c56808f169c48ee21ba6c0a9a0a2afe10baf5c38 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 31 Jan 2025 01:21:02 -0500 Subject: [PATCH 183/250] add better quick start --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 18d76021..429eae0b 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,17 @@ Features: - Json SPI - Virtual threads enabled by default +## Quick Start + +Add dependency: +```xml + + io.avaje + avaje-jex + ${jex.version} + +``` +Create Server: ```java Jex.create() .get("/", ctx -> ctx.text("hello")) @@ -41,7 +52,7 @@ The JDK provides an SPI to swap the underlying `HttpServer`, so you can easily u ### Robaho -[@robaho's implementation](https://github.com/robaho/httpserver?tab=readme-ov-file#performance) is an ultra-lightweight implementation that seems to increase performance by 10x over the default in certain benchmarks. +[@robaho's implementation](https://github.com/robaho/httpserver?tab=readme-ov-file#performance) is an ultra-lightweight implementation that seems to increase performance by 10x over the built-in implementation, and 5x over Jetty in certain benchmarks. [![Maven Central](https://img.shields.io/maven-central/v/io.github.robaho/httpserver.svg?label=robaho.version)](https://mvnrepository.com/artifact/io.github.robaho/httpserver) ```xml @@ -60,7 +71,7 @@ The JDK provides an SPI to swap the underlying `HttpServer`, so you can easily u ### Eclipse Jetty -Jetty is a classic embedded server that needs no introduction. +[Jetty](https://jetty.org/) is a classic embedded server that needs no introduction. [![Maven Central](https://img.shields.io/maven-central/v/org.eclipse.jetty/jetty-http-spi.svg?label=jetty.version)](https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-http-spi) ```xml From 081b5442d1076d2782c8099cf60eede4e4020fa6 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 31 Jan 2025 12:06:44 -0500 Subject: [PATCH 184/250] Move up avaje http section --- README.md | 97 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 429eae0b..a0292dc7 100644 --- a/README.md +++ b/README.md @@ -46,54 +46,6 @@ Create Server: .start(); ``` -## Alternate `HttpServer` Implementations - -The JDK provides an SPI to swap the underlying `HttpServer`, so you can easily use jex with alternate implementations by adding them as a dependency. - -### Robaho - -[@robaho's implementation](https://github.com/robaho/httpserver?tab=readme-ov-file#performance) is an ultra-lightweight implementation that seems to increase performance by 10x over the built-in implementation, and 5x over Jetty in certain benchmarks. - -[![Maven Central](https://img.shields.io/maven-central/v/io.github.robaho/httpserver.svg?label=robaho.version)](https://mvnrepository.com/artifact/io.github.robaho/httpserver) -```xml - - io.avaje - avaje-jex - ${jex.version} - - - - io.github.robaho - httpserver - ${robaho.version} - -``` - -### Eclipse Jetty - -[Jetty](https://jetty.org/) is a classic embedded server that needs no introduction. - -[![Maven Central](https://img.shields.io/maven-central/v/org.eclipse.jetty/jetty-http-spi.svg?label=jetty.version)](https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-http-spi) -```xml - - io.avaje - avaje-jex - ${jex.version} - - - - org.eclipse.jetty - jetty-server - ${jetty.version} - - - - org.eclipse.jetty - jetty-http-spi - ${jetty.version} - -``` - ## Use with [Avaje Http](https://avaje.io/http/) If you find yourself pining for the JAX-RS style of controllers, you can have avaje http generate jex adapters for your annotated classes. @@ -209,6 +161,55 @@ public class Main { } ``` + +## Alternate `HttpServer` Implementations + +The JDK provides an SPI to swap the underlying `HttpServer`, so you can easily use jex with alternate implementations by adding them as a dependency. + +### Robaho + +[@robaho's httpserver](https://github.com/robaho/httpserver?tab=readme-ov-file#performance) is a zero-dependency implementation that seems to increase performance by 10x over the built-in implementation, and 5x over Jetty in certain benchmarks. + +[![Maven Central](https://img.shields.io/maven-central/v/io.github.robaho/httpserver.svg?label=robaho.version)](https://mvnrepository.com/artifact/io.github.robaho/httpserver) +```xml + + io.avaje + avaje-jex + ${jex.version} + + + + io.github.robaho + httpserver + ${robaho.version} + +``` + +### Eclipse Jetty + +[Jetty](https://jetty.org/) is a classic embedded server with a long and distinguished history. + +[![Maven Central](https://img.shields.io/maven-central/v/org.eclipse.jetty/jetty-http-spi.svg?label=jetty.version)](https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-http-spi) +```xml + + io.avaje + avaje-jex + ${jex.version} + + + + org.eclipse.jetty + jetty-server + ${jetty.version} + + + + org.eclipse.jetty + jetty-http-spi + ${jetty.version} + +``` + See also: - [Javalin](https://github.com/javalin/javalin) (A lightweight wrapper over Jetty) From 199ba3a2c66b335f46c7fa6a2e21e06f242801e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 04:01:33 +0000 Subject: [PATCH 185/250] Bump the dependencies group with 8 updates Bumps the dependencies group with 8 updates: | Package | From | To | | --- | --- | --- | | [io.avaje:avaje-inject](https://github.com/avaje/avaje-inject) | `11.2-RC2` | `11.2` | | io.avaje:avaje-inject-test | `11.2-RC2` | `11.2` | | io.avaje:avaje-inject-generator | `11.2-RC2` | `11.2` | | io.avaje:avaje-inject-maven-plugin | `11.2-RC2` | `11.2` | | io.avaje:avaje-validator-constraints | `2.5` | `2.6` | | [io.avaje:avaje-validator](https://github.com/avaje/avaje-validator) | `2.5` | `2.6` | | io.avaje:avaje-validator-generator | `2.5` | `2.6` | | [io.github.robaho:httpserver](https://github.com/robaho/httpserver) | `1.0.21` | `1.0.22` | Updates `io.avaje:avaje-inject` from 11.2-RC2 to 11.2 - [Release notes](https://github.com/avaje/avaje-inject/releases) - [Commits](https://github.com/avaje/avaje-inject/commits/11.2) Updates `io.avaje:avaje-inject-test` from 11.2-RC2 to 11.2 Updates `io.avaje:avaje-inject-generator` from 11.2-RC2 to 11.2 Updates `io.avaje:avaje-inject-maven-plugin` from 11.2-RC2 to 11.2 Updates `io.avaje:avaje-inject-test` from 11.2-RC2 to 11.2 Updates `io.avaje:avaje-validator-constraints` from 2.5 to 2.6 Updates `io.avaje:avaje-validator` from 2.5 to 2.6 - [Release notes](https://github.com/avaje/avaje-validator/releases) - [Commits](https://github.com/avaje/avaje-validator/compare/2.5...2.6) Updates `io.avaje:avaje-validator-generator` from 2.5 to 2.6 Updates `io.avaje:avaje-validator` from 2.5 to 2.6 - [Release notes](https://github.com/avaje/avaje-validator/releases) - [Commits](https://github.com/avaje/avaje-validator/compare/2.5...2.6) Updates `io.avaje:avaje-inject-generator` from 11.2-RC2 to 11.2 Updates `io.avaje:avaje-validator-generator` from 2.5 to 2.6 Updates `io.github.robaho:httpserver` from 1.0.21 to 1.0.22 - [Commits](https://github.com/robaho/httpserver/commits) Updates `io.avaje:avaje-inject-maven-plugin` from 11.2-RC2 to 11.2 --- updated-dependencies: - dependency-name: io.avaje:avaje-inject dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-test dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-maven-plugin dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-test dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-validator-constraints dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-validator-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.github.robaho:httpserver dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-maven-plugin dependency-type: direct:production dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- examples/example-robaho/pom.xml | 2 +- pom.xml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index f682da74..ab2c26e5 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -15,7 +15,7 @@ io.github.robaho httpserver - 1.0.21 + 1.0.22 diff --git a/pom.xml b/pom.xml index bea23808..5f8da997 100644 --- a/pom.xml +++ b/pom.xml @@ -26,11 +26,11 @@ 21 full 4.0 - 11.2-RC2 + 11.2 3.0-RC6 3.0-RC1 9.4 - 2.5 + 2.6 1.2 2.9 1.36 @@ -206,7 +206,7 @@ io.github.robaho httpserver - 1.0.21 + 1.0.22 From ec026d0c7b5f168a0ae4f6ad06329f734083b498 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 3 Feb 2025 09:17:53 -0500 Subject: [PATCH 186/250] Add `HttpServerProvider` config (#178) * Add server provider config * rename --- .../main/java/io/avaje/jex/DJexConfig.java | 13 +++++++ .../src/main/java/io/avaje/jex/JexConfig.java | 19 ++++++++- .../io/avaje/jex/core/BootstrapServer.java | 39 ++++++++----------- .../test/java/io/avaje/jex/CookieTest.java | 1 - 4 files changed, 47 insertions(+), 25 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 50b2f093..8c135a3a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -7,6 +7,7 @@ import java.util.function.Consumer; import com.sun.net.httpserver.HttpsConfigurator; +import com.sun.net.httpserver.spi.HttpServerProvider; import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.spi.JsonService; @@ -28,6 +29,7 @@ final class DJexConfig implements JexConfig { private final CompressionConfig compression = new CompressionConfig(); private int bufferInitial = 256; private long bufferMax = 1024L; + private HttpServerProvider serverProvider; @Override public JexConfig host(String host) { @@ -195,4 +197,15 @@ public JexConfig maxStreamBufferSize(long maxSize) { bufferMax = maxSize; return this; } + + @Override + public HttpServerProvider serverProvider() { + return this.serverProvider != null ? serverProvider : HttpServerProvider.provider(); + } + + @Override + public JexConfig serverProvider(HttpServerProvider serverProvider) { + this.serverProvider = serverProvider; + return this; + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index 0a1e8ec5..549410c1 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -5,8 +5,9 @@ import java.util.concurrent.Executors; import java.util.function.Consumer; -import com.sun.net.httpserver.HttpsConfigurator; - +import com.sun.net.httpserver.*; +import com.sun.net.httpserver.spi.HttpServerProvider; +import com.sun.net.httpserver.HttpServer; import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.spi.JexPlugin; import io.avaje.jex.spi.JsonService; @@ -165,6 +166,20 @@ public sealed interface JexConfig permits DJexConfig { /** Return the schema as http or https. */ String scheme(); + /** + * Provide the provider used to create the {@link HttpServer} instance. If not set, {@link + * HttpServerProvider#provider()} will be used to create the server + */ + HttpServerProvider serverProvider(); + + /** + * Configure Provider used to created {@link HttpServer} instances. If not set, {@link + * HttpServerProvider#provider()} will be used to create the server. + * + * @param serverProvider provider used to create the server + */ + JexConfig serverProvider(HttpServerProvider serverProvider); + /** Return the socket backlog. */ int socketBacklog(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java index a52dc780..49583bd1 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java @@ -1,13 +1,7 @@ package io.avaje.jex.core; -import com.sun.net.httpserver.HttpServer; -import com.sun.net.httpserver.HttpsServer; - -import io.avaje.jex.AppLifecycle; -import io.avaje.jex.Jex; -import io.avaje.jex.JexConfig; -import io.avaje.jex.routes.RoutesBuilder; -import io.avaje.jex.routes.SpiRoutes; +import static java.lang.System.Logger.Level.DEBUG; +import static java.lang.System.Logger.Level.INFO; import java.io.IOException; import java.io.UncheckedIOException; @@ -15,11 +9,18 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; -import static java.lang.System.Logger.Level.DEBUG; -import static java.lang.System.Logger.Level.INFO; +import com.sun.net.httpserver.HttpServer; + +import io.avaje.jex.AppLifecycle; +import io.avaje.jex.Jex; +import io.avaje.jex.JexConfig; +import io.avaje.jex.routes.RoutesBuilder; +import io.avaje.jex.routes.SpiRoutes; public final class BootstrapServer { + private BootstrapServer() {} + private static final System.Logger log = System.getLogger("io.avaje.jex"); public static Jex.Server start(Jex jex) { @@ -32,25 +33,24 @@ public static Jex.Server start(Jex jex) { CoreServiceLoader.plugins().forEach(p -> p.apply(jex)); } - final SpiRoutes routes = - new RoutesBuilder(jex.routing(), config).build(); + final SpiRoutes routes = new RoutesBuilder(jex.routing(), config).build(); return start(jex, routes); } static Jex.Server start(Jex jex, SpiRoutes routes) { - try { + try { final var config = jex.config(); final var socketAddress = createSocketAddress(config); final var https = config.httpsConfig(); - + final var provider = config.serverProvider(); final HttpServer server; if (https != null) { - var httpsServer = HttpsServer.create(socketAddress, config.socketBacklog()); + var httpsServer = provider.createHttpsServer(socketAddress, config.socketBacklog()); httpsServer.setHttpsConfigurator(https); server = httpsServer; } else { - server = HttpServer.create(socketAddress, config.socketBacklog()); + server = provider.createHttpServer(socketAddress, config.socketBacklog()); } final var scheme = config.scheme(); @@ -70,12 +70,7 @@ static Jex.Server start(Jex jex, SpiRoutes routes) { server.start(); jex.lifecycle().status(AppLifecycle.Status.STARTED); - log.log( - INFO, - "Avaje Jex started {0} on port {1}://{2}", - serverClass, - scheme, - socketAddress); + log.log(INFO, "Avaje Jex started {0} on port {1}://{2}", serverClass, scheme, socketAddress); log.log(DEBUG, routes); return new JdkJexServer(server, jex.lifecycle(), handler); } catch (IOException e) { diff --git a/avaje-jex/src/test/java/io/avaje/jex/CookieTest.java b/avaje-jex/src/test/java/io/avaje/jex/CookieTest.java index 2fe830dc..98ba3123 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/CookieTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/CookieTest.java @@ -2,7 +2,6 @@ import io.avaje.jex.Context.Cookie; import org.junit.jupiter.api.Test; -import org.mockito.junit.MockitoJUnitRunner; import java.time.Duration; From 0c67b39d1568c75059e27100d0886fb1ec01b9b3 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 5 Feb 2025 08:25:40 +1300 Subject: [PATCH 187/250] Version 3.0-RC17 --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-htmx/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- examples/example-http-generation/pom.xml | 2 +- examples/example-jdk-jsonb/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-jetty/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 14 +++++++------- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 4ec2f100..ec16fb0d 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC16 + 3.0-RC17 avaje-jex-freemarker diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 7f354584..0d56d4f6 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC16 + 3.0-RC17 avaje-jex-htmx diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 763b1f20..9536e3ea 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC16 + 3.0-RC17 avaje-jex-mustache diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index bc6138ec..4cd9985a 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC16 + 3.0-RC17 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 68028c86..140b904e 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC16 + 3.0-RC17 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 850cfd2b..2e16dbf6 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC16 + 3.0-RC17 avaje-jex diff --git a/examples/example-http-generation/pom.xml b/examples/example-http-generation/pom.xml index 7119cbe3..a6942951 100644 --- a/examples/example-http-generation/pom.xml +++ b/examples/example-http-generation/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC16 + 3.0-RC17 4.0.0 diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index 42a50a82..56ffbbb2 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -6,7 +6,7 @@ avaje-jex-parent io.avaje - 3.0-RC16 + 3.0-RC17 org.example diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 707b451d..a6469285 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC16 + 3.0-RC17 diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index 1a559622..8f0f135c 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC16 + 3.0-RC17 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index ab2c26e5..91b652d7 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC16 + 3.0-RC17 diff --git a/examples/pom.xml b/examples/pom.xml index 9fdb4100..06bb1772 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC16 + 3.0-RC17 examples diff --git a/pom.xml b/pom.xml index 5f8da997..ab3bdb9b 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ io.avaje avaje-jex-parent - 3.0-RC16 + 3.0-RC17 pom @@ -176,32 +176,32 @@ io.avaje avaje-jex - 3.0-RC16 + 3.0-RC17 io.avaje avaje-jex-test - 3.0-RC16 + 3.0-RC17 io.avaje avaje-jex-freemarker - 3.0-RC16 + 3.0-RC17 io.avaje avaje-jex-mustache - 3.0-RC16 + 3.0-RC17 io.avaje avaje-jex-htmx - 3.0-RC16 + 3.0-RC17 io.avaje avaje-jex-static-content - 3.0-RC16 + 3.0-RC17 io.github.robaho From 6d7d84a1625febad78b6829515d840ea8afa6091 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 9 Feb 2025 17:32:58 -0500 Subject: [PATCH 188/250] Enum Response ContentStatus (#181) --- .../src/main/java/io/avaje/jex/Context.java | 13 +++- .../io/avaje/jex/core/ExceptionManager.java | 6 +- .../java/io/avaje/jex/core/JdkContext.java | 4 +- .../java/io/avaje/jex/http/ContentType.java | 76 ++++++++++++++++++ .../java/io/avaje/jex/http/ErrorCode.java | 45 ----------- .../java/io/avaje/jex/http/HttpStatus.java | 78 +++++++++++++++++++ .../avaje/jex/core/ExceptionManagerTest.java | 4 +- 7 files changed, 173 insertions(+), 53 deletions(-) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/ContentType.java delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/HttpStatus.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/Context.java index bec468cb..5ad59e1e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Context.java @@ -20,13 +20,14 @@ import com.sun.net.httpserver.HttpExchange; import io.avaje.jex.core.Constants; +import io.avaje.jex.http.ContentType; +import io.avaje.jex.http.HttpStatus; import io.avaje.jex.security.BasicAuthCredentials; import io.avaje.jex.security.Role; /** Provides access to functions for handling the request and response. */ public interface Context { - /** * Gets the attribute with the specified key from the request. * @@ -94,6 +95,11 @@ public interface Context { /** Set the response content type. */ Context contentType(String contentType); + /** Set the response content type. */ + default Context contentType(ContentType contentType) { + return contentType(contentType.contentType()); + } + /** Return the request context path. */ String contextPath(); @@ -423,6 +429,11 @@ default Context render(String name) { /** Set the status code on the response. */ Context status(int statusCode); + /** Set the status code on the response. */ + default Context status(HttpStatus statusCode) { + return status(statusCode.status()); + } + /** Write plain text content to the response. */ void text(String content); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index cb6034c9..8cf32bbb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -6,7 +6,7 @@ import io.avaje.jex.Context; import io.avaje.jex.ExceptionHandler; -import io.avaje.jex.http.ErrorCode; +import io.avaje.jex.http.HttpStatus; import io.avaje.jex.http.HttpResponseException; import io.avaje.jex.http.InternalServerErrorException; @@ -52,13 +52,13 @@ void handle(JdkContext ctx, Exception e) { private void unhandledException(JdkContext ctx, Exception e) { log.log(ERROR, "Uncaught exception", e); - defaultHandling(ctx, new InternalServerErrorException(ErrorCode.INTERNAL_SERVER_ERROR.message())); + defaultHandling(ctx, new InternalServerErrorException("Internal Server Error")); } private void defaultHandling(JdkContext ctx, HttpResponseException exception) { ctx.status(exception.status()); var jsonResponse = exception.jsonResponse(); - if (exception.status() == ErrorCode.REDIRECT.status()) { + if (exception.status() == HttpStatus.FOUND_302.status()) { ctx.performRedirect(); } else if (jsonResponse != null) { ctx.json(jsonResponse); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index 3eed10f2..7b8269d4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -33,7 +33,7 @@ import com.sun.net.httpserver.HttpsExchange; import io.avaje.jex.Context; -import io.avaje.jex.http.ErrorCode; +import io.avaje.jex.http.HttpStatus; import io.avaje.jex.http.RedirectException; import io.avaje.jex.security.BasicAuthCredentials; import io.avaje.jex.security.Role; @@ -415,7 +415,7 @@ public void redirect(String location, int statusCode) { header(Constants.LOCATION, location); status(statusCode); if (mode != Mode.EXCHANGE) { - throw new RedirectException(ErrorCode.REDIRECT.message()); + throw new RedirectException("Redirect"); } else { performRedirect(); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/ContentType.java b/avaje-jex/src/main/java/io/avaje/jex/http/ContentType.java new file mode 100644 index 00000000..c91de7b4 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/http/ContentType.java @@ -0,0 +1,76 @@ +package io.avaje.jex.http; + +public enum ContentType { + + TEXT_PLAIN("text/plain"), + TEXT_CSS("text/css"), + TEXT_CSV("text/csv"), + TEXT_HTML("text/html"), + TEXT_JS("text/javascript"), + TEXT_MARKDOWN("text/markdown"), + TEXT_PROPERTIES("text/x-java-properties"), + TEXT_XML("text/xml"), + + IMAGE_AVIF("image/avif"), + IMAGE_BMP("image/bmp"), + IMAGE_GIF("image/gif"), + IMAGE_ICO("image/vnd.microsoft.icon"), + IMAGE_JPEG("image/jpeg"), + IMAGE_PNG("image/png"), + IMAGE_SVG("image/svg+xml"), + IMAGE_TIFF("image/tiff"), + IMAGE_WEBP("image/webp"), + + AUDIO_AAC("audio/aac"), + AUDIO_MIDI("audio/midi"), + AUDIO_MPEG("audio/mpeg"), + AUDIO_OGA("audio/ogg"), + AUDIO_OPUS("audio/opus"), + AUDIO_WAV("audio/wav"), + AUDIO_WEBA("audio/weba"), + + VIDEO_AVI("video/x-msvideo"), + VIDEO_MP4("video/mp4"), + VIDEO_MPEG("video/mpeg"), + VIDEO_OGG("video/ogg"), + VIDEO_WEBM("video/webm"), + + FONT_OTF("font/otf"), + FONT_TTF("font/ttf"), + FONT_WOFF("font/woff"), + FONT_WOFF2("font/woff2"), + + APPLICATION_OCTET_STREAM("application/octet-stream"), + APPLICATION_BZ("application/x-bzip"), + APPLICATION_BZ2("application/x-bzip2"), + APPLICATION_CDN("application/cdn"), + APPLICATION_DOC("application/msword"), + APPLICATION_DOCX("application/vnd.openxmlformats-officedocument.wordprocessingml.document"), + APPLICATION_EPUB("application/epub+zip"), + APPLICATION_GZ("application/gzip"), + APPLICATION_JSON("application/json"), + APPLICATION_MPKG("application/vnd.apple.installer+xml"), + APPLICATION_JAR("application/java-archive"), + APPLICATION_PDF("application/pdf"), + APPLICATION_POM("application/xml"), + APPLICATION_RAR("application/vnd.rar"), + APPLICATION_SH("application/x-sh"), + APPLICATION_SWF("application/x-shockwave-flash"), + APPLICATION_TAR("application/x-tar"), + APPLICATION_XHTML("application/xhtml+xml"), + APPLICATION_YAML("application/yaml"), + APPLICATION_ZIP("application/zip"), + APPLICATION_7Z("application/x-7z-compressed"), + + MULTIPART_FORM_DATA("multipart/form-data"); + + private final String content; + + ContentType(String contentType) { + this.content = contentType; + } + + public String contentType() { + return content; + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java b/avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java deleted file mode 100644 index d9686381..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/http/ErrorCode.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.avaje.jex.http; - -/** Http Error Status codes */ -public enum ErrorCode { - REDIRECT(302, "Redirect"), - - // 4xx - BAD_REQUEST(400, "Bad Request"), - UNAUTHORIZED(401, "Unauthorized"), - FORBIDDEN(403, "Forbidden"), - NOT_FOUND(404, "Not Found"), - METHOD_NOT_ALLOWED(405, "Method Not Allowed"), - REQUEST_TIMEOUT(408, "Request Timeout"), - CONFLICT(409, "Conflict"), - GONE(410, "Gone"), - - // 5xx Server Error - INTERNAL_SERVER_ERROR(500, "Internal Server Error"), - BAD_GATEWAY(502, "Bad Gateway"), - SERVICE_UNAVAILABLE(503, "HttpService Unavailable"), - GATEWAY_TIMEOUT(504, "Gateway Timeout"), - HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version Not Supported"), - VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates"), - INSUFFICIENT_STORAGE(507, "Insufficient Storage"), - LOOP_DETECTED(508, "Loop Detected"), - BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded"), - NOT_EXTENDED(510, "Not Extended"), - NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required"); - - private final int status; - private final String message; - - ErrorCode(int status, String message) { - this.status = status; - this.message = message; - } - - public int status() { - return status; - } - - public String message() { - return message; - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/HttpStatus.java b/avaje-jex/src/main/java/io/avaje/jex/http/HttpStatus.java new file mode 100644 index 00000000..9b679c33 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/http/HttpStatus.java @@ -0,0 +1,78 @@ +package io.avaje.jex.http; + +/** Http Error Status codes */ +public enum HttpStatus { + + // 1xx Informational + CONTINUE_100(100), + SWITCHING_PROTOCOLS_101(101), + + // 2xx Success + OK_200(200), + CREATED_201(201), + ACCEPTED_202(202), + NON_AUTHORITATIVE_INFORMATION_203(203), + NO_CONTENT_204(204), + RESET_CONTENT_205(205), + PARTIAL_CONTENT_206(206), + MULTI_STATUS_207(207), + + // 3xx Redirection + MOVED_PERMANENTLY_301(301), + FOUND_302(302), + SEE_OTHER_303(303), + NOT_MODIFIED_304(304), + USE_PROXY_305(305), + TEMPORARY_REDIRECT_307(307), + PERMANENT_REDIRECT_308(308), + + // 4xx Client Error + BAD_REQUEST_400(400), + UNAUTHORIZED_401(401), + PAYMENT_REQUIRED_402(402), + FORBIDDEN_403(403), + NOT_FOUND_404(404), + METHOD_NOT_ALLOWED_405(405), + NOT_ACCEPTABLE_406(406), + PROXY_AUTHENTICATION_REQUIRED_407(407), + REQUEST_TIMEOUT_408(408), + CONFLICT_409(409), + GONE_410(410), + LENGTH_REQUIRED_411(411), + PRECONDITION_FAILED_412(412), + REQUEST_ENTITY_TOO_LARGE_413(413), + REQUEST_URI_TOO_LONG_414(414), + UNSUPPORTED_MEDIA_TYPE_415(415), + REQUESTED_RANGE_NOT_SATISFIABLE_416(416), + EXPECTATION_FAILED_417(417), + I_AM_A_TEAPOT_418(418), + MISDIRECTED_REQUEST_421(421), + UNPROCESSABLE_CONTENT_422(422), + LOCKED_423(423), + FAILED_DEPENDENCY_424(424), + UPGRADE_REQUIRED_426(426), + PRECONDITION_REQUIRED_428(428), + TOO_MANY_REQUESTS_429(429), + + // 5xx Server Error + INTERNAL_SERVER_ERROR_500(500), + NOT_IMPLEMENTED_501(501), + BAD_GATEWAY_502(502), + SERVICE_UNAVAILABLE_503(503), + GATEWAY_TIMEOUT_504(504), + HTTP_VERSION_NOT_SUPPORTED_505(505), + INSUFFICIENT_STORAGE_507(507), + LOOP_DETECTED_508(508), + NOT_EXTENDED_510(510), + NETWORK_AUTHENTICATION_REQUIRED_511(511); + + private final int status; + + HttpStatus(int status) { + this.status = status; + } + + public int status() { + return status; + } +} diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/ExceptionManagerTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ExceptionManagerTest.java index 01b1af61..a63d89ef 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/ExceptionManagerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ExceptionManagerTest.java @@ -10,7 +10,7 @@ import io.avaje.jex.Jex; import io.avaje.jex.http.BadRequestException; -import io.avaje.jex.http.ErrorCode; +import io.avaje.jex.http.HttpStatus; import io.avaje.jex.http.HttpResponseException; import io.avaje.json.JsonException; @@ -22,7 +22,7 @@ static TestPair init() { final Jex app = Jex.create() .routing(routing -> routing .get("/", ctx -> { - throw new HttpResponseException(ErrorCode.FORBIDDEN.status(), ErrorCode.FORBIDDEN.message()); + throw new HttpResponseException(HttpStatus.FORBIDDEN_403.status(), "Forbidden"); }) .post("/", ctx -> { throw new IllegalStateException("foo"); From 1ed9cb909da45119d55fb702be3f7117bff0e25d Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 9 Feb 2025 17:33:21 -0500 Subject: [PATCH 189/250] add serverProvider to auto config (#180) --- avaje-jex/src/main/java/io/avaje/jex/AvajeJex.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/DJex.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/AvajeJex.java b/avaje-jex/src/main/java/io/avaje/jex/AvajeJex.java index 93904513..b603a332 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/AvajeJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/AvajeJex.java @@ -51,7 +51,7 @@ static Server start(BeanScope beanScope) { JexConfig config = jex.config(); config.port(Config.getInt("server.port", config.port())); config.contextPath(Config.get("server.context.path", config.contextPath())); - config.host(Config.get("server.context.host", config.host())); + config.host(Config.getNullable("server.context.host", config.host())); return jex.start(); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index 36abb7ea..455590b1 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -4,6 +4,7 @@ import io.avaje.jex.core.BootstrapServer; import io.avaje.jex.spi.*; import com.sun.net.httpserver.HttpsConfigurator; +import com.sun.net.httpserver.spi.HttpServerProvider; import java.util.*; import java.util.function.Consumer; @@ -57,6 +58,7 @@ public Jex configureWith(BeanScope beanScope) { routing.addAll(beanScope.list(Routing.HttpService.class)); beanScope.getOptional(JsonService.class).ifPresent(this::jsonService); beanScope.getOptional(HttpsConfigurator.class).ifPresent(config()::httpsConfig); + beanScope.getOptional(HttpServerProvider.class).ifPresent(config()::serverProvider); return this; } From a431a234706bdfdaee336e45f387dc4c36997e13 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 9 Feb 2025 22:08:57 -0500 Subject: [PATCH 190/250] increase default max buffer size (#183) --- avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/JexConfig.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 8c135a3a..89c3e3f5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -28,7 +28,7 @@ final class DJexConfig implements JexConfig { private boolean useJexSpi = true; private final CompressionConfig compression = new CompressionConfig(); private int bufferInitial = 256; - private long bufferMax = 1024L; + private long bufferMax = 4096L; private HttpServerProvider serverProvider; @Override diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index 549410c1..bcf661dd 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -129,11 +129,11 @@ public sealed interface JexConfig permits DJexConfig { long maxStreamBufferSize(); /** - * Set the maximum size of the response stream buffer. If the response data exceeds this size, - * then it will be written to the client using chunked transfer encoding. Otherwise, the response - * will be sent using a Content-Length header with the exact size of the response data. + * Set the maximum size of the response stream buffer. If the response data exceeds this size, it + * will be written to the client using chunked transfer encoding. Otherwise, the response will be + * sent using a Content-Length header with the exact size of the response data. * - *

    Defaults to 1024 + *

    Defaults to 4096 * * @param maxSize The maximum size of the response */ From 18421661fbcf1eb5b4b290667e70cda4ffa139c9 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 9 Feb 2025 22:09:24 -0500 Subject: [PATCH 191/250] Move handler classes to http package (#182) * http package * Update pom.xml * Update README.md --- README.md | 3 ++- .../io/avaje/jex/render/freemarker/FreeMarkerRender.java | 2 +- .../src/main/java/io/avaje/jex/htmx/DHxHandler.java | 4 ++-- .../src/main/java/io/avaje/jex/htmx/DHxHandlerBuilder.java | 2 +- .../src/main/java/io/avaje/jex/htmx/HxHandler.java | 2 +- avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxReq.java | 2 +- .../main/java/io/avaje/jex/htmx/TemplateContentCache.java | 2 +- .../java/io/avaje/jex/render/mustache/MustacheRender.java | 3 ++- .../io/avaje/jex/staticcontent/AbstractStaticHandler.java | 4 ++-- .../avaje/jex/staticcontent/StaticClassResourceHandler.java | 2 +- .../main/java/io/avaje/jex/staticcontent/StaticContent.java | 2 +- .../java/io/avaje/jex/staticcontent/StaticFileHandler.java | 2 +- .../jex/staticcontent/StaticResourceHandlerBuilder.java | 4 ++-- avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java | 3 +++ avaje-jex/src/main/java/io/avaje/jex/Jex.java | 4 ++++ avaje-jex/src/main/java/io/avaje/jex/Routing.java | 4 ++++ .../io/avaje/jex/compression/CompressedOutputStream.java | 2 +- .../src/main/java/io/avaje/jex/core/BaseFilterChain.java | 6 +++--- .../src/main/java/io/avaje/jex/core/ExceptionManager.java | 4 ++-- avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java | 2 +- .../src/main/java/io/avaje/jex/core/RoutingHandler.java | 2 +- .../src/main/java/io/avaje/jex/core/ServiceManager.java | 2 +- .../src/main/java/io/avaje/jex/core/TemplateManager.java | 2 +- .../src/main/java/io/avaje/jex/{ => http}/Context.java | 4 +--- .../src/main/java/io/avaje/jex/{ => http}/DCookie.java | 2 +- .../main/java/io/avaje/jex/{ => http}/ExceptionHandler.java | 4 +++- .../main/java/io/avaje/jex/{ => http}/ExchangeHandler.java | 2 +- .../src/main/java/io/avaje/jex/{ => http}/HttpFilter.java | 2 +- .../src/main/java/io/avaje/jex/routes/MultiHandler.java | 4 ++-- avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java | 2 +- .../src/main/java/io/avaje/jex/routes/RouteIndexBuild.java | 4 ++-- avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java | 2 +- .../src/main/java/io/avaje/jex/routes/RoutesBuilder.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java | 4 ++-- avaje-jex/src/main/java/io/avaje/jex/security/Role.java | 2 +- .../src/main/java/io/avaje/jex/spi/TemplateRender.java | 2 +- avaje-jex/src/test/java/io/avaje/jex/CookieTest.java | 3 ++- .../src/test/java/io/avaje/jex/core/CookieServerTest.java | 3 ++- .../java/io/avaje/jex/core/DefaultErrorHandlingTest.java | 5 +++-- .../example-jdk-jsonb/src/main/java/org/example/Main.java | 2 +- examples/example-jdk/src/main/java/org/example/Main.java | 3 ++- examples/pom.xml | 2 +- 43 files changed, 69 insertions(+), 52 deletions(-) rename avaje-jex/src/main/java/io/avaje/jex/{ => http}/Context.java (99%) rename avaje-jex/src/main/java/io/avaje/jex/{ => http}/DCookie.java (99%) rename avaje-jex/src/main/java/io/avaje/jex/{ => http}/ExceptionHandler.java (90%) rename avaje-jex/src/main/java/io/avaje/jex/{ => http}/ExchangeHandler.java (97%) rename avaje-jex/src/main/java/io/avaje/jex/{ => http}/HttpFilter.java (98%) diff --git a/README.md b/README.md index a0292dc7..9d15e78f 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,13 @@ Lightweight (~110KB) wrapper over the JDK's built-in [HTTP server](https://docs. Features: -- [Context](https://javadoc.io/doc/io.avaje/avaje-jex/latest/io.avaje.jex/io/avaje/jex/Context.html) abstraction over `HttpExchange` to easily retrieve and send request/response data. +- [Context](https://javadoc.io/doc/io.avaje/avaje-jex/latest/io.avaje.jex/io/avaje/jex/http/Context.html) abstraction over `HttpExchange` to easily retrieve and send request/response data. - Fluent API - Static resource handling - Compression SPI - Json SPI - Virtual threads enabled by default +- Multi-Server with any implementation of `jdk.httpserver` (Jetty, etc) ## Quick Start diff --git a/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java b/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java index 67d4370a..1c5af89c 100644 --- a/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java +++ b/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java @@ -9,7 +9,7 @@ import freemarker.template.Template; import freemarker.template.TemplateException; import freemarker.template.Version; -import io.avaje.jex.Context; +import io.avaje.jex.http.Context; import io.avaje.jex.spi.TemplateRender; import io.avaje.spi.ServiceProvider; diff --git a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandler.java b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandler.java index ea3a682c..d892d4d0 100644 --- a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandler.java +++ b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandler.java @@ -5,8 +5,8 @@ import static io.avaje.jex.htmx.HxHeaders.HX_TRIGGER; import static io.avaje.jex.htmx.HxHeaders.HX_TRIGGER_NAME; -import io.avaje.jex.Context; -import io.avaje.jex.ExchangeHandler; +import io.avaje.jex.http.Context; +import io.avaje.jex.http.ExchangeHandler; final class DHxHandler implements ExchangeHandler { diff --git a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandlerBuilder.java b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandlerBuilder.java index 5cc18313..eff808e9 100644 --- a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandlerBuilder.java +++ b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/DHxHandlerBuilder.java @@ -1,6 +1,6 @@ package io.avaje.jex.htmx; -import io.avaje.jex.ExchangeHandler; +import io.avaje.jex.http.ExchangeHandler; final class DHxHandlerBuilder implements HxHandler.Builder { diff --git a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxHandler.java b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxHandler.java index 55b61293..b684b0f3 100644 --- a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxHandler.java +++ b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxHandler.java @@ -1,6 +1,6 @@ package io.avaje.jex.htmx; -import io.avaje.jex.ExchangeHandler; +import io.avaje.jex.http.ExchangeHandler; /** * Wrap a Handler with filtering for Htmx specific headers. diff --git a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxReq.java b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxReq.java index e51274ab..8b4cfb5e 100644 --- a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxReq.java +++ b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/HxReq.java @@ -1,7 +1,7 @@ package io.avaje.jex.htmx; import io.avaje.htmx.api.HtmxRequest; -import io.avaje.jex.Context; +import io.avaje.jex.http.Context; /** * Obtain the HtmxRequest for the given Jex Context. diff --git a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/TemplateContentCache.java b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/TemplateContentCache.java index a2df8740..5472bfa6 100644 --- a/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/TemplateContentCache.java +++ b/avaje-jex-htmx/src/main/java/io/avaje/jex/htmx/TemplateContentCache.java @@ -1,6 +1,6 @@ package io.avaje.jex.htmx; -import io.avaje.jex.Context; +import io.avaje.jex.http.Context; /** * Defines caching of template content. diff --git a/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java b/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java index f2aaf27c..547db319 100644 --- a/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java +++ b/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java @@ -2,7 +2,8 @@ import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.MustacheFactory; -import io.avaje.jex.Context; + +import io.avaje.jex.http.Context; import io.avaje.jex.spi.TemplateRender; import io.avaje.spi.ServiceProvider; diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/AbstractStaticHandler.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/AbstractStaticHandler.java index 818f8351..c28667b9 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/AbstractStaticHandler.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/AbstractStaticHandler.java @@ -12,11 +12,11 @@ import com.sun.net.httpserver.HttpExchange; -import io.avaje.jex.Context; -import io.avaje.jex.ExchangeHandler; import io.avaje.jex.compression.CompressedOutputStream; import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.http.BadRequestException; +import io.avaje.jex.http.Context; +import io.avaje.jex.http.ExchangeHandler; import io.avaje.jex.http.NotFoundException; abstract sealed class AbstractStaticHandler implements ExchangeHandler diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticClassResourceHandler.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticClassResourceHandler.java index 6b066839..dd864834 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticClassResourceHandler.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticClassResourceHandler.java @@ -5,8 +5,8 @@ import java.util.Map; import java.util.function.Predicate; -import io.avaje.jex.Context; import io.avaje.jex.compression.CompressionConfig; +import io.avaje.jex.http.Context; final class StaticClassResourceHandler extends AbstractStaticHandler { diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java index 2a9e81ab..0efd677e 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java @@ -3,7 +3,7 @@ import java.net.URLConnection; import java.util.function.Predicate; -import io.avaje.jex.Context; +import io.avaje.jex.http.Context; import io.avaje.jex.security.Role; import io.avaje.jex.spi.JexPlugin; diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java index da4564ba..2b40731a 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java @@ -9,8 +9,8 @@ import com.sun.net.httpserver.HttpExchange; -import io.avaje.jex.Context; import io.avaje.jex.compression.CompressionConfig; +import io.avaje.jex.http.Context; final class StaticFileHandler extends AbstractStaticHandler { diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java index c21daaf9..290f4375 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java @@ -7,10 +7,10 @@ import java.util.Objects; import java.util.function.Predicate; -import io.avaje.jex.Context; -import io.avaje.jex.ExchangeHandler; import io.avaje.jex.Jex; import io.avaje.jex.compression.CompressionConfig; +import io.avaje.jex.http.Context; +import io.avaje.jex.http.ExchangeHandler; import io.avaje.jex.security.Role; final class StaticResourceHandlerBuilder implements StaticContent.Builder, StaticContent { diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index f2cb1561..daea48bc 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -9,6 +9,9 @@ import java.util.Map; import java.util.Set; +import io.avaje.jex.http.ExceptionHandler; +import io.avaje.jex.http.ExchangeHandler; +import io.avaje.jex.http.HttpFilter; import io.avaje.jex.security.Role; final class DefaultRouting implements Routing { diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 1bb00762..b405178b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -5,6 +5,10 @@ import io.avaje.inject.BeanScope; import io.avaje.jex.Routing.HttpService; +import io.avaje.jex.http.Context; +import io.avaje.jex.http.ExceptionHandler; +import io.avaje.jex.http.ExchangeHandler; +import io.avaje.jex.http.HttpFilter; import io.avaje.jex.security.Role; import io.avaje.jex.spi.JexPlugin; import io.avaje.jex.spi.JsonService; diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index ae9aaa20..059f6a93 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -6,6 +6,10 @@ import java.util.Set; import java.util.function.Consumer; +import io.avaje.jex.http.Context; +import io.avaje.jex.http.ExceptionHandler; +import io.avaje.jex.http.ExchangeHandler; +import io.avaje.jex.http.HttpFilter; import io.avaje.jex.security.Role; /** Routing abstraction. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java index ce07b41e..3e0e608e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java @@ -6,8 +6,8 @@ import java.util.Objects; import java.util.Optional; -import io.avaje.jex.Context; import io.avaje.jex.core.Constants; +import io.avaje.jex.http.Context; /** * OutputStream implementation that conditionally compresses the output based on configuration and diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BaseFilterChain.java b/avaje-jex/src/main/java/io/avaje/jex/core/BaseFilterChain.java index 586905c1..095feab3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BaseFilterChain.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BaseFilterChain.java @@ -2,9 +2,9 @@ import java.util.Iterator; -import io.avaje.jex.ExchangeHandler; -import io.avaje.jex.HttpFilter; -import io.avaje.jex.HttpFilter.FilterChain; +import io.avaje.jex.http.ExchangeHandler; +import io.avaje.jex.http.HttpFilter; +import io.avaje.jex.http.HttpFilter.FilterChain; final class BaseFilterChain implements FilterChain { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index 8cf32bbb..52f4d020 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -4,9 +4,9 @@ import java.util.Map; -import io.avaje.jex.Context; -import io.avaje.jex.ExceptionHandler; import io.avaje.jex.http.HttpStatus; +import io.avaje.jex.http.Context; +import io.avaje.jex.http.ExceptionHandler; import io.avaje.jex.http.HttpResponseException; import io.avaje.jex.http.InternalServerErrorException; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java b/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java index 30939756..6a5a5277 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java @@ -1,8 +1,8 @@ package io.avaje.jex.core; import io.avaje.jex.AppLifecycle; -import io.avaje.jex.Context; import io.avaje.jex.Jex; +import io.avaje.jex.http.Context; import io.avaje.jex.spi.JexPlugin; /** diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index 7b8269d4..0c7c142c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -32,7 +32,7 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpsExchange; -import io.avaje.jex.Context; +import io.avaje.jex.http.Context; import io.avaje.jex.http.HttpStatus; import io.avaje.jex.http.RedirectException; import io.avaje.jex.security.BasicAuthCredentials; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java index 3bb8e03b..cd91a6ef 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java @@ -8,7 +8,7 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; -import io.avaje.jex.HttpFilter; +import io.avaje.jex.http.HttpFilter; import io.avaje.jex.http.NotFoundException; import io.avaje.jex.routes.SpiRoutes; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java index a04c7e9f..8413032a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java @@ -15,13 +15,13 @@ import java.util.Map; import java.util.stream.Stream; -import io.avaje.jex.Context; import io.avaje.jex.Jex; import io.avaje.jex.Routing; import io.avaje.jex.compression.CompressedOutputStream; import io.avaje.jex.compression.CompressionConfig; import io.avaje.jex.core.json.JacksonJsonService; import io.avaje.jex.core.json.JsonbJsonService; +import io.avaje.jex.http.Context; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java index 23043dd1..f72c5f33 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java @@ -1,6 +1,6 @@ package io.avaje.jex.core; -import io.avaje.jex.Context; +import io.avaje.jex.http.Context; import io.avaje.jex.spi.TemplateRender; import java.util.HashMap; diff --git a/avaje-jex/src/main/java/io/avaje/jex/Context.java b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java similarity index 99% rename from avaje-jex/src/main/java/io/avaje/jex/Context.java rename to avaje-jex/src/main/java/io/avaje/jex/http/Context.java index 5ad59e1e..ec50bab7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java @@ -1,4 +1,4 @@ -package io.avaje.jex; +package io.avaje.jex.http; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; @@ -20,8 +20,6 @@ import com.sun.net.httpserver.HttpExchange; import io.avaje.jex.core.Constants; -import io.avaje.jex.http.ContentType; -import io.avaje.jex.http.HttpStatus; import io.avaje.jex.security.BasicAuthCredentials; import io.avaje.jex.security.Role; diff --git a/avaje-jex/src/main/java/io/avaje/jex/DCookie.java b/avaje-jex/src/main/java/io/avaje/jex/http/DCookie.java similarity index 99% rename from avaje-jex/src/main/java/io/avaje/jex/DCookie.java rename to avaje-jex/src/main/java/io/avaje/jex/http/DCookie.java index 7df6d8ef..33887b9a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DCookie.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/DCookie.java @@ -1,4 +1,4 @@ -package io.avaje.jex; +package io.avaje.jex.http; import java.time.Duration; import java.time.LocalDateTime; diff --git a/avaje-jex/src/main/java/io/avaje/jex/ExceptionHandler.java b/avaje-jex/src/main/java/io/avaje/jex/http/ExceptionHandler.java similarity index 90% rename from avaje-jex/src/main/java/io/avaje/jex/ExceptionHandler.java rename to avaje-jex/src/main/java/io/avaje/jex/http/ExceptionHandler.java index 07aafaad..b4932c51 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/ExceptionHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/ExceptionHandler.java @@ -1,4 +1,6 @@ -package io.avaje.jex; +package io.avaje.jex.http; + +import io.avaje.jex.Routing; /** * The routing error handler. Can be mapped to the error cause in {@link Routing}. diff --git a/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java b/avaje-jex/src/main/java/io/avaje/jex/http/ExchangeHandler.java similarity index 97% rename from avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java rename to avaje-jex/src/main/java/io/avaje/jex/http/ExchangeHandler.java index 249b6174..70577324 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/ExchangeHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/ExchangeHandler.java @@ -1,4 +1,4 @@ -package io.avaje.jex; +package io.avaje.jex.http; import java.io.IOException; diff --git a/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java b/avaje-jex/src/main/java/io/avaje/jex/http/HttpFilter.java similarity index 98% rename from avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java rename to avaje-jex/src/main/java/io/avaje/jex/http/HttpFilter.java index c1059a0c..53bc24ba 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/HttpFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/HttpFilter.java @@ -1,4 +1,4 @@ -package io.avaje.jex; +package io.avaje.jex.http; import com.sun.net.httpserver.HttpExchange; diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/MultiHandler.java b/avaje-jex/src/main/java/io/avaje/jex/routes/MultiHandler.java index 85b2685d..599cb2d9 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/MultiHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/MultiHandler.java @@ -1,7 +1,7 @@ package io.avaje.jex.routes; -import io.avaje.jex.Context; -import io.avaje.jex.ExchangeHandler; +import io.avaje.jex.http.Context; +import io.avaje.jex.http.ExchangeHandler; final class MultiHandler implements ExchangeHandler { diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java index d7e60350..3cec6748 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java @@ -4,7 +4,7 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicLong; -import io.avaje.jex.ExchangeHandler; +import io.avaje.jex.http.ExchangeHandler; import io.avaje.jex.security.Role; final class RouteEntry implements SpiRoutes.Entry { diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndexBuild.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndexBuild.java index e13259cf..b25a5529 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndexBuild.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndexBuild.java @@ -1,9 +1,9 @@ package io.avaje.jex.routes; -import io.avaje.jex.ExchangeHandler; - import java.util.*; +import io.avaje.jex.http.ExchangeHandler; + /** * Build the RouteIndex. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java b/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java index f8a4a0d4..85fcdf13 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/Routes.java @@ -6,8 +6,8 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.LockSupport; -import io.avaje.jex.HttpFilter; import io.avaje.jex.Routing; +import io.avaje.jex.http.HttpFilter; final class Routes implements SpiRoutes { diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java index 1880f11d..3602cf0d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RoutesBuilder.java @@ -4,9 +4,9 @@ import java.util.LinkedHashMap; import java.util.List; -import io.avaje.jex.HttpFilter; import io.avaje.jex.JexConfig; import io.avaje.jex.Routing; +import io.avaje.jex.http.HttpFilter; public final class RoutesBuilder { diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java b/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java index 7a96f9fb..5dc91a65 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java @@ -4,9 +4,9 @@ import java.util.Map; import java.util.Set; -import io.avaje.jex.ExchangeHandler; -import io.avaje.jex.HttpFilter; import io.avaje.jex.Routing; +import io.avaje.jex.http.ExchangeHandler; +import io.avaje.jex.http.HttpFilter; import io.avaje.jex.security.Role; /** diff --git a/avaje-jex/src/main/java/io/avaje/jex/security/Role.java b/avaje-jex/src/main/java/io/avaje/jex/security/Role.java index 95c28594..7e898a6b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/security/Role.java +++ b/avaje-jex/src/main/java/io/avaje/jex/security/Role.java @@ -1,6 +1,6 @@ package io.avaje.jex.security; -import io.avaje.jex.Context; +import io.avaje.jex.http.Context; /** Marker interface for roles used in route declarations. See {@link Context#routeRoles()}. */ public interface Role {} diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java b/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java index 99304bbb..407e72ee 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java @@ -2,7 +2,7 @@ import java.util.Map; -import io.avaje.jex.Context; +import io.avaje.jex.http.Context; /** * Template rendering typically of html. diff --git a/avaje-jex/src/test/java/io/avaje/jex/CookieTest.java b/avaje-jex/src/test/java/io/avaje/jex/CookieTest.java index 98ba3123..9f39670d 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/CookieTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/CookieTest.java @@ -1,8 +1,9 @@ package io.avaje.jex; -import io.avaje.jex.Context.Cookie; import org.junit.jupiter.api.Test; +import io.avaje.jex.http.Context.Cookie; + import java.time.Duration; import static org.junit.jupiter.api.Assertions.*; diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/CookieServerTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/CookieServerTest.java index b81746c1..6c8433e9 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/CookieServerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/CookieServerTest.java @@ -1,7 +1,8 @@ package io.avaje.jex.core; -import io.avaje.jex.Context; import io.avaje.jex.Jex; +import io.avaje.jex.http.Context; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/DefaultErrorHandlingTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/DefaultErrorHandlingTest.java index a022e107..e32650b7 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/DefaultErrorHandlingTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/DefaultErrorHandlingTest.java @@ -4,10 +4,11 @@ import java.nio.file.DirectoryIteratorException; -import io.avaje.jex.Context; -import io.avaje.jex.ExceptionHandler; import io.avaje.jex.Jex; import io.avaje.jex.Routing; +import io.avaje.jex.http.Context; +import io.avaje.jex.http.ExceptionHandler; + import org.junit.jupiter.api.Test; class DefaultErrorHandlingTest { diff --git a/examples/example-jdk-jsonb/src/main/java/org/example/Main.java b/examples/example-jdk-jsonb/src/main/java/org/example/Main.java index 15fd557e..724bcad0 100644 --- a/examples/example-jdk-jsonb/src/main/java/org/example/Main.java +++ b/examples/example-jdk-jsonb/src/main/java/org/example/Main.java @@ -3,9 +3,9 @@ import java.util.Map; import java.util.Set; -import io.avaje.jex.Context; import io.avaje.jex.Jex; import io.avaje.jex.core.json.JsonbJsonService; +import io.avaje.jex.http.Context; import io.avaje.jsonb.Jsonb; public class Main { diff --git a/examples/example-jdk/src/main/java/org/example/Main.java b/examples/example-jdk/src/main/java/org/example/Main.java index 4934c1b8..279abede 100644 --- a/examples/example-jdk/src/main/java/org/example/Main.java +++ b/examples/example-jdk/src/main/java/org/example/Main.java @@ -1,7 +1,8 @@ package org.example; -import io.avaje.jex.Context; import io.avaje.jex.Jex; +import io.avaje.jex.http.Context; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/examples/pom.xml b/examples/pom.xml index 06bb1772..ccee5b92 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -13,7 +13,7 @@ pom - example-http-generation + example-jdk example-jdk-jsonb example-robaho From 3045f9517a029cfddd6a5d2bb42fc1aec80c299e Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 10 Feb 2025 16:17:33 +1300 Subject: [PATCH 192/250] Version 3.0-RC18 --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-htmx/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- examples/example-jdk-jsonb/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-jetty/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 14 +++++++------- 12 files changed, 18 insertions(+), 18 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index ec16fb0d..b174ef73 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC17 + 3.0-RC18 avaje-jex-freemarker diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 0d56d4f6..28ba02f3 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC17 + 3.0-RC18 avaje-jex-htmx diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 9536e3ea..7ca2ccc0 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC17 + 3.0-RC18 avaje-jex-mustache diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index 4cd9985a..e2e6dd6d 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC17 + 3.0-RC18 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 140b904e..de22ae18 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC17 + 3.0-RC18 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 2e16dbf6..1677646d 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC17 + 3.0-RC18 avaje-jex diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index 56ffbbb2..eff952d5 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -6,7 +6,7 @@ avaje-jex-parent io.avaje - 3.0-RC17 + 3.0-RC18 org.example diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index a6469285..0fe739fa 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC17 + 3.0-RC18 diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index 8f0f135c..6bfe117c 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC17 + 3.0-RC18 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 91b652d7..d6444edd 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC17 + 3.0-RC18 diff --git a/examples/pom.xml b/examples/pom.xml index ccee5b92..1c060780 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC17 + 3.0-RC18 examples diff --git a/pom.xml b/pom.xml index ab3bdb9b..3332ccf9 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ io.avaje avaje-jex-parent - 3.0-RC17 + 3.0-RC18 pom @@ -176,32 +176,32 @@ io.avaje avaje-jex - 3.0-RC17 + 3.0-RC18 io.avaje avaje-jex-test - 3.0-RC17 + 3.0-RC18 io.avaje avaje-jex-freemarker - 3.0-RC17 + 3.0-RC18 io.avaje avaje-jex-mustache - 3.0-RC17 + 3.0-RC18 io.avaje avaje-jex-htmx - 3.0-RC17 + 3.0-RC18 io.avaje avaje-jex-static-content - 3.0-RC17 + 3.0-RC18 io.github.robaho From 4d7baa6602c221d3d6eab5b14dbdbf7a97ad2baa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 9 Feb 2025 22:35:09 -0500 Subject: [PATCH 193/250] Bump the dependencies group with 6 updates (#184) * Bump the dependencies group with 6 updates Bumps the dependencies group with 6 updates: | Package | From | To | | --- | --- | --- | | io.ebean:ebean-bom | `15.8.1` | `15.8.2` | | io.ebean:querybean-generator | `15.8.1` | `15.8.2` | | [io.ebean:ebean-maven-plugin](https://github.com/ebean-orm-tools/ebean-maven-plugin) | `15.8.1` | `15.8.2` | | [io.avaje:avaje-jsonb](https://github.com/avaje/avaje-jsonb) | `3.0-RC6` | `3.0-RC7` | | io.avaje:avaje-jsonb-generator | `3.0-RC6` | `3.0-RC7` | | [org.graalvm.buildtools:native-maven-plugin](https://github.com/graalvm/native-build-tools) | `0.10.4` | `0.10.5` | Updates `io.ebean:ebean-bom` from 15.8.1 to 15.8.2 Updates `io.ebean:querybean-generator` from 15.8.1 to 15.8.2 Updates `io.ebean:ebean-maven-plugin` from 15.8.1 to 15.8.2 - [Release notes](https://github.com/ebean-orm-tools/ebean-maven-plugin/releases) - [Changelog](https://github.com/ebean-orm-tools/ebean-maven-plugin/blob/master/release.properties) - [Commits](https://github.com/ebean-orm-tools/ebean-maven-plugin/commits) Updates `io.avaje:avaje-jsonb` from 3.0-RC6 to 3.0-RC7 - [Release notes](https://github.com/avaje/avaje-jsonb/releases) - [Commits](https://github.com/avaje/avaje-jsonb/commits) Updates `io.avaje:avaje-jsonb-generator` from 3.0-RC6 to 3.0-RC7 Updates `io.avaje:avaje-jsonb-generator` from 3.0-RC6 to 3.0-RC7 Updates `io.ebean:querybean-generator` from 15.8.1 to 15.8.2 Updates `io.ebean:ebean-maven-plugin` from 15.8.1 to 15.8.2 - [Release notes](https://github.com/ebean-orm-tools/ebean-maven-plugin/releases) - [Changelog](https://github.com/ebean-orm-tools/ebean-maven-plugin/blob/master/release.properties) - [Commits](https://github.com/ebean-orm-tools/ebean-maven-plugin/commits) Updates `org.graalvm.buildtools:native-maven-plugin` from 0.10.4 to 0.10.5 - [Release notes](https://github.com/graalvm/native-build-tools/releases) - [Commits](https://github.com/graalvm/native-build-tools/compare/0.10.4...0.10.5) --- updated-dependencies: - dependency-name: io.ebean:ebean-bom dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: io.ebean:querybean-generator dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: io.ebean:ebean-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: io.avaje:avaje-jsonb dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-jsonb-generator dependency-type: direct:development dependency-group: dependencies - dependency-name: io.avaje:avaje-jsonb-generator dependency-type: direct:development dependency-group: dependencies - dependency-name: io.ebean:querybean-generator dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: io.ebean:ebean-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: org.graalvm.buildtools:native-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] * Update README.md --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Josiah Noel <32279667+SentryMan@users.noreply.github.com> --- README.md | 38 +++++++++++++++--------------- examples/example-jdk-jsonb/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 9d15e78f..b8b352f5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Features: - Compression SPI - Json SPI - Virtual threads enabled by default -- Multi-Server with any implementation of `jdk.httpserver` (Jetty, etc) +- Multi-Server with any implementation of `jdk.httpserver` (Jetty, Robaho, built-in, etc) ## Quick Start @@ -167,11 +167,11 @@ public class Main { The JDK provides an SPI to swap the underlying `HttpServer`, so you can easily use jex with alternate implementations by adding them as a dependency. -### Robaho +### Eclipse Jetty -[@robaho's httpserver](https://github.com/robaho/httpserver?tab=readme-ov-file#performance) is a zero-dependency implementation that seems to increase performance by 10x over the built-in implementation, and 5x over Jetty in certain benchmarks. +[Jetty](https://jetty.org/) is a classic embedded server with a long and distinguished history. -[![Maven Central](https://img.shields.io/maven-central/v/io.github.robaho/httpserver.svg?label=robaho.version)](https://mvnrepository.com/artifact/io.github.robaho/httpserver) +[![Maven Central](https://img.shields.io/maven-central/v/org.eclipse.jetty/jetty-http-spi.svg?label=jetty.version)](https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-http-spi) ```xml io.avaje @@ -180,17 +180,23 @@ The JDK provides an SPI to swap the underlying `HttpServer`, so you can easily u - io.github.robaho - httpserver - ${robaho.version} + org.eclipse.jetty + jetty-server + ${jetty.version} + + + + org.eclipse.jetty + jetty-http-spi + ${jetty.version} ``` -### Eclipse Jetty +### Robaho -[Jetty](https://jetty.org/) is a classic embedded server with a long and distinguished history. +[@robaho's httpserver](https://github.com/robaho/httpserver?tab=readme-ov-file#performance) is a zero-dependency implementation that seems to increase performance by 10x over the built-in implementation, and 5x over Jetty in certain benchmarks. -[![Maven Central](https://img.shields.io/maven-central/v/org.eclipse.jetty/jetty-http-spi.svg?label=jetty.version)](https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-http-spi) +[![Maven Central](https://img.shields.io/maven-central/v/io.github.robaho/httpserver.svg?label=robaho.version)](https://mvnrepository.com/artifact/io.github.robaho/httpserver) ```xml io.avaje @@ -199,15 +205,9 @@ The JDK provides an SPI to swap the underlying `HttpServer`, so you can easily u - org.eclipse.jetty - jetty-server - ${jetty.version} - - - - org.eclipse.jetty - jetty-http-spi - ${jetty.version} + io.github.robaho + httpserver + ${robaho.version} ``` diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index eff952d5..89d0f244 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -63,7 +63,7 @@ org.graalvm.buildtools native-maven-plugin - 0.10.4 + 0.10.5 true diff --git a/pom.xml b/pom.xml index 3332ccf9..31bdab12 100644 --- a/pom.xml +++ b/pom.xml @@ -27,14 +27,14 @@ full 4.0 11.2 - 3.0-RC6 + 3.0-RC7 3.0-RC1 9.4 2.6 1.2 2.9 1.36 - 15.8.1 + 15.8.2 From d1aa6d145d168ae338f464e85c19a4c2f71cf6c9 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 10 Feb 2025 10:08:50 -0500 Subject: [PATCH 194/250] more tests (#186) --- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 2 +- .../java/io/avaje/jex/routes/PathParser.java | 2 +- .../java/io/avaje/jex/routes/RegBuilder.java | 10 +++++ .../test/java/io/avaje/jex/ShutDownTest.java | 25 +++++++++++ .../jex/compression/CompressionTest.java | 4 +- .../io/avaje/jex/http/TrailingSlashTest.java | 42 +++++++++++++++++++ .../io/avaje/jex/routes/PathParserTest.java | 2 +- 7 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 avaje-jex/src/test/java/io/avaje/jex/ShutDownTest.java create mode 100644 avaje-jex/src/test/java/io/avaje/jex/http/TrailingSlashTest.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index b405178b..5aee33bc 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -104,7 +104,7 @@ default Jex post(String path, ExchangeHandler handler, Role... roles) { * @param roles An array of roles that are associated with this endpoint. */ default Jex put(String path, ExchangeHandler handler, Role... roles) { - routing().get(path, handler, roles); + routing().put(path, handler, roles); return this; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/PathParser.java b/avaje-jex/src/main/java/io/avaje/jex/routes/PathParser.java index e70402b3..c60d0b71 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/PathParser.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/PathParser.java @@ -16,7 +16,7 @@ final class PathParser { PathParser(String path, boolean ignoreTrailingSlashes) { this.rawPath = path; - final RegBuilder regBuilder = new RegBuilder(); + final RegBuilder regBuilder = new RegBuilder(ignoreTrailingSlashes); for (String rawSeg : path.split("/")) { if (!rawSeg.isEmpty()) { segmentCount++; diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RegBuilder.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RegBuilder.java index 1c97de19..7501352c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RegBuilder.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RegBuilder.java @@ -10,10 +10,15 @@ final class RegBuilder { private final StringJoiner full = new StringJoiner("/"); private final StringJoiner extract = new StringJoiner("/"); + private final boolean ignoreTrailingSlashes; private boolean trailingSlash; private boolean multiSlash; private boolean literal = true; + public RegBuilder(boolean ignoreTrailingSlashes) { + this.ignoreTrailingSlashes = ignoreTrailingSlashes; + } + void add(PathSegment pathSegment, List paramNames) { full.add(pathSegment.asRegexString(false)); extract.add(pathSegment.asRegexString(true)); @@ -48,6 +53,11 @@ private String wrap(String parts) { if (trailingSlash) { parts += "\\/"; } + + if (!ignoreTrailingSlashes) { + return "^/" + parts; + } + return "^/" + parts + "/?$"; } diff --git a/avaje-jex/src/test/java/io/avaje/jex/ShutDownTest.java b/avaje-jex/src/test/java/io/avaje/jex/ShutDownTest.java new file mode 100644 index 00000000..488ecba0 --- /dev/null +++ b/avaje-jex/src/test/java/io/avaje/jex/ShutDownTest.java @@ -0,0 +1,25 @@ +package io.avaje.jex; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +class ShutDownTest { + + @Test + void shutDownHooks() { + + List results = new ArrayList<>(); + var jex = Jex.create().config(c -> c.socketBacklog(0)); + jex.lifecycle().onShutdown(() -> results.add("onShut")); + jex.lifecycle().registerShutdownHook(() -> results.add("onHook")); + var server = jex.start(); + + server.onShutdown(() -> results.add("serverShut")); + server.shutdown(); + assertThat(results).hasSize(2); // 2 because jvm shutdown won't run in a junit + } +} diff --git a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java index 4091e88b..c7f320d4 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java @@ -13,6 +13,7 @@ import io.avaje.jex.Jex; import io.avaje.jex.core.Constants; import io.avaje.jex.core.TestPair; +import io.avaje.jex.http.ContentType; class CompressionTest { @@ -27,7 +28,8 @@ static TestPair init() { r.get( "/compress", ctx -> - ctx.write(CompressionTest.class.getResourceAsStream("/64KB.json"))) + ctx.contentType(ContentType.APPLICATION_JSON) + .write(CompressionTest.class.getResourceAsStream("/64KB.json"))) .get( "/sus", ctx -> diff --git a/avaje-jex/src/test/java/io/avaje/jex/http/TrailingSlashTest.java b/avaje-jex/src/test/java/io/avaje/jex/http/TrailingSlashTest.java new file mode 100644 index 00000000..260d495f --- /dev/null +++ b/avaje-jex/src/test/java/io/avaje/jex/http/TrailingSlashTest.java @@ -0,0 +1,42 @@ +package io.avaje.jex.http; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.http.HttpResponse; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.avaje.jex.Jex; +import io.avaje.jex.core.TestPair; + +class TrailingSlashTest { + + static TestPair pair = init(); + + static TestPair init() { + final Jex app = + Jex.create() + .config(c -> c.socketBacklog(0).ignoreTrailingSlashes(false)) + .get("/slash", ctx -> {}); + + return TestPair.create(app); + } + + @AfterAll + static void end() { + pair.shutdown(); + } + + @Test + void get() { + HttpResponse res = pair.request().path("slash/").GET().asString(); + assertThat(res.statusCode()).isEqualTo(404); + } + + @Test + void getNoTrailing() { + HttpResponse res = pair.request().path("slash").GET().asString(); + assertThat(res.statusCode()).isEqualTo(204); + } +} diff --git a/avaje-jex/src/test/java/io/avaje/jex/routes/PathParserTest.java b/avaje-jex/src/test/java/io/avaje/jex/routes/PathParserTest.java index dcfaf889..b5ec772f 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/routes/PathParserTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/routes/PathParserTest.java @@ -21,7 +21,7 @@ void matches_trailingSlash_honor() { assertTrue(pathParser.matches("/one/1/")); assertTrue(pathParser.matches("/one/2/")); - assertTrue(pathParser.matches("/one/3//")); // accepts trailing double slash? + assertFalse(pathParser.matches("/one/3//")); // accepts trailing double slash? assertFalse(pathParser.matches("/one/3///")); // but not triple slash? assertFalse(pathParser.matches("/one/1")); assertFalse(pathParser.matches("/one/2")); From 9ddddc285d37e372dfecdf832042436d319f0405 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 10 Feb 2025 10:11:52 -0500 Subject: [PATCH 195/250] re-enable generation example (#185) --- examples/pom.xml | 2 +- pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index 1c060780..2e10f419 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -13,7 +13,7 @@ pom - + example-http-generation example-jdk example-jdk-jsonb example-robaho diff --git a/pom.xml b/pom.xml index 31bdab12..74c25091 100644 --- a/pom.xml +++ b/pom.xml @@ -27,8 +27,8 @@ full 4.0 11.2 - 3.0-RC7 - 3.0-RC1 + 3.0 + 3.0 9.4 2.6 1.2 From 7b806eeb24e4a7fc083c1ed6a5935114d5c5a4d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:13:11 +0000 Subject: [PATCH 196/250] Bump the dependencies group across 1 directory with 4 updates Bumps the dependencies group with 4 updates in the / directory: io.avaje:avaje-logback-encoder, io.avaje:avaje-validator-constraints, [io.avaje:avaje-validator](https://github.com/avaje/avaje-validator) and io.avaje:avaje-validator-generator. Updates `io.avaje:avaje-logback-encoder` from 0.10 to 0.11 Updates `io.avaje:avaje-validator-constraints` from 2.6 to 2.7 Updates `io.avaje:avaje-validator` from 2.6 to 2.7 - [Release notes](https://github.com/avaje/avaje-validator/releases) - [Commits](https://github.com/avaje/avaje-validator/compare/2.6...2.7) Updates `io.avaje:avaje-validator-generator` from 2.6 to 2.7 Updates `io.avaje:avaje-validator` from 2.6 to 2.7 - [Release notes](https://github.com/avaje/avaje-validator/releases) - [Commits](https://github.com/avaje/avaje-validator/compare/2.6...2.7) Updates `io.avaje:avaje-validator-generator` from 2.6 to 2.7 --- updated-dependencies: - dependency-name: io.avaje:avaje-logback-encoder dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator-constraints dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 74c25091..aba47cb5 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ 3.0 3.0 9.4 - 2.6 + 2.7 1.2 2.9 1.36 @@ -58,7 +58,7 @@ io.avaje avaje-logback-encoder - 0.10 + 0.11 io.avaje From a6132bd30156974427feb0dc8198974bbb2d6634 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 10 Feb 2025 17:36:31 -0500 Subject: [PATCH 197/250] fix contextPath error (#189) fixes error where providing a "/" to context path fails to start server --- .../src/main/java/io/avaje/jex/DJexConfig.java | 11 +++++++---- .../src/test/java/io/avaje/jex/AvajeJexTest.java | 13 +++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 avaje-jex/src/test/java/io/avaje/jex/AvajeJexTest.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 89c3e3f5..3caf0038 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -45,10 +45,13 @@ public JexConfig port(int port) { @Override public JexConfig contextPath(String contextPath) { - this.contextPath = - contextPath - .transform(s -> s.startsWith("/") ? s : "/" + s) - .transform(s -> s.endsWith("/") ? s.substring(0, s.lastIndexOf("/")) : s); + if (!this.contextPath.equals(contextPath)) { + + this.contextPath = + contextPath + .transform(s -> s.startsWith("/") ? s : "/" + s) + .transform(s -> s.endsWith("/") ? s.substring(0, s.lastIndexOf("/")) : s); + } return this; } diff --git a/avaje-jex/src/test/java/io/avaje/jex/AvajeJexTest.java b/avaje-jex/src/test/java/io/avaje/jex/AvajeJexTest.java new file mode 100644 index 00000000..57457d3b --- /dev/null +++ b/avaje-jex/src/test/java/io/avaje/jex/AvajeJexTest.java @@ -0,0 +1,13 @@ +package io.avaje.jex; + +import static org.assertj.core.api.Assertions.assertThatNoException; + +import org.junit.jupiter.api.Test; + +public class AvajeJexTest { + + @Test + void canStart() { + assertThatNoException().isThrownBy(() -> AvajeJex.start().shutdown()); + } +} From e530b03990c0a7c92cfe9c1547eb186b27f9d8b2 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 10 Feb 2025 20:34:09 -0500 Subject: [PATCH 198/250] Fix path methods (#190) * fix path methods * fix test --- .../main/java/io/avaje/jex/DJexConfig.java | 12 ------- .../src/main/java/io/avaje/jex/JexConfig.java | 14 ++------- .../io/avaje/jex/core/BootstrapServer.java | 4 +-- .../java/io/avaje/jex/core/JdkContext.java | 20 ++++++------ .../io/avaje/jex/core/ServiceManager.java | 8 ----- .../main/java/io/avaje/jex/http/Context.java | 9 +++--- .../io/avaje/jex/core/ContextLengthTest.java | 31 ++++++++++++++----- .../java/io/avaje/jex/core/FilterTest.java | 4 +-- .../java/io/avaje/jex/core/RedirectTest.java | 2 +- 9 files changed, 45 insertions(+), 59 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 3caf0038..1d5473e0 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -25,7 +25,6 @@ final class DJexConfig implements JexConfig { private JsonService jsonService; private final Map renderers = new HashMap<>(); private HttpsConfigurator httpsConfig; - private boolean useJexSpi = true; private final CompressionConfig compression = new CompressionConfig(); private int bufferInitial = 256; private long bufferMax = 4096L; @@ -168,17 +167,6 @@ public CompressionConfig compression() { return compression; } - @Override - public DJexConfig disableSpiPlugins() { - useJexSpi = false; - return this; - } - - @Override - public boolean useSpiPlugins() { - return useJexSpi; - } - @Override public long maxStreamBufferSize() { return bufferMax; diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index bcf661dd..3d98c701 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -5,11 +5,11 @@ import java.util.concurrent.Executors; import java.util.function.Consumer; -import com.sun.net.httpserver.*; -import com.sun.net.httpserver.spi.HttpServerProvider; import com.sun.net.httpserver.HttpServer; +import com.sun.net.httpserver.HttpsConfigurator; +import com.sun.net.httpserver.spi.HttpServerProvider; + import io.avaje.jex.compression.CompressionConfig; -import io.avaje.jex.spi.JexPlugin; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; @@ -43,12 +43,6 @@ public sealed interface JexConfig permits DJexConfig { */ JexConfig contextPath(String contextPath); - /** - * Disables auto-configuring the current instance with {@link JexPlugin} loaded using the - * ServiceLoader. - */ - JexConfig disableSpiPlugins(); - /** * Executor for serving requests. Defaults to a {@link * Executors#newVirtualThreadPerTaskExecutor()} @@ -192,6 +186,4 @@ public sealed interface JexConfig permits DJexConfig { */ JexConfig socketBacklog(int backlog); - /** Return true if SPI plugins should be loaded and registered. */ - boolean useSpiPlugins(); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java index 49583bd1..fe0c8198 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java @@ -29,9 +29,7 @@ public static Jex.Server start(Jex jex) { jex.plugin(new HealthPlugin()); } - if (config.useSpiPlugins()) { - CoreServiceLoader.plugins().forEach(p -> p.apply(jex)); - } + CoreServiceLoader.plugins().forEach(p -> p.apply(jex)); final SpiRoutes routes = new RoutesBuilder(jex.routing(), config).build(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index 0c7c142c..51745967 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -14,6 +14,7 @@ import java.lang.reflect.Type; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.URI; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.time.Duration; @@ -41,11 +42,10 @@ final class JdkContext implements Context { private static final String UTF8 = "UTF8"; - private static final int SC_MOVED_TEMPORARILY = 302; private static final String SET_COOKIE = "Set-Cookie"; private static final String COOKIE = "Cookie"; private final ServiceManager mgr; - private final String path; + private final String matchedPath; private final Map pathParams; private final Map attributes = new HashMap<>(); private final Set roles; @@ -67,7 +67,7 @@ final class JdkContext implements Context { this.mgr = mgr; this.roles = roles; this.exchange = exchange; - this.path = path; + this.matchedPath = path; this.pathParams = pathParams; } @@ -80,7 +80,7 @@ final class JdkContext implements Context { this.mgr = mgr; this.roles = roles; this.exchange = exchange; - this.path = path; + this.matchedPath = path; this.pathParams = null; } @@ -173,7 +173,7 @@ public Context contentType(String contentType) { @Override public String contextPath() { - return mgr.contextPath(); + return exchange.getHttpContext().getPath(); } @Override @@ -310,7 +310,7 @@ public void jsonStream(Stream stream) { @Override public String matchedPath() { - return path; + return matchedPath; } @Override @@ -333,7 +333,7 @@ private Map parseCookies() { @Override public String path() { - return path; + return exchange.getRequestURI().getPath(); } @Override @@ -407,7 +407,7 @@ public String queryString() { @Override public void redirect(String location) { - redirect(location, SC_MOVED_TEMPORARILY); + redirect(location, HttpStatus.FOUND_302.status()); } @Override @@ -490,8 +490,8 @@ public void text(String content) { } @Override - public String url() { - return scheme() + "://" + host() + path; + public URI uri() { + return exchange.getRequestURI(); } @Override diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java index 8413032a..7edf2ab7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java @@ -36,7 +36,6 @@ final class ServiceManager { private final ExceptionManager exceptionHandler; private final TemplateManager templateManager; private final String scheme; - private final String contextPath; private final int bufferInitial; private final long bufferMax; @@ -50,7 +49,6 @@ static ServiceManager create(Jex jex) { ExceptionManager manager, TemplateManager templateManager, String scheme, - String contextPath, long bufferMax, int bufferInitial) { this.compressionConfig = compressionConfig; @@ -58,7 +56,6 @@ static ServiceManager create(Jex jex) { this.exceptionHandler = manager; this.templateManager = templateManager; this.scheme = scheme; - this.contextPath = contextPath; this.bufferInitial = bufferInitial; this.bufferMax = bufferMax; } @@ -165,10 +162,6 @@ String scheme() { return scheme; } - String contextPath() { - return contextPath; - } - private static final class Builder { private final Jex jex; @@ -184,7 +177,6 @@ ServiceManager build() { new ExceptionManager(jex.routing().errorHandlers()), initTemplateMgr(), jex.config().scheme(), - jex.config().contextPath(), jex.config().maxStreamBufferSize(), jex.config().initialStreamBufferSize()); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java index ec50bab7..e7af6a64 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java @@ -6,6 +6,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Type; +import java.net.URI; import java.time.Duration; import java.time.ZonedDateTime; import java.util.Iterator; @@ -165,9 +166,7 @@ default List formParams(String key) { /** Return the full request url, including query string (if present) */ default String fullUrl() { - final String url = url(); - final String qs = queryString(); - return qs == null ? url : url + '?' + qs; + return scheme() + "://" + host() + uri().toString(); } /** @@ -435,8 +434,8 @@ default Context status(HttpStatus statusCode) { /** Write plain text content to the response. */ void text(String content); - /** Return the request url. */ - String url(); + /** Return the request uri. */ + URI uri(); /** Return the request user agent, or null. */ default String userAgent() { diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/ContextLengthTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ContextLengthTest.java index a12b0998..ec94ffb4 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/ContextLengthTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ContextLengthTest.java @@ -16,8 +16,9 @@ static TestPair init() { var app = Jex.create() .routing(routing -> routing .post("/", ctx -> ctx.text("contentLength:" + ctx.contentLength() + " type:" + ctx.contentType())) - .get("/url", ctx -> ctx.text("url:" + ctx.url())) - .get("/fullUrl", ctx -> ctx.text("fullUrl:" + ctx.fullUrl())) + .get("/uri/{param}", ctx -> ctx.text("uri:" + ctx.uri())) + .get("/matchedPath/{param}", ctx -> ctx.text("matchedPath:" + ctx.matchedPath())) + .get("/fullUrl/{param}", ctx -> ctx.text("fullUrl:" + ctx.fullUrl())) .get("/contextPath", ctx -> ctx.text("contextPath:" + ctx.contextPath())) .get("/userAgent", ctx -> ctx.text("userAgent:" + ctx.userAgent())) ); @@ -50,36 +51,52 @@ void requestContentLengthAndType_notReqContentType() { } @Test - void url() { + void uri() { HttpResponse res = pair.request() - .path("url") + .path("uri") + .path("uriTest") .queryParam("a", "av") .GET().asString(); assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("url:http://localhost:" + pair.port() + "/url"); + assertThat(res.body()).isEqualTo("uri:/uri/uriTest?a=av"); } @Test void fullUrl_no_queryString() { HttpResponse res = pair.request() .path("fullUrl") + .path("noQuery") .GET().asString(); assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("fullUrl:http://localhost:" + pair.port() + "/fullUrl"); + assertThat(res.body()).isEqualTo("fullUrl:http://localhost:" + pair.port() + "/fullUrl/noQuery"); } @Test void fullUrl_queryString() { HttpResponse res = pair.request() .path("fullUrl") + .path("query") .queryParam("a", "av") .queryParam("b", "bv") .GET().asString(); assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("fullUrl:http://localhost:" + pair.port() + "/fullUrl?a=av&b=bv"); + assertThat(res.body()).isEqualTo("fullUrl:http://localhost:" + pair.port() + "/fullUrl/query?a=av&b=bv"); + } + + @Test + void matchedPath() { + HttpResponse res = pair.request() + .path("matchedPath") + .path("query") + .queryParam("a", "av") + .queryParam("b", "bv") + .GET().asString(); + + assertThat(res.statusCode()).isEqualTo(200); + assertThat(res.body()).isEqualTo("matchedPath:/matchedPath/{param}"); } @Test diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java index a46e284e..36b9b097 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java @@ -32,7 +32,7 @@ static TestPair init() { .before(ctx -> ctx.header("before-all", "set")) .filter( (ctx, chain) -> { - if (ctx.url().contains("/two/")) { + if (ctx.path().contains("/two/")) { ctx.header("before-two", "set"); } chain.proceed(); @@ -41,7 +41,7 @@ static TestPair init() { .filter( (ctx, chain) -> { chain.proceed(); - if (ctx.url().contains("/two/")) { + if (ctx.path().contains("/two/")) { afterTwo.set("set"); } }) diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/RedirectTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/RedirectTest.java index dc5756ad..9680812b 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/RedirectTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/RedirectTest.java @@ -20,7 +20,7 @@ static TestPair init() { routing .filter( (ctx, chain) -> { - if (ctx.url().contains("/other/")) ctx.redirect("/two?from=filter"); + if (ctx.path().contains("/other/")) ctx.redirect("/two?from=filter"); chain.proceed(); }) .get("/one", ctx -> ctx.text("one")) From 023d5cdaf0f2da1eff28e8fcdaba97b6eb63b523 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 11 Feb 2025 00:49:46 -0500 Subject: [PATCH 199/250] Create Jdk Filter Mapper (#192) * Create Jdk Filter Mapper * Update JdkFilter.java * Format only changes * Add javadoc --------- Co-authored-by: Rob Bygrave --- .../src/main/java/io/avaje/jex/Routing.java | 7 ++ .../java/io/avaje/jex/http/HttpFilter.java | 8 ++ .../java/io/avaje/jex/http/JdkFilter.java | 26 ++++ .../jex/compression/CompressionTest.java | 1 - .../java/io/avaje/jex/http/JdkFilterTest.java | 119 ++++++++++++++++++ 5 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/JdkFilter.java create mode 100644 avaje-jex/src/test/java/io/avaje/jex/http/JdkFilterTest.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index 059f6a93..29407665 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -6,6 +6,8 @@ import java.util.Set; import java.util.function.Consumer; +import com.sun.net.httpserver.Filter; + import io.avaje.jex.http.Context; import io.avaje.jex.http.ExceptionHandler; import io.avaje.jex.http.ExchangeHandler; @@ -123,6 +125,11 @@ public sealed interface Routing permits DefaultRouting { /** Add a filter for all matched requests. */ Routing filter(HttpFilter handler); + /** Add a filter for all matched requests. */ + default Routing filter(Filter handler) { + return filter(HttpFilter.fromJdkFilter(handler)); + } + /** Add a pre-processing filter for all matched requests. */ default Routing before(Consumer handler) { return filter( diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/HttpFilter.java b/avaje-jex/src/main/java/io/avaje/jex/http/HttpFilter.java index 53bc24ba..c6af85a7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/HttpFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/HttpFilter.java @@ -1,5 +1,6 @@ package io.avaje.jex.http; +import com.sun.net.httpserver.Filter; import com.sun.net.httpserver.HttpExchange; /** @@ -52,4 +53,11 @@ interface FilterChain { */ void proceed(); } + + /** + * Convert the JDK Filter into a Jex HttpFilter. + */ + static HttpFilter fromJdkFilter(Filter filter) { + return new JdkFilter(filter); + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/JdkFilter.java b/avaje-jex/src/main/java/io/avaje/jex/http/JdkFilter.java new file mode 100644 index 00000000..60f69798 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/http/JdkFilter.java @@ -0,0 +1,26 @@ +package io.avaje.jex.http; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.List; + +import com.sun.net.httpserver.Filter; +import com.sun.net.httpserver.Filter.Chain; + +final class JdkFilter implements HttpFilter { + + private final Filter delegate; + + JdkFilter(Filter delegate) { + this.delegate = delegate; + } + + @Override + public void filter(Context ctx, FilterChain chain) { + try { + delegate.doFilter(ctx.exchange(), new Chain(List.of(), ex -> chain.proceed())); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java index c7f320d4..1f241201 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.net.http.HttpResponse; -import java.util.Arrays; import java.util.zip.GZIPInputStream; import org.junit.jupiter.api.AfterAll; diff --git a/avaje-jex/src/test/java/io/avaje/jex/http/JdkFilterTest.java b/avaje-jex/src/test/java/io/avaje/jex/http/JdkFilterTest.java new file mode 100644 index 00000000..3d33724b --- /dev/null +++ b/avaje-jex/src/test/java/io/avaje/jex/http/JdkFilterTest.java @@ -0,0 +1,119 @@ +package io.avaje.jex.http; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.http.HttpHeaders; +import java.net.http.HttpResponse; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import com.sun.net.httpserver.Filter; + +import io.avaje.jex.Jex; +import io.avaje.jex.core.TestPair; + +class JdkFilterTest { + + static final TestPair pair = init(); + static final AtomicReference afterAll = new AtomicReference<>(); + static final AtomicReference afterTwo = new AtomicReference<>(); + + static TestPair init() { + final Jex app = + Jex.create() + .routing( + routing -> + routing + .get("/", ctx -> ctx.text("roo")) + .get("/noResponse", ctx -> {}) + .get("/one", ctx -> ctx.text("one")) + .get("/two", ctx -> ctx.text("two")) + .get("/two/{id}", ctx -> ctx.text("two-id")) + .filter( + Filter.beforeHandler( + "before", ex -> ex.getResponseHeaders().set("before-all", "set"))) + .filter( + (ctx, chain) -> { + if (ctx.path().contains("/two/")) { + ctx.header("before-two", "set"); + } + chain.proceed(); + }) + .filter(Filter.afterHandler("after", ex -> afterAll.set("set"))) + .filter( + (ctx, chain) -> { + chain.proceed(); + if (ctx.path().contains("/two/")) { + afterTwo.set("set"); + } + }) + .get("/dummy", ctx -> ctx.text("dummy"))); + + return TestPair.create(app); + } + + @AfterAll + static void end() { + pair.shutdown(); + } + + void clearAfter() { + afterAll.set(null); + afterTwo.set(null); + } + + @Test + void get() { + clearAfter(); + HttpResponse res = pair.request().GET().asString(); + assertHasBeforeAfterAll(res); + assertNoBeforeAfterTwo(res); + + clearAfter(); + res = pair.request().path("one").GET().asString(); + assertHasBeforeAfterAll(res); + assertNoBeforeAfterTwo(res); + + clearAfter(); + res = pair.request().path("two").GET().asString(); + assertHasBeforeAfterAll(res); + assertNoBeforeAfterTwo(res); + } + + @Test + void getNoResponse() { + clearAfter(); + HttpResponse res = pair.request().path("noResponse").GET().asString(); + assertThat(res.statusCode()).isEqualTo(204); + assertHasBeforeAfterAll(res); + assertNoBeforeAfterTwo(res); + } + + @Test + void get_two_expect_extraFilters() { + clearAfter(); + HttpResponse res = pair.request().path("two/42").GET().asString(); + + final HttpHeaders headers = res.headers(); + assertHasBeforeAfterAll(res); + assertThat(headers.firstValue("before-two")).get().isEqualTo("set"); + assertThat(afterTwo.get()).isEqualTo("set"); + } + + private void assertNoBeforeAfterTwo(HttpResponse res) { + assertThat(res.statusCode()).isLessThan(300); + assertThat(res.headers().firstValue("before-two")).isEmpty(); + assertThat(afterTwo.get()).isNull(); + } + + private void assertHasBeforeAfterAll(HttpResponse res) { + assertThat(res.statusCode()).isLessThan(300); + assertThat(res.headers().firstValue("before-all")).get().isEqualTo("set"); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(2)); + assertThat(afterAll.get()).isEqualTo("set"); + } +} From fb7b95fc1612fa27b41ce1f15f0b85a8c096e043 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 13 Feb 2025 17:45:22 -0500 Subject: [PATCH 200/250] Avoid sending headers twice on write error (#194) * Avoid sending headers twice on write error * Format only --------- Co-authored-by: Rob Bygrave --- .../src/main/java/io/avaje/jex/core/ExceptionManager.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java index 52f4d020..58543396 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ExceptionManager.java @@ -56,6 +56,11 @@ private void unhandledException(JdkContext ctx, Exception e) { } private void defaultHandling(JdkContext ctx, HttpResponseException exception) { + if (ctx.responseSent()) { + // if already sent headers, can't send again + return; + } + ctx.status(exception.status()); var jsonResponse = exception.jsonResponse(); if (exception.status() == HttpStatus.FOUND_302.status()) { From 273c5a48c4d10d9d7ad8e91266df107d7034ff61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 03:53:46 +0000 Subject: [PATCH 201/250] Bump the dependencies group with 3 updates Bumps the dependencies group with 3 updates: io.ebean:ebean-bom, io.ebean:querybean-generator and [io.ebean:ebean-maven-plugin](https://github.com/ebean-orm-tools/ebean-maven-plugin). Updates `io.ebean:ebean-bom` from 15.8.2 to 15.9.0 Updates `io.ebean:querybean-generator` from 15.8.2 to 15.9.0 Updates `io.ebean:ebean-maven-plugin` from 15.8.2 to 15.9.0 - [Release notes](https://github.com/ebean-orm-tools/ebean-maven-plugin/releases) - [Changelog](https://github.com/ebean-orm-tools/ebean-maven-plugin/blob/master/release.properties) - [Commits](https://github.com/ebean-orm-tools/ebean-maven-plugin/commits) Updates `io.ebean:querybean-generator` from 15.8.2 to 15.9.0 Updates `io.ebean:ebean-maven-plugin` from 15.8.2 to 15.9.0 - [Release notes](https://github.com/ebean-orm-tools/ebean-maven-plugin/releases) - [Changelog](https://github.com/ebean-orm-tools/ebean-maven-plugin/blob/master/release.properties) - [Commits](https://github.com/ebean-orm-tools/ebean-maven-plugin/commits) --- updated-dependencies: - dependency-name: io.ebean:ebean-bom dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.ebean:querybean-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.ebean:ebean-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.ebean:querybean-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.ebean:ebean-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index aba47cb5..827dfc62 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ 1.2 2.9 1.36 - 15.8.2 + 15.9.0 From e2624820a08a27842cdab8e4f8f756d182dda947 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 17 Feb 2025 13:37:56 -0500 Subject: [PATCH 202/250] tidy internals (#195) - sort Context/JexConfig - fix query param `+` decoding - use `Charset` instead of string for character encoding --- .../java/io/avaje/jex/core/JdkContext.java | 9 ++--- .../io/avaje/jex/core/ServiceManager.java | 37 ++++++++----------- .../java/io/avaje/jex/http/ContentType.java | 2 +- .../java/io/avaje/jex/http/HttpFilter.java | 2 +- .../java/io/avaje/jex/routes/PathSegment.java | 4 +- .../java/io/avaje/jex/routes/RouteEntry.java | 4 +- .../java/io/avaje/jex/routes/UrlDecode.java | 10 +++-- .../io/avaje/jex/core/ContextUtilTest.java | 14 +++---- .../io/avaje/jex/core/QueryParamTest.java | 10 +++++ 9 files changed, 50 insertions(+), 42 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index 51745967..7e73622f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -41,7 +41,6 @@ final class JdkContext implements Context { - private static final String UTF8 = "UTF8"; private static final String SET_COOKIE = "Set-Cookie"; private static final String COOKIE = "Cookie"; private final ServiceManager mgr; @@ -56,7 +55,7 @@ final class JdkContext implements Context { private Map cookieMap; private int statusCode; - private String characterEncoding; + private Charset characterEncoding; JdkContext( ServiceManager mgr, @@ -120,7 +119,7 @@ private static BasicAuthCredentials getBasicAuthCredentials(String authorization @Override public String body() { - return new String(bodyAsBytes(), Charset.forName(characterEncoding())); + return new String(bodyAsBytes(), characterEncoding()); } @Override @@ -147,7 +146,7 @@ public T bodyAsType(Type beanType) { return mgr.fromJson(beanType, bodyAsInputStream()); } - private String characterEncoding() { + private Charset characterEncoding() { if (characterEncoding == null) { characterEncoding = mgr.requestCharset(this); } @@ -389,7 +388,7 @@ public Map queryParamMap() { private Map> queryParams() { if (queryParams == null) { - queryParams = mgr.parseParamMap(queryString(), UTF8); + queryParams = mgr.parseParamMap(queryString(), StandardCharsets.UTF_8); } return queryParams; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java index 7edf2ab7..206434a8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java @@ -2,11 +2,10 @@ import java.io.InputStream; import java.io.OutputStream; -import java.io.UncheckedIOException; -import java.io.UnsupportedEncodingException; import java.lang.System.Logger.Level; import java.lang.reflect.Type; -import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -22,6 +21,7 @@ import io.avaje.jex.core.json.JacksonJsonService; import io.avaje.jex.core.json.JsonbJsonService; import io.avaje.jex.http.Context; +import io.avaje.jex.routes.UrlDecode; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.TemplateRender; @@ -29,7 +29,6 @@ final class ServiceManager { private static final System.Logger log = System.getLogger("io.avaje.jex"); - private static final String UTF_8 = "UTF-8"; private final CompressionConfig compressionConfig; private final JsonService jsonService; @@ -120,42 +119,38 @@ void render(Context ctx, String name, Map model) { templateManager.render(ctx, name, model); } - String requestCharset(Context ctx) { + Charset requestCharset(Context ctx) { return parseCharset(ctx.header(Constants.CONTENT_TYPE)); } - static String parseCharset(String header) { + static Charset parseCharset(String header) { if (header != null) { for (String val : header.split(";")) { val = val.trim(); if (val.regionMatches(true, 0, "charset", 0, "charset".length())) { - return val.split("=")[1].trim(); + return Charset.forName(val.split("=")[1].trim()); } } } - return UTF_8; + return StandardCharsets.UTF_8; } - Map> formParamMap(Context ctx, String charset) { + Map> formParamMap(Context ctx, Charset charset) { return parseParamMap(ctx.body(), charset); } - Map> parseParamMap(String body, String charset) { + Map> parseParamMap(String body, Charset charset) { if (body == null || body.isEmpty()) { return Collections.emptyMap(); } - try { - Map> map = new LinkedHashMap<>(); - for (String pair : body.split("&")) { - final String[] split1 = pair.split("=", 2); - String key = URLDecoder.decode(split1[0], charset); - String val = split1.length > 1 ? URLDecoder.decode(split1[1], charset) : ""; - map.computeIfAbsent(key, s -> new ArrayList<>()).add(val); - } - return map; - } catch (UnsupportedEncodingException e) { - throw new UncheckedIOException(e); + Map> map = new LinkedHashMap<>(); + for (String pair : body.split("&")) { + final String[] split1 = pair.split("=", 2); + String key = UrlDecode.decode(split1[0], charset); + String val = split1.length > 1 ? UrlDecode.decode(split1[1], charset) : ""; + map.computeIfAbsent(key, s -> new ArrayList<>()).add(val); } + return map; } String scheme() { diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/ContentType.java b/avaje-jex/src/main/java/io/avaje/jex/http/ContentType.java index c91de7b4..95d0ca1c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/ContentType.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/ContentType.java @@ -1,7 +1,7 @@ package io.avaje.jex.http; +/** Common Content Types */ public enum ContentType { - TEXT_PLAIN("text/plain"), TEXT_CSS("text/css"), TEXT_CSV("text/csv"), diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/HttpFilter.java b/avaje-jex/src/main/java/io/avaje/jex/http/HttpFilter.java index c6af85a7..9cb1e53f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/HttpFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/HttpFilter.java @@ -55,7 +55,7 @@ interface FilterChain { } /** - * Convert the JDK Filter into a Jex HttpFilter. + * Convert a JDK {@link Filter} into a Jex HttpFilter. */ static HttpFilter fromJdkFilter(Filter filter) { return new JdkFilter(filter); diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegment.java b/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegment.java index 777efde6..c4a1bf5d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegment.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegment.java @@ -4,7 +4,7 @@ import static java.util.stream.Collectors.joining; -abstract class PathSegment { +abstract sealed class PathSegment { abstract String asRegexString(boolean extract); @@ -35,7 +35,7 @@ boolean multiSlash() { } } - private abstract static class Parameter extends PathSegment { + private abstract static sealed class Parameter extends PathSegment { private final String name; private final String regex; diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java index 3cec6748..30fc06c1 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java @@ -22,8 +22,8 @@ final class RouteEntry implements SpiRoutes.Entry { @Override public SpiRoutes.Entry multiHandler(ExchangeHandler[] handlers) { - final var handler = new MultiHandler(handlers); - return new RouteEntry(path, handler, roles); + final var multi = new MultiHandler(handlers); + return new RouteEntry(path, multi, roles); } @Override diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/UrlDecode.java b/avaje-jex/src/main/java/io/avaje/jex/routes/UrlDecode.java index 2c58df3a..5db32f66 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/UrlDecode.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/UrlDecode.java @@ -1,13 +1,17 @@ package io.avaje.jex.routes; import java.net.URLDecoder; +import java.nio.charset.Charset; import static java.nio.charset.StandardCharsets.UTF_8; -final class UrlDecode { +public final class UrlDecode { - static String decode(String s) { - return URLDecoder.decode(s.replace("+", "%2B"), UTF_8).replace("%2B", "+"); + public static String decode(String s) { + return decode(s, UTF_8); } + public static String decode(String s, Charset charset) { + return URLDecoder.decode(s.replace("+", "%2B"), charset).replace("%2B", "+"); + } } diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java index efb95ab2..d65d06cb 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ContextUtilTest.java @@ -10,16 +10,16 @@ class ContextUtilTest { @Test void parseCharset_defaults() { - assertThat(ServiceManager.parseCharset("")).isEqualTo(StandardCharsets.UTF_8.name()); - assertThat(ServiceManager.parseCharset("junk")).isEqualTo(StandardCharsets.UTF_8.name()); + assertThat(ServiceManager.parseCharset("")).isEqualTo(StandardCharsets.UTF_8); + assertThat(ServiceManager.parseCharset("junk")).isEqualTo(StandardCharsets.UTF_8); } @Test void parseCharset_caseCheck() { - assertThat(ServiceManager.parseCharset("app/foo; charset=ME")).isEqualTo("ME"); - assertThat(ServiceManager.parseCharset("app/foo;charset=ME")).isEqualTo("ME"); - assertThat(ServiceManager.parseCharset("app/foo;charset = ME ")).isEqualTo("ME"); - assertThat(ServiceManager.parseCharset("app/foo;charset = ME;")).isEqualTo("ME"); - assertThat(ServiceManager.parseCharset("app/foo;charset = ME;other=junk")).isEqualTo("ME"); + assertThat(ServiceManager.parseCharset("app/foo; charset=Us-AsCiI")).isEqualTo(StandardCharsets.US_ASCII); + assertThat(ServiceManager.parseCharset("app/foo;charset=Us-AsCiI")).isEqualTo(StandardCharsets.US_ASCII); + assertThat(ServiceManager.parseCharset("app/foo;charset = Us-AsCiI ")).isEqualTo(StandardCharsets.US_ASCII); + assertThat(ServiceManager.parseCharset("app/foo;charset = Us-AsCiI;")).isEqualTo(StandardCharsets.US_ASCII); + assertThat(ServiceManager.parseCharset("app/foo;charset = Us-AsCiI;other=junk")).isEqualTo(StandardCharsets.US_ASCII); } } diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/QueryParamTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/QueryParamTest.java index 442d09a7..7230ef37 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/QueryParamTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/QueryParamTest.java @@ -21,6 +21,7 @@ static TestPair init() { .get("/queryParamMap", ctx -> ctx.text("qpm: "+ctx.queryParamMap())) .get("/queryParams", ctx -> ctx.text("qps: "+ctx.queryParams("a"))) .get("/queryString", ctx -> ctx.text("qs: "+ctx.queryString())) + .get("/plus/{plus}", ctx -> ctx.text(ctx.pathParam("plus")+ctx.queryParam("plus"))) .get("/scheme", ctx -> ctx.text("scheme: "+ctx.scheme())) ); return TestPair.create(app); @@ -136,6 +137,15 @@ void queryString_when_set() { assertThat(res.body()).isEqualTo("qs: foo=f1&bar=b1&bar=b2"); } + @Test + void plus() { + HttpResponse res = pair.request().path("plus/+") + .queryParam("plus","+") + .GET().asString(); + assertThat(res.statusCode()).isEqualTo(200); + assertThat(res.body()).isEqualTo("++"); + } + @Test void scheme() { HttpResponse res = pair.request().path("scheme") From 0cc55137a35f71ee9dd464c11229e6377533b14e Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 19 Feb 2025 20:18:16 +1300 Subject: [PATCH 203/250] Version 3.0-RC19 --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-htmx/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- examples/example-http-generation/pom.xml | 2 +- examples/example-jdk-jsonb/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-jetty/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 14 +++++++------- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index b174ef73..048c6c48 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC18 + 3.0-RC19 avaje-jex-freemarker diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 28ba02f3..29a7662b 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC18 + 3.0-RC19 avaje-jex-htmx diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 7ca2ccc0..2885dd6d 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC18 + 3.0-RC19 avaje-jex-mustache diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index e2e6dd6d..33147018 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC18 + 3.0-RC19 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index de22ae18..a7d6be18 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC18 + 3.0-RC19 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 1677646d..bed20c1c 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC18 + 3.0-RC19 avaje-jex diff --git a/examples/example-http-generation/pom.xml b/examples/example-http-generation/pom.xml index a6942951..9a60274b 100644 --- a/examples/example-http-generation/pom.xml +++ b/examples/example-http-generation/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC17 + 3.0-RC19 4.0.0 diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index 89d0f244..1f329541 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -6,7 +6,7 @@ avaje-jex-parent io.avaje - 3.0-RC18 + 3.0-RC19 org.example diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 0fe739fa..9122b90d 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC18 + 3.0-RC19 diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index 6bfe117c..39f4e4ba 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC18 + 3.0-RC19 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index d6444edd..3023b42d 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC18 + 3.0-RC19 diff --git a/examples/pom.xml b/examples/pom.xml index 2e10f419..ac28c6da 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC18 + 3.0-RC19 examples diff --git a/pom.xml b/pom.xml index 827dfc62..90ec8516 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ io.avaje avaje-jex-parent - 3.0-RC18 + 3.0-RC19 pom @@ -176,32 +176,32 @@ io.avaje avaje-jex - 3.0-RC18 + 3.0-RC19 io.avaje avaje-jex-test - 3.0-RC18 + 3.0-RC19 io.avaje avaje-jex-freemarker - 3.0-RC18 + 3.0-RC19 io.avaje avaje-jex-mustache - 3.0-RC18 + 3.0-RC19 io.avaje avaje-jex-htmx - 3.0-RC18 + 3.0-RC19 io.avaje avaje-jex-static-content - 3.0-RC18 + 3.0-RC19 io.github.robaho From fd307a6465ed22281bb526d90976c6b4afaf05ab Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Thu, 20 Feb 2025 00:12:44 +1300 Subject: [PATCH 204/250] Fix Client.Imports replacing old types attribute to value (#198) --- .../java/org/foo/myapp/web/HelloClientInterfaceInMainTest.java | 2 +- .../org/foo/myapp/web/HelloClientInterfaceViaImportTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceInMainTest.java b/examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceInMainTest.java index 92161d41..1f23225d 100644 --- a/examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceInMainTest.java +++ b/examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceInMainTest.java @@ -11,7 +11,7 @@ /** * A `@Client` interface lives in src/main - not usually expected. */ -@Client.Import(types =HelloApi.class) +@Client.Import(HelloApi.class) @InjectTest class HelloClientInterfaceInMainTest { diff --git a/examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceViaImportTest.java b/examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceViaImportTest.java index 78917a0f..81bcab30 100644 --- a/examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceViaImportTest.java +++ b/examples/example-http-generation/src/test/java/org/foo/myapp/web/HelloClientInterfaceViaImportTest.java @@ -17,7 +17,7 @@ * Using Client.Import we get the client code generated in src/test. * Actually in target/generated-test-sources/ ... */ -@Client.Import(types = HiApi.class) +@Client.Import(HiApi.class) @InjectTest class HelloClientInterfaceViaImportTest { From 391b6c4c9cd780affcf780e83fe44da069f28da6 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Thu, 20 Feb 2025 11:22:12 +1300 Subject: [PATCH 205/250] Add avaje-jsonb JsonOutput as performance option (#199) * Fix Client.Imports replacing old types attribute to value * Add avaje-jsonb JsonOutput as performance option When using JsonOutput, with json content smaller than the avaje-jsonb buffer, this can be written as fixed length content directly from the avaje-jsonb buffer to the http server outputStream without any additional buffering required. --- .../io/avaje/jex/core/BufferedOutStream.java | 1 - .../java/io/avaje/jex/core/JdkContext.java | 10 +++ .../io/avaje/jex/core/json/JsonbOutput.java | 64 +++++++++++++++++++ .../main/java/io/avaje/jex/http/Context.java | 10 +++ .../test/java/io/avaje/jex/core/JsonTest.java | 23 +++++++ 5 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbOutput.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java b/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java index 48bbe9fd..43cffd30 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BufferedOutStream.java @@ -15,7 +15,6 @@ final class BufferedOutStream extends OutputStream { private long count; BufferedOutStream(JdkContext context, int initial, long max) { - this.context = context; this.max = max; this.buffer = new ByteArrayOutputStream(initial); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index 7e73622f..8f7d9d4c 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -503,6 +503,16 @@ public void write(byte[] bytes) { } } + @Override + public void write(byte[] bufferBytes, int length) { + try (var os = exchange.getResponseBody()) { + exchange.sendResponseHeaders(statusCode(), length == 0 ? -1 : length); + os.write(bufferBytes, 0, length); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + @Override public void write(InputStream is) { try (is; var os = outputStream()) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbOutput.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbOutput.java new file mode 100644 index 00000000..49623f15 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbOutput.java @@ -0,0 +1,64 @@ +package io.avaje.jex.core.json; + +import io.avaje.jex.http.Context; +import io.avaje.json.stream.JsonOutput; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * avaje-jsonb output that allows for writing fixed length content + * straight from the avaje-jsonb buffer, avoiding the jex side buffer. + */ +public final class JsonbOutput implements JsonOutput { + + private final Context context; + private OutputStream os; + + public static JsonOutput of(Context context) { + return new JsonbOutput(context); + } + + private JsonbOutput(Context context) { + this.context = context; + } + + @Override + public void write(byte[] content, int offset, int length) throws IOException { + if (os == null) { + // exceeds the avaje-jsonb buffer size + os = context.outputStream(); + } + os.write(content, offset, length); + } + + @Override + public void writeLast(byte[] content, int offset, int length) throws IOException { + if (os == null) { + // write as fixed length content straight from the avaje-jsonb buffer + context.write(content, length); + } else { + os.write(content, offset, length); + } + } + + @Override + public void flush() throws IOException { + if (os != null) { + os.flush(); + } + } + + @Override + public void close() throws IOException { + if (os != null) { + os.close(); + } + } + + @Override + public OutputStream unwrapOutputStream() { + return context.outputStream(); + } + +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java index e7af6a64..6ce09f8f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java @@ -449,6 +449,16 @@ default String userAgent() { */ void write(byte[] bytes); + /** + * Writes the first bytes from this buffer directly to the response. + * + *

    The bytes written will be from position 0 to length. + * + * @param bufferBytes The byte array to write. + * @param length The number of bytes to write from the buffer. + */ + void write(byte[] bufferBytes, int length); + /** * Writes the content from the given InputStream directly to the response body. * diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java index 99c20ede..66fc947c 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/JsonTest.java @@ -11,6 +11,7 @@ import java.util.concurrent.locks.LockSupport; import java.util.stream.Stream; +import io.avaje.jex.core.json.JsonbOutput; import io.avaje.jsonb.Json; import io.avaje.jsonb.JsonType; import io.avaje.jsonb.Jsonb; @@ -58,6 +59,13 @@ static TestPair init() { var result = HelloDto.rob(); jsonTypeHelloDto.toJson(result, ctx.outputStream()); }) + .get( + "/usingJsonOutput", + ctx -> { + ctx.status(200).contentType("application/json"); + var result = HelloDto.fi(); + jsonTypeHelloDto.toJson(result, JsonbOutput.of(ctx)); + }) .get("/iterate", ctx -> ctx.jsonStream(ITERATOR)) .get("/stream", ctx -> ctx.jsonStream(HELLO_BEANS.stream())) .post("/", ctx -> ctx.text("bean[" + ctx.bodyAsClass(HelloDto.class) + "]")); @@ -118,6 +126,21 @@ void usingOutputStream() { assertThat(bean.name).isEqualTo("rob"); } + @Test + void usingJsonOutput() { + var hres = pair.request().path("usingJsonOutput") + .GET() + .as(HelloDto.class); + + assertThat(hres.statusCode()).isEqualTo(200); + final HttpHeaders headers = hres.headers(); + assertThat(headers.firstValue("Content-Type").orElseThrow()).isEqualTo("application/json"); + + var bean = hres.body(); + assertThat(bean.id).isEqualTo(45); + assertThat(bean.name).isEqualTo("fi"); + } + @Test void stream_viaIterator() { From e348ca50006626a3f338ef3fae63ae5867bbd32b Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 20 Feb 2025 00:15:01 -0500 Subject: [PATCH 206/250] Add JsonType json method (#200) * add jsonType json method * Update Context.java * Update Context.java --- .../java/io/avaje/jex/core/JdkContext.java | 10 ---------- .../main/java/io/avaje/jex/http/Context.java | 18 ++++++++++++++++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index 8f7d9d4c..ba4250bc 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -493,16 +493,6 @@ public URI uri() { return exchange.getRequestURI(); } - @Override - public void write(byte[] bytes) { - try (var os = exchange.getResponseBody()) { - exchange.sendResponseHeaders(statusCode(), bytes.length == 0 ? -1 : bytes.length); - os.write(bytes); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - @Override public void write(byte[] bufferBytes, int length) { try (var os = exchange.getResponseBody()) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java index 6ce09f8f..c4ed3756 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java @@ -21,8 +21,10 @@ import com.sun.net.httpserver.HttpExchange; import io.avaje.jex.core.Constants; +import io.avaje.jex.core.json.JsonbOutput; import io.avaje.jex.security.BasicAuthCredentials; import io.avaje.jex.security.Role; +import io.avaje.jsonb.JsonType; /** Provides access to functions for handling the request and response. */ public interface Context { @@ -245,6 +247,16 @@ default Context headers(Map headers) { */ void json(Object bean); + /** + * Optimized json write using avaje jsonb + * + * @param jsonType the serializer for the value. + * @param value the pojo to serialize + */ + default void jsonb(JsonType jsonType, T value) { + jsonType.toJson(value, JsonbOutput.of(this.contentType(ContentType.APPLICATION_JSON))); + } + /** * Write the stream as a JSON stream with new line delimiters {@literal * application/x-json-stream}. @@ -447,10 +459,12 @@ default String userAgent() { * * @param bytes The byte array to write. */ - void write(byte[] bytes); + default void write(byte[] bytes) { + write(bytes, bytes.length); + } /** - * Writes the first bytes from this buffer directly to the response. + * Writes the given length of bytes from this buffer directly to the response. * *

    The bytes written will be from position 0 to length. * From 69f1f74fa3ef97dfcc9491a08bc9a2c108f6082e Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Thu, 20 Feb 2025 19:11:58 +1300 Subject: [PATCH 207/250] Fix javadoc bug with triple * instead of ** (#201) --- avaje-jex/src/main/java/io/avaje/jex/http/Context.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java index c4ed3756..b64e30cf 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java @@ -66,7 +66,7 @@ public interface Context { */ byte[] bodyAsBytes(); - /*** + /** * Return the request body as bean. * * @param beanType The bean type @@ -80,7 +80,7 @@ public interface Context { */ InputStream bodyAsInputStream(); - /*** + /** * Return the request body as bean. * * @param beanType The bean type @@ -154,7 +154,7 @@ default String formParam(String key) { /** Return the first form param value for the specified key or the default value. */ default String formParam(String key, String defaultValue) { final List values = formParamMap().get(key); - return values == null || values.isEmpty() ? defaultValue : values.get(0); + return values == null || values.isEmpty() ? defaultValue : values.getFirst(); } /** Returns a map with all the form param keys and values. */ From fb7177dca8e4785f9747e37779750281179e6753 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Sun, 23 Feb 2025 21:10:29 +1300 Subject: [PATCH 208/250] Version 3.0-RC20 --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-htmx/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- examples/example-http-generation/pom.xml | 2 +- examples/example-jdk-jsonb/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-jetty/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 14 +++++++------- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 048c6c48..48881d75 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC19 + 3.0-RC20 avaje-jex-freemarker diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 29a7662b..3423e154 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC19 + 3.0-RC20 avaje-jex-htmx diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 2885dd6d..cf704bb0 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC19 + 3.0-RC20 avaje-jex-mustache diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index 33147018..be3f27f2 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC19 + 3.0-RC20 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index a7d6be18..27580ed3 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC19 + 3.0-RC20 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index bed20c1c..9f19688d 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC19 + 3.0-RC20 avaje-jex diff --git a/examples/example-http-generation/pom.xml b/examples/example-http-generation/pom.xml index 9a60274b..46ac591c 100644 --- a/examples/example-http-generation/pom.xml +++ b/examples/example-http-generation/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC19 + 3.0-RC20 4.0.0 diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index 1f329541..dfa34d22 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -6,7 +6,7 @@ avaje-jex-parent io.avaje - 3.0-RC19 + 3.0-RC20 org.example diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 9122b90d..efa1371c 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC19 + 3.0-RC20 diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index 39f4e4ba..c85adddf 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC19 + 3.0-RC20 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 3023b42d..7060d8f0 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC19 + 3.0-RC20 diff --git a/examples/pom.xml b/examples/pom.xml index ac28c6da..a6a1fa74 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC19 + 3.0-RC20 examples diff --git a/pom.xml b/pom.xml index 90ec8516..4c056125 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ io.avaje avaje-jex-parent - 3.0-RC19 + 3.0-RC20 pom @@ -176,32 +176,32 @@ io.avaje avaje-jex - 3.0-RC19 + 3.0-RC20 io.avaje avaje-jex-test - 3.0-RC19 + 3.0-RC20 io.avaje avaje-jex-freemarker - 3.0-RC19 + 3.0-RC20 io.avaje avaje-jex-mustache - 3.0-RC19 + 3.0-RC20 io.avaje avaje-jex-htmx - 3.0-RC19 + 3.0-RC20 io.avaje avaje-jex-static-content - 3.0-RC19 + 3.0-RC20 io.github.robaho From d33d3920dd2765915f090c6e032f3cca1e91c475 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Feb 2025 12:17:14 -0800 Subject: [PATCH 209/250] Bump the dependencies group with 5 updates (#204) * Bump the dependencies group with 5 updates Bumps the dependencies group with 5 updates: | Package | From | To | | --- | --- | --- | | io.avaje:avaje-validator-constraints | `2.7` | `2.8` | | [io.avaje:avaje-validator](https://github.com/avaje/avaje-validator) | `2.7` | `2.8` | | io.avaje:avaje-validator-generator | `2.7` | `2.8` | | io.avaje:avaje-spi-service | `2.9` | `2.10` | | [org.apache.maven.plugins:maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) | `3.13.0` | `3.14.0` | Updates `io.avaje:avaje-validator-constraints` from 2.7 to 2.8 Updates `io.avaje:avaje-validator` from 2.7 to 2.8 - [Release notes](https://github.com/avaje/avaje-validator/releases) - [Commits](https://github.com/avaje/avaje-validator/compare/2.7...2.8) Updates `io.avaje:avaje-validator-generator` from 2.7 to 2.8 Updates `io.avaje:avaje-validator` from 2.7 to 2.8 - [Release notes](https://github.com/avaje/avaje-validator/releases) - [Commits](https://github.com/avaje/avaje-validator/compare/2.7...2.8) Updates `io.avaje:avaje-validator-generator` from 2.7 to 2.8 Updates `io.avaje:avaje-spi-service` from 2.9 to 2.10 Updates `org.apache.maven.plugins:maven-compiler-plugin` from 3.13.0 to 3.14.0 - [Release notes](https://github.com/apache/maven-compiler-plugin/releases) - [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.13.0...maven-compiler-plugin-3.14.0) --- updated-dependencies: - dependency-name: io.avaje:avaje-validator-constraints dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-spi-service dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: org.apache.maven.plugins:maven-compiler-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] * Update dependabot-merge.yml --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Josiah Noel <32279667+SentryMan@users.noreply.github.com> --- .github/workflows/dependabot-merge.yml | 2 +- avaje-jex-mustache/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dependabot-merge.yml b/.github/workflows/dependabot-merge.yml index c444bfa4..a6524841 100644 --- a/.github/workflows/dependabot-merge.yml +++ b/.github/workflows/dependabot-merge.yml @@ -22,7 +22,7 @@ jobs: GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} # Enable for automerge - name: Enable auto-merge for Dependabot PRs - run: gh pr merge --auto --merge "$PR_URL" + run: gh pr merge --auto --squash "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index cf704bb0..b79470dd 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -25,7 +25,7 @@ io.avaje avaje-spi-service - 2.9 + 2.10 provided diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index efa1371c..2d2104d4 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -52,7 +52,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.13.0 + 3.14.0 ${java.release} diff --git a/pom.xml b/pom.xml index 4c056125..2a85126f 100644 --- a/pom.xml +++ b/pom.xml @@ -30,9 +30,9 @@ 3.0 3.0 9.4 - 2.7 + 2.8 1.2 - 2.9 + 2.10 1.36 15.9.0 From e91d6d43acc900707663c75409d716c99b74c4a9 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 23 Feb 2025 12:53:38 -0800 Subject: [PATCH 210/250] Add SSE support (#203) * Add SSE support * Update README.md * Update SseHandler.java * pr comments * Update SseHandler.java --- README.md | 3 +- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 12 ++ .../src/main/java/io/avaje/jex/Routing.java | 12 ++ .../main/java/io/avaje/jex/SseHandler.java | 44 ++++ .../java/io/avaje/jex/core/JdkContext.java | 6 + .../io/avaje/jex/core/ServiceManager.java | 4 + .../jex/core/json/JacksonJsonService.java | 9 + .../avaje/jex/core/json/JsonbJsonService.java | 5 + .../main/java/io/avaje/jex/http/Context.java | 12 +- .../java/io/avaje/jex/http/sse/Emitter.java | 69 +++++++ .../java/io/avaje/jex/http/sse/SseClient.java | 74 +++++++ .../io/avaje/jex/http/sse/SseClientImpl.java | 102 +++++++++ .../java/io/avaje/jex/spi/JsonService.java | 11 + avaje-jex/src/main/java/module-info.java | 1 + .../io/avaje/jex/http/sse/SseClientTest.java | 195 ++++++++++++++++++ 15 files changed, 556 insertions(+), 3 deletions(-) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/SseHandler.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/sse/Emitter.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java create mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClientImpl.java create mode 100644 avaje-jex/src/test/java/io/avaje/jex/http/sse/SseClientTest.java diff --git a/README.md b/README.md index b8b352f5..e70f9d80 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,14 @@ [![javadoc](https://javadoc.io/badge2/io.avaje/avaje-jex/javadoc.svg?color=purple)](https://javadoc.io/doc/io.avaje/avaje-jex) # [Avaje-Jex](https://avaje.io/jex/) -Lightweight (~110KB) wrapper over the JDK's built-in [HTTP server](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html). +Lightweight (~120KB) wrapper over the JDK's built-in [HTTP server](https://docs.oracle.com/en/java/javase/23/docs/api/jdk.httpserver/module-summary.html). Features: - [Context](https://javadoc.io/doc/io.avaje/avaje-jex/latest/io.avaje.jex/io/avaje/jex/http/Context.html) abstraction over `HttpExchange` to easily retrieve and send request/response data. - Fluent API - Static resource handling +- Server Sent Events - Compression SPI - Json SPI - Virtual threads enabled by default diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index 5aee33bc..f2c399e9 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -9,6 +9,7 @@ import io.avaje.jex.http.ExceptionHandler; import io.avaje.jex.http.ExchangeHandler; import io.avaje.jex.http.HttpFilter; +import io.avaje.jex.http.sse.SseClient; import io.avaje.jex.security.Role; import io.avaje.jex.spi.JexPlugin; import io.avaje.jex.spi.JsonService; @@ -144,6 +145,17 @@ default Jex options(String path, ExchangeHandler handler, Role... roles) { return this; } + /** + * Adds an SSE handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The sse handler to invoke when a GET request matches the path. + * @param roles An array of roles that are associated with this endpoint. + */ + default Jex sse(String path, Consumer handler, Role... roles) { + return get(path, new SseHandler(handler), roles); + } + /** Add a filter for all matched requests. */ default Jex filter(HttpFilter handler) { routing().filter(handler); diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index 29407665..76aa110a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -12,6 +12,7 @@ import io.avaje.jex.http.ExceptionHandler; import io.avaje.jex.http.ExchangeHandler; import io.avaje.jex.http.HttpFilter; +import io.avaje.jex.http.sse.SseClient; import io.avaje.jex.security.Role; /** Routing abstraction. */ @@ -148,6 +149,17 @@ default Routing after(Consumer handler) { }); } + /** + * Adds an SSE handler to the route configuration. + * + * @param path The path pattern to match the request URI. + * @param handler The sse handler to invoke when a GET request matches the path. + * @param roles An array of roles that are associated with this endpoint. + */ + default Routing sse(String path, Consumer consumer, Role... roles) { + return get(path, new SseHandler(consumer), roles); + } + /** Return all the registered handlers. */ List handlers(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/SseHandler.java b/avaje-jex/src/main/java/io/avaje/jex/SseHandler.java new file mode 100644 index 00000000..4eae1bbd --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/SseHandler.java @@ -0,0 +1,44 @@ +package io.avaje.jex; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.function.Consumer; + +import io.avaje.jex.core.Constants; +import io.avaje.jex.http.BadRequestException; +import io.avaje.jex.http.Context; +import io.avaje.jex.http.ExchangeHandler; +import io.avaje.jex.http.sse.SseClient; + +/** Handler that configures a request for Server Sent Events */ +class SseHandler implements ExchangeHandler { + + private static final String TEXT_EVENT_STREAM = "text/event-stream"; + private final Consumer consumer; + + SseHandler(Consumer consumer) { + this.consumer = consumer; + } + + @Override + public void handle(Context ctx) throws Exception { + + if (!TEXT_EVENT_STREAM.equals(ctx.header(Constants.ACCEPT))) { + throw new BadRequestException("SSE Requests must have an 'Accept: text/event-stream' header"); + } + final var exchange = ctx.exchange(); + final var headers = exchange.getResponseHeaders(); + headers.add(Constants.CONTENT_TYPE, TEXT_EVENT_STREAM); + headers.add(Constants.CONTENT_ENCODING, "UTF-8"); + headers.add("Connection", "close"); + headers.add("Cache-Control", "no-cache"); + headers.add("X-Accel-Buffering", "no"); // See https://serverfault.com/a/801629 + + try (var sse = SseClient.of(ctx)) { + exchange.sendResponseHeaders(200, 0); + consumer.accept(sse); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index ba4250bc..3e5f5ddc 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -38,6 +38,7 @@ import io.avaje.jex.http.RedirectException; import io.avaje.jex.security.BasicAuthCredentials; import io.avaje.jex.security.Role; +import io.avaje.jex.spi.JsonService; final class JdkContext implements Context { @@ -516,4 +517,9 @@ public void write(InputStream is) { public void write(String content) { write(content.getBytes(StandardCharsets.UTF_8)); } + + @Override + public JsonService jsonService() { + return mgr.jsonService(); + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java index 206434a8..7f9d16ae 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java @@ -67,6 +67,10 @@ OutputStream createOutputStream(JdkContext jdkContext) { return out; } + JsonService jsonService() { + return jsonService; + } + T fromJson(Class type, InputStream is) { return jsonService.fromJson(type, is); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java index c9551950..577b70b8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java @@ -73,6 +73,15 @@ public void toJson(Object bean, OutputStream os) { } } + @Override + public String toJsonString(Object bean) { + try { + return mapper.writeValueAsString(bean); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + @Override public void toJsonStream(Iterator iterator, OutputStream os) { final JsonGenerator generator; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java index cebb9520..43f2a0c7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java @@ -40,6 +40,11 @@ public void toJson(Object bean, OutputStream os) { jsonb.toJson(bean, os); } + @Override + public String toJsonString(Object bean) { + return jsonb.toJson(bean); + } + @Override public void toJsonStream(Iterator iterator, OutputStream os) { try (JsonWriter writer = jsonb.writer(os)) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java index b64e30cf..8e942292 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java @@ -24,6 +24,7 @@ import io.avaje.jex.core.json.JsonbOutput; import io.avaje.jex.security.BasicAuthCredentials; import io.avaje.jex.security.Role; +import io.avaje.jex.spi.JsonService; import io.avaje.jsonb.JsonType; /** Provides access to functions for handling the request and response. */ @@ -153,7 +154,7 @@ default String formParam(String key) { /** Return the first form param value for the specified key or the default value. */ default String formParam(String key, String defaultValue) { - final List values = formParamMap().get(key); + final var values = formParamMap().get(key); return values == null || values.isEmpty() ? defaultValue : values.getFirst(); } @@ -162,7 +163,7 @@ default String formParam(String key, String defaultValue) { /** Return the form params for the specified key, or empty list. */ default List formParams(String key) { - final List values = formParamMap().get(key); + final var values = formParamMap().get(key); return values != null ? values : emptyList(); } @@ -273,6 +274,13 @@ default void jsonb(JsonType jsonType, T value) { */ void jsonStream(Stream stream); + /** + * Returns the configured {@link JsonService} instance.} + * + * @return The json service if configured. null otherwise. + */ + JsonService jsonService(); + /** * Returns the matched path as a raw expression, without any parameter substitution. * diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/sse/Emitter.java b/avaje-jex/src/main/java/io/avaje/jex/http/sse/Emitter.java new file mode 100644 index 00000000..36d49490 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/http/sse/Emitter.java @@ -0,0 +1,69 @@ +package io.avaje.jex.http.sse; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.locks.ReentrantLock; + +final class Emitter { + public static final String COMMENT_PREFIX = ":"; + public static final String NEW_LINE = "\n"; + + private final ReentrantLock lock = new ReentrantLock(); + private final OutputStream response; + private boolean closed = false; + + Emitter(OutputStream outputStream) { + this.response = outputStream; + } + + boolean isClosed() { + return closed; + } + + void emit(String event, InputStream data, String id) { + try { + lock.lock(); + + if (id != null) { + write("id: " + id + NEW_LINE); + } + write("event: " + event + NEW_LINE); + + try (var reader = + new BufferedReader(new InputStreamReader(data, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + write("data: " + line + NEW_LINE); + } + } + + write(NEW_LINE); + response.flush(); + + } catch (final IOException ignored) { + closed = true; + } finally { + lock.unlock(); + } + } + + void emit(String comment) { + try { + final var lines = comment.split(NEW_LINE); + for (final String line : lines) { + write(COMMENT_PREFIX + " " + line + NEW_LINE); + } + response.flush(); + } catch (final IOException ignored) { + closed = true; + } + } + + private void write(String value) throws IOException { + response.write(value.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java b/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java new file mode 100644 index 00000000..cfbf0dc2 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java @@ -0,0 +1,74 @@ +package io.avaje.jex.http.sse; + +import java.io.Closeable; + +import io.avaje.jex.http.Context; +import io.avaje.jex.spi.JsonService; + +/** + * A client for Server-Sent Events (SSE). This class handles the setup of the SSE connection, + * sending events and comments to the client, and managing the lifecycle of the connection. It + * ensures proper headers are set and provides methods for sending various types of data. + * + *

    This class implements {@link Closeable} to allow for proper resource management. The + * connection is automatically closed if the client disconnects or if an error occurs during event + * emission. + */ +public sealed interface SseClient extends Closeable permits SseClientImpl { + + /** + * @param ctx + * @return the new SseClient instance + */ + static SseClient of(Context ctx) { + + return new SseClientImpl(ctx); + } + + /** Close the SseClient and release keepAlive block if any */ + @Override + void close(); + + /** + * Return the request Context. + * + * @return the request + */ + Context ctx(); + + /** + * By blocking the SSE connection, you can share this client outside the handler to notify it from + * other sources. Keep in mind that this function will block the handler until the SSE client is + * released by another thread. + */ + void keepAlive(); + + /** + * Attempt to send a comment. If the {@link Emitter} fails to emit (remote client has + * disconnected), the {@link #close()} function will be called instead. + */ + void sendComment(String comment); + + /** Calls {@link #sendEvent(String, Object, String)} with event set to "message" */ + void sendEvent(Object data); + + /** Calls {@link #sendEvent(String, Object, String)} with id set to null */ + void sendEvent(String event, Object data); + + /** + * Attempt to send an event. If the {@link Emitter} fails to emit (remote client has + * disconnected), the {@link #close()} function will be called instead. + * + * @param event The name of the event. + * @param data The data to send in the event. This can be a String, an InputStream, or any object + * that can be serialized to JSON using the configured {@link JsonService}. + * @param id The ID of the event. + */ + void sendEvent(String event, Object data, String id); + + /** + * Returns true if {@link #close()} has been called. This can either be by the user, or by Jex + * upon detecting that the {@link Emitter} is closed. + */ + boolean terminated(); +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClientImpl.java b/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClientImpl.java new file mode 100644 index 00000000..4e5911a0 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClientImpl.java @@ -0,0 +1,102 @@ +package io.avaje.jex.http.sse; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.lang.System.Logger.Level; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; + +import io.avaje.jex.http.Context; +import io.avaje.jex.spi.JsonService; + +final class SseClientImpl implements SseClient { + + private static final System.Logger log = System.getLogger(SseClient.class.getCanonicalName()); + + private final AtomicBoolean terminated = new AtomicBoolean(false); + private final Emitter emitter; + private final JsonService jsonService; + private final Context ctx; + private CompletableFuture blockingFuture; + + SseClientImpl(Context ctx) { + this.emitter = new Emitter(ctx.exchange().getResponseBody()); + jsonService = ctx.jsonService(); + this.ctx = ctx; + } + + @Override + public void close() { + if (terminated.getAndSet(true) && blockingFuture != null) { + blockingFuture.complete(null); + } + } + + @Override + public Context ctx() { + return ctx; + } + + @Override + public void keepAlive() { + + if (terminated.get()) return; + + this.blockingFuture = new CompletableFuture<>(); + blockingFuture.join(); + } + + private void logTerminated() { + log.log(Level.WARNING, "Cannot send data, SseClient has been terminated."); + } + + @Override + public void sendComment(String comment) { + if (terminated.get()) { + logTerminated(); + return; + } + emitter.emit(comment); + if (emitter.isClosed()) { // can't detect if closed before we try emitting + close(); + } + } + + @Override + public void sendEvent(Object data) { + sendEvent("message", data); + } + + @Override + public void sendEvent(String event, Object data) { + sendEvent(event, data, null); + } + + @Override + public void sendEvent(String event, Object data, String id) { + if (terminated.get()) { + logTerminated(); + return; + } + + final var inputStream = + switch (data) { + case final InputStream is -> is; + case final String s -> new ByteArrayInputStream(s.getBytes(UTF_8)); + default -> new ByteArrayInputStream(jsonService.toJsonString(data).getBytes(UTF_8)); + }; + + emitter.emit(event, inputStream, id); + + if (emitter.isClosed()) { // can't detect if closed before we try emitting + close(); + } + } + + @Override + public boolean terminated() { + return terminated.get(); + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java index 0eac7be7..db114ce3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java @@ -23,6 +23,17 @@ public non-sealed interface JsonService extends JexExtension { */ void toJson(Object bean, OutputStream os); + /** + * **Writes a Java Object as a JSON string** + * + *

    Serializes a Java object into JSON string format and writes the resulting JSON to the specified + * output stream. + * + * @param bean the Java object to be serialized + * @return the serialized JSON string + */ + String toJsonString(Object bean); + /** * **Reads JSON from an InputStream** * diff --git a/avaje-jex/src/main/java/module-info.java b/avaje-jex/src/main/java/module-info.java index 52e36574..723e1894 100644 --- a/avaje-jex/src/main/java/module-info.java +++ b/avaje-jex/src/main/java/module-info.java @@ -22,6 +22,7 @@ exports io.avaje.jex; exports io.avaje.jex.compression; exports io.avaje.jex.http; + exports io.avaje.jex.http.sse; exports io.avaje.jex.core.json; exports io.avaje.jex.security; exports io.avaje.jex.spi; diff --git a/avaje-jex/src/test/java/io/avaje/jex/http/sse/SseClientTest.java b/avaje-jex/src/test/java/io/avaje/jex/http/sse/SseClientTest.java new file mode 100644 index 00000000..7c085c48 --- /dev/null +++ b/avaje-jex/src/test/java/io/avaje/jex/http/sse/SseClientTest.java @@ -0,0 +1,195 @@ +package io.avaje.jex.http.sse; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.avaje.jex.Jex; +import io.avaje.jex.core.Constants; +import io.avaje.jex.core.TestPair; +import io.avaje.jex.core.json.JacksonJsonService; + +class SseClientTest { + + static final TestPair pair = init(); + static final AtomicReference afterAll = new AtomicReference<>(); + static final AtomicReference afterTwo = new AtomicReference<>(); + + static TestPair init() { + final var app = + Jex.create() + .jsonService(new JacksonJsonService()) + .sse( + "/sse", + sse -> { + for (var i = 0; i < 4; i++) { + sse.sendEvent("count", "hi", i + ""); + } + }) + .sse( + "/is", + sse -> { + for (var i = 0; i < 2; i++) { + sse.sendEvent( + "count", + new ByteArrayInputStream(("IS val " + 1).getBytes(StandardCharsets.UTF_8)), + i + ""); + } + }) + .sse( + "/json", + sse -> { + for (var i = 0; i < 2; i++) { + sse.sendEvent("count", new JsonContent(i), i + ""); + } + }) + .sse( + "/keepAlive", + sse -> { + Thread.startVirtualThread( + () -> { + for (var i = 0; i < 2; i++) { + sse.sendComment("Sent And Closed"); + sse.close(); + } + }); + sse.keepAlive(); + }) + .sse( + "/multi", + sse -> { + sse.sendEvent("multi\nline"); + sse.sendComment("multi\nline"); + }); + + return TestPair.create(app); + } + + public record JsonContent(int value) {} + + @AfterAll + static void end() { + pair.shutdown(); + } + + @Test + void testSse() { + final var response = + pair.request() + .path("sse") + .header(Constants.ACCEPT, "text/event-stream") + .GET() + .asLines() + .body() + .toList(); + assertThat(response).hasSize(16); + + final var expected = + """ + id: 0 + event: count + data: hi + + id: 1 + event: count + data: hi + + id: 2 + event: count + data: hi + + id: 3 + event: count + data: hi + """; + assertThat(String.join("\n", response)).isEqualTo(expected); + } + + @Test + void testSseInputStream() { + final var response = + pair.request() + .path("is") + .header(Constants.ACCEPT, "text/event-stream") + .GET() + .asLines() + .body() + .toList(); + assertThat(response).hasSize(8); + + final var expected = + """ + id: 0 + event: count + data: IS val 1 + + id: 1 + event: count + data: IS val 1 + """; + assertThat(String.join("\n", response)).isEqualTo(expected); + } + + @Test + void testSseJson() { + final var response = + pair.request() + .path("json") + .header(Constants.ACCEPT, "text/event-stream") + .GET() + .asLines() + .body() + .toList(); + assertThat(response).hasSize(8); + + final var expected = + """ + id: 0 + event: count + data: {"value":0} + + id: 1 + event: count + data: {"value":1} + """; + assertThat(String.join("\n", response)).isEqualTo(expected); + } + + @Test + void testKeepAlive() { + final var response = + pair.request() + .path("keepAlive") + .header(Constants.ACCEPT, "text/event-stream") + .GET() + .asString() + .body(); + assertThat(response).isEqualTo(": Sent And Closed\n"); + } + + @Test + void testMultiLineData() { + final var response = + pair.request() + .path("multi") + .header(Constants.ACCEPT, "text/event-stream") + .GET() + .asString() + .body(); + final var expected = + """ + event: message + data: multi + data: line + + : multi + : line + """; + assertThat(response).isEqualTo(expected); + } +} From e6a3608dc57202fbde994eb18718cd6d8fb03446 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 24 Feb 2025 10:21:51 +1300 Subject: [PATCH 211/250] Move SseHandler into sse package and use SseClient.handler() method (#205) --- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/Routing.java | 4 ++-- .../main/java/io/avaje/jex/http/sse/SseClient.java | 12 +++++------- .../java/io/avaje/jex/{ => http/sse}/SseHandler.java | 7 +++---- 4 files changed, 11 insertions(+), 14 deletions(-) rename avaje-jex/src/main/java/io/avaje/jex/{ => http/sse}/SseHandler.java (89%) diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index f2c399e9..ef9e668e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -153,7 +153,7 @@ default Jex options(String path, ExchangeHandler handler, Role... roles) { * @param roles An array of roles that are associated with this endpoint. */ default Jex sse(String path, Consumer handler, Role... roles) { - return get(path, new SseHandler(handler), roles); + return get(path, SseClient.handler(handler), roles); } /** Add a filter for all matched requests. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index 76aa110a..dfa23a38 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -156,8 +156,8 @@ default Routing after(Consumer handler) { * @param handler The sse handler to invoke when a GET request matches the path. * @param roles An array of roles that are associated with this endpoint. */ - default Routing sse(String path, Consumer consumer, Role... roles) { - return get(path, new SseHandler(consumer), roles); + default Routing sse(String path, Consumer handler, Role... roles) { + return get(path, SseClient.handler(handler), roles); } /** Return all the registered handlers. */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java b/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java index cfbf0dc2..6f7f111d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java @@ -1,8 +1,10 @@ package io.avaje.jex.http.sse; import java.io.Closeable; +import java.util.function.Consumer; import io.avaje.jex.http.Context; +import io.avaje.jex.http.ExchangeHandler; import io.avaje.jex.spi.JsonService; /** @@ -16,13 +18,9 @@ */ public sealed interface SseClient extends Closeable permits SseClientImpl { - /** - * @param ctx - * @return the new SseClient instance - */ - static SseClient of(Context ctx) { - - return new SseClientImpl(ctx); + /** Return an SseClient handler. */ + static ExchangeHandler handler(Consumer consumer) { + return new SseHandler(consumer); } /** Close the SseClient and release keepAlive block if any */ diff --git a/avaje-jex/src/main/java/io/avaje/jex/SseHandler.java b/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseHandler.java similarity index 89% rename from avaje-jex/src/main/java/io/avaje/jex/SseHandler.java rename to avaje-jex/src/main/java/io/avaje/jex/http/sse/SseHandler.java index 4eae1bbd..4ceea3b4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/SseHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseHandler.java @@ -1,4 +1,4 @@ -package io.avaje.jex; +package io.avaje.jex.http.sse; import java.io.IOException; import java.io.UncheckedIOException; @@ -8,10 +8,9 @@ import io.avaje.jex.http.BadRequestException; import io.avaje.jex.http.Context; import io.avaje.jex.http.ExchangeHandler; -import io.avaje.jex.http.sse.SseClient; /** Handler that configures a request for Server Sent Events */ -class SseHandler implements ExchangeHandler { +final class SseHandler implements ExchangeHandler { private static final String TEXT_EVENT_STREAM = "text/event-stream"; private final Consumer consumer; @@ -34,7 +33,7 @@ public void handle(Context ctx) throws Exception { headers.add("Cache-Control", "no-cache"); headers.add("X-Accel-Buffering", "no"); // See https://serverfault.com/a/801629 - try (var sse = SseClient.of(ctx)) { + try (var sse = new SseClientImpl(ctx)) { exchange.sendResponseHeaders(200, 0); consumer.accept(sse); } catch (final IOException e) { From 2ea7e4bdeb7478a056d4c392d882b4c9aff90739 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 24 Feb 2025 13:34:43 -0800 Subject: [PATCH 212/250] unseal SseClient (#206) --- avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java b/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java index 6f7f111d..6c3c4adc 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java @@ -16,7 +16,7 @@ * connection is automatically closed if the client disconnects or if an error occurs during event * emission. */ -public sealed interface SseClient extends Closeable permits SseClientImpl { +public interface SseClient extends Closeable { /** Return an SseClient handler. */ static ExchangeHandler handler(Consumer consumer) { From e173f237dcfe93bf5c0edf06e46bc7bfb2b70071 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 25 Feb 2025 17:27:35 +1300 Subject: [PATCH 213/250] Remove sealed from API - Jex, JexConfig, Routing, AppLifecycle (#208) We desire to not restrict these interfaces allowing users of the API to easily test interactions with this API if they desire to. --- avaje-jex/src/main/java/io/avaje/jex/AppLifecycle.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/Jex.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/JexConfig.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/Routing.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/AppLifecycle.java b/avaje-jex/src/main/java/io/avaje/jex/AppLifecycle.java index ae02cf73..d9d749ad 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/AppLifecycle.java +++ b/avaje-jex/src/main/java/io/avaje/jex/AppLifecycle.java @@ -1,7 +1,7 @@ package io.avaje.jex; /** Defines the lifecycle configuration for an application. */ -public sealed interface AppLifecycle permits DefaultLifecycle { +public interface AppLifecycle { /** Represents the possible states of the application server. */ enum Status { diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index ef9e668e..caa06ac8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -30,7 +30,7 @@ * * }

    */ -public sealed interface Jex permits DJex { +public interface Jex { /** * Create Jex. diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index 3d98c701..6db7bf13 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -20,7 +20,7 @@ * endpoint, trailing slash handling, JSON service, template renderers, executor service, HTTPS * configuration, compression, and plugin loading. */ -public sealed interface JexConfig permits DJexConfig { +public interface JexConfig { /** Returns the configured compression settings. */ CompressionConfig compression(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/Routing.java b/avaje-jex/src/main/java/io/avaje/jex/Routing.java index dfa23a38..19d1870b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Routing.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Routing.java @@ -16,7 +16,7 @@ import io.avaje.jex.security.Role; /** Routing abstraction. */ -public sealed interface Routing permits DefaultRouting { +public interface Routing { /** Add the routes provided by the given HttpService. */ Routing add(Routing.HttpService service); From a8f741a933a5952f5506d23613610b442e80eb91 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:50:02 -0800 Subject: [PATCH 214/250] add onclose method to sse (#209) might be useful --- .../src/main/java/io/avaje/jex/http/sse/SseClient.java | 8 ++++++++ .../main/java/io/avaje/jex/http/sse/SseClientImpl.java | 10 +++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java b/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java index 6c3c4adc..97f7577d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClient.java @@ -41,6 +41,14 @@ static ExchangeHandler handler(Consumer consumer) { */ void keepAlive(); + /** + * Add a callback that will be called either when connection is closed through {@link #close()}, + * or when the {@link Emitter} is detected as closed. + * + * @param task task to run + */ + void onClose(Runnable task); + /** * Attempt to send a comment. If the {@link Emitter} fails to emit (remote client has * disconnected), the {@link #close()} function will be called instead. diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClientImpl.java b/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClientImpl.java index 4e5911a0..5db9c7a3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClientImpl.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/sse/SseClientImpl.java @@ -20,6 +20,7 @@ final class SseClientImpl implements SseClient { private final JsonService jsonService; private final Context ctx; private CompletableFuture blockingFuture; + private Runnable closeCallback = () -> {}; SseClientImpl(Context ctx) { this.emitter = new Emitter(ctx.exchange().getResponseBody()); @@ -27,9 +28,16 @@ final class SseClientImpl implements SseClient { this.ctx = ctx; } + @Override + public void onClose(Runnable task) { + this.closeCallback = task; + } + @Override public void close() { - if (terminated.getAndSet(true) && blockingFuture != null) { + if (terminated.getAndSet(true)) return; + closeCallback.run(); + if (blockingFuture != null) { blockingFuture.complete(null); } } From 3e4dcc486087077a22c4dec92adda8c773d8dd3d Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 1 Mar 2025 14:52:13 -0800 Subject: [PATCH 215/250] make `toJsonStream` a default method (#210) How often do people really use json streaming? Is it even an http standard media type? --- avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java index db114ce3..03ee5432 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java @@ -26,8 +26,8 @@ public non-sealed interface JsonService extends JexExtension { /** * **Writes a Java Object as a JSON string** * - *

    Serializes a Java object into JSON string format and writes the resulting JSON to the specified - * output stream. + *

    Serializes a Java object into JSON string format and writes the resulting JSON to the + * specified output stream. * * @param bean the Java object to be serialized * @return the serialized JSON string @@ -66,5 +66,7 @@ public non-sealed interface JsonService extends JexExtension { * @param iterator the stream of objects to be serialized * @param os the output stream to write the JSON-Stream data to */ - void toJsonStream(Iterator iterator, OutputStream os); + default void toJsonStream(Iterator iterator, OutputStream os) { + throw new UnsupportedOperationException("toJsonStream is unimplemented in this JsonService"); + } } From f188f39bd8cb81aff148e5929e207618b701b72d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 03:35:29 +0000 Subject: [PATCH 216/250] Bump the dependencies group with 4 updates (#211) Bumps the dependencies group with 4 updates: [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson), org.slf4j:slf4j-api, [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) and org.slf4j:slf4j-jdk-platform-logging. Updates `com.fasterxml.jackson.core:jackson-databind` from 2.18.2 to 2.18.3 - [Commits](https://github.com/FasterXML/jackson/commits) Updates `org.slf4j:slf4j-api` from 2.0.16 to 2.0.17 Updates `ch.qos.logback:logback-classic` from 1.5.16 to 1.5.17 - [Release notes](https://github.com/qos-ch/logback/releases) - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.16...v_1.5.17) Updates `org.slf4j:slf4j-jdk-platform-logging` from 2.0.16 to 2.0.17 --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: org.slf4j:slf4j-jdk-platform-logging dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/example-jdk/pom.xml | 6 +++--- examples/example-jetty/pom.xml | 4 ++-- examples/example-robaho/pom.xml | 4 ++-- pom.xml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 2d2104d4..2bfa605e 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -30,19 +30,19 @@ com.fasterxml.jackson.core jackson-databind - 2.18.2 + 2.18.3 org.slf4j slf4j-api - 2.0.16 + 2.0.17 ch.qos.logback logback-classic - 1.5.16 + 1.5.17 diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index c85adddf..ee4120e0 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -27,13 +27,13 @@ org.slf4j slf4j-jdk-platform-logging - 2.0.16 + 2.0.17 ch.qos.logback logback-classic - 1.5.16 + 1.5.17 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 7060d8f0..249d70c7 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -27,13 +27,13 @@ org.slf4j slf4j-jdk-platform-logging - 2.0.16 + 2.0.17 ch.qos.logback logback-classic - 1.5.16 + 1.5.17 diff --git a/pom.xml b/pom.xml index 2a85126f..e85c3da6 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ true - 2.18.2 + 2.18.3 false 21 full From d4259af1186f621c54425b14f92d60971ad68fac Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 9 Mar 2025 11:20:23 -0700 Subject: [PATCH 217/250] normalize header constants (#212) due to https://bugs.openjdk.org/browse/JDK-8347167, it's more efficient this way --- .../main/java/io/avaje/jex/test/TestPair.java | 19 ++++-------- .../java/io/avaje/jex/core/Constants.java | 15 ++++------ .../io/avaje/jex/core/CookieServerTest.java | 14 ++++----- .../java/io/avaje/jex/core/FilterTest.java | 15 ++++++---- .../test/java/io/avaje/jex/core/TestPair.java | 29 +++++-------------- 5 files changed, 35 insertions(+), 57 deletions(-) diff --git a/avaje-jex-test/src/main/java/io/avaje/jex/test/TestPair.java b/avaje-jex-test/src/main/java/io/avaje/jex/test/TestPair.java index 2a665c5b..5c0d3dee 100644 --- a/avaje-jex-test/src/main/java/io/avaje/jex/test/TestPair.java +++ b/avaje-jex-test/src/main/java/io/avaje/jex/test/TestPair.java @@ -1,14 +1,12 @@ package io.avaje.jex.test; -import java.util.Random; +import java.net.http.HttpClient.Version; import io.avaje.http.client.HttpClient; import io.avaje.http.client.HttpClientRequest; import io.avaje.jex.Jex; -/** - * Server and Client pair for a test. - */ +/** Server and Client pair for a test. */ public class TestPair { private final int port; @@ -39,18 +37,13 @@ public String url() { return client.url().build(); } - /** - * Create a Server and Client pair for a given set of tests. - */ + /** Create a Server and Client pair for a given set of tests. */ public static TestPair create(Jex app) { - int port = 10000 + new Random().nextInt(1000); - var jexServer = app.port(port).start(); - + var jexServer = app.port(0).start(); + var port = jexServer.port(); var url = "http://localhost:" + port; - var client = HttpClient.builder() - .baseUrl(url) - .build(); + var client = HttpClient.builder().version(Version.HTTP_1_1).baseUrl(url).build(); return new TestPair(port, jexServer, client); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/Constants.java b/avaje-jex/src/main/java/io/avaje/jex/core/Constants.java index 5a90e9bb..fc6a9132 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/Constants.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/Constants.java @@ -5,17 +5,13 @@ public final class Constants { private Constants() {} public static final String ACCEPT = "Accept"; - public static final String CONTENT_ENCODING = "Content-Encoding"; - public static final String CONTENT_DISPOSITION = "Content-Disposition"; - public static final String CONTENT_LANGUAGE = "Content-Language"; - public static final String CONTENT_LENGTH = "Content-Length"; - public static final String CONTENT_LOCATION = "Content-Location"; - public static final String CONTENT_RANGE = "Content-Range"; - public static final String CONTENT_TYPE = "Content-Type"; + public static final String CONTENT_ENCODING = "Content-encoding"; + public static final String CONTENT_LENGTH = "Content-length"; + public static final String CONTENT_TYPE = "Content-type"; public static final String LOCATION = "Location"; public static final String HOST = "Host"; - public static final String USER_AGENT = "User-Agent"; - public static final String ACCEPT_ENCODING = "Accept-Encoding"; + public static final String USER_AGENT = "User-agent"; + public static final String ACCEPT_ENCODING = "Accept-encoding"; public static final String TEXT_HTML = "text/html"; public static final String TEXT_PLAIN = "text/plain"; @@ -23,5 +19,4 @@ private Constants() {} public static final String TEXT_PLAIN_UTF8 = "text/plain;charset=utf-8"; public static final String APPLICATION_JSON = "application/json"; public static final String APPLICATION_X_JSON_STREAM = "application/x-json-stream"; - } diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/CookieServerTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/CookieServerTest.java index 6c8433e9..1061e37c 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/CookieServerTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/CookieServerTest.java @@ -1,15 +1,15 @@ package io.avaje.jex.core; -import io.avaje.jex.Jex; -import io.avaje.jex.http.Context; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.net.http.HttpResponse; import java.time.Duration; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.avaje.jex.Jex; +import io.avaje.jex.http.Context; class CookieServerTest { @@ -28,7 +28,7 @@ static TestPair init() { ctx.cookie(httpCookie).text("ok"); }) ); - return TestPair.create(app, 9001); + return TestPair.create(app); } @AfterAll diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java index 36b9b097..0945e695 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/FilterTest.java @@ -1,8 +1,6 @@ package io.avaje.jex.core; -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.net.http.HttpHeaders; import java.net.http.HttpResponse; @@ -10,7 +8,10 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.avaje.jex.Jex; class FilterTest { @@ -25,7 +26,11 @@ static TestPair init() { routing -> routing .get("/", ctx -> ctx.text("roo")) - .get("/noResponse", ctx -> {}) + .get( + "/noResponse", + ctx -> { + ctx.header("Content-Type", ""); + }) .get("/one", ctx -> ctx.text("one")) .get("/two", ctx -> ctx.text("two")) .get("/two/{id}", ctx -> ctx.text("two-id")) diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/TestPair.java b/avaje-jex/src/test/java/io/avaje/jex/core/TestPair.java index b53288c0..f9429ed2 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/TestPair.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/TestPair.java @@ -1,16 +1,12 @@ package io.avaje.jex.core; +import java.net.http.HttpClient.Version; + import io.avaje.http.client.HttpClient; import io.avaje.http.client.HttpClientRequest; -import io.avaje.http.client.JacksonBodyAdapter; import io.avaje.jex.Jex; -import java.time.Duration; -import java.util.Random; - -/** - * Server and Client pair for a test. - */ +/** Server and Client pair for a test. */ public class TestPair { private final int port; @@ -41,24 +37,13 @@ public String url() { return client.url().build(); } - /** - * Create a Server and Client pair for a given set of tests. - */ + /** Create a Server and Client pair for a given set of tests. */ public static TestPair create(Jex app) { - int port = 10000 + new Random().nextInt(1000); - return create(app, port); - } - - public static TestPair create(Jex app, int port) { - - var jexServer = app.port(port).start(); + var jexServer = app.port(0).start(); + var port = jexServer.port(); var url = "http://localhost:" + port; - var client = HttpClient.builder() - .baseUrl(url) - .bodyAdapter(new JacksonBodyAdapter()) - .requestTimeout(Duration.ofMinutes(2)) - .build(); + var client = HttpClient.builder().version(Version.HTTP_1_1).baseUrl(url).build(); return new TestPair(port, jexServer, client); } From cb01e340f880af8acf1373b001b79e501cc3db23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 03:12:24 +0000 Subject: [PATCH 218/250] Bump the dependencies group with 2 updates (#213) Bumps the dependencies group with 2 updates: org.eclipse.jetty:jetty-server and org.eclipse.jetty:jetty-http-spi. Updates `org.eclipse.jetty:jetty-server` from 12.0.16 to 12.0.17 Updates `org.eclipse.jetty:jetty-http-spi` from 12.0.16 to 12.0.17 --- updated-dependencies: - dependency-name: org.eclipse.jetty:jetty-server dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: org.eclipse.jetty:jetty-http-spi dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/example-jetty/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index ee4120e0..9b2d1971 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -10,13 +10,13 @@ org.eclipse.jetty jetty-server - 12.0.16 + 12.0.17 org.eclipse.jetty jetty-http-spi - 12.0.16 + 12.0.17 io.avaje From c8530c0d84c3a2db2cd28b57a338d79f6b77029d Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 9 Mar 2025 22:26:43 -0700 Subject: [PATCH 219/250] Fix startup log (#214) * fix startup log * fix examples --- README.md | 6 ------ .../main/java/io/avaje/jex/core/BootstrapServer.java | 10 ++++++++-- .../src/main/java/module-info.java | 1 + examples/example-jetty/pom.xml | 8 +------- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index e70f9d80..0d34ddb3 100644 --- a/README.md +++ b/README.md @@ -180,12 +180,6 @@ The JDK provides an SPI to swap the underlying `HttpServer`, so you can easily u ${jex.version} - - org.eclipse.jetty - jetty-server - ${jetty.version} - - org.eclipse.jetty jetty-http-spi diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java index fe0c8198..67f7d67f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java @@ -66,9 +66,15 @@ static Jex.Server start(Jex jex, SpiRoutes routes) { server.createContext(contextPath, handler); server.start(); - + var actualAddress = server.getAddress(); jex.lifecycle().status(AppLifecycle.Status.STARTED); - log.log(INFO, "Avaje Jex started {0} on port {1}://{2}", serverClass, scheme, socketAddress); + log.log( + INFO, + "Avaje Jex started {0} on port {1}://{2}:{3,number,#}", + serverClass, + scheme, + actualAddress.getHostName(), + actualAddress.getPort()); log.log(DEBUG, routes); return new JdkJexServer(server, jex.lifecycle(), handler); } catch (IOException e) { diff --git a/examples/example-http-generation/src/main/java/module-info.java b/examples/example-http-generation/src/main/java/module-info.java index bd9e29ea..c7884041 100644 --- a/examples/example-http-generation/src/main/java/module-info.java +++ b/examples/example-http-generation/src/main/java/module-info.java @@ -1,5 +1,6 @@ open module example.http.generation { + requires io.avaje.config; requires io.avaje.jsonb; requires io.avaje.http.api; requires io.avaje.jex; diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index 9b2d1971..885019a3 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -7,21 +7,15 @@ example-jetty - - org.eclipse.jetty - jetty-server - 12.0.17 - - org.eclipse.jetty jetty-http-spi 12.0.17 + io.avaje avaje-jex - 3.0-RC20 From 6fd12b028907df8376769de231a0c0656e73b868 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 11 Mar 2025 08:14:38 +1300 Subject: [PATCH 220/250] Version 3.0-RC21 --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-htmx/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- examples/example-http-generation/pom.xml | 2 +- examples/example-jdk-jsonb/pom.xml | 2 +- examples/example-jdk/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 2 +- pom.xml | 14 +++++++------- 12 files changed, 18 insertions(+), 18 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 48881d75..6c5ed200 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC20 + 3.0-RC21 avaje-jex-freemarker diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 3423e154..aa828ed3 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC20 + 3.0-RC21 avaje-jex-htmx diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index b79470dd..77735222 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC20 + 3.0-RC21 avaje-jex-mustache diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index be3f27f2..8ca41c7f 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC20 + 3.0-RC21 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 27580ed3..db481f1e 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC20 + 3.0-RC21 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 9f19688d..d04b34eb 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC20 + 3.0-RC21 avaje-jex diff --git a/examples/example-http-generation/pom.xml b/examples/example-http-generation/pom.xml index 46ac591c..1de82728 100644 --- a/examples/example-http-generation/pom.xml +++ b/examples/example-http-generation/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC20 + 3.0-RC21 4.0.0 diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index dfa34d22..609dcc6d 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -6,7 +6,7 @@ avaje-jex-parent io.avaje - 3.0-RC20 + 3.0-RC21 org.example diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 2bfa605e..810d0990 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -24,7 +24,7 @@ io.avaje avaje-jex - 3.0-RC20 + 3.0-RC21 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 249d70c7..0b305361 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC20 + 3.0-RC21 diff --git a/examples/pom.xml b/examples/pom.xml index a6a1fa74..4b4ba355 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC20 + 3.0-RC21 examples diff --git a/pom.xml b/pom.xml index e85c3da6..12e8ff1c 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ io.avaje avaje-jex-parent - 3.0-RC20 + 3.0-RC21 pom @@ -176,32 +176,32 @@ io.avaje avaje-jex - 3.0-RC20 + 3.0-RC21 io.avaje avaje-jex-test - 3.0-RC20 + 3.0-RC21 io.avaje avaje-jex-freemarker - 3.0-RC20 + 3.0-RC21 io.avaje avaje-jex-mustache - 3.0-RC20 + 3.0-RC21 io.avaje avaje-jex-htmx - 3.0-RC20 + 3.0-RC21 io.avaje avaje-jex-static-content - 3.0-RC20 + 3.0-RC21 io.github.robaho From 2c00ec8e1d091060d7ba4cf14201f1039a20a59f Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 11 Mar 2025 01:43:26 -0700 Subject: [PATCH 221/250] fix example builds (#215) now shouldn't fail build on version bump --- avaje-jex/src/main/java/io/avaje/jex/http/HttpStatus.java | 2 +- .../src/main/java/io/avaje/jex/http/package-info.java | 2 +- .../src/main/java/io/avaje/jex/http/sse/package-info.java | 2 ++ examples/example-http-generation/pom.xml | 2 +- examples/example-jdk-jsonb/pom.xml | 3 +-- examples/example-jdk/pom.xml | 7 +++---- examples/example-jetty/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- examples/pom.xml | 1 - 9 files changed, 11 insertions(+), 12 deletions(-) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/http/sse/package-info.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/HttpStatus.java b/avaje-jex/src/main/java/io/avaje/jex/http/HttpStatus.java index 9b679c33..d664fabe 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/HttpStatus.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/HttpStatus.java @@ -1,6 +1,6 @@ package io.avaje.jex.http; -/** Http Error Status codes */ +/** Http Status codes */ public enum HttpStatus { // 1xx Informational diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/package-info.java b/avaje-jex/src/main/java/io/avaje/jex/http/package-info.java index 714cc482..c95e9040 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/package-info.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/package-info.java @@ -1,2 +1,2 @@ -/** Http Exceptions */ +/** Http Constructs (Handlers, Filters, etc.) */ package io.avaje.jex.http; diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/sse/package-info.java b/avaje-jex/src/main/java/io/avaje/jex/http/sse/package-info.java new file mode 100644 index 00000000..065474b6 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/http/sse/package-info.java @@ -0,0 +1,2 @@ +/** Server Sent Event Classes */ +package io.avaje.jex.http.sse; diff --git a/examples/example-http-generation/pom.xml b/examples/example-http-generation/pom.xml index 1de82728..1dd67b7b 100644 --- a/examples/example-http-generation/pom.xml +++ b/examples/example-http-generation/pom.xml @@ -3,8 +3,8 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - avaje-jex-parent io.avaje + examples 3.0-RC21 4.0.0 diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index 609dcc6d..61602898 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -4,14 +4,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - avaje-jex-parent io.avaje + examples 3.0-RC21 org.example example-jdk-jsonb - 1 21 diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 810d0990..e1ad4c67 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -4,10 +4,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - org.avaje - java11-oss - 4.5 - + io.avaje + examples + 3.0-RC21 org.example diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index 885019a3..71d05c82 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -3,7 +3,7 @@ io.avaje examples - 0.1 + 3.0-RC21 example-jetty diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 0b305361..e98ebcd3 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -6,7 +6,7 @@ io.avaje examples - 0.1 + 3.0-RC21 example-robaho diff --git a/examples/pom.xml b/examples/pom.xml index 4b4ba355..c88a350e 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -9,7 +9,6 @@ examples - 0.1 pom From 312961685e1c68e6d2dd4772e088bf711596f849 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 11 Mar 2025 13:18:50 -0700 Subject: [PATCH 222/250] Update PathSegmentParser.java (#216) --- .../io/avaje/jex/routes/PathSegmentParser.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java b/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java index f450f449..27fc8ff4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java @@ -5,18 +5,16 @@ import java.util.regex.MatchResult; import java.util.regex.Pattern; -import static java.util.stream.Collectors.toList; - final class PathSegmentParser { private static final PathSegment WILDCARD = new PathSegment.Wildcard(); private static final String[] ADJACENT_VIOLATIONS = {"*{", "*<", "}*", ">*"}; - private static final String NAME_STR = "[a-zA-Z0-9_-]+"; - private static final String NAME_OPT = "([:][^/^{]+([{][0-9]+[}])?)?"; // Optional regex - private static final String SLASH_STR = "[<]" + NAME_STR + "[>]"; - private static final String PARAM_STR = "[{]" + NAME_STR + NAME_OPT + "[}]"; + private static final String NAME_STR = "[\\w-]+"; + private static final String NAME_OPT = "(:[^/^{]+([{]\\d+})?)?"; // Optional regex + private static final String SLASH_STR = "<" + NAME_STR + ">"; + private static final String PARAM_STR = "[{]" + NAME_STR + NAME_OPT + "}"; private static final String MULTI_STR = "(" + PARAM_STR + "|" + SLASH_STR + "|[*]|" + "[^{ multi(String input) { - return MATCH_MULTI.matcher(input).results() - .map(MatchResult::group) - .collect(toList()); + return MATCH_MULTI.matcher(input).results().map(MatchResult::group).toList(); } static boolean matchLiteral(String segment) { @@ -130,5 +126,4 @@ boolean endCharOnly(char c) { // last char matches and no prior matching char return segment.indexOf(c) == segment.length() - 1; } - } From 6df747dd2fac6813ad263822243bd16a266a13be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Mar 2025 23:36:46 +0000 Subject: [PATCH 223/250] Bump the dependencies group with 4 updates (#217) Bumps the dependencies group with 4 updates: io.ebean:ebean-bom, io.ebean:querybean-generator, [io.ebean:ebean-maven-plugin](https://github.com/ebean-orm-tools/ebean-maven-plugin) and [org.graalvm.buildtools:native-maven-plugin](https://github.com/graalvm/native-build-tools). Updates `io.ebean:ebean-bom` from 15.9.0 to 15.10.0 Updates `io.ebean:querybean-generator` from 15.9.0 to 15.10.0 Updates `io.ebean:ebean-maven-plugin` from 15.9.0 to 15.10.0 - [Release notes](https://github.com/ebean-orm-tools/ebean-maven-plugin/releases) - [Changelog](https://github.com/ebean-orm-tools/ebean-maven-plugin/blob/master/release.properties) - [Commits](https://github.com/ebean-orm-tools/ebean-maven-plugin/commits) Updates `io.ebean:querybean-generator` from 15.9.0 to 15.10.0 Updates `io.ebean:ebean-maven-plugin` from 15.9.0 to 15.10.0 - [Release notes](https://github.com/ebean-orm-tools/ebean-maven-plugin/releases) - [Changelog](https://github.com/ebean-orm-tools/ebean-maven-plugin/blob/master/release.properties) - [Commits](https://github.com/ebean-orm-tools/ebean-maven-plugin/commits) Updates `org.graalvm.buildtools:native-maven-plugin` from 0.10.5 to 0.10.6 - [Release notes](https://github.com/graalvm/native-build-tools/releases) - [Commits](https://github.com/graalvm/native-build-tools/commits) --- updated-dependencies: - dependency-name: io.ebean:ebean-bom dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.ebean:querybean-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.ebean:ebean-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.ebean:querybean-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.ebean:ebean-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: org.graalvm.buildtools:native-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/example-jdk-jsonb/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index 61602898..64a24921 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -62,7 +62,7 @@ org.graalvm.buildtools native-maven-plugin - 0.10.5 + 0.10.6 true diff --git a/pom.xml b/pom.xml index 12e8ff1c..03f6c854 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ 1.2 2.10 1.36 - 15.9.0 + 15.10.0 From 6ec6c902f296e3027c27554f73cd23cf3042970b Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 12 Mar 2025 19:37:19 -0700 Subject: [PATCH 224/250] rename static content builder method (#218) create seems misleading --- .../java/io/avaje/jex/staticcontent/StaticContent.java | 6 +++--- .../avaje/jex/staticcontent/CompressedStaticFileTest.java | 8 ++++---- .../java/io/avaje/jex/staticcontent/StaticFileTest.java | 8 ++++---- avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java index 0efd677e..337d9fee 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java @@ -31,7 +31,7 @@ public sealed interface StaticContent extends JexPlugin * * @param resourceRoot The file to serve, or the directory the files are located in. */ - static Builder createCP(String resourceRoot) { + static Builder ofClassPath(String resourceRoot) { return StaticResourceHandlerBuilder.builder(resourceRoot); } @@ -39,7 +39,7 @@ static Builder createCP(String resourceRoot) { * Create and return a new static content class path configuration with the * `/public` directory as the root. */ - static Builder createCP() { + static Builder ofClassPath() { return StaticResourceHandlerBuilder.builder("/public/"); } @@ -48,7 +48,7 @@ static Builder createCP() { * * @param resourceRoot The path of the file to serve, or the directory the files are located in. */ - static Builder createFile(String resourceRoot) { + static Builder ofFile(String resourceRoot) { return StaticResourceHandlerBuilder.builder(resourceRoot).file(); } diff --git a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java index ae712260..b57259f8 100644 --- a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java +++ b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java @@ -25,22 +25,22 @@ static TestPair init() { .plugin(defaultFile().route("/indexWildFile/*").build()) .plugin(defaultCP().route("/sus/").build()) .plugin(defaultFile().route("/susFile/*").build()) - .plugin(StaticContent.createCP("/logback.xml").route("/single").build()) + .plugin(StaticContent.ofClassPath("/logback.xml").route("/single").build()) .plugin( - StaticContent.createFile("src/test/resources/logback.xml") + StaticContent.ofFile("src/test/resources/logback.xml") .route("/singleFile").build()); return TestPair.create(app); } private static StaticContent.Builder defaultFile() { - return StaticContent.createFile("src/test/resources/public") + return StaticContent.ofFile("src/test/resources/public") .directoryIndex("index.html") .preCompress(); } private static StaticContent.Builder defaultCP() { - return StaticContent.createCP("/public") + return StaticContent.ofClassPath("/public") .directoryIndex("index.html") .preCompress(); } diff --git a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java index 752828ba..ffe40d85 100644 --- a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java +++ b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/StaticFileTest.java @@ -25,21 +25,21 @@ static TestPair init() { .plugin(defaultFile().route("/indexWildFile/*").build()) .plugin(defaultCP().route("/sus/").build()) .plugin(defaultFile().route("/susFile/*").build()) - .plugin(StaticContent.createCP("/logback.xml").route("/single").build()) + .plugin(StaticContent.ofClassPath("/logback.xml").route("/single").build()) .plugin( - StaticContent.createFile("src/test/resources/logback.xml") + StaticContent.ofFile("src/test/resources/logback.xml") .route("/singleFile").build()); return TestPair.create(app); } private static StaticContent.Builder defaultFile() { - return StaticContent.createFile("src/test/resources/public") + return StaticContent.ofFile("src/test/resources/public") .directoryIndex("index.html"); } private static StaticContent.Builder defaultCP() { - return StaticContent.createCP("/public").directoryIndex("index.html"); + return StaticContent.ofClassPath("/public").directoryIndex("index.html"); } @AfterAll diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index 3e5f5ddc..cfd2a8d1 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -222,7 +222,7 @@ public Map> formParamMap() { private String header(Headers headers, String name) { final List values = headers.get(name); - return (values == null || values.isEmpty()) ? null : values.getFirst(); + return values == null || values.isEmpty() ? null : values.getFirst(); } @Override From c48190017609b25c69f986e3bc00af92f6789a4c Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 13 Mar 2025 00:32:55 -0700 Subject: [PATCH 225/250] Remove `fromJson(Class)` (#219) * make `fromJson(Class)` default again I really can't see why this can't be a default method * bodyAsClass default * remove fromJsonClass --- .../main/java/io/avaje/jex/core/BootstrapServer.java | 3 ++- .../src/main/java/io/avaje/jex/core/JdkContext.java | 5 ----- .../main/java/io/avaje/jex/core/ServiceManager.java | 4 ---- .../io/avaje/jex/core/json/JacksonJsonService.java | 9 --------- .../io/avaje/jex/core/json/JsonbJsonService.java | 5 ----- .../src/main/java/io/avaje/jex/http/Context.java | 4 +++- .../src/main/java/io/avaje/jex/spi/JsonService.java | 12 ------------ 7 files changed, 5 insertions(+), 37 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java index 67f7d67f..c266742f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java @@ -70,7 +70,8 @@ static Jex.Server start(Jex jex, SpiRoutes routes) { jex.lifecycle().status(AppLifecycle.Status.STARTED); log.log( INFO, - "Avaje Jex started {0} on port {1}://{2}:{3,number,#}", + "Avaje Jex {0} started {1} on {2}://{3}:{4,number,#}", + BootstrapServer.class.getPackage().getImplementationVersion(), serverClass, scheme, actualAddress.getHostName(), diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index cfd2a8d1..818f2e7e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -132,11 +132,6 @@ public byte[] bodyAsBytes() { } } - @Override - public T bodyAsClass(Class beanType) { - return mgr.fromJson(beanType, bodyAsInputStream()); - } - @Override public InputStream bodyAsInputStream() { return exchange.getRequestBody(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java index 7f9d16ae..0d3eabd0 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java @@ -71,10 +71,6 @@ JsonService jsonService() { return jsonService; } - T fromJson(Class type, InputStream is) { - return jsonService.fromJson(type, is); - } - T fromJson(Type type, InputStream is) { return jsonService.fromJson(type, is); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java index 577b70b8..3c9ace0f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java @@ -33,15 +33,6 @@ public JacksonJsonService(ObjectMapper mapper) { this.mapper = mapper; } - @Override - public T fromJson(Class type, InputStream is) { - try { - return mapper.readValue(is, type); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - @Override public T fromJson(Type type, InputStream is) { try { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java index 43f2a0c7..7094fd45 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java @@ -25,11 +25,6 @@ public JsonbJsonService(Jsonb jsonb) { this.jsonb = jsonb; } - @Override - public T fromJson(Class clazz, InputStream is) { - return jsonb.type(clazz).fromJson(is); - } - @Override public T fromJson(Type clazz, InputStream is) { return jsonb.type(clazz).fromJson(is); diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java index 8e942292..f8a4bc07 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java @@ -72,7 +72,9 @@ public interface Context { * * @param beanType The bean type */ - T bodyAsClass(Class beanType); + default T bodyAsClass(Class beanType) { + return bodyAsType(beanType); + } /** * Returns the request body as an input stream. diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java index 03ee5432..65bb74e3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java @@ -34,18 +34,6 @@ public non-sealed interface JsonService extends JexExtension { */ String toJsonString(Object bean); - /** - * **Reads JSON from an InputStream** - * - *

    Reads a JSON-formatted input stream and deserializes it into a Java object of the specified - * type. - * - * @param type the Class object of the desired type - * @param is the input stream containing the JSON data - * @return the deserialized object - */ - T fromJson(Class type, InputStream is); - /** * **Reads JSON from an InputStream** * From 0e5f1789d6e492ab6a5d7abc107a6ae89f88fc2a Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 13 Mar 2025 00:49:32 -0700 Subject: [PATCH 226/250] Remove flush from JsonOutput (#220) * remove flush from JsonOutput flush() should only be used when you have a long lived push type/events endpoint. * remove jackson flush --- .../jex/core/json/JacksonJsonService.java | 17 +++------ .../avaje/jex/core/json/JsonbJsonService.java | 2 +- .../io/avaje/jex/core/json/JsonbOutput.java | 12 +++---- .../jex/core/json/NoFlushJsonOutput.java | 35 +++++++++++++++++++ pom.xml | 2 +- 5 files changed, 46 insertions(+), 22 deletions(-) create mode 100644 avaje-jex/src/main/java/io/avaje/jex/core/json/NoFlushJsonOutput.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java index 3c9ace0f..6cd3d31b 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java @@ -49,15 +49,13 @@ public T fromJson(Type type, InputStream is) { @Override public void toJson(Object bean, OutputStream os) { try { - try (JsonGenerator generator = mapper.createGenerator(os)) { + try (var generator = mapper.createGenerator(os)) { // only flush to underlying OutputStream on success generator.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); generator.disable(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM); generator.disable(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT); mapper.writeValue(generator, bean); - generator.flush(); } - os.flush(); os.close(); } catch (IOException e) { throw new UncheckedIOException(e); @@ -75,17 +73,10 @@ public String toJsonString(Object bean) { @Override public void toJsonStream(Iterator iterator, OutputStream os) { - final JsonGenerator generator; - try { - generator = mapper.createGenerator(os); + try (var generator = mapper.createGenerator(os)) { generator.setPrettyPrinter(null); - try { - while (iterator.hasNext()) { - write(iterator, generator); - } - } finally { - generator.flush(); - generator.close(); + while (iterator.hasNext()) { + write(iterator, generator); } } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java index 7094fd45..ee03d7c7 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java @@ -32,7 +32,7 @@ public T fromJson(Type clazz, InputStream is) { @Override public void toJson(Object bean, OutputStream os) { - jsonb.toJson(bean, os); + jsonb.toJson(bean, new NoFlushJsonOutput(os)); } @Override diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbOutput.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbOutput.java index 49623f15..a977ea17 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbOutput.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbOutput.java @@ -1,11 +1,11 @@ package io.avaje.jex.core.json; -import io.avaje.jex.http.Context; -import io.avaje.json.stream.JsonOutput; - import java.io.IOException; import java.io.OutputStream; +import io.avaje.jex.http.Context; +import io.avaje.json.stream.JsonOutput; + /** * avaje-jsonb output that allows for writing fixed length content * straight from the avaje-jsonb buffer, avoiding the jex side buffer. @@ -43,10 +43,8 @@ public void writeLast(byte[] content, int offset, int length) throws IOException } @Override - public void flush() throws IOException { - if (os != null) { - os.flush(); - } + public void flush() { + // shouldn't manually flush } @Override diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/NoFlushJsonOutput.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/NoFlushJsonOutput.java new file mode 100644 index 00000000..9c877b79 --- /dev/null +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/NoFlushJsonOutput.java @@ -0,0 +1,35 @@ +package io.avaje.jex.core.json; + +import java.io.IOException; +import java.io.OutputStream; + +import io.avaje.json.stream.JsonOutput; + +final class NoFlushJsonOutput implements JsonOutput { + + private final OutputStream outputStream; + + NoFlushJsonOutput(OutputStream outputStream) { + this.outputStream = outputStream; + } + + @Override + public void write(byte[] content, int offset, int length) throws IOException { + outputStream.write(content, offset, length); + } + + @Override + public void flush() throws IOException { + // no flush + } + + @Override + public void close() throws IOException { + outputStream.close(); + } + + @Override + public OutputStream unwrapOutputStream() { + return outputStream; + } +} diff --git a/pom.xml b/pom.xml index 03f6c854..b6406bb2 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ full 4.0 11.2 - 3.0 + 3.1-RC2 3.0 9.4 2.8 From 9128f440085277db9da70c7698c9f475ce8931ee Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Thu, 13 Mar 2025 21:25:50 +1300 Subject: [PATCH 227/250] Version 3.0-RC22 --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-htmx/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- examples/example-http-generation/pom.xml | 2 +- examples/example-jdk-jsonb/pom.xml | 2 +- examples/example-jdk/pom.xml | 4 ++-- examples/example-jetty/pom.xml | 2 +- examples/example-robaho/pom.xml | 4 ++-- examples/pom.xml | 2 +- pom.xml | 14 +++++++------- 13 files changed, 21 insertions(+), 21 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 6c5ed200..4515e6f5 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC21 + 3.0-RC22 avaje-jex-freemarker diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index aa828ed3..130fda5c 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC21 + 3.0-RC22 avaje-jex-htmx diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 77735222..04890dbb 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC21 + 3.0-RC22 avaje-jex-mustache diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index 8ca41c7f..c70c68ee 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC21 + 3.0-RC22 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index db481f1e..b33c7cf3 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC21 + 3.0-RC22 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index d04b34eb..47abfab6 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC21 + 3.0-RC22 avaje-jex diff --git a/examples/example-http-generation/pom.xml b/examples/example-http-generation/pom.xml index 1dd67b7b..608a2303 100644 --- a/examples/example-http-generation/pom.xml +++ b/examples/example-http-generation/pom.xml @@ -5,7 +5,7 @@ io.avaje examples - 3.0-RC21 + 3.0-RC22 4.0.0 diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index 64a24921..ce62b9cc 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -6,7 +6,7 @@ io.avaje examples - 3.0-RC21 + 3.0-RC22 org.example diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index e1ad4c67..fa5814da 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -6,7 +6,7 @@ io.avaje examples - 3.0-RC21 + 3.0-RC22 org.example @@ -23,7 +23,7 @@ io.avaje avaje-jex - 3.0-RC21 + 3.0-RC22 diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index 71d05c82..2b3ac11e 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -3,7 +3,7 @@ io.avaje examples - 3.0-RC21 + 3.0-RC22 example-jetty diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index e98ebcd3..1038e70f 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -6,7 +6,7 @@ io.avaje examples - 3.0-RC21 + 3.0-RC22 example-robaho @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC21 + 3.0-RC22 diff --git a/examples/pom.xml b/examples/pom.xml index c88a350e..d430a39f 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC21 + 3.0-RC22 examples diff --git a/pom.xml b/pom.xml index b6406bb2..e1fbe643 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ io.avaje avaje-jex-parent - 3.0-RC21 + 3.0-RC22 pom @@ -176,32 +176,32 @@ io.avaje avaje-jex - 3.0-RC21 + 3.0-RC22 io.avaje avaje-jex-test - 3.0-RC21 + 3.0-RC22 io.avaje avaje-jex-freemarker - 3.0-RC21 + 3.0-RC22 io.avaje avaje-jex-mustache - 3.0-RC21 + 3.0-RC22 io.avaje avaje-jex-htmx - 3.0-RC21 + 3.0-RC22 io.avaje avaje-jex-static-content - 3.0-RC21 + 3.0-RC22 io.github.robaho From 11920614c3d4a5a64f4e9e1fda0ffac391c91314 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 14 Mar 2025 16:47:35 -0700 Subject: [PATCH 228/250] [static content] add default resourceLoader method (#221) --- .../avaje/jex/staticcontent/StaticContent.java | 16 ++++++++++++++-- .../java/io/avaje/jex/core/BootstrapServer.java | 3 +-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java index 337d9fee..7e54811d 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticContent.java @@ -93,13 +93,25 @@ sealed interface Builder */ Builder resourceLoader(ClassResourceLoader resourceLoader); + /** + * Sets a custom resource loader for loading class/module path resources using the given class. + * This is normally used when running the application on the module path when files cannot be + * discovered. + * + * @param clazz the class used to custom load resources + * @return the updated configuration + */ + default Builder resourceLoader(Class clazz) { + return resourceLoader(ClassResourceLoader.fromClass(clazz)); + } + /** * Adds a new MIME type mapping to the configuration. (Default: uses {@link * URLConnection#getFileNameMap()} * - * @param ext the file extension (e.g., "html", "css", "js") + * @param ext the file extension (e.g., "html", "css", "js") * @param mimeType the corresponding MIME type (e.g., "text/html", "text/css", - * "application/javascript") + * "application/javascript") * @return the updated configuration */ Builder putMimeTypeMapping(String ext, String mimeType); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java index c266742f..9348feba 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java @@ -70,8 +70,7 @@ static Jex.Server start(Jex jex, SpiRoutes routes) { jex.lifecycle().status(AppLifecycle.Status.STARTED); log.log( INFO, - "Avaje Jex {0} started {1} on {2}://{3}:{4,number,#}", - BootstrapServer.class.getPackage().getImplementationVersion(), + "Avaje Jex started {0} on {1}://{2}:{3,number,#}", serverClass, scheme, actualAddress.getHostName(), From 454da97788b94330ac0a3879ad25d06cda7dfb70 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 16 Mar 2025 00:15:53 -0400 Subject: [PATCH 229/250] add m2e lifecycle config (#222) with this eclipse can run the plugins automatically if they're added to the project --- pom.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e1fbe643..0fbca1f9 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ 21 full 4.0 - 11.2 + 11.3-RC1 3.1-RC2 3.0 9.4 @@ -227,6 +227,7 @@ ${avaje.inject.version} + process-sources provides @@ -276,6 +277,7 @@ 2.1 + disable-apt-validation add-module-spi @@ -289,6 +291,7 @@ ${ebean.version} + enhance testEnhance From c6016563bd20ba7a76b52a4d027cc2cff12d64e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Mar 2025 04:35:58 +0000 Subject: [PATCH 230/250] Bump the dependencies group with 5 updates (#223) Bumps the dependencies group with 5 updates: | Package | From | To | | --- | --- | --- | | [io.avaje:avaje-jsonb](https://github.com/avaje/avaje-jsonb) | `3.1-RC2` | `3.1` | | io.avaje:avaje-jsonb-generator | `3.1-RC2` | `3.1` | | io.avaje:avaje-validator-constraints | `2.8` | `2.9` | | [io.avaje:avaje-validator](https://github.com/avaje/avaje-validator) | `2.8` | `2.9` | | io.avaje:avaje-validator-generator | `2.8` | `2.9` | Updates `io.avaje:avaje-jsonb` from 3.1-RC2 to 3.1 - [Release notes](https://github.com/avaje/avaje-jsonb/releases) - [Commits](https://github.com/avaje/avaje-jsonb/commits/3.1) Updates `io.avaje:avaje-jsonb-generator` from 3.1-RC2 to 3.1 Updates `io.avaje:avaje-validator-constraints` from 2.8 to 2.9 Updates `io.avaje:avaje-validator` from 2.8 to 2.9 - [Release notes](https://github.com/avaje/avaje-validator/releases) - [Commits](https://github.com/avaje/avaje-validator/compare/2.8...2.9) Updates `io.avaje:avaje-validator-generator` from 2.8 to 2.9 Updates `io.avaje:avaje-validator` from 2.8 to 2.9 - [Release notes](https://github.com/avaje/avaje-validator/releases) - [Commits](https://github.com/avaje/avaje-validator/compare/2.8...2.9) Updates `io.avaje:avaje-jsonb-generator` from 3.1-RC2 to 3.1 Updates `io.avaje:avaje-validator-generator` from 2.8 to 2.9 --- updated-dependencies: - dependency-name: io.avaje:avaje-jsonb dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-jsonb-generator dependency-type: direct:development dependency-group: dependencies - dependency-name: io.avaje:avaje-validator-constraints dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-validator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-jsonb-generator dependency-type: direct:development dependency-group: dependencies - dependency-name: io.avaje:avaje-validator-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0fbca1f9..3747639b 100644 --- a/pom.xml +++ b/pom.xml @@ -27,10 +27,10 @@ full 4.0 11.3-RC1 - 3.1-RC2 + 3.1 3.0 9.4 - 2.8 + 2.9 1.2 2.10 1.36 From b026dd3e162ad52fdf4f734df6cfd83827f86412 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 03:47:23 +0000 Subject: [PATCH 231/250] Bump the dependencies group with 4 updates (#226) Bumps the dependencies group with 4 updates: [io.avaje:avaje-inject](https://github.com/avaje/avaje-inject), io.avaje:avaje-inject-test, io.avaje:avaje-inject-generator and io.avaje:avaje-inject-maven-plugin. Updates `io.avaje:avaje-inject` from 11.3-RC1 to 11.3 - [Release notes](https://github.com/avaje/avaje-inject/releases) - [Commits](https://github.com/avaje/avaje-inject/commits/11.3) Updates `io.avaje:avaje-inject-test` from 11.3-RC1 to 11.3 Updates `io.avaje:avaje-inject-generator` from 11.3-RC1 to 11.3 Updates `io.avaje:avaje-inject-maven-plugin` from 11.3-RC1 to 11.3 Updates `io.avaje:avaje-inject-test` from 11.3-RC1 to 11.3 Updates `io.avaje:avaje-inject-generator` from 11.3-RC1 to 11.3 Updates `io.avaje:avaje-inject-maven-plugin` from 11.3-RC1 to 11.3 --- updated-dependencies: - dependency-name: io.avaje:avaje-inject dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-test dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-maven-plugin dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-test dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-generator dependency-type: direct:production dependency-group: dependencies - dependency-name: io.avaje:avaje-inject-maven-plugin dependency-type: direct:production dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3747639b..136bda97 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ 21 full 4.0 - 11.3-RC1 + 11.3 3.1 3.0 9.4 From 43e2b7d40d4b4f9607697b7c0e589f5dee20a807 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 17 Mar 2025 00:14:48 -0400 Subject: [PATCH 232/250] Fix compression content types (#225) it seems we were checking the request content type rather than the response content type --- .../avaje/jex/staticcontent/StaticClassResourceHandler.java | 4 +++- .../java/io/avaje/jex/staticcontent/StaticFileHandler.java | 4 +++- .../java/io/avaje/jex/compression/CompressedOutputStream.java | 3 ++- avaje-jex/src/main/java/module-info.java | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticClassResourceHandler.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticClassResourceHandler.java index dd864834..a2bb40c0 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticClassResourceHandler.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticClassResourceHandler.java @@ -1,5 +1,7 @@ package io.avaje.jex.staticcontent; +import static io.avaje.jex.core.Constants.CONTENT_TYPE; + import java.net.URL; import java.nio.file.Paths; import java.util.Map; @@ -85,7 +87,7 @@ public void handle(Context ctx) { private void sendURL(Context ctx, String urlPath, URL path) { try (var fis = path.openStream()) { - ctx.header("Content-Type", lookupMime(urlPath)); + ctx.header(CONTENT_TYPE, lookupMime(urlPath)); ctx.headers(headers); if (precompress) { addCachedEntry(ctx, urlPath, fis); diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java index 2b40731a..e5a4e899 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticFileHandler.java @@ -1,5 +1,7 @@ package io.avaje.jex.staticcontent; +import static io.avaje.jex.core.Constants.CONTENT_TYPE; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -100,7 +102,7 @@ private void sendFile(Context ctx, HttpExchange jdkExchange, String urlPath, Fil throws IOException { try (var fis = new FileInputStream(canonicalFile)) { String mimeType = lookupMime(urlPath); - ctx.header("Content-Type", mimeType); + ctx.header(CONTENT_TYPE, mimeType); ctx.headers(headers); if (precompress) { addCachedEntry(ctx, urlPath, fis); diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java index 3e0e608e..6f80934a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java @@ -34,7 +34,8 @@ public CompressedOutputStream( private void decideCompression(int length) throws IOException { if (!compressionDecided) { boolean compressionAllowed = - compressedStream == null && compression.allowsForCompression(ctx.contentType()); + compressedStream == null + && compression.allowsForCompression(ctx.responseHeader(Constants.CONTENT_TYPE)); if (compressionAllowed && length >= minSizeForCompression) { Optional compressor; diff --git a/avaje-jex/src/main/java/module-info.java b/avaje-jex/src/main/java/module-info.java index 723e1894..c1fe1579 100644 --- a/avaje-jex/src/main/java/module-info.java +++ b/avaje-jex/src/main/java/module-info.java @@ -23,6 +23,7 @@ exports io.avaje.jex.compression; exports io.avaje.jex.http; exports io.avaje.jex.http.sse; + exports io.avaje.jex.core to io.avaje.jex.staticcontent; exports io.avaje.jex.core.json; exports io.avaje.jex.security; exports io.avaje.jex.spi; From 5630d33236bc1072bfaf0c52eff99be81c609728 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 17 Mar 2025 00:52:59 -0400 Subject: [PATCH 233/250] Cache `bodyAsBytes` result (#224) * allow reading request body multiple times As we know, reading the request `InputStream` makes it so the stream cannot be read again. This means that filters cannot use the request body or else the handler will be unable to read it. - now body reading methods use bodyAsBytes - bodyAsBytes will cache the bytes in the request context - add bodyStreamAs json methods * Update ContextTest.java * Format and extract helper method --------- Co-authored-by: Rob Bygrave --- .../java/io/avaje/jex/core/JdkContext.java | 19 +++++---- .../io/avaje/jex/core/ServiceManager.java | 4 ++ .../jex/core/json/JacksonJsonService.java | 22 +++++++--- .../avaje/jex/core/json/JsonbJsonService.java | 5 +++ .../main/java/io/avaje/jex/http/Context.java | 17 ++++++++ .../java/io/avaje/jex/spi/JsonService.java | 14 +++++-- .../java/io/avaje/jex/core/ContextTest.java | 41 ++++++++++++++++--- 7 files changed, 99 insertions(+), 23 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index 818f2e7e..70fd667a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -55,6 +55,7 @@ final class JdkContext implements Context { private Map> queryParams; private Map cookieMap; private int statusCode; + private byte[] bodyBytes; private Charset characterEncoding; @@ -72,11 +73,7 @@ final class JdkContext implements Context { } /** Create when no route matched. */ - JdkContext( - ServiceManager mgr, - HttpExchange exchange, - String path, - Set roles) { + JdkContext(ServiceManager mgr, HttpExchange exchange, String path, Set roles) { this.mgr = mgr; this.roles = roles; this.exchange = exchange; @@ -126,7 +123,10 @@ public String body() { @Override public byte[] bodyAsBytes() { try { - return exchange.getRequestBody().readAllBytes(); + if (bodyBytes == null) { + bodyBytes = exchange.getRequestBody().readAllBytes(); + } + return bodyBytes; } catch (IOException e) { throw new UncheckedIOException(e); } @@ -139,7 +139,12 @@ public InputStream bodyAsInputStream() { @Override public T bodyAsType(Type beanType) { - return mgr.fromJson(beanType, bodyAsInputStream()); + return mgr.fromJson(beanType, bodyAsBytes()); + } + + @Override + public T bodyStreamAsType(Type beanType) { + return bodyBytes == null ? mgr.fromJson(beanType, bodyAsInputStream()) : bodyAsType(beanType); } private Charset characterEncoding() { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java index 0d3eabd0..8f6509d4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java @@ -75,6 +75,10 @@ T fromJson(Type type, InputStream is) { return jsonService.fromJson(type, is); } + T fromJson(Type type, byte[] data) { + return jsonService.fromJson(type, data); + } + void toJson(Object bean, OutputStream os) { jsonService.toJson(bean, os); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java index 6cd3d31b..ac8ee4b5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JacksonJsonService.java @@ -24,8 +24,7 @@ public final class JacksonJsonService implements JsonService { /** Create with defaults for Jackson */ public JacksonJsonService() { - this.mapper = - new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this.mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } /** Create with a Jackson instance that might have custom configuration. */ @@ -36,16 +35,27 @@ public JacksonJsonService(ObjectMapper mapper) { @Override public T fromJson(Type type, InputStream is) { try { - final var javaType = - javaTypes.computeIfAbsent( - type.getTypeName(), k -> mapper.getTypeFactory().constructType(type)); - + final var javaType = javaType(type); return mapper.readValue(is, javaType); } catch (IOException e) { throw new UncheckedIOException(e); } } + @Override + public T fromJson(Type type, byte[] data) { + try { + final var javaType = javaType(type); + return mapper.readValue(data, javaType); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private JavaType javaType(Type type) { + return javaTypes.computeIfAbsent(type.getTypeName(), k -> mapper.getTypeFactory().constructType(type)); + } + @Override public void toJson(Object bean, OutputStream os) { try { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java index ee03d7c7..e07f4bc3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/json/JsonbJsonService.java @@ -30,6 +30,11 @@ public T fromJson(Type clazz, InputStream is) { return jsonb.type(clazz).fromJson(is); } + @Override + public T fromJson(Type clazz, byte[] data) { + return jsonb.type(clazz).fromJson(data); + } + @Override public void toJson(Object bean, OutputStream os) { jsonb.toJson(bean, new NoFlushJsonOutput(os)); diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java index f8a4bc07..45b8aa5f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java @@ -80,6 +80,7 @@ default T bodyAsClass(Class beanType) { * Returns the request body as an input stream. * * @return The request body as an input stream. + * @implNote will return an empty stream if any of the various body methods were called */ InputStream bodyAsInputStream(); @@ -89,6 +90,22 @@ default T bodyAsClass(Class beanType) { * @param beanType The bean type */ T bodyAsType(Type beanType); + + /** + * Return the request body as bean using {@link #bodyAsInputStream()}. + * + * @param beanType The bean type + */ + default T bodyStreamAsClass(Class beanType) { + return bodyAsType(beanType); + } + + /** + * Return the request body as bean of the given type using {@link #bodyAsInputStream()}. + * + * @param beanType The bean type + */ + T bodyStreamAsType(Type beanType); /** Return the request content length. */ long contentLength(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java index 65bb74e3..bab6d9f0 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JsonService.java @@ -35,10 +35,7 @@ public non-sealed interface JsonService extends JexExtension { String toJsonString(Object bean); /** - * **Reads JSON from an InputStream** - * - *

    Reads a JSON-formatted input stream and deserializes it into a Java object of the specified - * type. + * Deserializes a json input stream and deserializes it into a Java object of the specified type. * * @param type the Type object of the desired type * @param is the input stream containing the JSON data @@ -46,6 +43,15 @@ public non-sealed interface JsonService extends JexExtension { */ T fromJson(Type type, InputStream is); + /** + * Deserializes a json byte[] into a Java object of the specified type. + * + * @param type the Type object of the desired type + * @param data the byte[] containing the JSON data + * @return the deserialized object + */ + T fromJson(Type type, byte[] data); + /** * Serializes a stream of Java objects into a JSON-Stream format, using the {@code x-json-stream} * media type. Each object in the stream is serialized as a separate JSON object, and the objects diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/ContextTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ContextTest.java index 35912af2..476fb66f 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/ContextTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ContextTest.java @@ -1,14 +1,16 @@ package io.avaje.jex.core; -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; import java.net.http.HttpResponse; +import java.util.Map; import java.util.Optional; -import static java.util.Objects.requireNonNull; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.avaje.jex.Jex; class ContextTest { @@ -38,6 +40,15 @@ static TestPair init() { .post("/echo", ctx -> ctx.text("req-body[" + ctx.body() + "]")) .get("/{a}/{b}", ctx -> ctx.text("ze-get-" + ctx.pathParamMap())) .post("/{a}/{b}", ctx -> ctx.text("ze-post-" + ctx.pathParamMap())) + .post("/doubleJsonStream", ctx -> { + ctx.bodyAsInputStream().readAllBytes(); + ctx.text(ctx.bodyAsClass(Map.class)+""); + }) + .post("/doubleJsonStreamBytes", ctx -> { + ctx.body(); + ctx.text(ctx.bodyAsClass(Map.class)+""); + }) + .post("/doubleString", ctx -> ctx.text(ctx.body() + ctx.body())) .get("/status", ctx -> { ctx.status(201); ctx.text("status:" + ctx.status()); @@ -119,11 +130,29 @@ void ctx_methodPathPortProtocol() { } @Test - void post_body() { + void post_double_string() { HttpResponse res = pair.request().path("echo").body("simple").POST().asString(); assertThat(res.body()).isEqualTo("req-body[simple]"); } + @Test + void post_double_json_fail() { + HttpResponse res = pair.request().path("doubleJsonStream").body("{}").POST().asString(); + assertThat(res.body()).isEqualTo("Internal Server Error"); + } + + @Test + void post_double_json_bytes() { + HttpResponse res = pair.request().path("doubleJsonStreamBytes").body("{}").POST().asString(); + assertThat(res.body()).isEqualTo("{}"); + } + + @Test + void post_body() { + HttpResponse res = pair.request().path("doubleString").body("simple").POST().asString(); + assertThat(res.body()).isEqualTo("simplesimple"); + } + @Test void get_path_path() { var res = pair.request() From 6ef66814a9752de2aa8d6015d25d6e0b6396dbff Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 18 Mar 2025 05:28:58 -0400 Subject: [PATCH 234/250] Create overview.html (#228) adds a javadoc overview --- avaje-jex/src/main/javadoc/overview.html | 136 +++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 avaje-jex/src/main/javadoc/overview.html diff --git a/avaje-jex/src/main/javadoc/overview.html b/avaje-jex/src/main/javadoc/overview.html new file mode 100644 index 00000000..b12b25ca --- /dev/null +++ b/avaje-jex/src/main/javadoc/overview.html @@ -0,0 +1,136 @@ + + + + Avaje Jex Overview + + + +

    Avaje Jex

    + +

    + Avaje Jex is a wrapper over the JDK's built-in HTTP server, providing an + elegant and developer-friendly API for building web applications in Java. +

    + +

    Getting Started

    + +

    Here's a simple example to create a basic web server with Jex:

    + +
    +Jex.create()
    +    .get("/", ctx -> ctx.text("hello"))
    +    .get("/one/{id}", ctx -> ctx.text("one-" + ctx.pathParam("id")))
    +    .filter(
    +        (ctx, chain) -> {
    +          System.out.println("before request");
    +          chain.proceed();
    +          System.out.println("after request");
    +        })
    +    .error(
    +        IllegalStateException.class,
    +        (ctx, exception) -> ctx.status(500).text(exception.getMessage()))
    +    .port(8080)
    +    .start();
    +    
    + +

    Key Concepts

    + +

    Request Handling

    +

    + Jex provides three main handler types: +

    +
      +
    • Endpoint Handlers - Define API endpoints for HTTP methods (GET, POST, etc.)
    • +
    • Filters - Pre/post process requests for authentication, logging, etc.
    • +
    • Exception Handlers - Handle exceptions during request processing
    • +
    + +

    Context Object

    +

    + The Context object is central to Jex's API, providing methods for: +

    +
      +
    • Reading request data (headers, parameters, body)
    • +
    • Setting response data (status, headers, content)
    • +
    • Managing cookies
    • +
    • Handling request attributes and path information
    • +
    + +

    Path Parameters

    +

    + Jex supports flexible path parameter options: +

    +
    +// Standard path parameters with {} syntax
    +app.get("/hello/{name}", ctx -> ctx.write("Hello: " + ctx.pathParam("name")));
    +
    +// Path parameters that can include slashes with <> syntax
    +app.get("/hello/<name>", ctx -> ctx.write("Hello: " + ctx.pathParam("name")));
    +
    +// Wildcard parameters
    +app.get("/path/*", ctx -> ctx.write("Matched: " + ctx.matchedPath()));
    +    
    + +

    Advanced Features

    + +

    JSON Support

    +

    + Jex provides a JsonService SPI for JSON serialization/deserialization, with automatic + detection of Jackson or Avaje-jsonb libraries: +

    +
    +Jex.create()
    +    .jsonService(new JacksonJsonService())
    +    .post(
    +        "/json",
    +        ctx -> {
    +          MyBody body = ctx.bodyAsClass(MyBody.class);
    +          ctx.json(new CustomResponse());
    +        });
    +    
    + +

    Server-Sent Events

    +

    + Jex supports Server-Sent Events (SSE) for real-time updates: +

    +
    +app.sse("/sse", client -> {
    +    client.sendEvent("connected", "Hello, SSE");
    +    client.onClose(() -> System.out.println("Client disconnected"));
    +});
    +    
    + +

    Access Management

    +

    + Jex provides built-in support for role-based access control: +

    +
    +// Custom enum for access roles
    +enum Access implements Role {
    +  USER,
    +  ADMIN
    +}
    +
    +Jex.create()
    +    .get("/user", ctx -> ctx.text("user"), Access.USER)
    +    .get("/admin", ctx -> ctx.text("admin"), Access.ADMIN)
    +    .filter(
    +        (ctx, chain) -> {
    +          Access userRole = getUserRole(ctx);
    +          if (!ctx.routeRoles().contains(userRole)) {
    +            throw new HttpResponseException(403, "unauthorized");
    +          }
    +          chain.proceed();
    +        });
    +    
    + +

    Additional Resources

    +

    + For more informationm visit: +

    + + + From 47c2829b4fd9e5c654bd387f7097b896c6d185f7 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 18 Mar 2025 14:52:12 -0400 Subject: [PATCH 235/250] remove executor config (#227) This lib is only meant to be used with virtual threads, what need is there to customize the executor? --- .../main/java/io/avaje/jex/DJexConfig.java | 19 ------------------- .../src/main/java/io/avaje/jex/JexConfig.java | 15 --------------- .../io/avaje/jex/core/BootstrapServer.java | 5 ++++- .../io/avaje/jex/http/ExchangeHandler.java | 4 +--- 4 files changed, 5 insertions(+), 38 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 1d5473e0..bdcd54b8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -2,8 +2,6 @@ import java.util.HashMap; import java.util.Map; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; import java.util.function.Consumer; import com.sun.net.httpserver.HttpsConfigurator; @@ -21,7 +19,6 @@ final class DJexConfig implements JexConfig { private int socketBacklog = 0; private boolean health = true; private boolean ignoreTrailingSlashes = true; - private Executor executor; private JsonService jsonService; private final Map renderers = new HashMap<>(); private HttpsConfigurator httpsConfig; @@ -84,22 +81,6 @@ public JexConfig renderer(String extension, TemplateRender renderer) { return this; } - @Override - public Executor executor() { - if (executor == null) { - executor = - Executors.newThreadPerTaskExecutor( - Thread.ofVirtual().name("avaje-jex-http-", 0).factory()); - } - return executor; - } - - @Override - public JexConfig executor(Executor executor) { - this.executor = executor; - return this; - } - @Override public String host() { return host; diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index 6db7bf13..9005fd39 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -1,8 +1,6 @@ package io.avaje.jex; import java.util.Map; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; import java.util.function.Consumer; import com.sun.net.httpserver.HttpServer; @@ -43,19 +41,6 @@ public interface JexConfig { */ JexConfig contextPath(String contextPath); - /** - * Executor for serving requests. Defaults to a {@link - * Executors#newVirtualThreadPerTaskExecutor()} - */ - Executor executor(); - - /** - * Sets the executor service used to handle incoming requests. - * - * @param executor The executor service. - */ - JexConfig executor(Executor executor); - /** Returns whether the health endpoint is enabled. */ boolean health(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java index 9348feba..a82824ea 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java @@ -8,6 +8,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; +import java.util.concurrent.Executors; import com.sun.net.httpserver.HttpServer; @@ -61,7 +62,9 @@ static Jex.Server start(Jex jex, SpiRoutes routes) { // jetty's server does not support setExecutor with virtual threads (VT) // as it has it's own impl that will auto-use VTs if (!serverClass.getName().contains("jetty")) { - server.setExecutor(config.executor()); + server.setExecutor( + Executors.newThreadPerTaskExecutor( + Thread.ofVirtual().name("avaje-jex-http-", 0).factory())); } server.createContext(contextPath, handler); diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/ExchangeHandler.java b/avaje-jex/src/main/java/io/avaje/jex/http/ExchangeHandler.java index 70577324..2b9ef63f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/ExchangeHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/ExchangeHandler.java @@ -1,7 +1,5 @@ package io.avaje.jex.http; -import java.io.IOException; - /** * A functional interface representing an HTTP request handler. * @@ -21,7 +19,7 @@ public interface ExchangeHandler { * parameters, and body, as well as methods for constructing and sending the response. * * @param ctx The context object containing the request and response details. - * @throws IOException if an I/O error occurs during request processing or response generation. + * @throws Exception if an error occurs during request processing or response generation. */ void handle(Context ctx) throws Exception; } From 26e60a1d1ac864fb4bf6cf33252fc09423f4eae7 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 19 Mar 2025 07:55:45 +1300 Subject: [PATCH 236/250] Version 3.0-RC23 --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-htmx/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- examples/example-http-generation/pom.xml | 2 +- examples/example-jdk-jsonb/pom.xml | 2 +- examples/example-jdk/pom.xml | 4 ++-- examples/example-jetty/pom.xml | 2 +- examples/example-robaho/pom.xml | 4 ++-- examples/pom.xml | 2 +- pom.xml | 14 +++++++------- 13 files changed, 21 insertions(+), 21 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 4515e6f5..7c968d2f 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC22 + 3.0-RC23 avaje-jex-freemarker diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 130fda5c..054d53f6 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC22 + 3.0-RC23 avaje-jex-htmx diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 04890dbb..d05afdf1 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC22 + 3.0-RC23 avaje-jex-mustache diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index c70c68ee..a98446cd 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC22 + 3.0-RC23 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index b33c7cf3..213d6698 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC22 + 3.0-RC23 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 47abfab6..a730f4e9 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC22 + 3.0-RC23 avaje-jex diff --git a/examples/example-http-generation/pom.xml b/examples/example-http-generation/pom.xml index 608a2303..22587876 100644 --- a/examples/example-http-generation/pom.xml +++ b/examples/example-http-generation/pom.xml @@ -5,7 +5,7 @@ io.avaje examples - 3.0-RC22 + 3.0-RC23 4.0.0 diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index ce62b9cc..49e1b438 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -6,7 +6,7 @@ io.avaje examples - 3.0-RC22 + 3.0-RC23 org.example diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index fa5814da..3875f54a 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -6,7 +6,7 @@ io.avaje examples - 3.0-RC22 + 3.0-RC23 org.example @@ -23,7 +23,7 @@ io.avaje avaje-jex - 3.0-RC22 + 3.0-RC23 diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index 2b3ac11e..e392b5f2 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -3,7 +3,7 @@ io.avaje examples - 3.0-RC22 + 3.0-RC23 example-jetty diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 1038e70f..81895f42 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -6,7 +6,7 @@ io.avaje examples - 3.0-RC22 + 3.0-RC23 example-robaho @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC22 + 3.0-RC23 diff --git a/examples/pom.xml b/examples/pom.xml index d430a39f..d17c8e6a 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC22 + 3.0-RC23 examples diff --git a/pom.xml b/pom.xml index 136bda97..3ba4bcb8 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ io.avaje avaje-jex-parent - 3.0-RC22 + 3.0-RC23 pom @@ -176,32 +176,32 @@ io.avaje avaje-jex - 3.0-RC22 + 3.0-RC23 io.avaje avaje-jex-test - 3.0-RC22 + 3.0-RC23 io.avaje avaje-jex-freemarker - 3.0-RC22 + 3.0-RC23 io.avaje avaje-jex-mustache - 3.0-RC22 + 3.0-RC23 io.avaje avaje-jex-htmx - 3.0-RC22 + 3.0-RC23 io.avaje avaje-jex-static-content - 3.0-RC22 + 3.0-RC23 io.github.robaho From 323cd8b96d18831e0399a401fd9ab9bfab1d7641 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 19:11:48 +0000 Subject: [PATCH 237/250] Bump the dependencies group with 7 updates (#229) Bumps the dependencies group with 7 updates: | Package | From | To | | --- | --- | --- | | io.avaje:avaje-http-api | `3.0` | `3.1` | | io.avaje:avaje-htmx-api | `3.0` | `3.1` | | [io.avaje:avaje-http-client](https://github.com/avaje/avaje-http-client) | `3.0` | `3.1` | | io.avaje:avaje-http-client-generator | `3.0` | `3.1` | | io.avaje:avaje-http-jex-generator | `3.0` | `3.1` | | [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) | `1.5.17` | `1.5.18` | | org.eclipse.jetty:jetty-http-spi | `12.0.17` | `12.0.18` | Updates `io.avaje:avaje-http-api` from 3.0 to 3.1 Updates `io.avaje:avaje-htmx-api` from 3.0 to 3.1 Updates `io.avaje:avaje-http-client` from 3.0 to 3.1 - [Release notes](https://github.com/avaje/avaje-http-client/releases) - [Commits](https://github.com/avaje/avaje-http-client/commits) Updates `io.avaje:avaje-http-client-generator` from 3.0 to 3.1 Updates `io.avaje:avaje-http-jex-generator` from 3.0 to 3.1 Updates `io.avaje:avaje-htmx-api` from 3.0 to 3.1 Updates `io.avaje:avaje-http-client` from 3.0 to 3.1 - [Release notes](https://github.com/avaje/avaje-http-client/releases) - [Commits](https://github.com/avaje/avaje-http-client/commits) Updates `io.avaje:avaje-http-client-generator` from 3.0 to 3.1 Updates `io.avaje:avaje-http-jex-generator` from 3.0 to 3.1 Updates `ch.qos.logback:logback-classic` from 1.5.17 to 1.5.18 - [Release notes](https://github.com/qos-ch/logback/releases) - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.17...v_1.5.18) Updates `org.eclipse.jetty:jetty-http-spi` from 12.0.17 to 12.0.18 --- updated-dependencies: - dependency-name: io.avaje:avaje-http-api dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-htmx-api dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-http-jex-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-htmx-api dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-http-client-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-http-jex-generator dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies - dependency-name: org.eclipse.jetty:jetty-http-spi dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/example-jdk/pom.xml | 2 +- examples/example-jetty/pom.xml | 4 ++-- examples/example-robaho/pom.xml | 2 +- pom.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 3875f54a..c6b2829b 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -41,7 +41,7 @@ ch.qos.logback logback-classic - 1.5.17 + 1.5.18 diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index e392b5f2..4c3bba7e 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -10,7 +10,7 @@ org.eclipse.jetty jetty-http-spi - 12.0.17 + 12.0.18 @@ -27,7 +27,7 @@ ch.qos.logback logback-classic - 1.5.17 + 1.5.18 diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index 81895f42..c705b473 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -33,7 +33,7 @@ ch.qos.logback logback-classic - 1.5.17 + 1.5.18 diff --git a/pom.xml b/pom.xml index 3ba4bcb8..06ec7825 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ 4.0 11.3 3.1 - 3.0 + 3.1 9.4 2.9 1.2 From d622115fa3560e4334d6813ba59e87909842c267 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 19 Mar 2025 18:36:27 -0400 Subject: [PATCH 238/250] Revert "remove executor config (#227)" (#230) This reverts commit 47c2829b4fd9e5c654bd387f7097b896c6d185f7. --- .../main/java/io/avaje/jex/DJexConfig.java | 19 +++++++++++++++++++ .../src/main/java/io/avaje/jex/JexConfig.java | 15 +++++++++++++++ .../io/avaje/jex/core/BootstrapServer.java | 5 +---- .../io/avaje/jex/http/ExchangeHandler.java | 4 +++- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index bdcd54b8..1d5473e0 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -2,6 +2,8 @@ import java.util.HashMap; import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.function.Consumer; import com.sun.net.httpserver.HttpsConfigurator; @@ -19,6 +21,7 @@ final class DJexConfig implements JexConfig { private int socketBacklog = 0; private boolean health = true; private boolean ignoreTrailingSlashes = true; + private Executor executor; private JsonService jsonService; private final Map renderers = new HashMap<>(); private HttpsConfigurator httpsConfig; @@ -81,6 +84,22 @@ public JexConfig renderer(String extension, TemplateRender renderer) { return this; } + @Override + public Executor executor() { + if (executor == null) { + executor = + Executors.newThreadPerTaskExecutor( + Thread.ofVirtual().name("avaje-jex-http-", 0).factory()); + } + return executor; + } + + @Override + public JexConfig executor(Executor executor) { + this.executor = executor; + return this; + } + @Override public String host() { return host; diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index 9005fd39..6db7bf13 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -1,6 +1,8 @@ package io.avaje.jex; import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.function.Consumer; import com.sun.net.httpserver.HttpServer; @@ -41,6 +43,19 @@ public interface JexConfig { */ JexConfig contextPath(String contextPath); + /** + * Executor for serving requests. Defaults to a {@link + * Executors#newVirtualThreadPerTaskExecutor()} + */ + Executor executor(); + + /** + * Sets the executor service used to handle incoming requests. + * + * @param executor The executor service. + */ + JexConfig executor(Executor executor); + /** Returns whether the health endpoint is enabled. */ boolean health(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java index a82824ea..9348feba 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/BootstrapServer.java @@ -8,7 +8,6 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; -import java.util.concurrent.Executors; import com.sun.net.httpserver.HttpServer; @@ -62,9 +61,7 @@ static Jex.Server start(Jex jex, SpiRoutes routes) { // jetty's server does not support setExecutor with virtual threads (VT) // as it has it's own impl that will auto-use VTs if (!serverClass.getName().contains("jetty")) { - server.setExecutor( - Executors.newThreadPerTaskExecutor( - Thread.ofVirtual().name("avaje-jex-http-", 0).factory())); + server.setExecutor(config.executor()); } server.createContext(contextPath, handler); diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/ExchangeHandler.java b/avaje-jex/src/main/java/io/avaje/jex/http/ExchangeHandler.java index 2b9ef63f..70577324 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/ExchangeHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/ExchangeHandler.java @@ -1,5 +1,7 @@ package io.avaje.jex.http; +import java.io.IOException; + /** * A functional interface representing an HTTP request handler. * @@ -19,7 +21,7 @@ public interface ExchangeHandler { * parameters, and body, as well as methods for constructing and sending the response. * * @param ctx The context object containing the request and response details. - * @throws Exception if an error occurs during request processing or response generation. + * @throws IOException if an I/O error occurs during request processing or response generation. */ void handle(Context ctx) throws Exception; } From 659b89b1a6a865370b7bc8dd6f73b4c4112c3665 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 21 Mar 2025 03:15:48 -0400 Subject: [PATCH 239/250] Fix root static content (#231) fix issue where static content file paths matched all unknown requests when registered on root --- .../StaticResourceHandlerBuilder.java | 10 +++++++--- .../staticcontent/CompressedStaticFileTest.java | 15 +++++++++++---- pom.xml | 2 +- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java index 290f4375..9b07810b 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java @@ -42,6 +42,11 @@ static StaticResourceHandlerBuilder builder(String root) { @Override public void apply(Jex jex) { + + path = + Objects.requireNonNull(path) + .transform(s -> path.endsWith("/") && directoryIndex != null ? path + "*" : path); + jex.get(path, createHandler(jex.config().compression()), roles); } @@ -52,8 +57,7 @@ public StaticContent build() { ExchangeHandler createHandler(CompressionConfig compress) { path = - Objects.requireNonNull(path) - .transform(this::prependSlash) + path.transform(this::prependSlash) .transform(s -> s.endsWith("/*") ? s.substring(0, s.length() - 2) : s); root = isClasspath ? root.transform(this::prependSlash) : root; @@ -76,7 +80,7 @@ ExchangeHandler createHandler(CompressionConfig compress) { @Override public StaticResourceHandlerBuilder route(String path, Role... roles) { - this.path = path.endsWith("/") ? path + "*" : path; + this.path = path; this.roles = roles; return this; } diff --git a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java index b57259f8..557def3d 100644 --- a/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java +++ b/avaje-jex-static-content/src/test/java/io/avaje/jex/staticcontent/CompressedStaticFileTest.java @@ -25,10 +25,11 @@ static TestPair init() { .plugin(defaultFile().route("/indexWildFile/*").build()) .plugin(defaultCP().route("/sus/").build()) .plugin(defaultFile().route("/susFile/*").build()) - .plugin(StaticContent.ofClassPath("/logback.xml").route("/single").build()) + .plugin(StaticContent.ofClassPath("/logback.xml").route("/").build()) .plugin( StaticContent.ofFile("src/test/resources/logback.xml") - .route("/singleFile").build()); + .route("/singleFile") + .build()); return TestPair.create(app); } @@ -90,12 +91,18 @@ void getDirContentCP() { @Test void getSingleFileCP() { - pair.request().path("single").GET().asString(); - HttpResponse res = pair.request().path("single").GET().asString(); + pair.request().GET().asString(); + HttpResponse res = pair.request().GET().asString(); assertThat(res.statusCode()).isEqualTo(200); assertThat(res.headers().firstValue("Content-Type").orElseThrow()).contains("xml"); } + @Test + void get404() { + HttpResponse res = pair.request().path("unknown").path("index.html").GET().asString(); + assertThat(res.statusCode()).isEqualTo(404); + } + @Test void getIndexFile() { pair.request().path("indexFile").GET().asString(); diff --git a/pom.xml b/pom.xml index 06ec7825..4a7f6556 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ full 4.0 11.3 - 3.1 + 3.2 3.1 9.4 2.9 From 2d32ffff7095e71f68528cd8704cf50f10eb64ae Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 21 Mar 2025 21:32:59 -0400 Subject: [PATCH 240/250] Add Grizzly `jdk.httpserver` SPI (#232) --- avaje-jex-grizzly-spi/pom.xml | 39 ++++ .../jex/grizzly/spi/GrizzlyExchange.java | 10 + .../avaje/jex/grizzly/spi/GrizzlyHandler.java | 48 +++++ .../jex/grizzly/spi/GrizzlyHttpContext.java | 77 +++++++ .../jex/grizzly/spi/GrizzlyHttpExchange.java | 129 ++++++++++++ .../spi/GrizzlyHttpExchangeDelegate.java | 193 ++++++++++++++++++ .../jex/grizzly/spi/GrizzlyHttpServer.java | 161 +++++++++++++++ .../spi/GrizzlyHttpServerProvider.java | 50 +++++ .../jex/grizzly/spi/GrizzlyHttpsExchange.java | 125 ++++++++++++ .../src/main/java/module-info.java | 17 ++ .../io/avaje/jex/grizzly/spi/FilterTest.java | 119 +++++++++++ .../io/avaje/jex/core/RoutingHandler.java | 2 +- pom.xml | 5 +- 13 files changed, 972 insertions(+), 3 deletions(-) create mode 100644 avaje-jex-grizzly-spi/pom.xml create mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyExchange.java create mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHandler.java create mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpContext.java create mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpExchange.java create mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpExchangeDelegate.java create mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpServer.java create mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpServerProvider.java create mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpsExchange.java create mode 100644 avaje-jex-grizzly-spi/src/main/java/module-info.java create mode 100644 avaje-jex-grizzly-spi/src/test/java/io/avaje/jex/grizzly/spi/FilterTest.java diff --git a/avaje-jex-grizzly-spi/pom.xml b/avaje-jex-grizzly-spi/pom.xml new file mode 100644 index 00000000..e7c61d20 --- /dev/null +++ b/avaje-jex-grizzly-spi/pom.xml @@ -0,0 +1,39 @@ + + 4.0.0 + + io.avaje + avaje-jex-parent + 3.0-RC23 + + 0.1 + avaje-jex-grizzly-spi + + + + + io.avaje + avaje-jex + + + + org.glassfish.grizzly + grizzly-http-server + 4.1.0-M1 + + + io.avaje + avaje-spi-service + provided + true + + + + io.avaje + avaje-jex-test + test + + + + diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyExchange.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyExchange.java new file mode 100644 index 00000000..10c7fb4f --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyExchange.java @@ -0,0 +1,10 @@ +package io.avaje.jex.grizzly.spi; + +import com.sun.net.httpserver.HttpPrincipal; + +sealed interface GrizzlyExchange permits GrizzlyHttpExchange, GrizzlyHttpsExchange { + + HttpPrincipal getPrincipal(); + + void setPrincipal(HttpPrincipal principal); +} diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHandler.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHandler.java new file mode 100644 index 00000000..4ea79c85 --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHandler.java @@ -0,0 +1,48 @@ +package io.avaje.jex.grizzly.spi; + +import java.io.IOException; +import java.io.UncheckedIOException; + +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; + +import com.sun.net.httpserver.Filter.Chain; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +final class GrizzlyHandler extends org.glassfish.grizzly.http.server.HttpHandler { + + private final HttpContext httpContext; + + private HttpHandler handler; + + GrizzlyHandler(HttpContext httpContext, HttpHandler httpHandler) { + super(httpContext.getPath()); + this.httpContext = httpContext; + this.handler = httpHandler; + } + + @Override + public void service(Request request, Response response) { + + try (HttpExchange exchange = + request.isSecure() + ? new GrizzlyHttpsExchange(httpContext, request, response) + : new GrizzlyHttpExchange(httpContext, request, response)) { + + new Chain(httpContext.getFilters(), handler).doFilter(exchange); + + } catch (IOException ex) { + throw new UncheckedIOException(null); + } + } + + public HttpHandler getHttpHandler() { + return handler; + } + + public void setHttpHandler(HttpHandler handler) { + this.handler = handler; + } +} diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpContext.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpContext.java new file mode 100644 index 00000000..5ede6a56 --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpContext.java @@ -0,0 +1,77 @@ +package io.avaje.jex.grizzly.spi; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.Filter; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +final class GrizzlyHttpContext extends com.sun.net.httpserver.HttpContext { + + private final GrizzlyHandler grizzlyHandler; + private final HttpServer server; + + private final Map attributes = new HashMap<>(); + + private final List filters = new ArrayList<>(); + + private Authenticator authenticator; + + private String contextPath; + + protected GrizzlyHttpContext(HttpServer server, String contextPath, HttpHandler handler) { + this.server = server; + this.grizzlyHandler = new GrizzlyHandler(this, handler); + this.contextPath = contextPath; + } + + GrizzlyHandler getGrizzlyHandler() { + return grizzlyHandler; + } + + @Override + public HttpHandler getHandler() { + return grizzlyHandler.getHttpHandler(); + } + + @Override + public void setHandler(HttpHandler h) { + grizzlyHandler.setHttpHandler(h); + } + + @Override + public String getPath() { + return contextPath; + } + + @Override + public HttpServer getServer() { + return server; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public List getFilters() { + return filters; + } + + @Override + public Authenticator setAuthenticator(Authenticator auth) { + Authenticator previous = authenticator; + authenticator = auth; + return previous; + } + + @Override + public Authenticator getAuthenticator() { + return authenticator; + } +} diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpExchange.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpExchange.java new file mode 100644 index 00000000..38fc59c8 --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpExchange.java @@ -0,0 +1,129 @@ +package io.avaje.jex.grizzly.spi; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; + +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpPrincipal; + +final class GrizzlyHttpExchange extends HttpExchange implements GrizzlyExchange { + private final GrizzlyHttpExchangeDelegate delegate; + + public GrizzlyHttpExchange(HttpContext context, Request req, Response resp) { + + delegate = new GrizzlyHttpExchangeDelegate(context, req, resp); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public Headers getRequestHeaders() { + return delegate.getRequestHeaders(); + } + + @Override + public Headers getResponseHeaders() { + return delegate.getResponseHeaders(); + } + + @Override + public URI getRequestURI() { + return delegate.getRequestURI(); + } + + @Override + public String getRequestMethod() { + return delegate.getRequestMethod(); + } + + @Override + public HttpContext getHttpContext() { + return delegate.getHttpContext(); + } + + @Override + public void close() { + delegate.close(); + } + + @Override + public boolean equals(Object obj) { + return delegate.equals(obj); + } + + @Override + public InputStream getRequestBody() { + return delegate.getRequestBody(); + } + + @Override + public OutputStream getResponseBody() { + return delegate.getResponseBody(); + } + + @Override + public void sendResponseHeaders(int rCode, long responseLength) throws IOException { + delegate.sendResponseHeaders(rCode, responseLength); + } + + @Override + public InetSocketAddress getRemoteAddress() { + return delegate.getRemoteAddress(); + } + + @Override + public int getResponseCode() { + return delegate.getResponseCode(); + } + + @Override + public InetSocketAddress getLocalAddress() { + return delegate.getLocalAddress(); + } + + @Override + public String getProtocol() { + return delegate.getProtocol(); + } + + @Override + public Object getAttribute(String name) { + return delegate.getAttribute(name); + } + + @Override + public void setAttribute(String name, Object value) { + delegate.setAttribute(name, value); + } + + @Override + public void setStreams(InputStream i, OutputStream o) { + delegate.setStreams(i, o); + } + + @Override + public HttpPrincipal getPrincipal() { + return delegate.getPrincipal(); + } + + @Override + public void setPrincipal(HttpPrincipal principal) { + delegate.setPrincipal(principal); + } + + @Override + public String toString() { + return delegate.toString(); + } +} diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpExchangeDelegate.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpExchangeDelegate.java new file mode 100644 index 00000000..a6b6982c --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpExchangeDelegate.java @@ -0,0 +1,193 @@ +package io.avaje.jex.grizzly.spi; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpPrincipal; + +final class GrizzlyHttpExchangeDelegate extends HttpExchange { + + /** Set of headers that RFC9110 says will not have a value list */ + private static final Set SINGLE_VALUE_HEADERS = + Set.of( + "authorization", + "content-length", + "date", + "expires", + "host", + "if-modified-since", + "if-unmodified-since", + "if-range", + "last-modified", + "location", + "referer", + "retry-after", + "user-agent"); + + private final HttpContext context; + + private final Request request; + + private final Headers responseHeaders = new Headers(); + + private Headers requestHeaders = new Headers(); + + private int statusCode = 0; + + private InputStream inputStream; + + private OutputStream outputStream; + + private HttpPrincipal httpPrincipal; + + private Response response; + + GrizzlyHttpExchangeDelegate(HttpContext httpSpiContext, Request request, Response response) { + this.context = httpSpiContext; + this.request = request; + this.response = response; + this.inputStream = request.getInputStream(); + this.outputStream = response.getOutputStream(); + } + + @Override + public Headers getRequestHeaders() { + + if (!requestHeaders.isEmpty()) { + return requestHeaders; + } + for (var name : request.getHeaderNames()) { + + if (!SINGLE_VALUE_HEADERS.contains(name.toLowerCase())) { + + for (String value : request.getHeaders(name)) { + requestHeaders.add(name, value); + } + } else { + requestHeaders.add(name, request.getHeader(name)); + } + } + return requestHeaders; + } + + @Override + public Headers getResponseHeaders() { + return responseHeaders; + } + + @Override + public URI getRequestURI() { + return URI.create(request.getRequestURI()); + } + + @Override + public String getRequestMethod() { + return request.getMethod().getMethodString(); + } + + @Override + public HttpContext getHttpContext() { + return context; + } + + @Override + public void close() { + try { + outputStream.close(); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + @Override + public InputStream getRequestBody() { + return inputStream; + } + + @Override + public OutputStream getResponseBody() { + return outputStream; + } + + @Override + public void sendResponseHeaders(int rCode, long responseLength) throws IOException { + this.statusCode = rCode; + + for (Map.Entry> stringListEntry : responseHeaders.entrySet()) { + String name = stringListEntry.getKey(); + List values = stringListEntry.getValue(); + for (String value : values) { + response.addHeader(name, value); + } + } + + if (responseLength == -1) { + response.setContentLengthLong(0); + } else if (responseLength == 0) { + response.setContentLengthLong(-1); + } else { + response.setContentLengthLong(responseLength); + } + + response.setStatus(rCode); + } + + @Override + public InetSocketAddress getRemoteAddress() { + return InetSocketAddress.createUnresolved(request.getRemoteAddr(), request.getRemotePort()); + } + + @Override + public int getResponseCode() { + return statusCode; + } + + @Override + public InetSocketAddress getLocalAddress() { + return new InetSocketAddress(request.getLocalAddr(), request.getLocalPort()); + } + + @Override + public String getProtocol() { + return request.getProtocol().getProtocolString(); + } + + @Override + public Object getAttribute(String name) { + return request.getAttribute(name); + } + + @Override + public void setAttribute(String name, Object value) { + request.setAttribute(name, value); + } + + @Override + public void setStreams(InputStream i, OutputStream o) { + assert inputStream != null; + if (i != null) inputStream = i; + if (o != null) outputStream = o; + } + + @Override + public HttpPrincipal getPrincipal() { + return httpPrincipal; + } + + public void setPrincipal(HttpPrincipal principal) { + this.httpPrincipal = principal; + } +} diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpServer.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpServer.java new file mode 100644 index 00000000..cdd9f79c --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpServer.java @@ -0,0 +1,161 @@ +package io.avaje.jex.grizzly.spi; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.System.Logger.Level; +import java.net.InetSocketAddress; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.grizzly.http.server.NetworkListener; +import org.glassfish.grizzly.http.server.ServerConfiguration; +import org.glassfish.grizzly.ssl.SSLEngineConfigurator; + +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpsConfigurator; + +final class GrizzlyHttpServer extends com.sun.net.httpserver.HttpsServer { + private static final System.Logger LOG = + System.getLogger(GrizzlyHttpServer.class.getCanonicalName()); + private final HttpServer server; + private InetSocketAddress addr; + private ServerConfiguration httpConfiguration; + private ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); + private HttpsConfigurator httpsConfig; + + public GrizzlyHttpServer(HttpServer server) { + + this(server, server.getServerConfiguration()); + } + + public GrizzlyHttpServer(HttpServer server, ServerConfiguration configuration) { + this.server = server; + this.httpConfiguration = configuration; + } + + public ServerConfiguration getHttpConfiguration() { + return httpConfiguration; + } + + @Override + public void bind(InetSocketAddress addr, int backlog) throws IOException { + + this.addr = addr; + // check if there is already a connector listening + var connectors = server.getListeners(); + if (connectors != null) { + for (var connector : connectors) { + if (connector.getPort() == addr.getPort()) { + LOG.log( + Level.DEBUG, "server already bound to port {}, no need to rebind", addr.getPort()); + return; + } + } + } + + if (LOG.isLoggable(Level.DEBUG)) { + LOG.log(Level.DEBUG, "binding server to port " + addr.getPort()); + } + var listener = new NetworkListener("rizzly", addr.getHostName(), addr.getPort()); + listener.getTransport().setWorkerThreadPool(executor); + if (backlog != 0) { + listener.getTransport().setServerConnectionBackLog(backlog); + } + if (httpsConfig != null) { + listener.setSSLEngineConfig(new SSLEngineConfigurator(httpsConfig.getSSLContext())); + } + + server.addListener(listener); + } + + protected HttpServer getServer() { + return server; + } + + @Override + public InetSocketAddress getAddress() { + if (addr.getPort() == 0 && server.isStarted()) { + return new InetSocketAddress(addr.getHostString(), server.getListener("rizzly").getPort()); + } + return addr; + } + + @Override + public void start() { + + try { + server.start(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public void setExecutor(Executor executor) { + if (executor instanceof ExecutorService service) { + this.executor = service; + } else { + throw new IllegalArgumentException("Grizzly only accepts an instance of ExecutorService"); + } + } + + @Override + public Executor getExecutor() { + return executor; + } + + @Override + public void stop(int delay) { + server.shutdownNow(); + } + + @Override + public HttpContext createContext(String path, HttpHandler httpHandler) { + + GrizzlyHttpContext context = new GrizzlyHttpContext(this, path, httpHandler); + GrizzlyHandler jettyContextHandler = context.getGrizzlyHandler(); + + httpConfiguration.addHttpHandler( + jettyContextHandler, path.transform(this::prependSlash).transform(this::appendSlash)); + + return context; + } + + private String prependSlash(String s) { + return s.startsWith("/") ? s : "/" + s; + } + + private String appendSlash(String s) { + return s.endsWith("/") ? s + "*" : s + "/*"; + } + + @Override + public HttpContext createContext(String path) { + return createContext(path, null); + } + + @Override + public void removeContext(String path) { + + throw new UnsupportedOperationException("notImplemented"); + } + + @Override + public void removeContext(HttpContext context) { + + throw new UnsupportedOperationException("notImplemented"); + } + + @Override + public void setHttpsConfigurator(HttpsConfigurator config) { + httpsConfig = config; + } + + @Override + public HttpsConfigurator getHttpsConfigurator() { + return httpsConfig; + } +} diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpServerProvider.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpServerProvider.java new file mode 100644 index 00000000..bd21367d --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpServerProvider.java @@ -0,0 +1,50 @@ +package io.avaje.jex.grizzly.spi; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import org.glassfish.grizzly.http.server.HttpServer; + +import com.sun.net.httpserver.HttpsServer; +import com.sun.net.httpserver.spi.HttpServerProvider; + +import io.avaje.spi.ServiceProvider; + +@ServiceProvider +public class GrizzlyHttpServerProvider extends HttpServerProvider { + + private org.glassfish.grizzly.http.server.HttpServer server; + + public GrizzlyHttpServerProvider(HttpServer server) { + + this.server = server; + } + + public GrizzlyHttpServerProvider() { + + this.server = new HttpServer(); + } + + @Override + public com.sun.net.httpserver.HttpServer createHttpServer(InetSocketAddress addr, int backlog) + throws IOException { + + return createServer(addr, backlog); + } + + @Override + public HttpsServer createHttpsServer(InetSocketAddress addr, int backlog) throws IOException { + return createServer(addr, backlog); + } + + private com.sun.net.httpserver.HttpsServer createServer(InetSocketAddress addr, int backlog) + throws IOException { + if (server == null) { + server = new HttpServer(); + } + + GrizzlyHttpServer jettyHttpServer = new GrizzlyHttpServer(server); + if (addr != null) jettyHttpServer.bind(addr, backlog); + return jettyHttpServer; + } +} diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpsExchange.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpsExchange.java new file mode 100644 index 00000000..d1e686d6 --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpsExchange.java @@ -0,0 +1,125 @@ +package io.avaje.jex.grizzly.spi; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; + +import javax.net.ssl.SSLSession; + +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpPrincipal; +import com.sun.net.httpserver.HttpsExchange; + +final class GrizzlyHttpsExchange extends HttpsExchange implements GrizzlyExchange { + private final GrizzlyHttpExchangeDelegate delegate; + + public GrizzlyHttpsExchange(HttpContext jaxWsContext, Request req, Response resp) { + delegate = new GrizzlyHttpExchangeDelegate(jaxWsContext, req, resp); + } + + @Override + public Headers getRequestHeaders() { + return delegate.getRequestHeaders(); + } + + @Override + public Headers getResponseHeaders() { + return delegate.getResponseHeaders(); + } + + @Override + public URI getRequestURI() { + return delegate.getRequestURI(); + } + + @Override + public String getRequestMethod() { + return delegate.getRequestMethod(); + } + + @Override + public HttpContext getHttpContext() { + return delegate.getHttpContext(); + } + + @Override + public void close() { + delegate.close(); + } + + @Override + public InputStream getRequestBody() { + return delegate.getRequestBody(); + } + + @Override + public OutputStream getResponseBody() { + return delegate.getResponseBody(); + } + + @Override + public void sendResponseHeaders(int rCode, long responseLength) throws IOException { + delegate.sendResponseHeaders(rCode, responseLength); + } + + @Override + public InetSocketAddress getRemoteAddress() { + return delegate.getRemoteAddress(); + } + + @Override + public int getResponseCode() { + return delegate.getResponseCode(); + } + + @Override + public InetSocketAddress getLocalAddress() { + return delegate.getLocalAddress(); + } + + @Override + public String getProtocol() { + return delegate.getProtocol(); + } + + @Override + public Object getAttribute(String name) { + return delegate.getAttribute(name); + } + + @Override + public void setAttribute(String name, Object value) { + delegate.setAttribute(name, value); + } + + @Override + public void setStreams(InputStream i, OutputStream o) { + delegate.setStreams(i, o); + } + + @Override + public HttpPrincipal getPrincipal() { + return delegate.getPrincipal(); + } + + @Override + public void setPrincipal(HttpPrincipal principal) { + delegate.setPrincipal(principal); + } + + @Override + public String toString() { + return delegate.toString(); + } + + @Override + public SSLSession getSSLSession() { + return null; + } +} diff --git a/avaje-jex-grizzly-spi/src/main/java/module-info.java b/avaje-jex-grizzly-spi/src/main/java/module-info.java new file mode 100644 index 00000000..6378b580 --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/module-info.java @@ -0,0 +1,17 @@ +import com.sun.net.httpserver.spi.HttpServerProvider; + +module io.avaje.jex.grizzly { + + exports io.avaje.jex.grizzly.spi; + + requires transitive io.avaje.jex; + requires transitive jdk.httpserver; + requires transitive org.glassfish.grizzly.http.server; + requires transitive org.glassfish.grizzly.http; + requires transitive org.glassfish.grizzly; + + requires static io.avaje.spi; + requires static java.net.http; + + provides HttpServerProvider with io.avaje.jex.grizzly.spi.GrizzlyHttpServerProvider; +} diff --git a/avaje-jex-grizzly-spi/src/test/java/io/avaje/jex/grizzly/spi/FilterTest.java b/avaje-jex-grizzly-spi/src/test/java/io/avaje/jex/grizzly/spi/FilterTest.java new file mode 100644 index 00000000..fc6cc485 --- /dev/null +++ b/avaje-jex-grizzly-spi/src/test/java/io/avaje/jex/grizzly/spi/FilterTest.java @@ -0,0 +1,119 @@ +package io.avaje.jex.grizzly.spi; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.http.HttpHeaders; +import java.net.http.HttpResponse; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.avaje.jex.Jex; +import io.avaje.jex.test.TestPair; + +class FilterTest { + + static final TestPair pair = init(); + static final AtomicReference afterAll = new AtomicReference<>(); + static final AtomicReference afterTwo = new AtomicReference<>(); + + static TestPair init() { + final Jex app = + Jex.create() + .routing( + routing -> + routing + .get("/", ctx -> ctx.text("roo")) + .get( + "/noResponse", + ctx -> { + ctx.header("Content-Type", ""); + }) + .get("/one", ctx -> ctx.text("one")) + .get("/two", ctx -> ctx.text("two")) + .get("/two/{id}", ctx -> ctx.text("two-id")) + .before(ctx -> ctx.header("before-all", "set")) + .filter( + (ctx, chain) -> { + if (ctx.path().contains("/two/")) { + ctx.header("before-two", "set"); + } + chain.proceed(); + }) + .after(ctx -> afterAll.set("set")) + .filter( + (ctx, chain) -> { + chain.proceed(); + if (ctx.path().contains("/two/")) { + afterTwo.set("set"); + } + }) + .get("/dummy", ctx -> ctx.text("dummy"))); + + return TestPair.create(app); + } + + @AfterAll + static void end() { + pair.shutdown(); + } + + void clearAfter() { + afterAll.set(null); + afterTwo.set(null); + } + + @Test + void get() { + clearAfter(); + HttpResponse res = pair.request().GET().asString(); + assertHasBeforeAfterAll(res); + assertNoBeforeAfterTwo(res); + + clearAfter(); + res = pair.request().path("one").GET().asString(); + assertHasBeforeAfterAll(res); + assertNoBeforeAfterTwo(res); + + clearAfter(); + res = pair.request().path("two").GET().asString(); + assertHasBeforeAfterAll(res); + assertNoBeforeAfterTwo(res); + } + + @Test + void getNoResponse() { + clearAfter(); + HttpResponse res = pair.request().path("noResponse").GET().asString(); + assertThat(res.statusCode()).isEqualTo(204); + assertHasBeforeAfterAll(res); + assertNoBeforeAfterTwo(res); + } + + @Test + void get_two_expect_extraFilters() { + clearAfter(); + HttpResponse res = pair.request().path("two/42").GET().asString(); + + final HttpHeaders headers = res.headers(); + assertHasBeforeAfterAll(res); + assertThat(headers.firstValue("before-two")).get().isEqualTo("set"); + assertThat(afterTwo.get()).isEqualTo("set"); + } + + private void assertNoBeforeAfterTwo(HttpResponse res) { + assertThat(res.statusCode()).isLessThan(300); + assertThat(res.headers().firstValue("before-two")).isEmpty(); + assertThat(afterTwo.get()).isNull(); + } + + private void assertHasBeforeAfterAll(HttpResponse res) { + assertThat(res.statusCode()).isLessThan(300); + assertThat(res.headers().firstValue("before-all")).get().isEqualTo("set"); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(2)); + assertThat(afterAll.get()).isEqualTo("set"); + } +} diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java index cd91a6ef..f224049d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java @@ -60,7 +60,7 @@ public void handle(HttpExchange exchange) { } private void handleNoResponse(HttpExchange exchange) throws IOException { - if (exchange.getResponseCode() == -1) { + if (exchange.getResponseCode() < 1) { exchange.sendResponseHeaders(204, -1); } } diff --git a/pom.xml b/pom.xml index 4a7f6556..3cb28519 100644 --- a/pom.xml +++ b/pom.xml @@ -39,11 +39,12 @@ avaje-jex - avaje-jex-test avaje-jex-freemarker - avaje-jex-mustache + avaje-jex-grizzly-spi avaje-jex-htmx + avaje-jex-mustache avaje-jex-static-content + avaje-jex-test From f6ff51af2f7910472a438e13ef98758ce9d5b11a Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 21 Mar 2025 21:33:53 -0400 Subject: [PATCH 241/250] fix jetty no response (#233) jetty can return 0 instead of -1 for unsent headers From fdebfcc59412c0269e81af4ad5653c7860d9ce58 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 23 Mar 2025 22:46:29 -0400 Subject: [PATCH 242/250] Add multivalue header methods (#234) * Add multivalue header methods - add multi-value header methods - fix compression matching * Update JsonTest.java * fix compression --- .../compression/CompressedOutputStream.java | 13 ++++++-- .../java/io/avaje/jex/core/JdkContext.java | 20 ++++++++++-- .../main/java/io/avaje/jex/http/Context.java | 32 ++++++++++++++++--- .../jex/compression/CompressionTest.java | 13 +------- .../test/java/io/avaje/jex/core/TestPair.java | 8 ++++- 5 files changed, 62 insertions(+), 24 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java index 6f80934a..f323036f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java +++ b/avaje-jex/src/main/java/io/avaje/jex/compression/CompressedOutputStream.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; +import java.util.List; import java.util.Objects; import java.util.Optional; @@ -39,7 +40,7 @@ private void decideCompression(int length) throws IOException { if (compressionAllowed && length >= minSizeForCompression) { Optional compressor; - compressor = findMatchingCompressor(ctx.header(Constants.ACCEPT_ENCODING)); + compressor = findMatchingCompressor(ctx.headerValues(Constants.ACCEPT_ENCODING)); if (compressor.isPresent()) { this.compressedStream = compressor.get().compress(originStream); ctx.header(Constants.CONTENT_ENCODING, compressor.get().encoding()); @@ -69,9 +70,15 @@ public void close() throws IOException { originStream.close(); } - private Optional findMatchingCompressor(String acceptedEncoding) { + private Optional findMatchingCompressor(List acceptedEncoding) { if (acceptedEncoding != null) { - return Arrays.stream(acceptedEncoding.split(",")) + // it seems jetty may handle multi-value headers differently + var stream = + acceptedEncoding.size() > 1 + ? acceptedEncoding.stream() + : Arrays.stream(acceptedEncoding.getFirst().split(",")); + + return stream .map(e -> e.trim().split(";")[0]) .map(e -> "*".equals(e) ? "gzip" : e.toLowerCase()) .map(compression::forType) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index 70fd667a..cf02ceb0 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -221,8 +221,7 @@ public Map> formParamMap() { } private String header(Headers headers, String name) { - final List values = headers.get(name); - return values == null || values.isEmpty() ? null : values.getFirst(); + return headers.getFirst(name); } @Override @@ -230,6 +229,11 @@ public String header(String key) { return header(exchange.getRequestHeaders(), key); } + @Override + public List headerValues(String key) { + return exchange.getRequestHeaders().get(key); + } + @Override public Context header(String key, List value) { exchange.getResponseHeaders().put(key, value); @@ -261,10 +265,20 @@ public Context headerMap(Map> map) { } @Override - public Headers headers() { + public Headers requestHeaders() { return exchange.getRequestHeaders(); } + @Override + public Headers responseHeaders() { + return exchange.getResponseHeaders(); + } + + @Override + public List responseHeaderValues(String key) { + return exchange.getResponseHeaders().get(key); + } + @Override public String host() { return header(Constants.HOST); diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java index 45b8aa5f..0b01619d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java @@ -90,7 +90,7 @@ default T bodyAsClass(Class beanType) { * @param beanType The bean type */ T bodyAsType(Type beanType); - + /** * Return the request body as bean using {@link #bodyAsInputStream()}. * @@ -99,7 +99,7 @@ default T bodyAsClass(Class beanType) { default T bodyStreamAsClass(Class beanType) { return bodyAsType(beanType); } - + /** * Return the request body as bean of the given type using {@link #bodyAsInputStream()}. * @@ -192,12 +192,19 @@ default String fullUrl() { } /** - * Return the request header. + * Return the request header value by name. * - * @param key The header key + * @param key The first value of the header */ String header(String key); + /** + * Return the request headers. + * + * @param key all values of the header key + */ + List headerValues(String key); + /** * Set the response header. * @@ -235,7 +242,14 @@ default String fullUrl() { * * @return the request headers */ - Headers headers(); + Headers requestHeaders(); + + /** + * Return underlying response headers. + * + * @return the response headers + */ + Headers responseHeaders(); /** Add the response headers using the provided map. */ default Context headers(Map headers) { @@ -439,6 +453,14 @@ default Context render(String name) { */ String responseHeader(String key); + /** + * Returns the value of the specified response header. + * + * @param key The name of the header. + * @return The value of the header, or null if not found. + */ + List responseHeaderValues(String key); + /** Return true if the response has been sent. */ boolean responseSent(); diff --git a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java index 1f241201..2ee5612a 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/compression/CompressionTest.java @@ -33,10 +33,7 @@ static TestPair init() { "/sus", ctx -> ctx.write( - CompressionTest.class.getResourceAsStream("/public/sus.txt"))) - .get( - "/forced", - ctx -> ctx.header(Constants.CONTENT_ENCODING, "gzip").text("hi"))); + CompressionTest.class.getResourceAsStream("/public/sus.txt")))); return TestPair.create(app); } @@ -72,12 +69,4 @@ void testNoCompression() { assertThat(res.statusCode()).isEqualTo(200); assertThat(res.headers().firstValue(Constants.CONTENT_ENCODING)).isEmpty(); } - - @Test - void testForcedCompression() { - HttpResponse res = - pair.request().header(Constants.ACCEPT_ENCODING, "gzip").path("forced").GET().asString(); - assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.headers().firstValue(Constants.CONTENT_ENCODING)).contains("gzip"); - } } diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/TestPair.java b/avaje-jex/src/test/java/io/avaje/jex/core/TestPair.java index f9429ed2..2bab7988 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/TestPair.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/TestPair.java @@ -1,6 +1,7 @@ package io.avaje.jex.core; import java.net.http.HttpClient.Version; +import java.time.Duration; import io.avaje.http.client.HttpClient; import io.avaje.http.client.HttpClientRequest; @@ -43,7 +44,12 @@ public static TestPair create(Jex app) { var jexServer = app.port(0).start(); var port = jexServer.port(); var url = "http://localhost:" + port; - var client = HttpClient.builder().version(Version.HTTP_1_1).baseUrl(url).build(); + var client = + HttpClient.builder() + .version(Version.HTTP_1_1) + .requestTimeout(Duration.ofDays(1)) + .baseUrl(url) + .build(); return new TestPair(port, jexServer, client); } From fc608e10029c38b949e3add27cc660157f7c6459 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 23 Mar 2025 22:52:39 -0400 Subject: [PATCH 243/250] Fix `fullUrl` on jetty (#235) * fix fullUrl on jetty it seems that jetty already gives the full url * Format only --------- Co-authored-by: Rob Bygrave --- .../src/main/java/io/avaje/jex/http/Context.java | 3 ++- .../java/io/avaje/jex/core/ContextLengthTest.java | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java index 0b01619d..97af9bc8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java @@ -188,7 +188,8 @@ default List formParams(String key) { /** Return the full request url, including query string (if present) */ default String fullUrl() { - return scheme() + "://" + host() + uri().toString(); + var uri = uri().toString(); + return !uri.startsWith("/") ? uri : scheme() + "://" + host() + uri; } /** diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/ContextLengthTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ContextLengthTest.java index ec94ffb4..abe4bc1d 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/ContextLengthTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ContextLengthTest.java @@ -1,12 +1,13 @@ package io.avaje.jex.core; -import io.avaje.jex.Jex; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.net.http.HttpResponse; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.avaje.jex.Jex; class ContextLengthTest { @@ -59,7 +60,7 @@ void uri() { .GET().asString(); assertThat(res.statusCode()).isEqualTo(200); - assertThat(res.body()).isEqualTo("uri:/uri/uriTest?a=av"); + assertThat(res.body()).contains("/uri/uriTest?a=av"); } @Test From 7bf24287b00b8190d730f7062b682babb0d91fea Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sun, 23 Mar 2025 23:20:03 -0400 Subject: [PATCH 244/250] use charAt instead off startsWith (#237) if you're checking whether a single character is in front of the string `charAt` is more efficient --- .../avaje/jex/staticcontent/StaticResourceHandlerBuilder.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java | 2 +- avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java | 4 ++-- avaje-jex/src/main/java/io/avaje/jex/http/Context.java | 2 +- .../src/main/java/io/avaje/jex/routes/PathSegmentParser.java | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java index 9b07810b..b0f0b642 100644 --- a/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java +++ b/avaje-jex-static-content/src/main/java/io/avaje/jex/staticcontent/StaticResourceHandlerBuilder.java @@ -127,7 +127,7 @@ StaticResourceHandlerBuilder file() { } private String prependSlash(String s) { - return s.startsWith("/") ? s : "/" + s; + return s.charAt(0) == '/' ? s : "/" + s; } private String appendSlash(String s) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 1d5473e0..877beea3 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -48,7 +48,7 @@ public JexConfig contextPath(String contextPath) { this.contextPath = contextPath - .transform(s -> s.startsWith("/") ? s : "/" + s) + .transform(s -> s.charAt(0) == '/' ? s : "/" + s) .transform(s -> s.endsWith("/") ? s.substring(0, s.lastIndexOf("/")) : s); } return this; diff --git a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java index daea48bc..b50ebffb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DefaultRouting.java @@ -37,11 +37,11 @@ public Map, ExceptionHandler> errorHandlers() { } private String path(String path) { - return String.join("", pathDeque) + ((path.startsWith("/") || path.isEmpty()) ? path : "/" + path); + return String.join("", pathDeque) + (path.charAt(0) == '/' || path.isEmpty() ? path : "/" + path); } private void addEndpoints(String path, HttpService group) { - path = path.startsWith("/") ? path : "/" + path; + path = path.charAt(0) == '/' ? path : "/" + path; pathDeque.addLast(path); group.add(this); pathDeque.removeLast(); diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java index 97af9bc8..d4ccf5b1 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/Context.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/Context.java @@ -189,7 +189,7 @@ default List formParams(String key) { /** Return the full request url, including query string (if present) */ default String fullUrl() { var uri = uri().toString(); - return !uri.startsWith("/") ? uri : scheme() + "://" + host() + uri; + return uri.charAt(0) != '/' ? uri : scheme() + "://" + host() + uri; } /** diff --git a/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java b/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java index 27fc8ff4..3e33b6db 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java +++ b/avaje-jex/src/main/java/io/avaje/jex/routes/PathSegmentParser.java @@ -69,9 +69,9 @@ private PathSegment parseMultiSegment() { private PathSegment tokenSegment(String token) { if ("*".equals(token)) { return WILDCARD; - } else if (token.startsWith("<")) { + } else if (token.charAt(0) == '<') { return slashAccepting(token); - } else if (token.startsWith("{")) { + } else if (token.charAt(0) == '{') { return slashIgnoring(token); } else { return new PathSegment.Literal(token); From a21f5e7e97fc2aa32124cc419bc4b6baceb552e8 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 24 Mar 2025 13:17:57 -0400 Subject: [PATCH 245/250] add robaho workflow (#238) --- .github/workflows/robaho.yml | 39 ++++++++++++++++++++++++++++++++++++ avaje-jex/pom.xml | 20 +++++++++++++----- pom.xml | 2 +- 3 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/robaho.yml diff --git a/.github/workflows/robaho.yml b/.github/workflows/robaho.yml new file mode 100644 index 00000000..fdf06229 --- /dev/null +++ b/.github/workflows/robaho.yml @@ -0,0 +1,39 @@ + +name: Robaho + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + build: + runs-on: ${{ matrix.os }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + java_version: [21] + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.java_version }} + distribution: 'zulu' + - name: Maven cache + uses: actions/cache@v4 + env: + cache-name: maven-cache + with: + path: + ~/.m2 + key: build-${{ env.cache-name }} + - name: Build with Maven + run: mvn clean test -Probaho + + diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index a730f4e9..5312368e 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -64,9 +64,19 @@
    - - - - - + + + robaho + + false + + + + io.github.robaho + httpserver + test + + + + diff --git a/pom.xml b/pom.xml index 3cb28519..4d02165e 100644 --- a/pom.xml +++ b/pom.xml @@ -207,7 +207,7 @@ io.github.robaho httpserver - 1.0.22 + 1.0.23 From 48f2f96e4018b98773981ee270962baec7459359 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Thu, 27 Mar 2025 08:05:18 +1300 Subject: [PATCH 246/250] Version 3.0-RC24 --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-grizzly-spi/pom.xml | 2 +- avaje-jex-htmx/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- examples/example-http-generation/pom.xml | 2 +- examples/example-jdk-jsonb/pom.xml | 2 +- examples/example-jdk/pom.xml | 4 ++-- examples/example-jetty/pom.xml | 2 +- examples/example-robaho/pom.xml | 4 ++-- examples/pom.xml | 2 +- pom.xml | 14 +++++++------- 14 files changed, 22 insertions(+), 22 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index 7c968d2f..a3b9ddf0 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC23 + 3.0-RC24 avaje-jex-freemarker diff --git a/avaje-jex-grizzly-spi/pom.xml b/avaje-jex-grizzly-spi/pom.xml index e7c61d20..8583551c 100644 --- a/avaje-jex-grizzly-spi/pom.xml +++ b/avaje-jex-grizzly-spi/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC23 + 3.0-RC24 0.1 avaje-jex-grizzly-spi diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 054d53f6..157fd158 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC23 + 3.0-RC24 avaje-jex-htmx diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index d05afdf1..fba67284 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC23 + 3.0-RC24 avaje-jex-mustache diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index a98446cd..2ba8d1be 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC23 + 3.0-RC24 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index 213d6698..cbcae223 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC23 + 3.0-RC24 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index 5312368e..b18fec5d 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC23 + 3.0-RC24 avaje-jex diff --git a/examples/example-http-generation/pom.xml b/examples/example-http-generation/pom.xml index 22587876..a2cb1695 100644 --- a/examples/example-http-generation/pom.xml +++ b/examples/example-http-generation/pom.xml @@ -5,7 +5,7 @@ io.avaje examples - 3.0-RC23 + 3.0-RC24 4.0.0 diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index 49e1b438..0a56b211 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -6,7 +6,7 @@ io.avaje examples - 3.0-RC23 + 3.0-RC24 org.example diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index c6b2829b..61afeb25 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -6,7 +6,7 @@ io.avaje examples - 3.0-RC23 + 3.0-RC24 org.example @@ -23,7 +23,7 @@ io.avaje avaje-jex - 3.0-RC23 + 3.0-RC24 diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index 4c3bba7e..633c56dd 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -3,7 +3,7 @@ io.avaje examples - 3.0-RC23 + 3.0-RC24 example-jetty diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index c705b473..fb3eb5ec 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -6,7 +6,7 @@ io.avaje examples - 3.0-RC23 + 3.0-RC24 example-robaho @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC23 + 3.0-RC24 diff --git a/examples/pom.xml b/examples/pom.xml index d17c8e6a..c043fa35 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC23 + 3.0-RC24 examples diff --git a/pom.xml b/pom.xml index 4d02165e..d940b144 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ io.avaje avaje-jex-parent - 3.0-RC23 + 3.0-RC24 pom @@ -177,32 +177,32 @@ io.avaje avaje-jex - 3.0-RC23 + 3.0-RC24 io.avaje avaje-jex-test - 3.0-RC23 + 3.0-RC24 io.avaje avaje-jex-freemarker - 3.0-RC23 + 3.0-RC24 io.avaje avaje-jex-mustache - 3.0-RC23 + 3.0-RC24 io.avaje avaje-jex-htmx - 3.0-RC23 + 3.0-RC24 io.avaje avaje-jex-static-content - 3.0-RC23 + 3.0-RC24 io.github.robaho From e4590c07fde39261f8156af247be4e4e7f056326 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:55:40 -0400 Subject: [PATCH 247/250] basic auth tests (#239) --- .../java/io/avaje/jex/security/AppRole.java | 38 +++++++++++++ .../security/BasicAuthCredentialsTest.java | 57 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 avaje-jex/src/test/java/io/avaje/jex/security/AppRole.java create mode 100644 avaje-jex/src/test/java/io/avaje/jex/security/BasicAuthCredentialsTest.java diff --git a/avaje-jex/src/test/java/io/avaje/jex/security/AppRole.java b/avaje-jex/src/test/java/io/avaje/jex/security/AppRole.java new file mode 100644 index 00000000..63506445 --- /dev/null +++ b/avaje-jex/src/test/java/io/avaje/jex/security/AppRole.java @@ -0,0 +1,38 @@ +package io.avaje.jex.security; + +import java.util.HashMap; +import java.util.Map; + +import io.avaje.config.Config; +import io.avaje.jex.http.Context; + +public enum AppRole implements Role { + ANYONE("", ""), + USER(Config.get("roles.user.username", "test"), Config.get("roles.user.password", "test")); + + private final String username; + private final String password; + + AppRole(String username, String password) { + this.username = username; + this.password = password; + } + + private static final Map ROLE_MAP = createRoleMap(); + + private static Map createRoleMap() { + Map map = new HashMap<>(); + for (AppRole role : AppRole.values()) { + if (!ANYONE.equals(role)) { + map.put(role.username + ":" + role.password, role); + } + } + return map; + } + + public static AppRole getRole(Context ctx) { + + final var auth = ctx.basicAuthCredentials(); + return ROLE_MAP.getOrDefault(auth.userName() + ":" + auth.password(), ANYONE); + } +} diff --git a/avaje-jex/src/test/java/io/avaje/jex/security/BasicAuthCredentialsTest.java b/avaje-jex/src/test/java/io/avaje/jex/security/BasicAuthCredentialsTest.java new file mode 100644 index 00000000..d07ada2f --- /dev/null +++ b/avaje-jex/src/test/java/io/avaje/jex/security/BasicAuthCredentialsTest.java @@ -0,0 +1,57 @@ +package io.avaje.jex.security; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.avaje.http.client.BasicAuthIntercept; +import io.avaje.jex.Jex; +import io.avaje.jex.core.TestPair; + +class BasicAuthCredentialsTest { + + static TestPair pair = init(); + + static TestPair init() { + + final Jex app = + Jex.create() + .get("/", ctx -> {}, AppRole.USER) + .filter( + (ctx, chain) -> { + if (!ctx.routeRoles().contains(AppRole.getRole(ctx))) { + + ctx.status(401).text(""); + } else { + chain.proceed(); + } + }); + + return TestPair.create(app); + } + + @AfterAll + static void end() { + pair.shutdown(); + } + + @Test + void testSuccess() { + var intercept = new BasicAuthIntercept("test", "test"); + var req = pair.request(); + intercept.beforeRequest(req); + var res = req.GET().asDiscarding(); + assertThat(res.statusCode()).isEqualTo(204); + } + + @Test + void testIncorrect() { + var intercept = new BasicAuthIntercept("test1", "test1"); + var req = pair.request(); + intercept.beforeRequest(req); + var res = req.GET().asDiscarding(); + assertThat(res.statusCode()).isEqualTo(401); + } + +} From c2a5a5e88295b36ca8e5b4a72490455f5b8f7e9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 30 Mar 2025 03:22:59 +0000 Subject: [PATCH 248/250] Bump the dependencies group with 3 updates (#240) Bumps the dependencies group with 3 updates: io.avaje:avaje-logback-encoder, io.avaje:avaje-spi-service and [io.github.robaho:httpserver](https://github.com/robaho/httpserver). Updates `io.avaje:avaje-logback-encoder` from 0.11 to 0.12 Updates `io.avaje:avaje-spi-service` from 2.10 to 2.11 Updates `io.github.robaho:httpserver` from 1.0.22 to 1.0.23 - [Commits](https://github.com/robaho/httpserver/compare/1.0.22...1.0.23) --- updated-dependencies: - dependency-name: io.avaje:avaje-logback-encoder dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.avaje:avaje-spi-service dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: io.github.robaho:httpserver dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- avaje-jex-mustache/pom.xml | 2 +- examples/example-robaho/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index fba67284..8db5fa83 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -25,7 +25,7 @@ io.avaje avaje-spi-service - 2.10 + 2.11 provided diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index fb3eb5ec..f79603ea 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -15,7 +15,7 @@ io.github.robaho httpserver - 1.0.22 + 1.0.23 diff --git a/pom.xml b/pom.xml index d940b144..8c6df2fb 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ 9.4 2.9 1.2 - 2.10 + 2.11 1.36 15.10.0 @@ -59,7 +59,7 @@ io.avaje avaje-logback-encoder - 0.11 + 0.12 io.avaje From 8ddaec729f756f7dabb1a59ea5924a9786889fb5 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Sun, 30 Mar 2025 21:48:49 +1300 Subject: [PATCH 249/250] Bump avaje-inject to 11.4 and prisms 1.42 (#241) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8c6df2fb..ddcd2473 100644 --- a/pom.xml +++ b/pom.xml @@ -26,14 +26,14 @@ 21 full 4.0 - 11.3 + 11.4 3.2 3.1 9.4 2.9 1.2 2.11 - 1.36 + 1.42 15.10.0 From b07884ef95a5973d4515f510525cf78a37f1a0b6 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Sun, 30 Mar 2025 21:58:53 +1300 Subject: [PATCH 250/250] Version 3.0 --- avaje-jex-freemarker/pom.xml | 2 +- avaje-jex-grizzly-spi/pom.xml | 10 +++++----- avaje-jex-htmx/pom.xml | 2 +- avaje-jex-mustache/pom.xml | 2 +- avaje-jex-static-content/pom.xml | 2 +- avaje-jex-test/pom.xml | 2 +- avaje-jex/pom.xml | 2 +- examples/example-http-generation/pom.xml | 2 +- examples/example-jdk-jsonb/pom.xml | 2 +- examples/example-jdk/pom.xml | 4 ++-- examples/example-jetty/pom.xml | 2 +- examples/example-robaho/pom.xml | 4 ++-- examples/pom.xml | 2 +- pom.xml | 14 +++++++------- 14 files changed, 26 insertions(+), 26 deletions(-) diff --git a/avaje-jex-freemarker/pom.xml b/avaje-jex-freemarker/pom.xml index a3b9ddf0..65fe95e8 100644 --- a/avaje-jex-freemarker/pom.xml +++ b/avaje-jex-freemarker/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC24 + 3.0 avaje-jex-freemarker diff --git a/avaje-jex-grizzly-spi/pom.xml b/avaje-jex-grizzly-spi/pom.xml index 8583551c..70b6f7a3 100644 --- a/avaje-jex-grizzly-spi/pom.xml +++ b/avaje-jex-grizzly-spi/pom.xml @@ -5,18 +5,18 @@ io.avaje avaje-jex-parent - 3.0-RC24 + 3.0 - 0.1 + 0.2 avaje-jex-grizzly-spi - + io.avaje avaje-jex - + org.glassfish.grizzly grizzly-http-server @@ -28,7 +28,7 @@ provided true - + io.avaje avaje-jex-test diff --git a/avaje-jex-htmx/pom.xml b/avaje-jex-htmx/pom.xml index 157fd158..8ee18a29 100644 --- a/avaje-jex-htmx/pom.xml +++ b/avaje-jex-htmx/pom.xml @@ -6,7 +6,7 @@ io.avaje avaje-jex-parent - 3.0-RC24 + 3.0 avaje-jex-htmx diff --git a/avaje-jex-mustache/pom.xml b/avaje-jex-mustache/pom.xml index 8db5fa83..31e66cf2 100644 --- a/avaje-jex-mustache/pom.xml +++ b/avaje-jex-mustache/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC24 + 3.0 avaje-jex-mustache diff --git a/avaje-jex-static-content/pom.xml b/avaje-jex-static-content/pom.xml index 2ba8d1be..af2d27e8 100644 --- a/avaje-jex-static-content/pom.xml +++ b/avaje-jex-static-content/pom.xml @@ -5,7 +5,7 @@ io.avaje avaje-jex-parent - 3.0-RC24 + 3.0 avaje-jex-static-content diff --git a/avaje-jex-test/pom.xml b/avaje-jex-test/pom.xml index cbcae223..97e5427f 100644 --- a/avaje-jex-test/pom.xml +++ b/avaje-jex-test/pom.xml @@ -4,7 +4,7 @@ avaje-jex-parent io.avaje - 3.0-RC24 + 3.0 avaje-jex-test diff --git a/avaje-jex/pom.xml b/avaje-jex/pom.xml index b18fec5d..03d99032 100644 --- a/avaje-jex/pom.xml +++ b/avaje-jex/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jex-parent - 3.0-RC24 + 3.0 avaje-jex diff --git a/examples/example-http-generation/pom.xml b/examples/example-http-generation/pom.xml index a2cb1695..1a3aeacc 100644 --- a/examples/example-http-generation/pom.xml +++ b/examples/example-http-generation/pom.xml @@ -5,7 +5,7 @@ io.avaje examples - 3.0-RC24 + 3.0 4.0.0 diff --git a/examples/example-jdk-jsonb/pom.xml b/examples/example-jdk-jsonb/pom.xml index 0a56b211..369efda3 100644 --- a/examples/example-jdk-jsonb/pom.xml +++ b/examples/example-jdk-jsonb/pom.xml @@ -6,7 +6,7 @@ io.avaje examples - 3.0-RC24 + 3.0 org.example diff --git a/examples/example-jdk/pom.xml b/examples/example-jdk/pom.xml index 61afeb25..1af079c4 100644 --- a/examples/example-jdk/pom.xml +++ b/examples/example-jdk/pom.xml @@ -6,7 +6,7 @@ io.avaje examples - 3.0-RC24 + 3.0 org.example @@ -23,7 +23,7 @@ io.avaje avaje-jex - 3.0-RC24 + 3.0 diff --git a/examples/example-jetty/pom.xml b/examples/example-jetty/pom.xml index 633c56dd..c9a8fc88 100644 --- a/examples/example-jetty/pom.xml +++ b/examples/example-jetty/pom.xml @@ -3,7 +3,7 @@ io.avaje examples - 3.0-RC24 + 3.0 example-jetty diff --git a/examples/example-robaho/pom.xml b/examples/example-robaho/pom.xml index f79603ea..baad65c0 100644 --- a/examples/example-robaho/pom.xml +++ b/examples/example-robaho/pom.xml @@ -6,7 +6,7 @@ io.avaje examples - 3.0-RC24 + 3.0 example-robaho @@ -21,7 +21,7 @@ io.avaje avaje-jex - 3.0-RC24 + 3.0 diff --git a/examples/pom.xml b/examples/pom.xml index c043fa35..940110af 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ avaje-jex-parent io.avaje - 3.0-RC24 + 3.0 examples diff --git a/pom.xml b/pom.xml index ddcd2473..5cf03e14 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ io.avaje avaje-jex-parent - 3.0-RC24 + 3.0 pom @@ -177,32 +177,32 @@ io.avaje avaje-jex - 3.0-RC24 + 3.0 io.avaje avaje-jex-test - 3.0-RC24 + 3.0 io.avaje avaje-jex-freemarker - 3.0-RC24 + 3.0 io.avaje avaje-jex-mustache - 3.0-RC24 + 3.0 io.avaje avaje-jex-htmx - 3.0-RC24 + 3.0 io.avaje avaje-jex-static-content - 3.0-RC24 + 3.0 io.github.robaho