diff --git a/examples/microprofile/openapi/basic/README.md b/examples/microprofile/openapi/basic/README.md
new file mode 100644
index 000000000..bcdf6e213
--- /dev/null
+++ b/examples/microprofile/openapi/basic/README.md
@@ -0,0 +1,33 @@
+# Helidon MP OpenAPI Example
+
+This example shows a simple greeting application, similar to the one from the
+Helidon MP QuickStart, enhanced with OpenAPI support.
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-examples-microprofile-openapi-basic.jar
+```
+
+Try the endpoints:
+
+```shell
+curl -X GET http://localhost:8080/greet
+#Output: {"message":"Hello World!"}
+
+curl -X GET http://localhost:8080/greet/Joe
+#Output: {"message":"Hello Joe!"}
+
+curl -X PUT -H "Content-Type: application/json" -d '{"message" : "Hola"}' http://localhost:8080/greet/greeting
+
+curl -X GET http://localhost:8080/greet/Jose
+#Output: {"message":"Hola Jose!"}
+
+curl -X GET http://localhost:8080/openapi
+#Output: [lengthy OpenAPI document]
+```
+The output describes not only then endpoints from `GreetResource` but
+also one contributed by the `SimpleAPIModelReader`.
+
+
diff --git a/examples/microprofile/openapi/basic/pom.xml b/examples/microprofile/openapi/basic/pom.xml
new file mode 100644
index 000000000..1fe420dd4
--- /dev/null
+++ b/examples/microprofile/openapi/basic/pom.xml
@@ -0,0 +1,99 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.2.0-SNAPSHOT
+
+
+ io.helidon.examples.microprofile
+ helidon-examples-microprofile-openapi-basic
+ 1.0.0-SNAPSHOT
+ Helidon Examples Microprofile OpenAPI Basic
+
+
+
+ io.helidon.microprofile.bundles
+ helidon-microprofile-core
+
+
+ io.helidon.microprofile.openapi
+ helidon-microprofile-openapi
+
+
+ io.helidon.logging
+ helidon-logging-jul
+ runtime
+
+
+ org.glassfish.jersey.media
+ jersey-media-json-binding
+ runtime
+
+
+ io.smallrye
+ jandex
+ runtime
+ true
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.microprofile.testing
+ helidon-microprofile-testing-junit5
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+
+
diff --git a/examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/GreetResource.java b/examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/GreetResource.java
similarity index 97%
rename from examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/GreetResource.java
rename to examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/GreetResource.java
index f40c6fd6e..1c27a0f5d 100644
--- a/examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/GreetResource.java
+++ b/examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/GreetResource.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.helidon.microprofile.examples.openapi;
+package io.helidon.microprofile.examples.openapi.basic;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
@@ -50,7 +50,7 @@
*
* Note that the output will include not only the annotated endpoints from this
* class but also an endpoint added by the
- * {@link io.helidon.microprofile.examples.openapi.internal.SimpleAPIModelReader}.
+ * {@link io.helidon.microprofile.examples.openapi.basic.internal.SimpleAPIModelReader}.
*
* The message is returned as a JSON object.
*/
diff --git a/examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/GreetingMessage.java b/examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/GreetingMessage.java
similarity index 96%
rename from examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/GreetingMessage.java
rename to examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/GreetingMessage.java
index 752e9b132..a3b099697 100644
--- a/examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/GreetingMessage.java
+++ b/examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/GreetingMessage.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.helidon.microprofile.examples.openapi;
+package io.helidon.microprofile.examples.openapi.basic;
/**
* POJO defining the greeting message content.
diff --git a/examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/GreetingProvider.java b/examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/GreetingProvider.java
similarity index 96%
rename from examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/GreetingProvider.java
rename to examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/GreetingProvider.java
index 515214ac6..c06658227 100644
--- a/examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/GreetingProvider.java
+++ b/examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/GreetingProvider.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.helidon.microprofile.examples.openapi;
+package io.helidon.microprofile.examples.openapi.basic;
import java.util.concurrent.atomic.AtomicReference;
diff --git a/examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/internal/SimpleAPIFilter.java b/examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/internal/SimpleAPIFilter.java
similarity index 95%
rename from examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/internal/SimpleAPIFilter.java
rename to examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/internal/SimpleAPIFilter.java
index 60e872b80..d4316ff39 100644
--- a/examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/internal/SimpleAPIFilter.java
+++ b/examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/internal/SimpleAPIFilter.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.helidon.microprofile.examples.openapi.internal;
+package io.helidon.microprofile.examples.openapi.basic.internal;
import java.util.Map;
diff --git a/examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/internal/SimpleAPIModelReader.java b/examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/internal/SimpleAPIModelReader.java
similarity index 97%
rename from examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/internal/SimpleAPIModelReader.java
rename to examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/internal/SimpleAPIModelReader.java
index 6d60bfcd8..6eecf9b1b 100644
--- a/examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/internal/SimpleAPIModelReader.java
+++ b/examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/internal/SimpleAPIModelReader.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package io.helidon.microprofile.examples.openapi.internal;
+package io.helidon.microprofile.examples.openapi.basic.internal;
import org.eclipse.microprofile.openapi.OASFactory;
import org.eclipse.microprofile.openapi.OASModelReader;
diff --git a/examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/internal/package-info.java b/examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/internal/package-info.java
similarity index 91%
rename from examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/internal/package-info.java
rename to examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/internal/package-info.java
index e794f83fc..3104cd77b 100644
--- a/examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/internal/package-info.java
+++ b/examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/internal/package-info.java
@@ -17,4 +17,4 @@
/**
* Internal classes supporting Helidon MP OpenAPI.
*/
-package io.helidon.microprofile.examples.openapi.internal;
+package io.helidon.microprofile.examples.openapi.basic.internal;
diff --git a/examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/package-info.java b/examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/package-info.java
similarity index 92%
rename from examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/package-info.java
rename to examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/package-info.java
index 2d75af1aa..f3c2fe887 100644
--- a/examples/microprofile/openapi/src/main/java/io/helidon/microprofile/examples/openapi/package-info.java
+++ b/examples/microprofile/openapi/basic/src/main/java/io/helidon/microprofile/examples/openapi/basic/package-info.java
@@ -17,4 +17,4 @@
/**
* Helidon MicroProfile OpenAPI example.
*/
-package io.helidon.microprofile.examples.openapi;
+package io.helidon.microprofile.examples.openapi.basic;
diff --git a/examples/microprofile/openapi/src/main/resources/META-INF/beans.xml b/examples/microprofile/openapi/basic/src/main/resources/META-INF/beans.xml
similarity index 100%
rename from examples/microprofile/openapi/src/main/resources/META-INF/beans.xml
rename to examples/microprofile/openapi/basic/src/main/resources/META-INF/beans.xml
diff --git a/examples/microprofile/openapi/src/main/resources/META-INF/microprofile-config.properties b/examples/microprofile/openapi/basic/src/main/resources/META-INF/microprofile-config.properties
similarity index 86%
rename from examples/microprofile/openapi/src/main/resources/META-INF/microprofile-config.properties
rename to examples/microprofile/openapi/basic/src/main/resources/META-INF/microprofile-config.properties
index 393a125ef..8517ada2b 100644
--- a/examples/microprofile/openapi/src/main/resources/META-INF/microprofile-config.properties
+++ b/examples/microprofile/openapi/basic/src/main/resources/META-INF/microprofile-config.properties
@@ -21,5 +21,5 @@ app.greeting=Hello
server.port=8080
server.host=0.0.0.0
-mp.openapi.filter=io.helidon.microprofile.examples.openapi.internal.SimpleAPIFilter
-mp.openapi.model.reader=io.helidon.microprofile.examples.openapi.internal.SimpleAPIModelReader
+mp.openapi.filter=io.helidon.microprofile.examples.openapi.basic.internal.SimpleAPIFilter
+mp.openapi.model.reader=io.helidon.microprofile.examples.openapi.basic.internal.SimpleAPIModelReader
diff --git a/examples/microprofile/openapi/src/main/resources/logging.properties b/examples/microprofile/openapi/basic/src/main/resources/logging.properties
similarity index 100%
rename from examples/microprofile/openapi/src/main/resources/logging.properties
rename to examples/microprofile/openapi/basic/src/main/resources/logging.properties
diff --git a/examples/microprofile/openapi/src/test/java/io/helidon/microprofile/examples/openapi/MainTest.java b/examples/microprofile/openapi/basic/src/test/java/io/helidon/microprofile/examples/openapi/basic/MainTest.java
similarity index 95%
rename from examples/microprofile/openapi/src/test/java/io/helidon/microprofile/examples/openapi/MainTest.java
rename to examples/microprofile/openapi/basic/src/test/java/io/helidon/microprofile/examples/openapi/basic/MainTest.java
index 402f4572b..1a704b0d0 100644
--- a/examples/microprofile/openapi/src/test/java/io/helidon/microprofile/examples/openapi/MainTest.java
+++ b/examples/microprofile/openapi/basic/src/test/java/io/helidon/microprofile/examples/openapi/basic/MainTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package io.helidon.microprofile.examples.openapi;
+package io.helidon.microprofile.examples.openapi.basic;
-import io.helidon.microprofile.examples.openapi.internal.SimpleAPIModelReader;
+import io.helidon.microprofile.examples.openapi.basic.internal.SimpleAPIModelReader;
import io.helidon.microprofile.testing.junit5.HelidonTest;
import jakarta.inject.Inject;
diff --git a/examples/microprofile/openapi/src/test/resources/META-INF/microprofile-config.properties b/examples/microprofile/openapi/basic/src/test/resources/META-INF/microprofile-config.properties
similarity index 100%
rename from examples/microprofile/openapi/src/test/resources/META-INF/microprofile-config.properties
rename to examples/microprofile/openapi/basic/src/test/resources/META-INF/microprofile-config.properties
diff --git a/examples/microprofile/openapi/expanded-jandex/README.md b/examples/microprofile/openapi/expanded-jandex/README.md
new file mode 100644
index 000000000..dc10ffc97
--- /dev/null
+++ b/examples/microprofile/openapi/expanded-jandex/README.md
@@ -0,0 +1,74 @@
+# Helidon MP OpenAPI Expanded Jandex Indexing Example
+
+This example shows a simple greeting application, similar to the basic OpenAPI MP example, but with proper handling of types from _outside_ this project that are used in resource method signatures.
+
+### Why might I need expanded Jandex handling?
+Many Helidon MP applications, including those created by the Helidon command line and the starter, run the Jandex plug-in to create an index file for the types defined in the project.
+Helidon's OpenAPI support leverages much of SmallRye's OpenAPI implementation and SmallRye scans the resource methods in the application to generate an OpenAPI model. This scan uses the Jandex index file directly to get information about the types used in resource methods, and SmallRye uses this information in preparing the OpenAPI document.
+
+If a resource method's signature uses a type _outside_ the current project SmallRye's scan cannot usually find the type information because the Jandex index created by the typical project describes only the types defined in the current project, not types in any dependencies.
+
+### How does this affect my application?
+In such cases, SmallRye logs warning messages about the types it cannot find. This can clutter the application output.
+
+More importantly, the resulting OpenAPI model is less robust. When SmallRye cannot find a type it has no choice but to model that type as an opaque `Object`. The resulting OpenAPI model and document are less useful than they could be.
+
+Note that if the dependency contains _its own_ Jandex index for its own types then SmallRye can find information about those types.
+
+### What does this example do?
+This example shows how to expand the Jandex index built by your project to include selected types from _outside_ the project.
+There are two key differences from the basic OpenAPI example:
+
+1. The new resource method `mediaType`added to `GreetingResource` returns a `MediaType` from Jarkarta RESTful Web Wervices, a type from outside this project.
+2. The `pom.xml` configures its invocation of the Jandex plug-in to include that type in the generated Jandex index.
+
+The example `pom.xml` adds a single type from a single dependency to the index. If you need to for your application add more dependencies and more include entries.
+
+## Build and run
+
+```shell
+mvn package
+java -jar target/helidon-examples-microprofile-openapi-expanded-jandex.jar
+```
+
+Try the endpoints. These are the same actions supported by the basic OpenAPI example:
+
+```shell
+curl -X GET http://localhost:8080/greet
+#Output: {"message":"Hello World!"}
+
+curl -X GET http://localhost:8080/greet/Joe
+#Output: {"message":"Hello Joe!"}
+
+curl -X PUT -H "Content-Type: application/json" -d '{"message" : "Hola"}' http://localhost:8080/greet/greeting
+
+curl -X GET http://localhost:8080/greet/Jose
+#Output: {"message":"Hola Jose!"}
+
+curl -X GET http://localhost:8080/openapi
+#Output: [lengthy OpenAPI document]
+```
+After running the last command notice that the OpenAPI document's `components/schemas` section contains a declaration for `MediaType` that includes the `MediaType` properties (`type`, `subtype`, etc.). Further, the document's entry for `paths/greet/mediatype:get` declares the response content by referring to that exact `#/components/schemas/MediaType` entry.
+
+## Building and running without expanded Jandex processing
+You can simulate the build without the expanded Jandex processing to see the warning SmallRye logs during startup and to see the difference in the generated OpenAPI document.
+When you build the project specify the `normal-jandex` Maven profile and skip the unit tests.
+
+```shell
+mvn clean package -Pnormal-jandex -DskipTests
+java -jar target/helidon-examples-microprofile-openapi-expanded-jandex.jar
+```
+As the app starts notice a message like the following in the app output:
+```list
+WARN io.smallrye.openapi.runtime.scanner Thread[#1,main,5,main]: SROAP04005: Could not find schema class in index: jakarta.ws.rs.core.MediaType
+```
+
+Retrieve the OpenAPI document:
+```shell
+curl -X GET http://localhost:8080/openapi
+```
+Notice two things about the output:
+1. The `components/schemas` section contains no entry for `MediaType`. That's expected given that SmallRye could not find the type information for it.
+2. The response content for `paths/greet/mediatype:get` is simply `object`.
+
+ While it is true that the response is an `Object` this version of the OpenAPI document for your app is less useful than the earlier one because of the missing type information for `MediaType`.
\ No newline at end of file
diff --git a/examples/microprofile/openapi/expanded-jandex/pom.xml b/examples/microprofile/openapi/expanded-jandex/pom.xml
new file mode 100644
index 000000000..92a445810
--- /dev/null
+++ b/examples/microprofile/openapi/expanded-jandex/pom.xml
@@ -0,0 +1,139 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-mp
+ 4.2.0-SNAPSHOT
+
+
+ io.helidon.examples.microprofile
+ helidon-examples-microprofile-openapi-expanded-jandex
+ 1.0.0-SNAPSHOT
+ Helidon Examples Microprofile OpenAPI with expanded Jandex handling
+
+
+
+ io.helidon.microprofile.bundles
+ helidon-microprofile-core
+
+
+ io.helidon.microprofile.openapi
+ helidon-microprofile-openapi
+
+
+ io.helidon.logging
+ helidon-logging-jul
+ runtime
+
+
+ org.glassfish.jersey.media
+ jersey-media-json-binding
+ runtime
+
+
+ io.smallrye
+ jandex
+ runtime
+ true
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.microprofile.testing
+ helidon-microprofile-testing-junit5
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+
+
+ **/MediaType.class
+
+
+
+
+
+
+
+
+
+
+
+
+
+ normal-jandex
+
+
+
+ io.smallrye
+ jandex-maven-plugin
+
+
+ make-index
+
+
+
+
+
+
+
+
+
diff --git a/examples/microprofile/openapi/expanded-jandex/src/main/java/io/helidon/microprofile/examples/openapi/expandedJandex/GreetResource.java b/examples/microprofile/openapi/expanded-jandex/src/main/java/io/helidon/microprofile/examples/openapi/expandedJandex/GreetResource.java
new file mode 100644
index 000000000..163aa583a
--- /dev/null
+++ b/examples/microprofile/openapi/expanded-jandex/src/main/java/io/helidon/microprofile/examples/openapi/expandedJandex/GreetResource.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2024 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.microprofile.examples.openapi.expandedJandex;
+
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.eclipse.microprofile.openapi.annotations.Operation;
+import org.eclipse.microprofile.openapi.annotations.media.Content;
+import org.eclipse.microprofile.openapi.annotations.media.ExampleObject;
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
+
+/**
+ * A simple JAX-RS resource with OpenAPI annotations to greet you. Examples:
+ *
+ * Get default greeting message:
+ * curl -X GET http://localhost:8080/greet
+ *
+ * Get greeting message for Joe:
+ * curl -X GET http://localhost:8080/greet/Joe
+ *
+ * Change greeting
+ * curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:8080/greet/greeting
+ *
+ * Get OpenAPI document for the endpoints
+ * curl -X GET http://localhost:8080/openapi
+ *
+ * Note that the output will include not only the annotated endpoints from this
+ * class but also an endpoint added by the
+ * {@link io.helidon.microprofile.examples.openapi.expanded.jandex.internal.SimpleAPIModelReader}.
+ *
+ * The message is returned as a JSON object.
+ */
+@Path("/greet")
+@RequestScoped
+public class GreetResource {
+
+ /**
+ * The greeting message provider.
+ */
+ private final GreetingProvider greetingProvider;
+
+ /**
+ * Using constructor injection to get a configuration property.
+ * By default this gets the value from META-INF/microprofile-config
+ *
+ * @param greetingConfig the configured greeting message
+ */
+ @Inject
+ public GreetResource(GreetingProvider greetingConfig) {
+ this.greetingProvider = greetingConfig;
+ }
+
+ /**
+ * Return a worldly greeting message.
+ *
+ * @return {@link GreetingMessage}
+ */
+ @GET
+ @Operation(summary = "Returns a generic greeting",
+ description = "Greets the user generically")
+ @APIResponse(description = "Simple JSON containing the greeting",
+ content = @Content(mediaType = "application/json",
+ schema = @Schema(implementation = GreetingMessage.class)))
+ @Produces(MediaType.APPLICATION_JSON)
+ public GreetingMessage getDefaultMessage() {
+ return createResponse("World");
+ }
+
+ /**
+ * Return a greeting message using the name that was provided.
+ *
+ * @param name the name to greet
+ * @return {@link GreetingMessage}
+ */
+ @Path("/{name}")
+ @GET
+ @Operation(summary = "Returns a personalized greeting")
+ @APIResponse(description = "Simple JSON containing the greeting",
+ content = @Content(mediaType = "application/json",
+ schema = @Schema(implementation = GreetingMessage.class)))
+ @Produces(MediaType.APPLICATION_JSON)
+ public GreetingMessage getMessage(@PathParam("name") String name) {
+ return createResponse(name);
+ }
+
+ /**
+ * Set the greeting to use in future messages.
+ *
+ * @param message JSON containing the new greeting
+ * @return {@link Response}
+ */
+ @Path("/greeting")
+ @PUT
+ @Operation(summary = "Set the greeting prefix",
+ description = "Permits the client to set the prefix part of the greeting (\"Hello\")")
+ @RequestBody(
+ name = "greeting",
+ description = "Conveys the new greeting prefix to use in building greetings",
+ content = @Content(
+ mediaType = "application/json",
+ schema = @Schema(implementation = GreetingMessage.class),
+ examples = @ExampleObject(
+ name = "greeting",
+ summary = "Example greeting message to update",
+ value = "{\"greeting\": \"New greeting message\"}")))
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response updateGreeting(GreetingMessage message) {
+ if (message.getMessage() == null) {
+ GreetingMessage entity = new GreetingMessage("No greeting provided");
+ return Response.status(Response.Status.BAD_REQUEST).entity(entity).build();
+ }
+
+ greetingProvider.setMessage(message.getMessage());
+ return Response.status(Response.Status.NO_CONTENT).build();
+ }
+
+ /**
+ * Simplistic method to return a type from outside this project.
+ *
+ * @return {@link jakarta.ws.rs.core.MediaType}
+ */
+ @Path("/mediatype")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public MediaType mediatype() {
+ return MediaType.APPLICATION_JSON_TYPE;
+ }
+
+ private GreetingMessage createResponse(String who) {
+ String msg = String.format("%s %s!", greetingProvider.getMessage(), who);
+
+ return new GreetingMessage(msg);
+ }
+}
diff --git a/examples/microprofile/openapi/expanded-jandex/src/main/java/io/helidon/microprofile/examples/openapi/expandedJandex/GreetingMessage.java b/examples/microprofile/openapi/expanded-jandex/src/main/java/io/helidon/microprofile/examples/openapi/expandedJandex/GreetingMessage.java
new file mode 100644
index 000000000..823bd3fb5
--- /dev/null
+++ b/examples/microprofile/openapi/expanded-jandex/src/main/java/io/helidon/microprofile/examples/openapi/expandedJandex/GreetingMessage.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2024 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.microprofile.examples.openapi.expandedJandex;
+
+/**
+ * POJO defining the greeting message content.
+ */
+@SuppressWarnings("unused")
+public class GreetingMessage {
+ private String message;
+
+ /**
+ * Create a new GreetingMessage instance.
+ */
+ public GreetingMessage() {
+ }
+
+ /**
+ * Create a new GreetingMessage instance.
+ *
+ * @param message message
+ */
+ public GreetingMessage(String message) {
+ this.message = message;
+ }
+
+ /**
+ * Gets the message value.
+ *
+ * @return message value
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Sets the message value.
+ *
+ * @param message message value to set
+ */
+ public void setMessage(String message) {
+ this.message = message;
+ }
+}
diff --git a/examples/microprofile/openapi/expanded-jandex/src/main/java/io/helidon/microprofile/examples/openapi/expandedJandex/GreetingProvider.java b/examples/microprofile/openapi/expanded-jandex/src/main/java/io/helidon/microprofile/examples/openapi/expandedJandex/GreetingProvider.java
new file mode 100644
index 000000000..86e2acba6
--- /dev/null
+++ b/examples/microprofile/openapi/expanded-jandex/src/main/java/io/helidon/microprofile/examples/openapi/expandedJandex/GreetingProvider.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2024 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.microprofile.examples.openapi.expandedJandex;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+/**
+ * Provider for greeting message.
+ */
+@ApplicationScoped
+public class GreetingProvider {
+ private final AtomicReference message = new AtomicReference<>();
+
+ /**
+ * Create a new greeting provider, reading the message from configuration.
+ *
+ * @param message greeting to use
+ */
+ @Inject
+ public GreetingProvider(@ConfigProperty(name = "app.greeting") String message) {
+ this.message.set(message);
+ }
+
+ String getMessage() {
+ return message.get();
+ }
+
+ void setMessage(String message) {
+ this.message.set(message);
+ }
+}
diff --git a/examples/microprofile/openapi/expanded-jandex/src/main/java/io/helidon/microprofile/examples/openapi/expandedJandex/package-info.java b/examples/microprofile/openapi/expanded-jandex/src/main/java/io/helidon/microprofile/examples/openapi/expandedJandex/package-info.java
new file mode 100644
index 000000000..22d4277e1
--- /dev/null
+++ b/examples/microprofile/openapi/expanded-jandex/src/main/java/io/helidon/microprofile/examples/openapi/expandedJandex/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2024 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.
+ */
+
+/**
+ * Helidon MicroProfile OpenAPI example with expanded Jandex processing.
+ */
+package io.helidon.microprofile.examples.openapi.expandedJandex;
diff --git a/examples/microprofile/openapi/expanded-jandex/src/main/resources/META-INF/beans.xml b/examples/microprofile/openapi/expanded-jandex/src/main/resources/META-INF/beans.xml
new file mode 100644
index 000000000..52f89a20d
--- /dev/null
+++ b/examples/microprofile/openapi/expanded-jandex/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/examples/microprofile/openapi/expanded-jandex/src/main/resources/META-INF/microprofile-config.properties b/examples/microprofile/openapi/expanded-jandex/src/main/resources/META-INF/microprofile-config.properties
new file mode 100644
index 000000000..6546504fc
--- /dev/null
+++ b/examples/microprofile/openapi/expanded-jandex/src/main/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,22 @@
+#
+# Copyright (c) 2024 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.
+#
+
+# Application properties. This is the default greeting
+app.greeting=Hello
+
+# Microprofile server properties
+server.port=8080
+server.host=0.0.0.0
diff --git a/examples/microprofile/openapi/expanded-jandex/src/main/resources/logging.properties b/examples/microprofile/openapi/expanded-jandex/src/main/resources/logging.properties
new file mode 100644
index 000000000..f7313283e
--- /dev/null
+++ b/examples/microprofile/openapi/expanded-jandex/src/main/resources/logging.properties
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2024 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.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+# Component specific log levels
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.microprofile.level=INFO
+#io.helidon.common.level=INFO
+#org.glassfish.jersey.level=INFO
+#org.jboss.weld=INFO
diff --git a/examples/microprofile/openapi/expanded-jandex/src/test/java/io/helidon/microprofile/examples/openapi/expandedJandex/MainTest.java b/examples/microprofile/openapi/expanded-jandex/src/test/java/io/helidon/microprofile/examples/openapi/expandedJandex/MainTest.java
new file mode 100644
index 000000000..15f8369be
--- /dev/null
+++ b/examples/microprofile/openapi/expanded-jandex/src/test/java/io/helidon/microprofile/examples/openapi/expandedJandex/MainTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2019, 2024 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.microprofile.examples.openapi.expandedJandex;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import io.helidon.microprofile.testing.junit5.HelidonTest;
+
+import jakarta.inject.Inject;
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonPointer;
+import jakarta.json.JsonString;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.Index;
+import org.jboss.jandex.IndexReader;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+@HelidonTest
+class MainTest {
+
+ private final WebTarget target;
+
+ @Inject
+ MainTest(WebTarget target) {
+ this.target = target;
+ }
+
+ private static String escape(String path) {
+ return path.replace("/", "~1");
+ }
+
+ @Test
+ void testHelloWorld() {
+ GreetingMessage message = target.path("/greet")
+ .request()
+ .get(GreetingMessage.class);
+ assertThat("default message", message.getMessage(),
+ is("Hello World!"));
+
+ message = target.path("/greet/Joe")
+ .request()
+ .get(GreetingMessage.class);
+ assertThat("hello Joe message", message.getMessage(),
+ is("Hello Joe!"));
+
+ try (Response r = target.path("/greet/greeting")
+ .request()
+ .put(Entity.entity("{\"message\" : \"Hola\"}", MediaType.APPLICATION_JSON))) {
+ assertThat("PUT status code", r.getStatus(), is(204));
+ }
+
+ message = target.path("/greet/Jose")
+ .request()
+ .get(GreetingMessage.class);
+ assertThat("hola Jose message", message.getMessage(),
+ is("Hola Jose!"));
+ }
+
+ @Test
+ public void testOpenAPI() {
+ JsonObject jsonObject = target.path("/openapi")
+ .request(MediaType.APPLICATION_JSON)
+ .get(JsonObject.class);
+ JsonObject paths = jsonObject.get("paths").asJsonObject();
+
+ JsonPointer jp = Json.createPointer("/" + escape("/greet") + "/get/summary");
+ JsonString js = (JsonString) jp.getValue(paths);
+ assertThat("/greet GET summary did not match", js.getString(), is("Returns a generic greeting"));
+ }
+
+ @Test
+ void checkJandexContentsForExternalType() {
+ Index indexFromMavenBuild;
+ try (InputStream is = getClass().getResource("/META-INF/jandex.idx").openStream()) {
+ IndexReader indexReader = new IndexReader(is);
+ indexFromMavenBuild = indexReader.read();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ ClassInfo mediaTypeClassInfo = indexFromMavenBuild.getClassByName(MediaType.class);
+ assertThat("MediaType index information from generated Jandex file", mediaTypeClassInfo, notNullValue());
+ }
+}
diff --git a/examples/microprofile/openapi/expanded-jandex/src/test/resources/META-INF/microprofile-config.properties b/examples/microprofile/openapi/expanded-jandex/src/test/resources/META-INF/microprofile-config.properties
new file mode 100644
index 000000000..0502dc34c
--- /dev/null
+++ b/examples/microprofile/openapi/expanded-jandex/src/test/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,22 @@
+#
+# Copyright (c) 2019, 2024 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.
+#
+
+
+# Override configuration in main source branch, so we do not use 8080 port for tests
+config_ordinal=1000
+# Microprofile server properties
+server.port=-1
+server.host=0.0.0.0
diff --git a/examples/microprofile/openapi/pom.xml b/examples/microprofile/openapi/pom.xml
index 60aceefd4..e929d26e0 100644
--- a/examples/microprofile/openapi/pom.xml
+++ b/examples/microprofile/openapi/pom.xml
@@ -22,78 +22,18 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
- io.helidon.applications
- helidon-mp
- 4.2.0-SNAPSHOT
-
+ io.helidon.examples.microprofile
+ helidon-examples-microprofile-project
+ 1.0.0-SNAPSHOT
- io.helidon.examples.microprofile
+ io.helidon.examples.microprofile.openapi
helidon-examples-microprofile-openapi
1.0.0-SNAPSHOT
Helidon Examples Microprofile OpenAPI
+ pom
-
-
- io.helidon.microprofile.bundles
- helidon-microprofile-core
-
-
- io.helidon.microprofile.openapi
- helidon-microprofile-openapi
-
-
- io.helidon.logging
- helidon-logging-jul
- runtime
-
-
- org.glassfish.jersey.media
- jersey-media-json-binding
- runtime
-
-
- io.smallrye
- jandex
- runtime
- true
-
-
- org.junit.jupiter
- junit-jupiter-api
- test
-
-
- org.hamcrest
- hamcrest-all
- test
-
-
- io.helidon.microprofile.testing
- helidon-microprofile-testing-junit5
- test
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-dependency-plugin
-
-
- copy-libs
-
-
-
-
- io.smallrye
- jandex-maven-plugin
-
-
- make-index
-
-
-
-
-
+
+ basic
+ expanded-jandex
+
diff --git a/examples/webserver/concurrency-limits/README.md b/examples/webserver/concurrency-limits/README.md
new file mode 100644
index 000000000..14aeab27e
--- /dev/null
+++ b/examples/webserver/concurrency-limits/README.md
@@ -0,0 +1,139 @@
+# Helidon SE Concurrency Limits Example
+
+This example demonstrates the Concurrency Limits feature of Helidon WebServer.
+For other approaches for rate limiting see the [Rate Limit Example](../ratelimit).
+
+The Concurrency Limits feature provides mechanisms to limit concurrent execution of incoming requests.
+Currently two algorithms are supported:
+
+1. Fixed: A semaphore based limit that supports queuing for a permit and timeout on the queue.
+2. AIMD: Additive-increase/multiplicative-decrease. Uses a feedback control algorithm that combines linear growth of the request limit when there is no congestion with an exponential reduction when congestion is detected.
+
+The example does the following:
+
+1. Defines two ports for accepting incoming requests: 8080 and 8088
+2. 8080 is configured to use the Fixed concurrency limit algorithm
+3. 8088 is configured to use the AIMD concurrency limit algorithm
+
+The server has two endpoints: `fixed/sleep` and `aimd/sleep`. Both sleep for the specified number of seconds to simulate a slow workload.
+
+These values are configured in [`application.yaml`](./src/main/resources/application.yaml)
+
+## Build and run
+
+Build and start the server:
+```shell
+mvn package
+java -jar target/helidon-examples-webserver-concurrencylimits.jar
+```
+
+## Exercise the application
+
+### Fixed Concurrency Limit
+
+The server is configured to process 6 requests concurrently with a backlog queue of depth 4. Therefore it can handle bursts of up to 10 requests at a time.
+
+Send a burst of 15 concurrent requests to the `fixed/sleep` endpoint (each requesting a sleep of 3 seconds):
+```shell
+curl -s \
+ --noproxy '*' \
+ -o /dev/null \
+ --parallel \
+ --parallel-immediate \
+ -w "%{http_code}\n" \
+ "http://localhost:8080/fixed/sleep/3?c=[1-15]"
+```
+
+When the 15 concurrent requests hit the server:
+
+* 5 of the requests are rejected with a 503 because they exceed the size of the number of concurrent requests limit (6) plus the size of the queue (4).
+* 10 of the requests are accepted by the server
+* The first 6 of those return a 200 after sleeping for 3 seconds
+* The next 4 requests are then processed from the queue and return a 200 after sleeping for 3 more seconds.
+
+You will see this in the output:
+```
+503
+503
+503
+503
+503
+# Three second pause
+200
+200
+200
+200
+200
+200
+# Three second pause
+200
+200
+200
+200
+```
+
+### AIMD Concurrency Limit
+
+The AIMD limiter is [adaptive](https://en.wikipedia.org/wiki/Additive_increase/multiplicative_decrease)
+and will adjust according to the workload and responsiveness of the server.
+If the server successfully processes requests then the limiter will increase limits (up to a max limit).
+If the server starts to fail or timeout requests then the limiter will reduce limits (down to a min limit).
+
+In this example we set the initial and minimum limit to 6 and a max limit of 10 concurrent requests.
+We configure the timeout as 3 seconds. So requests that can't be serviced within 3 seconds fail -- which can lead to a reduction of the current limit.
+
+First execute 15 concurrent requests, each sleeping for 3 seconds.
+
+```shell
+curl -s \
+ --noproxy '*' \
+ -o /dev/null \
+ --parallel \
+ --parallel-immediate \
+ -w "%{http_code}\n" \
+ "http://localhost:8088/aimd/sleep/3?c=[1-15]"
+```
+
+This will process 6 request (the currently configured limit), and fail 9:
+
+```
+503
+503
+503
+503
+503
+503
+503
+503
+503
+# Pause for 3 seconds
+200
+200
+200
+200
+200
+200
+```
+
+Repeat the curl command and you will see the same results. Because we have the AIMD timeout set to 3 seconds, and our sleep operation is taking 3 seconds, the current limit is never increased.
+
+Now repeat the curl command, but specify a sleep time of 2 seconds:
+
+```shell
+curl -s \
+ --noproxy '*' \
+ -o /dev/null \
+ --parallel \
+ --parallel-immediate \
+ -w "%{http_code}\n" \
+ "http://localhost:8088/aimd/sleep/2?c=[1-15]"
+```
+
+As before the first invocation will process 6 requests. But as you
+repeat the curl command you will see more requests are
+successful until you hit the max concurrency limit of 10. Since
+each request is now taking 2 seconds (and not 3), the limiter
+is able to adapt and increase the current limit up to the maximum.
+
+Now go back and run the curl command a few times using a sleep time
+of 3, and you will see the limiter adapt again and lower the current limit.
diff --git a/examples/webserver/concurrency-limits/pom.xml b/examples/webserver/concurrency-limits/pom.xml
new file mode 100644
index 000000000..25eec9d18
--- /dev/null
+++ b/examples/webserver/concurrency-limits/pom.xml
@@ -0,0 +1,82 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.2.0-SNAPSHOT
+
+
+ io.helidon.examples.webserver
+ helidon-examples-webserver-concurrencylimits
+ 1.0.0-SNAPSHOT
+ Helidon Examples WebServer Concurrency Limits
+
+
+ io.helidon.examples.webserver.concurrencylimits.Main
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.logging
+ helidon-logging-jul
+ runtime
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.webserver.testing.junit5
+ helidon-webserver-testing-junit5
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
diff --git a/examples/webserver/concurrency-limits/src/main/java/io/helidon/examples/webserver/concurrencylimits/Main.java b/examples/webserver/concurrency-limits/src/main/java/io/helidon/examples/webserver/concurrencylimits/Main.java
new file mode 100644
index 000000000..5c06bc01d
--- /dev/null
+++ b/examples/webserver/concurrency-limits/src/main/java/io/helidon/examples/webserver/concurrencylimits/Main.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2024 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.examples.webserver.concurrencylimits;
+
+import io.helidon.config.Config;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.WebServerConfig;
+import io.helidon.webserver.http.HttpRouting;
+
+/**
+ * The application main class.
+ */
+public class Main {
+
+ private static final System.Logger LOGGER = System.getLogger(Main.class.getName());
+
+ /**
+ * Cannot be instantiated.
+ */
+ private Main() {
+ }
+
+ /**
+ * Application main entry point.
+ * @param args command line arguments.
+ */
+ public static void main(String[] args) {
+
+ // load logging configuration
+ LogConfig.configureRuntime();
+
+ // initialize global config from default configuration
+ Config config = Config.create();
+ Config.global(config);
+
+ // Default port uses the fixed limiter
+ // "aimd" port uses the AIMD limiter
+ WebServerConfig webserverConfig = WebServer.builder()
+ .config(config.get("server"))
+ .routing(Main::fixedRouting)
+ .routing("aimd", Main::aimdRouting)
+ .buildPrototype();
+
+ WebServer webserver = webserverConfig.build().start();
+
+ LOGGER.log(System.Logger.Level.INFO, "WEB server is up! http://localhost:" + webserver.port() + "/fixed/sleep"
+ + " " + "http://localhost:" + webserver.port("aimd") + "/aimd/sleep");
+ }
+
+ /**
+ * Updates HTTP Routing.
+ */
+ static void fixedRouting(HttpRouting.Builder routing) {
+ routing.register("/fixed", new SleepService());
+ }
+
+ static void aimdRouting(HttpRouting.Builder routing) {
+ routing.register("/aimd", new SleepService());
+ }
+}
diff --git a/examples/webserver/concurrency-limits/src/main/java/io/helidon/examples/webserver/concurrencylimits/SleepService.java b/examples/webserver/concurrency-limits/src/main/java/io/helidon/examples/webserver/concurrencylimits/SleepService.java
new file mode 100644
index 000000000..f8718457e
--- /dev/null
+++ b/examples/webserver/concurrency-limits/src/main/java/io/helidon/examples/webserver/concurrencylimits/SleepService.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2024 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.examples.webserver.concurrencylimits;
+
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+
+class SleepService implements HttpService {
+
+ private static final System.Logger LOGGER = System.getLogger(SleepService.class.getName());
+
+ SleepService() {
+ }
+
+ @Override
+ public void routing(HttpRules rules) {
+ rules.get("/sleep/{seconds}", this::sleepHandler);
+ }
+
+ /**
+ * Sleep for a specified number of seconds.
+ * The optional path parameter controls the number of seconds to sleep. Defaults to 1
+ *
+ * @param request server request
+ * @param response server response
+ */
+ private void sleepHandler(ServerRequest request, ServerResponse response) {
+ int seconds = request.path().pathParameters().first("seconds").asInt().orElse(1);
+ response.send(String.valueOf(sleep(seconds)));
+ }
+
+ /**
+ * Sleep current thread.
+ *
+ * @param seconds number of seconds to sleep
+ * @return number of seconds requested to sleep
+ */
+ private static int sleep(int seconds) {
+ LOGGER.log(System.Logger.Level.DEBUG, Thread.currentThread() + ": Sleeping for " + seconds + " seconds");
+ try {
+ Thread.sleep(seconds * 1_000L);
+ } catch (InterruptedException e) {
+ LOGGER.log(System.Logger.Level.WARNING, e);
+ }
+ return seconds;
+ }
+}
diff --git a/examples/webserver/concurrency-limits/src/main/java/io/helidon/examples/webserver/concurrencylimits/package-info.java b/examples/webserver/concurrency-limits/src/main/java/io/helidon/examples/webserver/concurrencylimits/package-info.java
new file mode 100644
index 000000000..2cd5203ca
--- /dev/null
+++ b/examples/webserver/concurrency-limits/src/main/java/io/helidon/examples/webserver/concurrencylimits/package-info.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2024 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.examples.webserver.concurrencylimits;
diff --git a/examples/webserver/concurrency-limits/src/main/resources/application.yaml b/examples/webserver/concurrency-limits/src/main/resources/application.yaml
new file mode 100644
index 000000000..0e9d72bbd
--- /dev/null
+++ b/examples/webserver/concurrency-limits/src/main/resources/application.yaml
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2024 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.
+#
+
+server:
+ port: 8080
+ host: 0.0.0.0
+ concurrency-limit:
+ # Default port uses fixed limit algorithm
+ fixed:
+ permits: 6
+ queue-length: 4
+ queue-timeout: PT10S
+ sockets:
+ - name: "aimd"
+ port: 8088
+ host: 0.0.0.0
+ concurrency-limit:
+ # Second port uses aimd limit algorithm
+ aimd:
+ min-limit: 6
+ initial-limit: 6
+ max-limit: 10
+ backoff-ratio: 0.5
+ timeout: PT3S
\ No newline at end of file
diff --git a/examples/webserver/concurrency-limits/src/main/resources/logging.properties b/examples/webserver/concurrency-limits/src/main/resources/logging.properties
new file mode 100644
index 000000000..1fe677223
--- /dev/null
+++ b/examples/webserver/concurrency-limits/src/main/resources/logging.properties
@@ -0,0 +1,22 @@
+#
+# Copyright (c) 2024 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.
+#
+
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS.%1$tL %5$s%6$s%n
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
+
+#io.helidon.examples.webserver.ratelimit.SleepService.level=FINE
diff --git a/examples/webserver/concurrency-limits/src/test/java/io/helidon/examples/webserver/concurrencylimits/MainTest.java b/examples/webserver/concurrency-limits/src/test/java/io/helidon/examples/webserver/concurrencylimits/MainTest.java
new file mode 100644
index 000000000..c80fcea43
--- /dev/null
+++ b/examples/webserver/concurrency-limits/src/test/java/io/helidon/examples/webserver/concurrencylimits/MainTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2024 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.examples.webserver.concurrencylimits;
+
+import io.helidon.http.Status;
+import io.helidon.webclient.api.HttpClientResponse;
+import io.helidon.webclient.api.WebClient;
+import io.helidon.webserver.http.HttpRouting;
+import io.helidon.webserver.testing.junit5.ServerTest;
+import io.helidon.webserver.testing.junit5.SetUpRoute;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@ServerTest
+class MainTest {
+ private final WebClient client;
+
+ protected MainTest(WebClient client) {
+ this.client = client;
+ }
+
+ @SetUpRoute
+ static void routing(HttpRouting.Builder builder) {
+ Main.fixedRouting(builder);
+ }
+
+ @Test
+ void testSleep() {
+ try (HttpClientResponse response = client.get("fixed/sleep/1").request()) {
+ assertThat(response.status(), is(Status.OK_200));
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/webserver/grpc-random/README.md b/examples/webserver/grpc-random/README.md
new file mode 100644
index 000000000..795fa079e
--- /dev/null
+++ b/examples/webserver/grpc-random/README.md
@@ -0,0 +1,21 @@
+# Helidon gRPC SE Random Example
+
+
+This example shows a simple _Random_ service written with the Helidon gRPC SE
+API. See `RandomService` for the service implementation and `RandomServiceTest`
+for how to use the Helidon's `WebClient` to access the service. .
+
+The gRPC service definition is found in the `random.proto` file which is compiled
+using `protoc` at build time.
+
+## Build and run tests
+
+```shell
+mvn package
+```
+
+## Run the app
+
+```shell
+java -jar target/helidon-examples-webserver-grpc-random.jar
+```
diff --git a/examples/webserver/grpc-random/pom.xml b/examples/webserver/grpc-random/pom.xml
new file mode 100644
index 000000000..b470ed81d
--- /dev/null
+++ b/examples/webserver/grpc-random/pom.xml
@@ -0,0 +1,135 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.2.0-SNAPSHOT
+
+
+
+ io.helidon.examples.webserver
+ helidon-examples-webserver-grpc-random
+ 1.0.0-SNAPSHOT
+ Helidon Examples WebServer gRPC Random
+
+
+ Application demonstrates the use of gRPC with a service that returns random numbers.
+
+
+
+ io.helidon.examples.webserver.grpc.random.Main
+
+
+
+
+ io.grpc
+ grpc-api
+
+
+ io.helidon.config
+ helidon-config-yaml
+
+
+ io.helidon.grpc
+ helidon-grpc-core
+
+
+ io.helidon.webserver
+ helidon-webserver-grpc
+
+
+ io.helidon.logging
+ helidon-logging-jul
+
+
+ com.google.protobuf
+ protobuf-java
+
+
+ io.helidon.webclient
+ helidon-webclient-grpc
+
+
+
+ javax.annotation
+ javax.annotation-api
+ provided
+
+
+ io.helidon.webserver.testing.junit5
+ helidon-webserver-testing-junit5
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
+
+
+
+ kr.motd.maven
+ os-maven-plugin
+ ${version.plugin.os}
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+
+
+
+ compile
+ compile-custom
+
+
+
+
+ com.google.protobuf:protoc:${version.lib.google-protobuf}:exe:${os.detected.classifier}
+ grpc-java
+ io.grpc:protoc-gen-grpc-java:${version.lib.grpc}:exe:${os.detected.classifier}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/Main.java b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/Main.java
new file mode 100644
index 000000000..023a656eb
--- /dev/null
+++ b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/Main.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2024 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.examples.webserver.grpc.random;
+
+import io.helidon.config.Config;
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.grpc.GrpcRouting;
+
+class Main {
+
+ private Main() {
+ }
+
+ /**
+ * Main method.
+ *
+ * @param args ignored
+ */
+ public static void main(String[] args) {
+ LogConfig.configureRuntime();
+
+ // initialize global config from default configuration
+ Config config = Config.create();
+ Config.global(config);
+ Config serverConfig = config.get("server");
+
+ // start server and register gRPC routing
+ WebServer.builder()
+ .config(serverConfig)
+ .addRouting(GrpcRouting.builder().service(new RandomService()))
+ .build()
+ .start();
+ }
+}
diff --git a/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/RandomService.java b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/RandomService.java
new file mode 100644
index 000000000..973f62867
--- /dev/null
+++ b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/RandomService.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2024 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.examples.webserver.grpc.random;
+
+import java.util.Random;
+
+import io.helidon.examples.webserver.grpc.random.Random.ParamMessage;
+import io.helidon.examples.webserver.grpc.random.Random.RandomMessage;
+import io.helidon.webserver.grpc.GrpcService;
+
+import com.google.protobuf.Descriptors;
+import io.grpc.stub.StreamObserver;
+
+class RandomService implements GrpcService {
+
+ private final Random random = new Random();
+
+ @Override
+ public Descriptors.FileDescriptor proto() {
+ return io.helidon.examples.webserver.grpc.random.Random.getDescriptor();
+ }
+
+ @Override
+ public void update(Routing router) {
+ router.unary("RandomSingle", this::randomSingle)
+ .bidi("RandomMany", this::randomMany);
+ }
+
+ private void randomSingle(ParamMessage request, StreamObserver response) {
+ int bound = request.getNumber();
+ response.onNext(newRandomMessage(random.nextInt(bound)));
+ response.onCompleted();
+ }
+
+ private StreamObserver randomMany(StreamObserver response) {
+ return new StreamObserver<>() {
+
+ private int bound = Integer.MIN_VALUE;
+ private int count = Integer.MIN_VALUE;
+
+ @Override
+ public void onNext(ParamMessage paramMessage) {
+ // collect bound and count, in that order
+ if (bound == Integer.MIN_VALUE) {
+ bound = paramMessage.getNumber();
+ } else if (count == Integer.MIN_VALUE) {
+ count = paramMessage.getNumber();
+ } else {
+ onError(new IllegalStateException("Received extra input params"));
+ }
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ response.onError(throwable);
+ }
+
+ @Override
+ public void onCompleted() {
+ // verify input params received
+ if (bound == Integer.MIN_VALUE || count == Integer.MIN_VALUE) {
+ onError(new IllegalStateException("Did not receive all input params"));
+ }
+
+ // send stream of random numbers
+ for (int i = 0; i < count; i++) {
+ response.onNext(newRandomMessage(random.nextInt(bound)));
+ }
+ response.onCompleted();
+ }
+ };
+ }
+
+ private static RandomMessage newRandomMessage(int random) {
+ return RandomMessage.newBuilder().setNumber(random).build();
+ }
+}
diff --git a/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/package-info.java b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/package-info.java
new file mode 100644
index 000000000..690840655
--- /dev/null
+++ b/examples/webserver/grpc-random/src/main/java/io/helidon/examples/webserver/grpc/random/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2024 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.
+ */
+
+/**
+ * Example of gRPC in webserver.
+ */
+package io.helidon.examples.webserver.grpc.random;
diff --git a/examples/webserver/grpc-random/src/main/proto/random.proto b/examples/webserver/grpc-random/src/main/proto/random.proto
new file mode 100644
index 000000000..559d69522
--- /dev/null
+++ b/examples/webserver/grpc-random/src/main/proto/random.proto
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2024 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.
+ */
+
+
+syntax = "proto3";
+option java_package = "io.helidon.examples.webserver.grpc.random";
+
+service RandomService {
+ rpc RandomSingle (ParamMessage) returns (RandomMessage) {}
+ rpc RandomMany (stream ParamMessage) returns (stream RandomMessage) {}
+}
+
+message ParamMessage {
+ int32 number = 1;
+}
+
+message RandomMessage {
+ int32 number = 1;
+}
diff --git a/examples/webserver/grpc-random/src/main/resources/application.yaml b/examples/webserver/grpc-random/src/main/resources/application.yaml
new file mode 100644
index 000000000..327fc590e
--- /dev/null
+++ b/examples/webserver/grpc-random/src/main/resources/application.yaml
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2024 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.
+#
+
+server:
+ port: 8080
+ tls:
+ trust:
+ keystore:
+ passphrase: "password"
+ trust-store: true
+ resource:
+ resource-path: "server.p12"
+ private-key:
+ keystore:
+ passphrase: "password"
+ resource:
+ resource-path: "server.p12"
diff --git a/examples/webserver/grpc-random/src/main/resources/client.p12 b/examples/webserver/grpc-random/src/main/resources/client.p12
new file mode 100644
index 000000000..4eb3b8325
Binary files /dev/null and b/examples/webserver/grpc-random/src/main/resources/client.p12 differ
diff --git a/examples/webserver/grpc-random/src/main/resources/logging.properties b/examples/webserver/grpc-random/src/main/resources/logging.properties
new file mode 100644
index 000000000..344ba7085
--- /dev/null
+++ b/examples/webserver/grpc-random/src/main/resources/logging.properties
@@ -0,0 +1,24 @@
+#
+# Copyright (c) 2024 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.
+#
+
+# Send messages to the console
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+
+# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+.level=INFO
+#io.helidon.webclient.grpc.level=FINEST
diff --git a/examples/webserver/grpc-random/src/main/resources/server.p12 b/examples/webserver/grpc-random/src/main/resources/server.p12
new file mode 100644
index 000000000..ff8e4ddfc
Binary files /dev/null and b/examples/webserver/grpc-random/src/main/resources/server.p12 differ
diff --git a/examples/webserver/grpc-random/src/test/java/io/helidon/examples/webserver/grpc/random/RandomServiceTest.java b/examples/webserver/grpc-random/src/test/java/io/helidon/examples/webserver/grpc/random/RandomServiceTest.java
new file mode 100644
index 000000000..3aa63562d
--- /dev/null
+++ b/examples/webserver/grpc-random/src/test/java/io/helidon/examples/webserver/grpc/random/RandomServiceTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2024 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.examples.webserver.grpc.random;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import io.helidon.common.configurable.Resource;
+import io.helidon.common.tls.Tls;
+import io.helidon.config.Config;
+import io.helidon.examples.webserver.grpc.random.Random.ParamMessage;
+import io.helidon.webclient.api.WebClient;
+import io.helidon.webclient.grpc.GrpcClient;
+import io.helidon.webclient.grpc.GrpcClientProtocolConfig;
+import io.helidon.webserver.Router;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.grpc.GrpcRouting;
+import io.helidon.webserver.testing.junit5.ServerTest;
+import io.helidon.webserver.testing.junit5.SetUpRoute;
+import io.helidon.examples.webserver.grpc.random.Random.ParamMessage;
+import io.helidon.examples.webserver.grpc.random.Random.RandomMessage;
+import io.helidon.examples.webserver.grpc.random.RandomServiceGrpc;
+import io.helidon.examples.webserver.grpc.random.RandomServiceGrpc.RandomServiceBlockingStub;
+
+import io.grpc.stub.StreamObserver;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.lessThan;
+
+/**
+ * Tests gRPC Strings service using {@link io.helidon.webclient.api.WebClient}.
+ */
+@ServerTest
+class RandomServiceTest {
+ private static final long TIMEOUT_SECONDS = 10;
+ private static final int BOUND = 100;
+ private static final int COUNT = 10;
+
+ private final WebClient webClient;
+
+ private RandomServiceTest(WebServer server) {
+ Tls clientTls = Tls.builder()
+ .trust(trust -> trust
+ .keystore(store -> store
+ .passphrase("password")
+ .trustStore(true)
+ .keystore(Resource.create("client.p12"))))
+ .build();
+ Config config = Config.create();
+ GrpcClientProtocolConfig protocolConfig = GrpcClientProtocolConfig.builder()
+ .config(config.get("grpc-protocol-config"))
+ .build();
+ this.webClient = WebClient.builder()
+ .tls(clientTls)
+ .baseUri("https://localhost:" + server.port())
+ .addProtocolConfig(protocolConfig)
+ .build();
+ }
+
+ @SetUpRoute
+ static void routing(Router.RouterBuilder> router) {
+ router.addRouting(GrpcRouting.builder().service(new RandomService()));
+ }
+
+ @Test
+ void testRandomSingle() {
+ GrpcClient grpcClient = webClient.client(GrpcClient.PROTOCOL);
+ RandomServiceBlockingStub service = RandomServiceGrpc.newBlockingStub(grpcClient.channel());
+ RandomMessage res = service.randomSingle(newParamMessage(BOUND));
+ assertThat(res.getNumber(), is(lessThan(BOUND)));
+ }
+
+ @Test
+ void testRandomMany() throws InterruptedException, TimeoutException, ExecutionException {
+ GrpcClient grpcClient = webClient.client(GrpcClient.PROTOCOL);
+ RandomServiceGrpc.RandomServiceStub service = RandomServiceGrpc.newStub(grpcClient.channel());
+ CompletableFuture> future = new CompletableFuture<>();
+ StreamObserver req = service.randomMany(multiStreamObserver(future));
+ req.onNext(newParamMessage(BOUND));
+ req.onNext(newParamMessage(COUNT));
+ req.onCompleted();
+ Iterator res = future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ int n = 0;
+ for (; res.hasNext(); n++) {
+ assertThat(res.next().getNumber(), is(lessThan(BOUND)));
+ }
+ assertThat(n, is(COUNT));
+ }
+
+ static ParamMessage newParamMessage(int n) {
+ return ParamMessage.newBuilder().setNumber(n).build();
+ }
+
+ static StreamObserver multiStreamObserver(CompletableFuture> future) {
+ return new StreamObserver<>() {
+ private final List value = new ArrayList<>();
+
+ @Override
+ public void onNext(ResT value) {
+ this.value.add(value);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ future.completeExceptionally(t);
+ }
+
+ @Override
+ public void onCompleted() {
+ future.complete(value.iterator());
+ }
+ };
+ }
+}
diff --git a/examples/webserver/grpc-random/src/test/resources/application.yaml b/examples/webserver/grpc-random/src/test/resources/application.yaml
new file mode 100644
index 000000000..a46f02dc4
--- /dev/null
+++ b/examples/webserver/grpc-random/src/test/resources/application.yaml
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2024 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.
+#
+
+server:
+ port: 0
+ tls:
+ trust:
+ keystore:
+ passphrase: "password"
+ trust-store: true
+ resource:
+ resource-path: "server.p12"
+ private-key:
+ keystore:
+ passphrase: "password"
+ resource:
+ resource-path: "server.p12"
+
+grpc-protocol-config:
+ poll-wait-time: PT30S
+ abort-poll-time-expired: true
+ init-buffer-size: 10000
+ heartbeat-period: PT10S
\ No newline at end of file
diff --git a/examples/webserver/grpc/src/test/resources/application.yaml b/examples/webserver/grpc/src/test/resources/application.yaml
index fa5b0ee7f..c77abef33 100644
--- a/examples/webserver/grpc/src/test/resources/application.yaml
+++ b/examples/webserver/grpc/src/test/resources/application.yaml
@@ -33,9 +33,3 @@ server:
observers:
health:
details: true
-
-grpc-client:
- poll-wait-time: PT30S
- abort-poll-time-expired: true
- init-buffer-size: 10000
- heartbeat-period: PT10S
\ No newline at end of file
diff --git a/examples/webserver/multiport/src/main/resources/application.yaml b/examples/webserver/multiport/src/main/resources/application.yaml
index 8335d53ef..6c1afb60c 100644
--- a/examples/webserver/multiport/src/main/resources/application.yaml
+++ b/examples/webserver/multiport/src/main/resources/application.yaml
@@ -19,10 +19,10 @@ server:
sockets:
- name: "private"
port: 8081
- bind-address: "localhost"
+ host: "localhost"
- name: "admin"
port: 8082
- bind-address: "localhost"
+ host: "localhost"
features:
observe:
sockets: "admin"
diff --git a/examples/webserver/pom.xml b/examples/webserver/pom.xml
index 74e48b0d1..75feadef2 100644
--- a/examples/webserver/pom.xml
+++ b/examples/webserver/pom.xml
@@ -36,9 +36,11 @@
basic
basics
comment-aas
+ concurrency-limits
echo
fault-tolerance
grpc
+ grpc-random
imperative
multiport
mutual-tls
@@ -53,5 +55,6 @@
tracing
tutorial
websocket
+ sse
diff --git a/examples/webserver/sse/README.md b/examples/webserver/sse/README.md
new file mode 100644
index 000000000..1742e3063
--- /dev/null
+++ b/examples/webserver/sse/README.md
@@ -0,0 +1,16 @@
+# Helidon SE SSE Example
+
+This example demonstrates how to use Server Sent Events (SSE) with both the WebServer and WebClient APIs.
+
+This project implements a couple of SSE endpoints that send either text or JSON messages.
+The unit test uses the WebClient API to test the endpoints.
+
+## Build, run and test
+
+Build and start the server:
+```shell
+mvn package
+java -jar target/helidon-examples-webserver-sse.jar
+```
+
+Then open http://localhost:8080 in your browser.
diff --git a/examples/webserver/sse/pom.xml b/examples/webserver/sse/pom.xml
new file mode 100644
index 000000000..a471da0ff
--- /dev/null
+++ b/examples/webserver/sse/pom.xml
@@ -0,0 +1,96 @@
+
+
+
+ 4.0.0
+
+ io.helidon.applications
+ helidon-se
+ 4.2.0-SNAPSHOT
+
+
+ io.helidon.examples.webserver
+ helidon-examples-webserver-sse
+ 1.0.0-SNAPSHOT
+ Helidon Examples WebServer SSE
+
+
+ io.helidon.examples.webserver.sse.Main
+
+
+
+
+ io.helidon.webserver
+ helidon-webserver
+
+
+ io.helidon.webserver
+ helidon-webserver-sse
+
+
+ io.helidon.webserver
+ helidon-webserver-static-content
+
+
+ jakarta.json
+ jakarta.json-api
+
+
+ io.helidon.http.media
+ helidon-http-media-jsonp
+
+
+ io.helidon.logging
+ helidon-logging-jul
+
+
+ io.helidon.webclient
+ helidon-webclient-sse
+ test
+
+
+ io.helidon.webserver.testing.junit5
+ helidon-webserver-testing-junit5
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-libs
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/webserver/sse/src/main/java/io/helidon/examples/webserver/sse/Main.java b/examples/webserver/sse/src/main/java/io/helidon/examples/webserver/sse/Main.java
new file mode 100644
index 000000000..9f90c429f
--- /dev/null
+++ b/examples/webserver/sse/src/main/java/io/helidon/examples/webserver/sse/Main.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2024 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.examples.webserver.sse;
+
+import io.helidon.logging.common.LogConfig;
+import io.helidon.webserver.WebServer;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.staticcontent.StaticContentService;
+
+/**
+ * This application provides a simple service with a UI to exercise Server Sent Events (SSE).
+ */
+class Main {
+
+ private Main() {
+ }
+
+ /**
+ * Executes the example.
+ *
+ * @param args command line arguments, ignored
+ */
+ public static void main(String[] args) {
+ // load logging configuration
+ LogConfig.configureRuntime();
+
+ WebServer server = WebServer.builder()
+ .routing(Main::routing)
+ .port(8080)
+ .build()
+ .start();
+
+ System.out.println("WEB server is up! http://localhost:" + server.port());
+ }
+
+ /**
+ * Updates the routing rules.
+ *
+ * @param rules routing rules
+ */
+ static void routing(HttpRules rules) {
+ rules.register("/", StaticContentService.builder("WEB")
+ .welcomeFileName("index.html")
+ .build())
+ .register("/api", new SseService());
+ }
+}
diff --git a/examples/webserver/sse/src/main/java/io/helidon/examples/webserver/sse/SseService.java b/examples/webserver/sse/src/main/java/io/helidon/examples/webserver/sse/SseService.java
new file mode 100644
index 000000000..eb21de213
--- /dev/null
+++ b/examples/webserver/sse/src/main/java/io/helidon/examples/webserver/sse/SseService.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2024 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.examples.webserver.sse;
+
+import java.io.UncheckedIOException;
+import java.lang.System.Logger.Level;
+import java.time.Duration;
+
+import io.helidon.http.sse.SseEvent;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.http.HttpService;
+import io.helidon.webserver.http.ServerRequest;
+import io.helidon.webserver.http.ServerResponse;
+import io.helidon.webserver.sse.SseSink;
+
+import jakarta.json.spi.JsonProvider;
+
+/**
+ * SSE service.
+ */
+class SseService implements HttpService {
+
+ private static final System.Logger LOGGER = System.getLogger(SseService.class.getName());
+ private static final JsonProvider JSON_PROVIDER = JsonProvider.provider();
+
+ @Override
+ public void routing(HttpRules httpRules) {
+ httpRules.get("/sse_text", this::sseText)
+ .get("/sse_json", this::sseJson);
+ }
+
+ void sseText(ServerRequest req, ServerResponse res) throws InterruptedException {
+ int count = req.query().first("count").asInt().orElse(1);
+ int delay = req.query().first("delay").asInt().orElse(0);
+ try (SseSink sseSink = res.sink(SseSink.TYPE)) {
+ for (int i = 0; i < count; i++) {
+ try {
+ sseSink.emit(SseEvent.builder()
+ .comment("comment#" + i)
+ .name("my-event")
+ .data("data#" + i)
+ .build());
+ } catch (UncheckedIOException e) {
+ LOGGER.log(Level.DEBUG, e.getMessage()); // connection close?
+ return;
+ }
+
+ if (delay > 0) {
+ Thread.sleep(Duration.ofSeconds(delay));
+ }
+ }
+ }
+ }
+
+ void sseJson(ServerRequest req, ServerResponse res) throws InterruptedException {
+ int count = req.query().first("count").asInt().orElse(1);
+ int delay = req.query().first("delay").asInt().orElse(0);
+ try (SseSink sseSink = res.sink(SseSink.TYPE)) {
+ for (int i = 0; i < count; i++) {
+ try {
+ sseSink.emit(SseEvent.create(JSON_PROVIDER.createObjectBuilder()
+ .add("data", "data#" + i)
+ .build()));
+ } catch (UncheckedIOException e) {
+ LOGGER.log(Level.DEBUG, e.getMessage()); // connection close?
+ return;
+ }
+
+ if (delay > 0) {
+ Thread.sleep(Duration.ofSeconds(delay));
+ }
+ }
+ }
+ }
+}
diff --git a/examples/webserver/sse/src/main/java/io/helidon/examples/webserver/sse/package-info.java b/examples/webserver/sse/src/main/java/io/helidon/examples/webserver/sse/package-info.java
new file mode 100644
index 000000000..687abe6d2
--- /dev/null
+++ b/examples/webserver/sse/src/main/java/io/helidon/examples/webserver/sse/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2024 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.
+ */
+
+/**
+ * Helidon Examples WebServer SSE.
+ */
+package io.helidon.examples.webserver.sse;
diff --git a/examples/webserver/sse/src/main/resources/WEB/index.html b/examples/webserver/sse/src/main/resources/WEB/index.html
new file mode 100644
index 000000000..633518a84
--- /dev/null
+++ b/examples/webserver/sse/src/main/resources/WEB/index.html
@@ -0,0 +1,107 @@
+
+
+
+
+
+ Helidon Examples WebServer SSE
+
+
+
+
+Text events
+
+JSON events
+
+
+
+
diff --git a/examples/webserver/sse/src/main/resources/logging.properties b/examples/webserver/sse/src/main/resources/logging.properties
new file mode 100644
index 000000000..452ea9d08
--- /dev/null
+++ b/examples/webserver/sse/src/main/resources/logging.properties
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2024 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.
+#
+
+handlers=io.helidon.logging.jul.HelidonConsoleHandler
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS.%1$tL %5$s%6$s%n
+# Global logging level. Can be overridden by specific loggers
+.level=INFO
diff --git a/examples/webserver/sse/src/test/java/io/helidon/examples/webserver/sse/SseServiceTest.java b/examples/webserver/sse/src/test/java/io/helidon/examples/webserver/sse/SseServiceTest.java
new file mode 100644
index 000000000..e249ccb07
--- /dev/null
+++ b/examples/webserver/sse/src/test/java/io/helidon/examples/webserver/sse/SseServiceTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2024 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.examples.webserver.sse;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import jakarta.json.JsonObject;
+
+import io.helidon.http.sse.SseEvent;
+import io.helidon.webclient.http1.Http1Client;
+import io.helidon.webclient.sse.SseSource;
+import io.helidon.webserver.http.HttpRules;
+import io.helidon.webserver.testing.junit5.ServerTest;
+import io.helidon.webserver.testing.junit5.SetUpRoute;
+
+import org.junit.jupiter.api.Test;
+
+import static io.helidon.http.HeaderValues.ACCEPT_EVENT_STREAM;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@ServerTest
+class SseServiceTest {
+
+ private final Http1Client client;
+
+ SseServiceTest(Http1Client client) {
+ this.client = client;
+ }
+
+ @SetUpRoute
+ static void routing(HttpRules rules) {
+ rules.register(new SseService());
+ }
+
+ @Test
+ void testSseText() throws InterruptedException {
+ try (var response = client.get("/sse_text")
+ .queryParam("count", "3")
+ .header(ACCEPT_EVENT_STREAM).request()) {
+ var latch = new CountDownLatch(3);
+ var events = new ArrayList();
+ response.source(SseSource.TYPE, event -> {
+ events.add(event);
+ latch.countDown();
+ });
+ assertThat(latch.await(5, TimeUnit.SECONDS), is(true));
+ assertThat(events.size(), is(3));
+ for (int i = 0; i < 3; i++) {
+ var event = events.get(i);
+ assertThat(event.comment().orElse(null), is("comment#" + i));
+ assertThat(event.name().orElse(null), is("my-event"));
+ assertThat(event.data(String.class), is("data#" + i));
+ }
+ }
+ }
+
+ @Test
+ void testSseJson() throws InterruptedException {
+ try (var response = client.get("/sse_json")
+ .queryParam("count", "3")
+ .header(ACCEPT_EVENT_STREAM).request()) {
+ var latch = new CountDownLatch(3);
+ var events = new ArrayList();
+ response.source(SseSource.TYPE, event -> {
+ events.add(event.data(JsonObject.class));
+ latch.countDown();
+ });
+ assertThat(latch.await(5, TimeUnit.SECONDS), is(true));
+ assertThat(events.size(), is(3));
+ for (int i = 0; i < 3; i++) {
+ var event = events.get(i);
+ assertThat(event, is(notNullValue()));
+ assertThat(event.getString("data", null), is("data#" + i));
+ }
+ }
+ }
+}