diff --git a/nima/tests/integration/webserver/webserver/pom.xml b/nima/tests/integration/webserver/webserver/pom.xml index 88934e31cc7..8af0ac6d4ed 100644 --- a/nima/tests/integration/webserver/webserver/pom.xml +++ b/nima/tests/integration/webserver/webserver/pom.xml @@ -42,6 +42,11 @@ junit-jupiter-api test + + org.junit.jupiter + junit-jupiter-params + test + org.hamcrest hamcrest-all diff --git a/nima/tests/integration/webserver/webserver/src/test/java/io/helidon/nima/tests/integration/server/RoutingTest.java b/nima/tests/integration/webserver/webserver/src/test/java/io/helidon/nima/tests/integration/server/RoutingTest.java index fa5f3e97de0..492e5c9fcab 100644 --- a/nima/tests/integration/webserver/webserver/src/test/java/io/helidon/nima/tests/integration/server/RoutingTest.java +++ b/nima/tests/integration/webserver/webserver/src/test/java/io/helidon/nima/tests/integration/server/RoutingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,23 @@ package io.helidon.nima.tests.integration.server; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + import io.helidon.common.http.Http; import io.helidon.nima.testing.junit5.webserver.ServerTest; import io.helidon.nima.testing.junit5.webserver.SetUpRoute; +import io.helidon.nima.testing.junit5.webserver.SetUpServer; import io.helidon.nima.webclient.http1.Http1Client; import io.helidon.nima.webclient.http1.Http1ClientResponse; +import io.helidon.nima.webclient.http1.Http1ClientRequest; +import io.helidon.nima.webserver.WebServer; import io.helidon.nima.webserver.http.HttpRouting; +import io.helidon.nima.webserver.http.ServerRequest; +import io.helidon.nima.webserver.http.ServerResponse; + +import static io.helidon.common.testing.http.junit5.HttpHeaderMatcher.hasHeader; import org.junit.jupiter.api.Test; @@ -29,39 +40,49 @@ import static org.hamcrest.MatcherAssert.assertThat; @ServerTest -class RoutingTest { - private final Http1Client client; +class RoutingTest extends RoutingTestBase { RoutingTest(Http1Client client) { this.client = client; } - @SetUpRoute - static void routing(HttpRouting.Builder router) { - router.get("/my path", (req, res) -> res.send("done")) - .get("/českáCesta", (req, res) -> res.send("done")); - } - - @Test - void testRouteWithSpace() { - try (Http1ClientResponse response = client.get("/my path").request()) { - - assertThat(response.status(), is(Http.Status.OK_200)); - - String message = response.as(String.class); - assertThat(message, is("done")); - } - } - - @Test - void testRouteWithUtf8() { - try (Http1ClientResponse response = client.get("/českáCesta").request()) { - - assertThat(response.status(), is(Http.Status.OK_200)); - - String message = response.as(String.class); - assertThat(message, is("done")); - } - + @SetUpServer + static void setUp(WebServer.Builder builder) { + builder.addRouting(HttpRouting.builder() + .get("/my path", (req, res) -> res.send("done")) + .get("/českáCesta", (req, res) -> res.send("done")) + // shortcut methods with path matchers + .get("/wildcard_*", (req, res) -> res.send("wildcard_test1")) + .post("/wildcard/*", (req, res) -> res.send("wildcard_test2")) + // shortcut methods with path + .get("/get", (req, res) -> res.send("get")) + .post("/post", (req, res) -> res.send("post")) + .put("/put", (req, res) -> res.send("put")) + .delete("/delete", (req, res) -> res.send("delete")) + .head("/head", (req, res) -> res.send("head")) + .options("/options", (req, res) -> res.send("options")) + .trace("/trace", (req, res) -> res.send("trace")) + .patch("/patch", (req, res) -> res.send("patch")) + .any("/any", (req, res) -> res.send("any")) + // shortcut methods using multiple handlers + .get("/get_multi", RoutingTestBase::multiHandler, (req, res) -> res.send("get_multi")) + .post("/post_multi", RoutingTestBase::multiHandler, (req, res) -> res.send("post_multi")) + .put("/put_multi", RoutingTestBase::multiHandler, (req, res) -> res.send("put_multi")) + .delete("/delete_multi", RoutingTestBase::multiHandler, (req, res) -> res.send("delete_multi")) + .head("/head_multi", RoutingTestBase::multiHandler, (req, res) -> res.send("head_multi")) + .options("/options_multi", + RoutingTestBase::multiHandler, + (req, res) -> res.send("options_multi")) + .trace("/trace_multi", RoutingTestBase::multiHandler, (req, res) -> res.send("trace_multi")) + .patch("/patch_multi", RoutingTestBase::multiHandler, (req, res) -> res.send("patch_multi")) + // shortcut methods with no path pattern + .get((req, res) -> res.send("get_catchall")) + .post((req, res) -> res.send("post_catchall")) + .put((req, res) -> res.send("put_catchall")) + .delete((req, res) -> res.send("delete_catchall")) + .head((req, res) -> res.send("head_catchall")) + .options((req, res) -> res.send("options_catchall")) + .trace((req, res) -> res.send("trace_catchall")) + .patch((req, res) -> res.send("patch_catchall"))); } } diff --git a/nima/tests/integration/webserver/webserver/src/test/java/io/helidon/nima/tests/integration/server/RoutingTestBase.java b/nima/tests/integration/webserver/webserver/src/test/java/io/helidon/nima/tests/integration/server/RoutingTestBase.java new file mode 100644 index 00000000000..3db3beab6bb --- /dev/null +++ b/nima/tests/integration/webserver/webserver/src/test/java/io/helidon/nima/tests/integration/server/RoutingTestBase.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.nima.tests.integration.server; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; + +import io.helidon.common.http.Http; +import io.helidon.nima.testing.junit5.webserver.ServerTest; +import io.helidon.nima.testing.junit5.webserver.SetUpRoute; +import io.helidon.nima.webclient.http1.Http1Client; +import io.helidon.nima.webclient.http1.Http1ClientResponse; +import io.helidon.nima.webclient.http1.Http1ClientRequest; +import io.helidon.nima.webserver.http.HttpRouting; +import io.helidon.nima.webserver.http.ServerRequest; +import io.helidon.nima.webserver.http.ServerResponse; + +import static io.helidon.common.testing.http.junit5.HttpHeaderMatcher.hasHeader; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +// Use by both RoutingTest and RulesTest to share the same test methods +class RoutingTestBase { + private static final Http.HeaderValue MULTI_HANDLER = Http.Header.createCached( + Http.Header.create("X-Multi-Handler"), "true"); + static Http1Client client; + // Functions that will be used to execute http webclient shortcut methods + private static Function get = x -> client.get(x); + private static Function post = x -> client.post(x); + private static Function put = x -> client.put(x); + private static Function delete = x -> client.delete(x); + private static Function head = x -> client.head(x); + private static Function options = x -> client.options(x); + private static Function trace = x -> client.trace(x); + private static Function patch = x -> client.patch(x); + + // Add header to indicate that this is a multi handler routing + static void multiHandler(ServerRequest req, ServerResponse res) { + res.headers().set(MULTI_HANDLER); + res.next(); + } + + @Test + void testRouteWithSpace() { + try (Http1ClientResponse response = client.get("/my path").request()) { + + assertThat(response.status(), is(Http.Status.OK_200)); + + String message = response.as(String.class); + assertThat(message, is("done")); + } + } + + @Test + void testRouteWithUtf8() { + try (Http1ClientResponse response = client.get("/českáCesta").request()) { + + assertThat(response.status(), is(Http.Status.OK_200)); + + String message = response.as(String.class); + assertThat(message, is("done")); + } + } + + @ParameterizedTest + @MethodSource("basic") + void testHttpShortcutMethods(Function request, String path, String responseMessage) { + try (Http1ClientResponse response = request.apply(path).request()) { + assertThat(response.status(), is(Http.Status.OK_200)); + assertThat(response.as(String.class), is(responseMessage)); + } + } + + @ParameterizedTest + @MethodSource("withoutPathPattern") + void testHttpShortcutMethodsWithoutPathPattern(Function request, + String path, + String responseMessage) { + try (Http1ClientResponse response = request.apply(path).request()) { + assertThat(response.status(), is(Http.Status.OK_200)); + assertThat(response.as(String.class), is(responseMessage)); + } + } + + @ParameterizedTest + @MethodSource("withPathMatcher") + void testHttpShortcutMethodsWithPathMatcher(Function request, + String path, + String responseMessage) { + try (Http1ClientResponse response = request.apply(path).request()) { + assertThat(response.status(), is(Http.Status.OK_200)); + assertThat(response.as(String.class), is(responseMessage)); + } + } + + @ParameterizedTest + @MethodSource("withMultiHandlers") + void testHttpShortcutMethodsWithMultiHandlers(Function request, + String path, + String responseMessage) { + try (Http1ClientResponse response = request.apply(path).request()) { + assertThat(response.status(), is(Http.Status.OK_200)); + assertThat(response.headers(), hasHeader(MULTI_HANDLER)); + assertThat(response.as(String.class), is(responseMessage)); + } + } + + private static Stream basic() { + return Stream.of( + arguments(get, "/get", "get"), + arguments(post, "/post", "post"), + arguments(put, "/put", "put"), + arguments(delete, "/delete", "delete"), + arguments(head, "/head", "head"), + arguments(options, "/options", "options"), + arguments(trace, "/trace", "trace"), + arguments(patch, "/patch", "patch"), + arguments(delete, "/any", "any"), + arguments(post, "/any", "any"), + arguments(get, "/any", "any") + ); + } + + private static Stream withoutPathPattern() { + return Stream.of( + arguments(get, "/get_catchall", "get_catchall"), + arguments(post, "/post_catchall", "post_catchall"), + arguments(put, "/put_catchall", "put_catchall"), + arguments(delete, "/delete_catchall", "delete_catchall"), + arguments(head, "/head_catchall", "head_catchall"), + arguments(options, "/options_catchall", "options_catchall"), + arguments(trace, "/trace_catchall", "trace_catchall"), + arguments(patch, "/patch_catchall", "patch_catchall") + ); + } + + private static Stream withPathMatcher() { + return Stream.of( + arguments(get, "/wildcard_any", "wildcard_test1"), + arguments(post, "/wildcard/any", "wildcard_test2") + ); + } + + private static Stream withMultiHandlers() { + return Stream.of( + arguments(get, "/get_multi", "get_multi"), + arguments(post, "/post_multi", "post_multi"), + arguments(put, "/put_multi", "put_multi"), + arguments(delete, "/delete_multi", "delete_multi"), + arguments(head, "/head_multi", "head_multi"), + arguments(options, "/options_multi", "options_multi"), + arguments(trace, "/trace_multi", "trace_multi"), + arguments(patch, "/patch_multi", "patch_multi") + ); + } +} diff --git a/nima/tests/integration/webserver/webserver/src/test/java/io/helidon/nima/tests/integration/server/RulesTest.java b/nima/tests/integration/webserver/webserver/src/test/java/io/helidon/nima/tests/integration/server/RulesTest.java new file mode 100644 index 00000000000..070020b4329 --- /dev/null +++ b/nima/tests/integration/webserver/webserver/src/test/java/io/helidon/nima/tests/integration/server/RulesTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.nima.tests.integration.server; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import io.helidon.common.http.Http; +import io.helidon.nima.testing.junit5.webserver.ServerTest; +import io.helidon.nima.testing.junit5.webserver.SetUpRoute; +import io.helidon.nima.webclient.http1.Http1Client; +import io.helidon.nima.webclient.http1.Http1ClientResponse; +import io.helidon.nima.webclient.http1.Http1ClientRequest; +import io.helidon.nima.webserver.http.HttpRules; +import io.helidon.nima.webserver.http.ServerRequest; +import io.helidon.nima.webserver.http.ServerResponse; + +import static io.helidon.common.testing.http.junit5.HttpHeaderMatcher.hasHeader; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +@ServerTest +class RulesTest extends RoutingTestBase { + + RulesTest(Http1Client client) { + this.client = client; + } + + @SetUpRoute + static void routing(HttpRules rules) { + rules.get("/my path", (req, res) -> res.send("done")) + .get("/českáCesta", (req, res) -> res.send("done")) + // shortcut methods with path matchers + .get("/wildcard_*", (req, res) -> res.send("wildcard_test1")) + .post("/wildcard/*", (req, res) -> res.send("wildcard_test2")) + // shortcut methods with path + .get("/get", (req, res) -> res.send("get")) + .post("/post", (req, res) -> res.send("post")) + .put("/put", (req, res) -> res.send("put")) + .delete("/delete", (req, res) -> res.send("delete")) + .head("/head", (req, res) -> res.send("head")) + .options("/options", (req, res) -> res.send("options")) + .trace("/trace", (req, res) -> res.send("trace")) + .patch("/patch", (req, res) -> res.send("patch")) + .any("/any", (req, res) -> res.send("any")) + // shortcut methods using multiple handlers + .get("/get_multi", RoutingTestBase::multiHandler, (req, res) -> res.send("get_multi")) + .post("/post_multi", RoutingTestBase::multiHandler, (req, res) -> res.send("post_multi")) + .put("/put_multi", RoutingTestBase::multiHandler, (req, res) -> res.send("put_multi")) + .delete("/delete_multi", RoutingTestBase::multiHandler, (req, res) -> res.send("delete_multi")) + .head("/head_multi", RoutingTestBase::multiHandler, (req, res) -> res.send("head_multi")) + .options("/options_multi", RoutingTestBase::multiHandler, (req, res) -> res.send("options_multi")) + .trace("/trace_multi", RoutingTestBase::multiHandler, (req, res) -> res.send("trace_multi")) + .patch("/patch_multi", RoutingTestBase::multiHandler, (req, res) -> res.send("patch_multi")) + // shortcut methods with no path pattern + .get((req, res) -> res.send("get_catchall")) + .post((req, res) -> res.send("post_catchall")) + .put((req, res) -> res.send("put_catchall")) + .delete((req, res) -> res.send("delete_catchall")) + .head((req, res) -> res.send("head_catchall")) + .options((req, res) -> res.send("options_catchall")) + .trace((req, res) -> res.send("trace_catchall")) + .patch((req, res) -> res.send("patch_catchall")); + } +} diff --git a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/HttpRouting.java b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/HttpRouting.java index ff7fdab3df9..35e96a52004 100644 --- a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/HttpRouting.java +++ b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/HttpRouting.java @@ -33,7 +33,6 @@ import io.helidon.common.http.HttpPrologue; import io.helidon.common.http.NotFoundException; import io.helidon.common.http.PathMatcher; -import io.helidon.common.http.PathMatchers; import io.helidon.common.http.RequestException; import io.helidon.nima.webserver.ConnectionContext; import io.helidon.nima.webserver.Routing; @@ -179,6 +178,13 @@ default Builder route(Predicate methodPredicate, PathMatcher pathMa .handler(handler)); } + @Override + default Builder route(Http.Method method, Handler handler) { + return route(HttpRoute.builder() + .methods(method) + .handler(handler)); + } + @Override default Builder get(String pathPattern, Handler... handlers) { for (Handler handler : handlers) { @@ -190,7 +196,55 @@ default Builder get(String pathPattern, Handler... handlers) { @Override default Builder get(Handler... handlers) { for (Handler handler : handlers) { - route(Http.Method.GET, PathMatchers.any(), handler); + route(Http.Method.GET, handler); + } + return this; + } + + @Override + default Builder post(String pathPattern, Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.POST, pathPattern, handler); + } + return this; + } + + @Override + default Builder post(Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.POST, handler); + } + return this; + } + + @Override + default Builder put(String pathPattern, Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.PUT, pathPattern, handler); + } + return this; + } + + @Override + default Builder put(Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.PUT, handler); + } + return this; + } + + @Override + default Builder delete(String pathPattern, Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.DELETE, pathPattern, handler); + } + return this; + } + + @Override + default Builder delete(Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.DELETE, handler); } return this; } @@ -203,6 +257,14 @@ default Builder head(String pathPattern, Handler... handlers) { return this; } + @Override + default Builder head(Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.HEAD, handler); + } + return this; + } + @Override default Builder options(String pathPattern, Handler... handlers) { for (Handler handler : handlers) { @@ -212,30 +274,62 @@ default Builder options(String pathPattern, Handler... handlers) { } @Override - default Builder put(String pathPattern, Handler handler) { - return route(Http.Method.PUT, pathPattern, handler); + default Builder options(Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.OPTIONS, handler); + } + return this; } @Override - default Builder post(String pathPattern, Handler... handlers) { + default Builder trace(String pathPattern, Handler... handlers) { for (Handler handler : handlers) { - route(Http.Method.POST, pathPattern, handler); + route(Http.Method.TRACE, pathPattern, handler); } return this; } @Override - default Builder post(String pathPattern, Handler handler) { - return route(HttpRoute.builder() - .methods(Http.Method.POST) - .path(pathPattern) - .handler(handler)); + default Builder trace(Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.TRACE, handler); + } + return this; } @Override - default Builder any(Handler handler) { - return route(HttpRoute.builder() - .handler(handler)); + default Builder patch(String pathPattern, Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.PATCH, pathPattern, handler); + } + return this; + } + + @Override + default Builder patch(Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.PATCH, handler); + } + return this; + } + + @Override + default Builder any(String pathPattern, Handler... handlers) { + for (Handler handler : handlers) { + route(HttpRoute.builder() + .path(pathPattern) + .handler(handler)); + } + return this; + } + + @Override + default Builder any(Handler... handlers) { + for (Handler handler : handlers) { + route(HttpRoute.builder() + .handler(handler)); + } + return this; } @Override diff --git a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/HttpRules.java b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/HttpRules.java index 31a38a6b2db..e2aeb7972f7 100644 --- a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/HttpRules.java +++ b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http/HttpRules.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ public interface HttpRules { /** * Register a service on sub-path of the current path. * - * @param pathPattern path pattern + * @param pathPattern URI path pattern * @param service service to register * @return updated rules */ @@ -68,9 +68,9 @@ default HttpRules route(Supplier route) { /** * Add a route. * - * @param method method to handle - * @param pathPattern path pattern - * @param handler handler to use + * @param method HTTP method to handle + * @param pathPattern URI path pattern + * @param handler handler to process HTTP request * @return updated rules */ default HttpRules route(Http.Method method, String pathPattern, Handler handler) { @@ -80,9 +80,9 @@ default HttpRules route(Http.Method method, String pathPattern, Handler handler) /** * Add a route. * - * @param method method to handle - * @param pathMatcher path matcher - * @param handler handler + * @param method HTTP method to handle + * @param pathMatcher URI path matcher, see {@link io.helidon.common.http.PathMatchers#create(String)} + * @param handler handler to process HTTP request * @return updated rules */ default HttpRules route(Http.Method method, PathMatcher pathMatcher, Handler handler) { @@ -92,9 +92,9 @@ default HttpRules route(Http.Method method, PathMatcher pathMatcher, Handler han /** * Add a route. * - * @param methodPredicate method predicate, see {@link Http.Method#predicate(io.helidon.common.http.Http.Method...)} - * @param pathMatcher path matcher, see {@link io.helidon.common.http.PathMatchers#create(String)} - * @param handler handler + * @param methodPredicate HTTP method predicate, see {@link Http.Method#predicate(io.helidon.common.http.Http.Method...)} + * @param pathMatcher URI path matcher, see {@link io.helidon.common.http.PathMatchers#create(String)} + * @param handler handler to process HTTP request * @return updated rules */ default HttpRules route(Predicate methodPredicate, PathMatcher pathMatcher, Handler handler) { @@ -105,11 +105,25 @@ default HttpRules route(Predicate methodPredicate, PathMatcher path .build()); } + /** + * Add a route. + * + * @param method HTTP method to handle + * @param handler handler to process HTTP request + * @return updated rules + */ + default HttpRules route(Http.Method method, Handler handler) { + return route(HttpRoute.builder() + .methods(method) + .handler(handler) + .build()); + } + /** * Add a get route. * - * @param pathPattern path pattern - * @param handlers handlers + * @param pathPattern URI path pattern + * @param handlers handlers to process HTTP request * @return updated rules */ default HttpRules get(String pathPattern, Handler... handlers) { @@ -122,12 +136,93 @@ default HttpRules get(String pathPattern, Handler... handlers) { /** * Add a get route. * - * @param handlers handlers + * @param handlers handlers to process HTTP request * @return updated rules */ default HttpRules get(Handler... handlers) { for (Handler handler : handlers) { - route(Http.Method.GET, PathMatchers.any(), handler); + route(Http.Method.GET, handler); + } + return this; + } + + /** + * Add a post route. + * + * @param pathPattern URI path pattern + * @param handlers handlers to process HTTP request + * @return updated rules + */ + default HttpRules post(String pathPattern, Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.POST, pathPattern, handler); + } + return this; + } + + /** + * Add a post route. + * + * @param handlers handlers to process HTTP request + * @return updated rules + */ + default HttpRules post(Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.POST, handler); + } + return this; + } + + /** + * Add a put route. + * + * @param pathPattern URI path pattern + * @param handlers handlers to process HTTP request + * @return updated rules + */ + default HttpRules put(String pathPattern, Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.PUT, pathPattern, handler); + } + return this; + } + + /** + * Add a put route. + * + * @param handlers handlers to process HTTP request + * @return updated rules + */ + default HttpRules put(Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.PUT, handler); + } + return this; + } + + /** + * Add a delete route. + * + * @param pathPattern URI path pattern + * @param handlers handlers to process HTTP request + * @return updated rules + */ + default HttpRules delete(String pathPattern, Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.DELETE, pathPattern, handler); + } + return this; + } + + /** + * Add a delete route. + * + * @param handlers handlers to process HTTP request + * @return updated rules + */ + default HttpRules delete(Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.DELETE, handler); } return this; } @@ -135,8 +230,8 @@ default HttpRules get(Handler... handlers) { /** * Add a head route. * - * @param pathPattern path pattern - * @param handlers handlers + * @param pathPattern URI path pattern + * @param handlers handlers to process HTTP request * @return updated rules */ default HttpRules head(String pathPattern, Handler... handlers) { @@ -147,124 +242,124 @@ default HttpRules head(String pathPattern, Handler... handlers) { } /** - * Add an options route. + * Add a head route. * - * @param pathPattern path pattern - * @param handlers handlers + * @param handlers handlers to process HTTP request * @return updated rules */ - default HttpRules options(String pathPattern, Handler... handlers) { + default HttpRules head(Handler... handlers) { for (Handler handler : handlers) { - route(Http.Method.OPTIONS, pathPattern, handler); + route(Http.Method.HEAD, handler); } return this; } /** - * Add a put route. + * Add an options route. * - * @param pathPattern path pattern - * @param handler handler + * @param pathPattern URI path pattern + * @param handlers handlers to process HTTP request * @return updated rules */ - default HttpRules put(String pathPattern, Handler handler) { - return route(Http.Method.PUT, pathPattern, handler); + default HttpRules options(String pathPattern, Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.OPTIONS, pathPattern, handler); + } + return this; } /** - * Add a post route. + * Add an options route. * - * @param pathPattern path pattern - * @param handlers handlers + * @param handlers handlers to process HTTP request * @return updated rules */ - default HttpRules post(String pathPattern, Handler... handlers) { + default HttpRules options(Handler... handlers) { for (Handler handler : handlers) { - route(Http.Method.POST, pathPattern, handler); + route(Http.Method.OPTIONS, handler); } return this; } /** - * Add a post route. + * Add an options route. * - * @param pathPattern path pattern - * @param handler handler - * @return updated builder + * @param pathPattern URI path pattern + * @param handlers handlers to process HTTP request + * @return updated rules */ - default HttpRules post(String pathPattern, Handler handler) { - return route(HttpRoute.builder() - .methods(Http.Method.POST) - .path(pathPattern) - .handler(handler)); + default HttpRules trace(String pathPattern, Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.TRACE, pathPattern, handler); + } + return this; } /** - * Add a route that executes on any HTTP method and any path. + * Add an options route. * - * @param handler handler + * @param handlers handlers to process HTTP request * @return updated rules */ - default HttpRules any(Handler handler) { - return route(HttpRoute.builder() - .handler(handler)); + default HttpRules trace(Handler... handlers) { + for (Handler handler : handlers) { + route(Http.Method.TRACE, handler); + } + return this; } /** - * Add a route that executes on any HTTP method and any path. + * Add an options route. * - * @param pathPattern path pattern - * @param handlers handlers + * @param pathPattern URI path pattern + * @param handlers handlers to process HTTP request * @return updated rules */ - default HttpRules any(String pathPattern, Handler... handlers) { + default HttpRules patch(String pathPattern, Handler... handlers) { for (Handler handler : handlers) { - route(HttpRoute.builder() - .path(pathPattern) - .handler(handler)); + route(Http.Method.PATCH, pathPattern, handler); } return this; } /** - * Add a delete route. + * Add an options route. * - * @param handlers handlers + * @param handlers handlers to process HTTP request * @return updated rules */ - default HttpRules delete(Handler... handlers) { + default HttpRules patch(Handler... handlers) { for (Handler handler : handlers) { - route(HttpRoute.builder() - .methods(Http.Method.DELETE) - .handler(handler)); + route(Http.Method.PATCH, handler); } return this; } /** - * Add a delete route. + * Add a route that executes on any HTTP method and any path. * - * @param pathPattern path pattern to register the handler(s) on - * @param handlers handlers + * @param pathPattern URI path pattern + * @param handlers handlers to process HTTP request * @return updated rules */ - default HttpRules delete(String pathPattern, Handler... handlers) { + default HttpRules any(String pathPattern, Handler... handlers) { for (Handler handler : handlers) { - route(Http.Method.DELETE, pathPattern, handler); + route(HttpRoute.builder() + .path(pathPattern) + .handler(handler)); } return this; } /** - * Add a put route. + * Add a route that executes on any HTTP method and any path. * - * @param handlers handlers + * @param handlers handlers to process HTTP request * @return updated rules */ - default HttpRules put(Handler... handlers) { + default HttpRules any(Handler... handlers) { for (Handler handler : handlers) { route(HttpRoute.builder() - .methods(Http.Method.PUT) .handler(handler)); } return this; @@ -273,8 +368,8 @@ default HttpRules put(Handler... handlers) { /** * Add a route. * - * @param method method to handle - * @param pathPattern path pattern + * @param method HTTP method to handle + * @param pathPattern URI path pattern * @param handler handler as a consumer of {@link ServerRequest} * @return updated builder */ @@ -288,8 +383,8 @@ default HttpRules route(Http.Method method, String pathPattern, Consumer hand .handler(Handler.create(handler))); } - } diff --git a/nima/webserver/webserver/src/test/java/io/helidon/nima/webserver/http/HttpRoutingTest.java b/nima/webserver/webserver/src/test/java/io/helidon/nima/webserver/http/HttpRoutingTest.java new file mode 100644 index 00000000000..279127bf9e0 --- /dev/null +++ b/nima/webserver/webserver/src/test/java/io/helidon/nima/webserver/http/HttpRoutingTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.nima.webserver.http; + +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import io.helidon.common.http.Http; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +class HttpRoutingTest { + private static final Handler handler = (req, res) -> res.send("done"); + + // Functions that will be used to execute Routing http method shortcuts + private static Function get = x -> + (FakeHttpRoutingBuilder) (x == null ? new FakeHttpRoutingBuilder().get(handler) : new FakeHttpRoutingBuilder().get(x, handler)); + private static Function post = x -> + (FakeHttpRoutingBuilder) (x == null ? new FakeHttpRoutingBuilder().post(handler) : new FakeHttpRoutingBuilder().post(x, handler)); + private static Function put = x -> + (FakeHttpRoutingBuilder) (x == null ? new FakeHttpRoutingBuilder().put(handler) : new FakeHttpRoutingBuilder().put(x, handler)); + private static Function delete = x -> + (FakeHttpRoutingBuilder) (x == null ? new FakeHttpRoutingBuilder().delete(handler) : new FakeHttpRoutingBuilder().delete(x, handler)); + private static Function head = x -> + (FakeHttpRoutingBuilder) (x == null ? new FakeHttpRoutingBuilder().head(handler) : new FakeHttpRoutingBuilder().head(x, handler)); + private static Function options = x -> + (FakeHttpRoutingBuilder) (x == null ? new FakeHttpRoutingBuilder().options(handler) : new FakeHttpRoutingBuilder().options(x, handler)); + private static Function trace = x -> + (FakeHttpRoutingBuilder) (x == null ? new FakeHttpRoutingBuilder().trace(handler) : new FakeHttpRoutingBuilder().trace(x, handler)); + private static Function patch = x -> + (FakeHttpRoutingBuilder) (x == null ? new FakeHttpRoutingBuilder().patch(handler) : new FakeHttpRoutingBuilder().patch(x, handler)); + + @ParameterizedTest + @MethodSource("httpMethodShortcut") + void testHttpMethodShortcut(Http.Method method, + Function request) { + FakeHttpRoutingBuilder rule = request.apply(null); + assertThat(rule.getMethod(), is(method)); + assertThat(rule.getPathPattern(), is(nullValue())); + assertThat(rule.getHandler(), is(handler)); + } + + @ParameterizedTest + @MethodSource("httpMethodShortcutWithPathPattern") + void testHttpMethodShortcutWithPathPattern(Http.Method method, + Function request, + String pathPattern) { + FakeHttpRoutingBuilder rule = request.apply(pathPattern); + assertThat(rule.getMethod(), is(method)); + assertThat(rule.getPathPattern(), is(pathPattern)); + assertThat(rule.getHandler(), is(handler)); + + } + + private static Stream httpMethodShortcut() { + return Stream.of( + arguments(Http.Method.GET, get), + arguments(Http.Method.POST, post), + arguments(Http.Method.PUT, put), + arguments(Http.Method.DELETE, delete), + arguments(Http.Method.HEAD, head), + arguments(Http.Method.OPTIONS, options), + arguments(Http.Method.TRACE, trace), + arguments(Http.Method.PATCH, patch) + ); + } + + private static Stream httpMethodShortcutWithPathPattern() { + return Stream.of( + arguments(Http.Method.GET, get, "/get"), + arguments(Http.Method.POST, post, "/post"), + arguments(Http.Method.PUT, put, "/put"), + arguments(Http.Method.DELETE, delete, "/delete"), + arguments(Http.Method.HEAD, head, "/head"), + arguments(Http.Method.OPTIONS, options, "/options"), + arguments(Http.Method.TRACE, trace, "/trace"), + arguments(Http.Method.PATCH, patch, "/patch") + ); + } + + private static class FakeHttpRoutingBuilder implements HttpRouting.Builder { + private Http.Method method; + private String pathPattern; + private Handler handler; + + public Http.Method getMethod() { + return method; + } + + public String getPathPattern() { + return pathPattern; + } + + public Handler getHandler() { + return handler; + } + + @Override + public HttpRouting.Builder register(Supplier... service) { + return null; + } + + @Override + public HttpRouting.Builder register(String path, Supplier... service) { + return null; + } + + @Override + public HttpRouting.Builder route(HttpRoute route) { + return null; + } + + @Override + public HttpRouting.Builder addFilter(Filter filter) { + return null; + } + + @Override + public HttpRouting.Builder addFeature(Supplier feature) { + return null; + } + + @Override + public HttpRouting.Builder error(Class exceptionClass, ErrorHandler handler) { + return null; + } + + @Override + public HttpRouting.Builder maxReRouteCount(int maxReRouteCount) { + return null; + } + + @Override + public HttpRouting.Builder security(HttpSecurity security) { + return null; + } + + @Override + public HttpRouting build() { + return null; + } + + @Override + public HttpRouting.Builder route(Http.Method method, String pathPattern, Handler handler) { + this.method = method; + this.pathPattern = pathPattern; + this.handler = handler; + return route(HttpRoute.builder().methods(method).path(pathPattern).handler(handler)); + } + + @Override + public HttpRouting.Builder route(Http.Method method, Handler handler) { + this.method = method; + this.pathPattern = null; + this.handler = handler; + return route(HttpRoute.builder().methods(method).handler(handler)); + } + } +} diff --git a/nima/webserver/webserver/src/test/java/io/helidon/nima/webserver/http/HttpRulesTest.java b/nima/webserver/webserver/src/test/java/io/helidon/nima/webserver/http/HttpRulesTest.java new file mode 100644 index 00000000000..a3efb768c2f --- /dev/null +++ b/nima/webserver/webserver/src/test/java/io/helidon/nima/webserver/http/HttpRulesTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.nima.webserver.http; + +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import io.helidon.common.http.Http; +import io.helidon.common.http.PathMatchers; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +class HttpRulesTest { + private static final Handler handler = (req, res) -> res.send("done"); + + // Functions that will be used to execute Rule http method shortcuts + private static Function get = x -> + (FakeHttpRules) (x == null ? new FakeHttpRules().get(handler) : new FakeHttpRules().get(x, handler)); + private static Function post = x -> + (FakeHttpRules) (x == null ? new FakeHttpRules().post(handler) : new FakeHttpRules().post(x, handler)); + private static Function put = x -> + (FakeHttpRules) (x == null ? new FakeHttpRules().put(handler) : new FakeHttpRules().put(x, handler)); + private static Function delete = x -> + (FakeHttpRules) (x == null ? new FakeHttpRules().delete(handler) : new FakeHttpRules().delete(x, handler)); + private static Function head = x -> + (FakeHttpRules) (x == null ? new FakeHttpRules().head(handler) : new FakeHttpRules().head(x, handler)); + private static Function options = x -> + (FakeHttpRules) (x == null ? new FakeHttpRules().options(handler) : new FakeHttpRules().options(x, handler)); + private static Function trace = x -> + (FakeHttpRules) (x == null ? new FakeHttpRules().trace(handler) : new FakeHttpRules().trace(x, handler)); + private static Function patch = x -> + (FakeHttpRules) (x == null ? new FakeHttpRules().patch(handler) : new FakeHttpRules().patch(x, handler)); + + @ParameterizedTest + @MethodSource("httpMethodShortcut") + void testHttpMethodShortcut(Http.Method method, + Function request) { + FakeHttpRules rule = request.apply(null); + assertThat(rule.getMethod(), is(method)); + assertThat(rule.getPathPattern(), is(nullValue())); + assertThat(rule.getHandler(), is(handler)); + } + + @ParameterizedTest + @MethodSource("httpMethodShortcutWithPathPattern") + void testHttpMethodShortcutWithPathPattern(Http.Method method, + Function request, + String pathPattern) { + FakeHttpRules rule = request.apply(pathPattern); + assertThat(rule.getMethod(), is(method)); + assertThat(rule.getPathPattern(), is(pathPattern)); + assertThat(rule.getHandler(), is(handler)); + + } + + private static Stream httpMethodShortcut() { + return Stream.of( + arguments(Http.Method.GET, get), + arguments(Http.Method.POST, post), + arguments(Http.Method.PUT, put), + arguments(Http.Method.DELETE, delete), + arguments(Http.Method.HEAD, head), + arguments(Http.Method.OPTIONS, options), + arguments(Http.Method.TRACE, trace), + arguments(Http.Method.PATCH, patch) + ); + } + + private static Stream httpMethodShortcutWithPathPattern() { + return Stream.of( + arguments(Http.Method.GET, get, "/get"), + arguments(Http.Method.POST, post, "/post"), + arguments(Http.Method.PUT, put, "/put"), + arguments(Http.Method.DELETE, delete, "/delete"), + arguments(Http.Method.HEAD, head, "/head"), + arguments(Http.Method.OPTIONS, options, "/options"), + arguments(Http.Method.TRACE, trace, "/trace"), + arguments(Http.Method.PATCH, patch, "/patch") + ); + } + + private static class FakeHttpRules implements HttpRules { + private Http.Method method; + private String pathPattern; + private Handler handler; + + public Http.Method getMethod() { + return method; + } + + public String getPathPattern() { + return pathPattern; + } + + public Handler getHandler() { + return handler; + } + + @Override + public HttpRules register(Supplier... service) { + return null; + } + + @Override + public HttpRules register(String pathPattern, Supplier... service) { + return null; + } + + @Override + public HttpRules route(HttpRoute route) { + return null; + } + + @Override + public HttpRules route(Http.Method method, String pathPattern, Handler handler) { + this.method = method; + this.pathPattern = pathPattern; + this.handler = handler; + return route(Http.Method.predicate(method), PathMatchers.create(pathPattern), handler); + } + + @Override + public HttpRules route(Http.Method method, Handler handler) { + this.method = method; + this.pathPattern = null; + this.handler = handler; + return route(HttpRoute.builder() + .methods(method) + .handler(handler)); + } + } + +}