From 3d4796d3ccf53036177b66331fa50a4c6be51d20 Mon Sep 17 00:00:00 2001 From: Romain Grecourt Date: Thu, 28 Sep 2023 13:36:16 -0700 Subject: [PATCH] Fix occurences of "reactive" left-over after the removal of the Reactive WebServer. (#7684) Fixes #7680 This changeset does not address all issues, a lot of the docs remain broken. - Replaced io.helidon.reactive.webserver with io.helidon.webserver - Replaced io.helidon.reactive.webserver.Routing with io.helidon.webserver.HttpRules - Replaced io.helidon.reactive.webserver.Service with io.helidon.webserver.HttpService - Replaced `public void routing(HttpRules rules) {` with `public void update(Routing.Rules rules) {` - Removed WebServer Jersey documentation - Removed Programmatic Media Support registration from the webserver docs - Updated HashiCorp Vault docs to refresh snippets to not be reactive - Replaced "Reactive Route" in MP server docs with "WebServer Route" - Replaced "Reactive Service" in MP server docs with "HTTP Service" --- docs/about/intro.adoc | 4 +- docs/includes/attributes.adoc | 15 +- docs/includes/cors.adoc | 6 +- docs/includes/security/helidon-endpoints.adoc | 4 +- docs/mp/cors/cors.adoc | 2 +- docs/mp/guides/mp-tutorial.adoc | 6 +- docs/mp/guides/se-services.adoc | 12 +- docs/mp/integrations/hcv.adoc | 6 +- docs/mp/jaxrs/jaxrs-applications.adoc | 2 +- docs/mp/server.adoc | 20 +- docs/se/dbclient.adoc | 9 +- docs/se/guides/dbclient.adoc | 21 +- docs/se/guides/metrics.adoc | 73 ++-- docs/se/guides/migration.adoc | 28 +- docs/se/guides/migration_3x.adoc | 8 +- docs/se/guides/overview.adoc | 4 +- docs/se/guides/security-oidc.adoc | 9 +- docs/se/guides/tracing.adoc | 12 +- docs/se/guides/webclient.adoc | 26 +- docs/se/health.adoc | 102 +++-- docs/se/inject/injection_intro.adoc | 8 +- docs/se/integrations/hcv.adoc | 412 +++++++++--------- docs/se/integrations/neo4j.adoc | 4 +- docs/se/introduction.adoc | 8 +- docs/se/metrics/metrics.adoc | 4 +- docs/se/metrics/micrometer.adoc | 4 +- docs/se/openapi/openapi-generator.adoc | 100 +---- docs/se/security/containers-integration.adoc | 108 +---- docs/se/tracing.adoc | 3 +- docs/se/webclient.adoc | 7 +- docs/se/webserver.adoc | 176 +------- docs/se/websocket.adoc | 84 ++-- docs/sitegen.yaml | 4 +- 33 files changed, 449 insertions(+), 842 deletions(-) diff --git a/docs/about/intro.adoc b/docs/about/intro.adoc index 29f934cb6e9..539fca2588d 100644 --- a/docs/about/intro.adoc +++ b/docs/about/intro.adoc @@ -34,11 +34,11 @@ * Upgraded MicroProfile support to MicroProfile 6 and Jakarta 10 Core Profile running on the Helidon Níma WebServer. Learn more: <> -* Java 21 is required to use Heldon 4. +* Java 21 is required to use Helidon 4. -== Helidon 4 WebServer +== Helidon 4 WebServer Before Helidon 4, the Helidon WebServer was built on Netty and had a reactive API. In Helidon 4 we have replaced this with a new server implementation (Project Níma) that is written from the ground up to take full advantage of Java 21's virtual threads. With virtual threads, threads are no longer a scarce resource to be carefully pooled and managed. Instead they are an abundant resource that can be created as needed to handle nearly unlimited concurrent requests. diff --git a/docs/includes/attributes.adoc b/docs/includes/attributes.adoc index 92fcd704198..0919a1d5df3 100644 --- a/docs/includes/attributes.adoc +++ b/docs/includes/attributes.adoc @@ -190,13 +190,13 @@ endif::[] :http-javadoc-base-url: {javadoc-base-url}/io.helidon.http :config-javadoc-base-url: {javadoc-base-url}/io.helidon.config :configurable-javadoc-base-url: {javadoc-base-url}/apidocs/io.helidon.common.configurable -:faulttolerance-javadoc-base-url: {javadoc-base-url}/io.helidon.reactive.faulttolerance +:faulttolerance-javadoc-base-url: {javadoc-base-url}/io.helidon.faulttolerance :grpc-server-javadoc-base-url: {javadoc-base-url}/io.helidon.grpc.server :health-javadoc-base-url: {javadoc-base-url}/io.helidon.health.checks :integration-oci-sdk-cdi-javadoc-base-url: {javadoc-base-url}/io.helidon.integrations.oci.sdk.cdi -:media-jsonp-javadoc-base-url: {javadoc-base-url}/io.helidon.reactive.media.jsonp -:media-jsonb-javadoc-base-url: {javadoc-base-url}/io.helidon.reactive.media.jsonb -:media-jackson-javadoc-base-url: {javadoc-base-url}/io.helidon.reactive.media.jackson +:media-jsonp-javadoc-base-url: {javadoc-base-url}/io.helidon.http.media.jsonp +:media-jsonb-javadoc-base-url: {javadoc-base-url}/io.helidon.http.media.jsonb +:media-jackson-javadoc-base-url: {javadoc-base-url}/io.helidon.http.media.jackson :metrics-javadoc-base-url: {javadoc-base-url}/io.helidon.metrics.api :metrics-mp-javadoc-base-url: {javadoc-base-url}/io.helidon.microprofile.metrics :metrics-serviceapi-javadoc-base-url: {javadoc-base-url}/io.helidon.metrics.serviceapi @@ -211,11 +211,10 @@ endif::[] :scheduling-javadoc-base-url: {javadoc-base-url}/io.helidon.microprofile.scheduling :security-integration-jersey-base-url: {javadoc-base-url}/io.helidon.security.integration.jersey :security-integration-webserver-base-url: {javadoc-base-url}/io.helidon.security.integration.webserver -:webclient-javadoc-base-url: {javadoc-base-url}/io.helidon.reactive.webclient -:webserver-javadoc-base-url: {javadoc-base-url}/io.helidon.reactive.webserver -:webserver-jersey-javadoc-base-url: {javadoc-base-url}/io.helidon.reactive.webserver.jersey +:webclient-javadoc-base-url: {javadoc-base-url}/io.helidon.webclient +:webserver-javadoc-base-url: {javadoc-base-url}/io.helidon.webserver :webserver-staticcontent-javadoc-base-url: {webserver-javadoc-base-url}.staticcontent -:webserver-cors-javadoc-base-url: {javadoc-base-url}/io.helidon.reactive.webserver.cors +:webserver-cors-javadoc-base-url: {javadoc-base-url}/io.helidon.webserver.cors :graphql-javadoc-base-url: {javadoc-base-url}/io.helidon.graphql.server :security-provider-oidc-base-url: {javadoc-base-url}/io.helidon.security.providers.oidc :security-provider-httpauth-base-url: {javadoc-base-url}/io.helidon.security.providers.httpauth diff --git a/docs/includes/cors.adoc b/docs/includes/cors.adoc index 040bc747009..9fef588a839 100644 --- a/docs/includes/cors.adoc +++ b/docs/includes/cors.adoc @@ -70,9 +70,9 @@ the following dependency in your project: Support in Helidon for CORS configuration uses two closely-related cross-origin configuration formats: basic and mapped. Each format corresponds to a class in the Helidon CORS library. -The basic format corresponds to the link:{webserver-cors-javadoc-base-url}/io/helidon/reactive/webserver/cors/CrossOriginConfig.html[`CrossOriginConfig`] +The basic format corresponds to the link:{webserver-cors-javadoc-base-url}/io/helidon/cors/CrossOriginConfig.html[`CrossOriginConfig`] class, and the mapped format corresponds to the -link:{webserver-cors-javadoc-base-url}/io/helidon/reactive/webserver/cors/MappedCrossOriginConfig.html[`MappedCrossOriginConfig`] class. +link:{webserver-cors-javadoc-base-url}/io/helidon/cors/MappedCrossOriginConfig.html[`MappedCrossOriginConfig`] class. //end::cors-configuration-formats-intro[] //tag::basic-cross-origin-config[] @@ -239,7 +239,7 @@ the top-level resource in the app (the `path-pattern` key and value). <6> Begins the basic CORS config section for `/`; it permits sharing of resources at the top-level path with all origins for the indicated HTTP methods. -Path patterns can be any expression accepted by the link:{webserver-javadoc-base-url}/io/helidon/reactive/webserver/PathMatcher.html[`PathMatcher`] class. +Path patterns can be any expression accepted by the link:{webserver-javadoc-base-url}/io/helidon/webserver/PathMatcher.html[`PathMatcher`] class. NOTE: Be sure to arrange the entries in the order that you want Helidon to check them. Helidon CORS support searches the cross-origin entries in the order you define them until it finds an entry that diff --git a/docs/includes/security/helidon-endpoints.adoc b/docs/includes/security/helidon-endpoints.adoc index 52c0c83c299..929357e53fc 100644 --- a/docs/includes/security/helidon-endpoints.adoc +++ b/docs/includes/security/helidon-endpoints.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2020, 2022 Oracle and/or its affiliates. + Copyright (c) 2020, 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. @@ -29,7 +29,7 @@ There are several endpoints provided by Helidon services, such as: - OpenAPI endpoint (`/openapi`) - Configured static content (can use any path configured) -These endpoints are all implemented using Helidon reactive WebServer and as such +These endpoints are all implemented using Helidon WebServer and as such can be protected only through Security integration with WebServer. The following section describes configuration of such protection using configuration files, diff --git a/docs/mp/cors/cors.adoc b/docs/mp/cors/cors.adoc index 5b5db34b02c..12e772dc5db 100644 --- a/docs/mp/cors/cors.adoc +++ b/docs/mp/cors/cors.adoc @@ -122,7 +122,7 @@ The index `_i_` is an integer (0, 1, 2, etc.). The system uses the index `_i_`, not the position in the config file, to identify the settings for a particular resource. -Path patterns can be any expression accepted by the link:{webserver-javadoc-base-url}/io/helidon/reactive/webserver/PathMatcher.html[`PathMatcher`] class. +Path patterns can be any expression accepted by the link:{webserver-javadoc-base-url}/io/helidon/webserver/PathMatcher.html[`PathMatcher`] class. NOTE: Helidon scans the cross-origin entries in index order (0, 1, 2, etc.) until it finds an entry that matches an incoming request's path and HTTP method, so be sure to assign index values to the entries so Helidon will check them in the order you want. In particular, use lower index values for entries with more specific path patterns. diff --git a/docs/mp/guides/mp-tutorial.adoc b/docs/mp/guides/mp-tutorial.adoc index 1d7b2fbf28d..6fb22815d07 100644 --- a/docs/mp/guides/mp-tutorial.adoc +++ b/docs/mp/guides/mp-tutorial.adoc @@ -660,15 +660,15 @@ Rebuild and run the application and notice the new logging format takes effect. .Log output ---- // before -Aug 22, 2019 11:10:11 AM io.helidon.reactive.webserver.NettyWebServer lambda$start$8 +Aug 22, 2019 11:10:11 AM io.helidon.webserver.LoomWebServer lambda$start$8 INFO: Channel '@default' started: [id: 0xd0afba31, L:/0:0:0:0:0:0:0:0:8080] Aug 22, 2019 11:10:11 AM io.helidon.microprofile.server.ServerImpl lambda$start$10 INFO: Server started on http://localhost:8080 (and all other host addresses) in 182 milliseconds. http://localhost:8080/greet // after -2019.08.22 11:24:42 INFO io.helidon.reactive.webserver.NettyWebServer Thread[main,5,main]: Version: 1.2.0 -2019.08.22 11:24:42 INFO io.helidon.reactive.webserver.NettyWebServer Thread[nioEventLoopGroup-2-1,10,main]: Channel '@default' started: [id: 0x8f652dfe, L:/0:0:0:0:0:0:0:0:8080] +2019.08.22 11:24:42 INFO io.helidon.webserver.LoomServer Thread[main,5,main]: Version: 1.2.0 +2019.08.22 11:24:42 INFO io.helidon.webserver.LoomServer Thread[nioEventLoopGroup-2-1,10,main]: Channel '@default' started: [id: 0x8f652dfe, L:/0:0:0:0:0:0:0:0:8080] 2019.08.22 11:24:42 INFO io.helidon.microprofile.server.ServerImpl Thread[nioEventLoopGroup-2-1,10,main]: Server started on http://localhost:8080 (and all other host addresses) in 237 milliseconds. http://localhost:8080/greet ---- diff --git a/docs/mp/guides/se-services.adoc b/docs/mp/guides/se-services.adoc index c0d4a913f6c..db427f0c854 100644 --- a/docs/mp/guides/se-services.adoc +++ b/docs/mp/guides/se-services.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2020, 2022 Oracle and/or its affiliates. + Copyright (c) 2020, 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. @@ -17,7 +17,7 @@ /////////////////////////////////////////////////////////////////////////////// = Reusing Helidon SE services -:description: Helidon Reactive Routing +:description: Helidon WebServer Routing :keywords: helidon, guide, routing :rootdir: {docdir}/../.. @@ -31,21 +31,21 @@ For this 10 minute tutorial, you will need the following: include::{rootdir}/includes/prerequisites.adoc[tag=prerequisites] -Helidon MP supports xref:../server.adoc[Reactive routing] which brings possibility for reusing -`io.helidon.reactive.webserver.Service` implementations in Helidon MP. Such feature can be quite useful for common +Helidon MP supports xref:../server.adoc[WebServer routing] which brings possibility for reusing +`io.helidon.webserver.HttpService` implementations in Helidon MP. Such feature can be quite useful for common solutions for filtering, auditing, logging or augmenting REST endpoints in hybrid Helidon SE/MP environment. Let's define simple Helidon SE Service for adding special header to every REST response: [source,java] ---- -public class CoolingService implements Service, Handler { +public class CoolingService implements HttpService, Handler { public static final String COOL_HEADER_NAME = "Cool-Header"; public static final String COOLING_VALUE = "This is way cooler response than "; @Override - public void update(Routing.Rules rules) { + public void routing(HttpRules rules) { rules.any(this); } diff --git a/docs/mp/integrations/hcv.adoc b/docs/mp/integrations/hcv.adoc index 6766fb6c47f..e852eff1efa 100644 --- a/docs/mp/integrations/hcv.adoc +++ b/docs/mp/integrations/hcv.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2021, 2022 Oracle and/or its affiliates. + Copyright (c) 2021, 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. @@ -35,7 +35,8 @@ include::{rootdir}/includes/mp.adoc[] == Overview -HashiCorp Vault is a commonly used Vault in many microservices. The APIs are REST-based and Helidon implements them using reactive client. +HashiCorp Vault is a commonly used Vault in many microservices. The APIs are REST-based and Helidon implements them using +xref:{webclient-page}[WebClient]. include::{rootdir}/includes/dependencies.adoc[] @@ -111,7 +112,6 @@ The following classes can be injected into any CDI bean (if appropriate module i * K8sAuth - Kubernetes authentication method (management operations) * TokenAuth - Token authentication method (management operations) * Sys - System operations (management of Vault - enabling/disabling secret engines and authentication methods) -* *Rx - reactive counterparts to the classes defined above, usually not recommended in CDI, as it is a blocking environment In addition to these features, Vault itself can be authenticated as follows: diff --git a/docs/mp/jaxrs/jaxrs-applications.adoc b/docs/mp/jaxrs/jaxrs-applications.adoc index 99fba6bbb03..38d2c904118 100644 --- a/docs/mp/jaxrs/jaxrs-applications.adoc +++ b/docs/mp/jaxrs/jaxrs-applications.adoc @@ -75,7 +75,7 @@ is provided via the `ServerRequest` 's context object as shown next. [source,java] ---- -import io.helidon.reactive.webserver.ServerRequest; +import io.helidon.webserver.ServerRequest; @Path("myresource") public class MyResource { diff --git a/docs/mp/server.adoc b/docs/mp/server.adoc index d7f6889ad87..e58a63fdcfd 100644 --- a/docs/mp/server.adoc +++ b/docs/mp/server.adoc @@ -78,7 +78,7 @@ as is done in the xref:guides/quickstart.adoc[Helidon MP Quickstart example]. No include::{rootdir}/config/io_helidon_microprofile_server_Server.adoc[leveloffset=+1,tag=config] The following table provides a brief description of routing annotations, including its parameters. More information -in `Configuring a reactive route` section. +in `Configuring a WebServer route` section. [cols="3,5", role="flex, sm10"] |=== @@ -222,12 +222,12 @@ health: routing: "admin" ---- -=== Configuring A Reactive Route +=== Configuring A WebServer Route [[ -Helidon MP Server will pick up CDI beans that implement the `io.helidon.reactive.webserver.Service` +Helidon MP Server will pick up CDI beans that implement the `io.helidon.webserver.HttpService` interface and configure them with the underlying WebServer. -This allows configuration of reactive routes to run alongside a JAX-RS application. +This allows configuration of WebServer routes to run alongside a JAX-RS application. The bean is expected to be either `ApplicationScoped` or `Dependent` and will be requested only once during the boot of the `Server`. @@ -236,17 +236,17 @@ The bean will support injection of `ApplicationScoped` and `Dependent` scoped be You cannot inject `RequestScoped` beans. Please use WebServer features to handle request related objects. -==== Customizing the reactive service +==== Customizing the HTTP service The service can be customized using annotations and/or configuration to be - registered on a specific path - registered with a named routing -==== Assigning a reactive service to named ports +==== Assigning an HTTP service to named ports Helidon has the concept of named routing. These correspond to the named ports configured with WebServer. -You can assign a reactive service to a named routing (and as a result to a named port) using +You can assign an HTTP service to a named routing (and as a result to a named port) using either an annotation or configuration (or both to override the value from annotation). ===== Annotation `@RoutingName` [[annotation-routing-name]] @@ -263,7 +263,7 @@ The annotation has two attributes: @ApplicationScoped @RoutingName(value="admin", required="true") @RoutingPath("/admin") -public class AdminService implements Service { +public class AdminService implements HttpService { } ---- @@ -287,7 +287,7 @@ io.helidon.examples.AdminService: required: false ---- -==== Configuring a reactive service path +==== Configuring an HTTP service path Each service is registered on a path. If none is configured, then the service would be configured on the root path. @@ -300,7 +300,7 @@ You can configure `@RoutingPath` to define the path a service is registered on. ===== Configuration override of routing path -For each reactive service class you can define the routing path by specifying a configuration +For each HTTP service class you can define the routing path by specifying a configuration option `class-name.routing-path.path`. Example (YAML) configuration for a class `io.helidon.example.AdminService` that changes the diff --git a/docs/se/dbclient.adoc b/docs/se/dbclient.adoc index 1082dbca35a..8174e4b3ecf 100644 --- a/docs/se/dbclient.adoc +++ b/docs/se/dbclient.adoc @@ -35,7 +35,7 @@ include::{rootdir}/includes/se.adoc[] == Overview -The Helidon SE DB Client provides a unified, reactive API for working with databases in non-blocking way. +The Helidon SE DB Client provides a unified API for working with databases. include::{rootdir}/includes/dependencies.adoc[] @@ -88,13 +88,6 @@ different databases on different environments without changing code. Thanks to the statement configuration abstraction, we can invoke a statement against a relational or non-relations databases (such as MySQL and MongoDB) without modifying source code -* Reactive database access with backpressure -+ -Current we support natively reactive driver for MongoDB, and an executor service wrapped -support for any JDBC driver. -This allows for seamless use of JDBC drivers in a reactive non-blocking environment, including support -for backpressure (result set is processed as requested by the query subscriber) - * Observability + + The API offers support for health checks, metrics and tracing. diff --git a/docs/se/guides/dbclient.adoc b/docs/se/guides/dbclient.adoc index cccb169be62..6160738be39 100644 --- a/docs/se/guides/dbclient.adoc +++ b/docs/se/guides/dbclient.adoc @@ -34,20 +34,15 @@ include::{rootdir}/includes/prerequisites.adoc[tag=prerequisites] == Introduction -The Helidon DB Client simplifies how you work with databases in reactive applications. It provides a unified, -reactive API for working with databases in a non-blocking way. Helidon team constantly updates this API in order -to make it more user-friendly and efficient. For this matter, it is recommended to use the latest helidon version. +The Helidon DB Client provides a unified API for working with databases. === Main Features The main features of Helidon DB Client are: * *Unified API for data access and query*: -The API was implemented as a layer above JDBC or MongoDB Reactive Streams Java Driver, so any relational databases +The API was implemented as a layer above JDBC or MongoDB Java Driver, so any relational databases with JDBC driver or MongoDB are supported. -* *Reactive database access with non-reactive drivers*: -Most JDBC drivers are blocking. Using them in a reactive application is problematic. Helidon DB Client allows the use -of blocking JDBC drivers in your reactive application by wrapping a blocking driver in an executor service. * *Observability*: Support for health checks, metrics and tracing. * *Backpressure*: @@ -120,9 +115,9 @@ A database stores the books from the library. H2 is a java SQL database that is If H2 is not installed on your machine, here are few steps to quickly download and set it up: 1. Download the latest H2 version from the official website: https://www.h2database.com/html/main.html - + * Note: Windows operating system users can download the Windows Installer. - + 2. Unzip the downloaded file into your directory. * Only the h2-\{latest-version}.jar, located in the h2/bin folder, will be needed. @@ -343,12 +338,12 @@ package io.helidon.examples.quickstart.se; import io.helidon.http.Http; // <1> import io.helidon.dbclient.DbClient; -import io.helidon.webserver.Routing; +import io.helidon.webserver.HttpRules; import io.helidon.webserver.ServerRequest; import io.helidon.webserver.ServerResponse; import io.helidon.webserver.Service; -public class LibraryService implements Service { +public class LibraryService implements HttpService { private final DbClient dbClient; // <2> @@ -368,7 +363,7 @@ It defines application endpoints and Http request which can be reached by client .Add update method to LibraryService ---- @Override -public void update(Routing.Rules rules) { +public void routing(HttpRules rules) { rules .get("/{name}", this::getBook) // <1> .put("/{name}", this::addBook) // <2> @@ -381,7 +376,7 @@ public void update(Routing.Rules rules) { <3> Remove a book from the library. <4> Return the book information in Json format. -To summarize, there is one endpoint that can manipulate books. +To summarize, there is one endpoint that can manipulate books. The number of endpoints and application features can be changed from these rules by creating or modifying methods. `\{name}` is a path parameter for the book name. The architecture of the application is defined, so the next step is to create these features. diff --git a/docs/se/guides/metrics.adoc b/docs/se/guides/metrics.adoc index fc3004fa00e..093e52963c9 100644 --- a/docs/se/guides/metrics.adoc +++ b/docs/se/guides/metrics.adoc @@ -225,10 +225,10 @@ will demonstrate how to use a `Counter` to track the number of times the `/cards package io.helidon.examples.quickstart.se; import io.helidon.metrics.RegistryFactory; -import io.helidon.reactive.webserver.Routing; -import io.helidon.reactive.webserver.ServerRequest; -import io.helidon.reactive.webserver.ServerResponse; -import io.helidon.reactive.webserver.Service; +import io.helidon.webserver.HttpRules; +import io.helidon.webserver.ServerRequest; +import io.helidon.webserver.ServerResponse; +import io.helidon.webserver.Service; import java.util.Collections; import jakarta.json.Json; import jakarta.json.JsonBuilderFactory; @@ -236,7 +236,7 @@ import jakarta.json.JsonObject; import org.eclipse.microprofile.metrics.Counter; // <1> import org.eclipse.microprofile.metrics.MetricRegistry; // <1> -public class GreetingCards implements Service { +public class GreetingCards implements HttpService { private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); private final Counter cardCounter; // <2> @@ -248,7 +248,7 @@ public class GreetingCards implements Service { } @Override - public void update(Routing.Rules rules) { + public void routing(HttpRules rules) { rules.get("/", this::getDefaultMessageHandler); } @@ -322,11 +322,10 @@ The following example marks an event each time the `/cards` endpoint is called. ---- package io.helidon.examples.quickstart.se; -import io.helidon.metrics.RegistryFactory; -import io.helidon.reactive.webserver.Routing; -import io.helidon.reactive.webserver.ServerRequest; -import io.helidon.reactive.webserver.ServerResponse; -import io.helidon.reactive.webserver.Service; +import io.helidon.webserver.HttpRules; +import io.helidon.webserver.ServerRequest; +import io.helidon.webserver.ServerResponse; +import io.helidon.webserver.Service; import java.util.Collections; import jakarta.json.Json; import jakarta.json.JsonBuilderFactory; @@ -334,7 +333,7 @@ import jakarta.json.JsonObject; import org.eclipse.microprofile.metrics.Meter; // <1> import org.eclipse.microprofile.metrics.MetricRegistry; // <1> -public class GreetingCards implements Service { +public class GreetingCards implements HttpService { private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); private final Meter cardMeter; // <2> @@ -346,7 +345,7 @@ public class GreetingCards implements Service { } @Override - public void update(Routing.Rules rules) { + public void routing(HttpRules rules) { rules.get("/", this::getDefaultMessageHandler); } @@ -409,10 +408,10 @@ endpoint is called, the `Timer` will be updated with additional timing informati package io.helidon.examples.quickstart.se; import io.helidon.metrics.RegistryFactory; -import io.helidon.reactive.webserver.Routing; -import io.helidon.reactive.webserver.ServerRequest; -import io.helidon.reactive.webserver.ServerResponse; -import io.helidon.reactive.webserver.Service; +import io.helidon.webserver.Routing; +import io.helidon.webserver.ServerRequest; +import io.helidon.webserver.ServerResponse; +import io.helidon.webserver.Service; import java.util.Collections; import jakarta.json.Json; import jakarta.json.JsonBuilderFactory; @@ -420,7 +419,7 @@ import jakarta.json.JsonObject; import org.eclipse.microprofile.metrics.MetricRegistry; // <1> import org.eclipse.microprofile.metrics.Timer; // <1> -public class GreetingCards implements Service { +public class GreetingCards implements HttpService { private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); private final Timer cardTimer; // <2> @@ -432,7 +431,7 @@ public class GreetingCards implements Service { } @Override - public void update(Routing.Rules rules) { + public void routing(HttpRules rules) { rules.get("/", this::getDefaultMessageHandler); } @@ -505,10 +504,10 @@ the `/cards` endpoint is invoked. package io.helidon.examples.quickstart.se; import io.helidon.metrics.RegistryFactory; -import io.helidon.reactive.webserver.Routing; -import io.helidon.reactive.webserver.ServerRequest; -import io.helidon.reactive.webserver.ServerResponse; -import io.helidon.reactive.webserver.Service; +import io.helidon.webserver.HttpRules; +import io.helidon.webserver.ServerRequest; +import io.helidon.webserver.ServerResponse; +import io.helidon.webserver.Service; import java.util.Collections; import java.util.Random; import jakarta.json.Json; @@ -517,7 +516,7 @@ import jakarta.json.JsonObject; import org.eclipse.microprofile.metrics.Histogram; // <1> import org.eclipse.microprofile.metrics.MetricRegistry; // <1> -public class GreetingCards implements Service { +public class GreetingCards implements HttpService { private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); private final Histogram cardHistogram; // <2> @@ -529,7 +528,7 @@ public class GreetingCards implements Service { } @Override - public void update(Routing.Rules rules) { + public void routing(HttpRules rules) { rules.get("/", this::getDefaultMessageHandler); } @@ -628,10 +627,10 @@ import org.eclipse.microprofile.metrics.MetricRegistry; package io.helidon.examples.quickstart.se; import io.helidon.metrics.RegistryFactory; -import io.helidon.reactive.webserver.Routing; -import io.helidon.reactive.webserver.ServerRequest; -import io.helidon.reactive.webserver.ServerResponse; -import io.helidon.reactive.webserver.Service; +import io.helidon.webserver.HttpRules; +import io.helidon.webserver.ServerRequest; +import io.helidon.webserver.ServerResponse; +import io.helidon.webserver.Service; import java.util.Collections; import jakarta.json.Json; import jakarta.json.JsonBuilderFactory; @@ -639,7 +638,7 @@ import jakarta.json.JsonObject; import org.eclipse.microprofile.metrics.Counter; import org.eclipse.microprofile.metrics.MetricRegistry; -public class GreetingCards implements Service { +public class GreetingCards implements HttpService { private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); private final Counter cardCounter; @@ -651,7 +650,7 @@ public class GreetingCards implements Service { } @Override - public void update(Routing.Rules rules) { + public void routing(HttpRules rules) { rules.get("/", this::getDefaultMessageHandler); } @@ -699,10 +698,10 @@ endpoint is called, the `SimpleTimer` updates its count and total elapsed time. package io.helidon.examples.quickstart.se; import io.helidon.metrics.RegistryFactory; -import io.helidon.reactive.webserver.Routing; -import io.helidon.reactive.webserver.ServerRequest; -import io.helidon.reactive.webserver.ServerResponse; -import io.helidon.reactive.webserver.Service; +import io.helidon.webserver.HttpRules; +import io.helidon.webserver.ServerRequest; +import io.helidon.webserver.ServerResponse; +import io.helidon.webserver.Service; import java.util.Collections; import jakarta.json.Json; import jakarta.json.JsonBuilderFactory; @@ -710,7 +709,7 @@ import jakarta.json.JsonObject; import org.eclipse.microprofile.metrics.MetricRegistry; // <1> import org.eclipse.microprofile.metrics.SimpleTimer; // -public class GreetingCards implements Service { +public class GreetingCards implements HttpService { private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); private final SimpleTimer cardTimer; // <2> @@ -722,7 +721,7 @@ public class GreetingCards implements Service { } @Override - public void update(Routing.Rules rules) { + public void routing(HttpRules rules) { rules.get("/", this::getDefaultMessageHandler); } diff --git a/docs/se/guides/migration.adoc b/docs/se/guides/migration.adoc index 1c860fac790..2131857a656 100644 --- a/docs/se/guides/migration.adoc +++ b/docs/se/guides/migration.adoc @@ -128,9 +128,9 @@ This replaces `Routing.builder().register(JsonSupport.create())...` The new JSON MediaSupport classes are: -* `io.helidon.reactive.media.jsonp.JsonpSupport` in module `io.helidon.reactive.media:helidon-reactive-media-jsonp` -* `io.helidon.reactive.media.jsonb.JsonbSupport` in module `io.helidon.reactive.media:helidon-reactive-media-jsonb` -* `io.helidon.reactive.media.jackson.JacksonSupport` in module `io.helidon.reactive.media:helidon-reactive-media-jackson` +* `io.helidon.http.media.jsonp.JsonpSupport` in module `io.helidon.http.media:helidon-media-jsonp` +* `io.helidon.http.media.jsonb.JsonbSupport` in module `io.helidon.http.media:helidon-media-jsonb` +* `io.helidon.http.media.jackson.JacksonSupport` in module `io.helidon.http.media:helidon-media-jackson` == Reactive @@ -203,9 +203,9 @@ Configuration has been updated to use the new `Resource` approach: == WebServer Configuration === SSL/TLS -There is a new class `io.helidon.reactive.webserver.WebServerTls` that can be used +There is a new class `io.helidon.webserver.WebServerTls` that can be used to configure TLS for a WebServer socket. -Class `io.helidon.reactive.webserver.SSLContextBuilder` has been deprecated and will +Class `io.helidon.webserver.SSLContextBuilder` has been deprecated and will be removed. The class uses a `Builder` pattern: @@ -265,7 +265,7 @@ together, to remove mapping through a name. === Deprecation of ServerConfiguration -`io.helidon.reactive.webserver.ServerConfiguration.Builder` is no longer used +`io.helidon.webserver.ServerConfiguration.Builder` is no longer used to configure `WebServer`. Most methods from this class have been moved to `WebServer.Builder` or deprecated. @@ -283,14 +283,14 @@ WebServer.builder() === Other Significant WebServer Deprecations -- `io.helidon.reactive.webserver.WebServer.Builder` - all methods that accept `ServerConfiguration` or its builder are deprecated, please use +- `io.helidon.webserver.WebServer.Builder` - all methods that accept `ServerConfiguration` or its builder are deprecated, please use methods on `WebServer.Builder` instead -- `io.helidon.reactive.webserver.WebServer.Builder` - all methods for socket configuration that accept a name +- `io.helidon.webserver.WebServer.Builder` - all methods for socket configuration that accept a name and socket are deprecated, socket name is now part of socket configuration itself -- `io.helidon.reactive.webserver.ResponseHeaders.whenSend()` - please use `whenSent()` -- `io.helidon.reactive.webserver.Routing.createServer(ServerConfiguration)` - please use `WebServer.builder()` -- `io.helidon.reactive.webserver.Routing.createServer()` - please use `WebServer.builder()` -- `io.helidon.reactive.webserver.SocketConfiguration.DEFAULT` - use a builder to create a named configuration -- `io.helidon.reactive.webserver.SocketConfiguration.Builder.ssl(SSLContext) - use `WebServerTls` instead -- `io.helidon.reactive.webserver.SocketConfiguration.Builder.enabledSSlProtocols(String...) - use `WebServerTls` instead +- `io.helidon.webserver.ResponseHeaders.whenSend()` - please use `whenSent()` +- `io.helidon.webserver.Routing.createServer(ServerConfiguration)` - please use `WebServer.builder()` +- `io.helidon.webserver.Routing.createServer()` - please use `WebServer.builder()` +- `io.helidon.webserver.SocketConfiguration.DEFAULT` - use a builder to create a named configuration +- `io.helidon.webserver.SocketConfiguration.Builder.ssl(SSLContext) - use `WebServerTls` instead +- `io.helidon.webserver.SocketConfiguration.Builder.enabledSSlProtocols(String...) - use `WebServerTls` instead diff --git a/docs/se/guides/migration_3x.adoc b/docs/se/guides/migration_3x.adoc index de7fb7e632d..d7a73125ebf 100644 --- a/docs/se/guides/migration_3x.adoc +++ b/docs/se/guides/migration_3x.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - 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. @@ -60,7 +60,7 @@ available on the classpath. [source,xml] ---- - io.helidon.reactive.webserver + io.helidon.webserver helidon-reactive-webserver-http2 ---- @@ -91,7 +91,7 @@ configure routing for both annotated and programmatic WebSocket endpoints. [source,xml] ---- - io.helidon.reactive.webserver + io.helidon.webserver helidon-reactive-webserver-tyrus ---- @@ -100,7 +100,7 @@ configure routing for both annotated and programmatic WebSocket endpoints. [source,xml] ---- - io.helidon.reactive.webserver + io.helidon.webserver helidon-reactive-webserver-websocket ---- diff --git a/docs/se/guides/overview.adoc b/docs/se/guides/overview.adoc index a25a72e3074..ae325540272 100644 --- a/docs/se/guides/overview.adoc +++ b/docs/se/guides/overview.adoc @@ -86,14 +86,14 @@ Learn how to Upgrade your Helidon SE application .Helidon SE WebClient Guide [link=webclient.adoc] -- -Learn how to use the Helidon SE Reactive WebClient +Learn how to use the Helidon SE WebClient -- [CARD] .Helidon SE DB Client Guide [link=dbclient.adoc] -- -Learn how to use the Helidon SE Reactive DB Client +Learn how to use the Helidon SE DB Client -- [CARD] diff --git a/docs/se/guides/security-oidc.adoc b/docs/se/guides/security-oidc.adoc index a5348d78623..dbf3c6da8fa 100644 --- a/docs/se/guides/security-oidc.adoc +++ b/docs/se/guides/security-oidc.adoc @@ -313,17 +313,18 @@ The `Main.createRouting` method gather all configuration properties. ---- import io.helidon.security.Security; import io.helidon.security.integration.webserver.WebSecurity; -import io.helidon.security.providers.oidc.reactive.OidcSupport; +import io.helidon.security.providers.oidc.OidcFeature; Security security = Security.create(config.get("security")); // <1> return Routing.builder() - .register(WebSecurity.create(security, config.get("security"))) // <2> - .register(OidcSupport.create(config)) // <3> + .addFeature(ContextFeature.create()) + .addFeature(SecurityFeature.create(security, config.get("security"))) // <2> + .addFeature(OidcFeature.create(config)) // <3> ---- <1> Create the Helidon `Security` instance using configuration. <2> Register Helidon `WebSecurity` instance using security instance and configuration. -<3> Register Helidon `OidcSupport` instance. +<3> Register Helidon `OidcFeature` instance. That code is extracting security properties from application.yaml into two steps. First the Security instance is used to bootstrap security, so the WebSecurity instance diff --git a/docs/se/guides/tracing.adoc b/docs/se/guides/tracing.adoc index eee4e170785..669134efd07 100644 --- a/docs/se/guides/tracing.adoc +++ b/docs/se/guides/tracing.adoc @@ -456,10 +456,10 @@ package io.helidon.examples.quickstart.se; import io.helidon.http.Http; import io.helidon.config.Config; import io.helidon.tracing.jersey.client.ClientTracingFilter; -import io.helidon.reactive.webserver.Routing; -import io.helidon.reactive.webserver.ServerRequest; -import io.helidon.reactive.webserver.ServerResponse; -import io.helidon.reactive.webserver.Service; +import io.helidon.webserver.HttpRules; +import io.helidon.webserver.ServerRequest; +import io.helidon.webserver.ServerResponse; +import io.helidon.webserver.Service; import io.opentracing.Span; import java.util.Collections; import java.util.concurrent.atomic.AtomicReference; @@ -471,7 +471,7 @@ import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.Invocation; import jakarta.ws.rs.client.WebTarget; -public class GreetService implements Service { +public class GreetService implements HttpService { private final AtomicReference greeting = new AtomicReference<>(); private WebTarget webTarget; @@ -486,7 +486,7 @@ public class GreetService implements Service { } @Override - public void update(Routing.Rules rules) { + public void routing(HttpRules rules) { rules .get("/", this::getDefaultMessageHandler) .get("/outbound", this::outboundMessageHandler) // <1> diff --git a/docs/se/guides/webclient.adoc b/docs/se/guides/webclient.adoc index a1276ed7305..1d6d42bf151 100644 --- a/docs/se/guides/webclient.adoc +++ b/docs/se/guides/webclient.adoc @@ -38,18 +38,13 @@ include::{rootdir}/includes/prerequisites.adoc[tag=prerequisites] === WebClient Features [[WebClient-features]] -Helidon's WebClient is used to perform HTTP REST requests to target endpoints and handle their responses. Built on top of -a reactive approach, you are no longer blocked while waiting for the data. +Helidon's WebClient is used to perform HTTP REST requests to target endpoints and handle their responses. *Note*: WebClient is still experimental and not intended for production use. APIs and features are not yet fully tested and are subject to change. WebClient provides the following features: - * *Reactive*: -As mentioned, the code execution is not blocked by waiting for server response when a request is performed. To avoid -memory overflow, the client has built-in back-pressure support. - * *User-friendly*: Every client and request is created by a builder pattern, so it improves readability and code maintenance. @@ -129,8 +124,8 @@ specific settings and improves the readability and simplicity of the code. [source,java] .Add WebClient instance to the main method: ---- -import io.helidon.reactive.media.jsonp.JsonpSupport; -import io.helidon.reactive.webclient.WebClient; +import io.helidon.http.media.jsonp.JsonpSupport; +import io.helidon.webclient.WebClient; WebClient webClient = WebClient.builder() .baseUri("http://localhost:8080") // <1> @@ -147,18 +142,13 @@ Once built, the WebClient can be used to send a GET request to the greeting appl [source,java] .Send a GET request to the target endpoint: ---- -webClient.get() // <1> +String entity = webClient.get() // <1> .path("/greet") // <2> - .request(String.class) // <3> - .peek(System.out::println) // <4> - .await(); // <5> + .request(String.class); // <3> ---- <1> Create a HTTP GET request. <2> Target endpoint path. -<3> Execute the request and return Single with response entity handled as a String. -<4> Print the response in the console. -<5> Wait for server response because of reactive approach. - +<3> Execute the request and return response entity handled as a String. The path method joins `/greet` to the WebClient base URI. The target URI for this request becomes @@ -292,8 +282,8 @@ that can be used with any metric. ---- import io.helidon.http.Http; import io.helidon.metrics.RegistryFactory; -import io.helidon.reactive.webclient.metrics.WebClientMetrics; -import io.helidon.reactive.webclient.spi.WebClientService; +import io.helidon.webclient.metrics.WebClientMetrics; +import io.helidon.webclient.spi.WebClientService; import org.eclipse.microprofile.metrics.MetricRegistry; import org.eclipse.microprofile.metrics|.Counter; |.Meter; diff --git a/docs/se/health.adoc b/docs/se/health.adoc index e244a88ad9c..3b765b463a8 100644 --- a/docs/se/health.adoc +++ b/docs/se/health.adoc @@ -180,21 +180,15 @@ The following table provides a summary of the Health Check API classes. [cols="4,6"] .Health check API classes |======= -| `org.eclipse.microprofile.health.HealthCheck` +| `io.helidon.health.HealthCheck` | Java functional interface representing the logic of a single health check -| `org.eclipse.microprofile.health.HealthCheckResponse` -| Result of a health check invocation that contains a status and a description. +| `io.helidon.health.HealthCheckResponse` +| Result of a health check invocation that contains a status -| `org.eclipse.microprofile.health.HealthCheckResponseBuilder` -| Builder class to create `HealthCheckResponse` instances - -| `io.helidon.reactive.health.HealthSupport` +| `io.helidon.webserver.observe.health.HealthObserver` | WebServer service that exposes `/health` and invokes the registered health checks - -| `io.helidon.reactive.health.HealthSupport.Builder` -| Builder class to create `HealthSupport` instances |======= === Built-In Health Checks @@ -233,18 +227,19 @@ The following code adds the default built-in health checks to your application: [source,java] ---- -HealthSupport health = HealthSupport.builder() - .add(HealthChecks.healthChecks()) // <1> +HealthObserver health = HealthObserver.builder() + .addChecks(HealthChecks.healthChecks()) // <1> .build(); Routing.builder() - .register(health) // <2> + .addFeature(ObserveFeature.builder() + .addObserver(health)) // <2> .build(); ---- <1> Add built-in health checks using defaults (requires the `helidon-health-checks` dependency). -<2> Register the created `HealthSupport` with web server routing (adds the -`/health` endpoint). +<2> Add the created `HealthObserver` to the `ObserveFeature` registered in the web server routing (adds the +`/observe/health` endpoint). You can control the thresholds for built-in health checks in either of two ways: @@ -424,42 +419,53 @@ specification is also provided for the Kubernetes service and deployment. [source,java] .Application code: ---- -Routing healthRouting = Routing.builder() - .register(JsonSupport.create()) - .register(HealthSupport.builder() - .webContext("/live") // <1> - .add(HealthChecks.healthChecks()) // <2> - .build()) - .register(HealthSupport.builder() - .webContext("/ready") // <3> - .addReadiness(() -> HealthCheckResponse.named("database").up().build()) // <4> - .build()) - .build(); - -Routing defaultRouting = Routing.builder() - .any((req, res) -> res.send("It works!")) // <5> - .build(); +ObserveFeature observeFeature = ObserveFeature.builder() + .addObserver(HealthObserver.builder() + .useSystemServices(false) + .endpoint("/health/live") // <1> + .addChecks(HealthChecks.healthChecks()) // <2> + .build()) + .addObserver(HealthObserver.builder() + .useSystemServices(false) + .endpoint("/health/ready") // <3> + .addCheck(new HealthCheck() { // <4> + @Override + public String name() { + return "database"; + } -WebServer server = WebServer.builder(defaultRouting) - .config(WebServer.builder() - .port(8080) // <6> - .addSocket("health", SocketConfiguration.builder() // <7> - .port(8081) - .build()) - .build()) - .addNamedRouting("health", healthRouting) // <8> - .build(); + @Override + public HealthCheckResponse call() { + return HealthCheckResponse.builder() + .status(true) + .build(); + } -server.start(); + @Override + public HealthCheckType type() { + return HealthCheckType.READINESS; + } + }) + .build()) + .build(); +WebServer server = WebServer.builder() + .putSocket("@default", socket -> socket + .port(8080) // <5> + .routing(r -> r.any((req, res) -> res.send("It works!")))) // <6> + .putSocket("observe", socket -> socket + .port(8081) // <7> + .routing(r -> r.addFeature(observeFeature))) // <8> + .build() + .start(); ---- -<1> The health service for the `liveness` probe is exposed at `/live`. +<1> The health service for the `liveness` probe is exposed at `/observe/health/live`. <2> Using the built-in health checks for the `liveness` probe. -<3> The health service for the `readiness` probe is exposed at `/ready`. +<3> The health service for the `readiness` probe is exposed at `/observe/health/ready`. <4> Using a custom health check for a pseudo database that is always `UP`. -<5> The default route: returns It works! for any request. -<6> The server uses port 8080 for the default routes. -<7> A socket configuration named `health` using port `8081`. -<8> Route the health services exclusively on the `health` socket. +<5> The default socket uses port 8080 for the default routes. +<6> The default route: returns It works! for any request. +<7> The `observe` socket uses port 8081 for the "/observe" routes. +<8> Route the `observe` feature exclusively on the `observe` socket. [source,yaml] .Kubernetes descriptor: @@ -502,7 +508,7 @@ spec: - containerPort: 8080 livenessProbe: httpGet: - path: /live # <3> + path: /observe/health/live # <3> port: 8081 initialDelaySeconds: 3 # <4> periodSeconds: 10 @@ -510,7 +516,7 @@ spec: failureThreshold: 3 readinessProbe: httpGet: - path: /ready # <5> + path: /observe/health/ready # <5> port: 8081 initialDelaySeconds: 10 # <6> periodSeconds: 30 diff --git a/docs/se/inject/injection_intro.adoc b/docs/se/inject/injection_intro.adoc index 7db17c072c0..57e86d002ab 100644 --- a/docs/se/inject/injection_intro.adoc +++ b/docs/se/inject/injection_intro.adoc @@ -18,7 +18,7 @@ = Helidon Injection :description: about Helidon Injection -:keywords: helidon, inject, injection, java, microservices, reactive, virtual threads +:keywords: helidon, inject, injection, java, microservices, virtual threads :feature-name: Helidon Injection :rootdir: {docdir}/.. @@ -37,11 +37,11 @@ == Overview -Helidon Injection is an optional feature in Helidon that provides service registry, a lifecycle engine and extensibility for customized code generation. +Helidon Injection is an optional feature in Helidon that provides service registry, a lifecycle engine and extensibility for customized code generation. -NOTE: Helidon Injection provides a way to develop non Helidon-specific declarative code using standard Javax/Jakarta annotation types. +NOTE: Helidon Injection provides a way to develop non Helidon-specific declarative code using standard Javax/Jakarta annotation types. -The Helidon Injection Framework provides a mix of declarative and programmatic ways to build your application. +The Helidon Injection Framework provides a mix of declarative and programmatic ways to build your application. Helidon Injection's minimalist, compile-time generated dependency injection (DI) framework and compile-time source code generation has a number of advantages, including: * enables declarative, Inversion of Control-style programming diff --git a/docs/se/integrations/hcv.adoc b/docs/se/integrations/hcv.adoc index c51b3306923..3f2ef103d4c 100644 --- a/docs/se/integrations/hcv.adoc +++ b/docs/se/integrations/hcv.adoc @@ -35,7 +35,8 @@ include::{rootdir}/includes/se.adoc[] == Overview -HashiCorp Vault is a commonly used Vault in many microservices. The APIs are REST-based and Helidon implements them using reactive client. +HashiCorp Vault is a commonly used Vault in many microservices. The APIs are REST-based and Helidon implements them using +xref:{webclient-page}[WebClient]. include::{rootdir}/includes/dependencies.adoc[] @@ -188,17 +189,16 @@ Then `WebService` has to be configured with endpoints routing registered: [source,java] ---- -SysRx sys = tokenVault.sys(SysRx.API); +Sys sys = tokenVault.sys(Sys.API); WebServer webServer = WebServer.builder() - .config(config.get("server")) - .routing(Routing.builder() - .register("/cubbyhole", new CubbyholeService(sys, tokenVault.secrets(CubbyholeSecretsRx.ENGINE))) - .register("/kv1", new Kv1Service(sys, tokenVault.secrets(Kv1SecretsRx.ENGINE))) - .register("/kv2", new Kv2Service(sys, tokenVault.secrets(Kv2SecretsRx.ENGINE))) - .register("/transit", new TransitService(sys, tokenVault.secrets(TransitSecretsRx.ENGINE)))) - .build() - .start() - .await(); + .config(config.get("server")) + .routing(routing -> routing + .register("/cubbyhole", new CubbyholeService(sys, tokenVault.secrets(CubbyholeSecrets.ENGINE))) + .register("/kv1", new Kv1Service(sys, tokenVault.secrets(Kv1Secrets.ENGINE))) + .register("/kv2", new Kv2Service(sys, tokenVault.secrets(Kv2Secrets.ENGINE))) + .register("/transit", new TransitService(sys, tokenVault.secrets(TransitSecrets.ENGINE)))) + .build() + .start(); ---- AppRole-based and Kubernetes authentications are available. @@ -210,31 +210,26 @@ Cubbyhole secrets engine operations: [source,java] ---- @Override -public void update(Routing.Rules rules) { +public void routing(HttpRules rules) { rules.get("/create", this::createSecrets) - .get("/secrets/{path:.*}", this::getSecret); + .get("/secrets/{path:.*}", this::getSecret); } -private void createSecrets(ServerRequest req, ServerResponse res) { <1> - secrets.create("first/secret", Map.of("key", "secretValue")) - .thenAccept(ignored -> res.send("Created secret on path /first/secret")) - .exceptionally(res::send); +private void createSecrets(ServerRequest req, ServerResponse res) { // <1> + secrets.create("first/secret", Map.of("key", "secretValue")); + res.send("Created secret on path /first/secret"); } -private void getSecret(ServerRequest req, ServerResponse res) { <2> - String path = req.path().param("path"); - - secrets.get(path) - .thenAccept(secret -> { - if (secret.isPresent()) { - // using toString so we do not need to depend on JSON-B - res.send(secret.get().values().toString()); - } else { - res.status(Http.Status.NOT_FOUND_404); - res.send(); - } - }) - .exceptionally(res::send); +private void getSecret(ServerRequest req, ServerResponse res) { // <2> + String path = req.path().pathParameters().get("path"); + Optional secret = secrets.get(path); + if (secret.isPresent()) { + // using toString so we do not need to depend on JSON-B + res.send(secret.get().values().toString()); + } else { + res.status(Status.NOT_FOUND_404); + res.send(); + } } ---- @@ -248,53 +243,46 @@ Key/Value version 1 secrets engine operations: [source,java] ---- @Override -public void update(Routing.Rules rules) { +public void routing(HttpRules rules) { rules.get("/enable", this::enableEngine) - .get("/create", this::createSecrets) - .get("/secrets/{path:.*}", this::getSecret) - .delete("/secrets/{path:.*}", this::deleteSecret) - .get("/disable", this::disableEngine); + .get("/create", this::createSecrets) + .get("/secrets/{path:.*}", this::getSecret) + .delete("/secrets/{path:.*}", this::deleteSecret) + .get("/disable", this::disableEngine); } -private void disableEngine(ServerRequest req, ServerResponse res) { <1> - sys.disableEngine(Kv1SecretsRx.ENGINE) - .thenAccept(ignored -> res.send("KV1 Secret engine disabled")) - .exceptionally(res::send); +private void disableEngine(ServerRequest req, ServerResponse res) { // <1> + sys.disableEngine(Kv1Secrets.ENGINE); + res.send("KV1 Secret engine disabled"); } -private void enableEngine(ServerRequest req, ServerResponse res) { <2> - sys.enableEngine(Kv1SecretsRx.ENGINE) - .thenAccept(ignored -> res.send("KV1 Secret engine enabled")) - .exceptionally(res::send); +private void enableEngine(ServerRequest req, ServerResponse res) { // <2> + sys.enableEngine(Kv1Secrets.ENGINE); + res.send("KV1 Secret engine enabled"); } -private void createSecrets(ServerRequest req, ServerResponse res) { <3> - secrets.create("first/secret", Map.of("key", "secretValue")) - .thenAccept(ignored -> res.send("Created secret on path /first/secret")) - .exceptionally(res::send); +private void createSecrets(ServerRequest req, ServerResponse res) { // <3> + secrets.create("first/secret", Map.of("key", "secretValue")); + res.send("Created secret on path /first/secret"); } -private void deleteSecret(ServerRequest req, ServerResponse res) { <4> - String path = req.path().param("path"); - - secrets.delete(path) - .thenAccept(ignored -> res.send("Deleted secret on path " + path)); +private void deleteSecret(ServerRequest req, ServerResponse res) { // <4> + String path = req.path().pathParameters().get("path"); + secrets.delete(path); + res.send("Deleted secret on path " + path); } -private void getSecret(ServerRequest req, ServerResponse res) { <5> - String path = req.path().param("path"); - - secrets.get(path) - .thenAccept(secret -> { - if (secret.isPresent()) { - // using toString so we do not need to depend on JSON-B - res.send(secret.get().values().toString()); - } else { - res.status(Http.Status.NOT_FOUND_404); - res.send(); - } - }) - .exceptionally(res::send); +private void getSecret(ServerRequest req, ServerResponse res) { // <5> + String path = req.path().pathParameters().get("path"); + + Optional secret = secrets.get(path); + if (secret.isPresent()) { + // using toString so we do not need to depend on JSON-B + res.send(secret.get().values().toString()); + } else { + res.status(Status.NOT_FOUND_404); + res.send(); + } } ---- @@ -311,40 +299,35 @@ Key/Value version 2 secrets engine operations: [source,java] ---- @Override -public void update(Routing.Rules rules) { +public void routing(HttpRules rules) { rules.get("/create", this::createSecrets) - .get("/secrets/{path:.*}", this::getSecret) - .delete("/secrets/{path:.*}", this::deleteSecret); + .get("/secrets/{path:.*}", this::getSecret) + .delete("/secrets/{path:.*}", this::deleteSecret); } -private void createSecrets(ServerRequest req, ServerResponse res) { <1> - secrets.create("first/secret", Map.of("key", "secretValue")) - .thenAccept(ignored -> res.send("Created secret on path /first/secret")) - .exceptionally(res::send); +private void createSecrets(ServerRequest req, ServerResponse res) { // <1> + secrets.create("first/secret", Map.of("key", "secretValue")); + res.send("Created secret on path /first/secret"); } -private void deleteSecret(ServerRequest req, ServerResponse res) { <2> - String path = req.path().param("path"); - - secrets.deleteAll(path) - .thenAccept(ignored -> res.send("Deleted secret on path " + path)); +private void deleteSecret(ServerRequest req, ServerResponse res) { // <2> + String path = req.path().pathParameters().get("path"); + secrets.deleteAll(path); + res.send("Deleted secret on path " + path); } -private void getSecret(ServerRequest req, ServerResponse res) { <3> - String path = req.path().param("path"); - - secrets.get(path) - .thenAccept(secret -> { - if (secret.isPresent()) { - // using toString so we do not need to depend on JSON-B - Kv2Secret kv2Secret = secret.get(); - res.send("Version " + kv2Secret.metadata().version() + ", secret: " + kv2Secret.values().toString()); - } else { - res.status(Http.Status.NOT_FOUND_404); - res.send(); - } - }) - .exceptionally(res::send); +private void getSecret(ServerRequest req, ServerResponse res) { // <3> + String path = req.path().pathParameters().get("path"); + + Optional secret = secrets.get(path); + if (secret.isPresent()) { + // using toString so we do not need to depend on JSON-B + Kv2Secret kv2Secret = secret.get(); + res.send("Version " + kv2Secret.metadata().version() + ", secret: " + kv2Secret.values().toString()); + } else { + res.status(Status.NOT_FOUND_404); + res.send(); + } } ---- @@ -356,114 +339,113 @@ private void getSecret(ServerRequest req, ServerResponse res) { <3> Transit secrets engine operations: -[source,bash] +[source,java] ---- @Override -public void update(Routing.Rules rules) { +public void routing(HttpRules rules) { rules.get("/enable", this::enableEngine) - .get("/keys", this::createKeys) - .delete("/keys", this::deleteKeys) - .get("/batch", this::batch) - .get("/encrypt/{text:.*}", this::encryptSecret) - .get("/decrypt/{text:.*}", this::decryptSecret) - .get("/sign", this::sign) - .get("/hmac", this::hmac) - .get("/verify/sign/{text:.*}", this::verify) - .get("/verify/hmac/{text:.*}", this::verifyHmac) - .get("/disable", this::disableEngine); + .get("/keys", this::createKeys) + .delete("/keys", this::deleteKeys) + .get("/batch", this::batch) + .get("/encrypt/{text:.*}", this::encryptSecret) + .get("/decrypt/{text:.*}", this::decryptSecret) + .get("/sign", this::sign) + .get("/hmac", this::hmac) + .get("/verify/sign/{text:.*}", this::verify) + .get("/verify/hmac/{text:.*}", this::verifyHmac) + .get("/disable", this::disableEngine); } -private void enableEngine(ServerRequest req, ServerResponse res) { <1> - sys.enableEngine(TransitSecretsRx.ENGINE) - .thenAccept(ignored -> res.send("Transit Secret engine enabled")) - .exceptionally(res::send); +private void enableEngine(ServerRequest req, ServerResponse res) { // <1> + sys.enableEngine(TransitSecrets.ENGINE); + res.send("Transit Secret engine enabled"); } -private void disableEngine(ServerRequest req, ServerResponse res) { <2> - sys.disableEngine(TransitSecretsRx.ENGINE) - .thenAccept(ignored -> res.send("Transit Secret engine disabled")) - .exceptionally(res::send); +private void disableEngine(ServerRequest req, ServerResponse res) { // <2> + sys.disableEngine(TransitSecrets.ENGINE); + res.send("Transit Secret engine disabled"); } -private void createKeys(ServerRequest req, ServerResponse res) { <3> +private void createKeys(ServerRequest req, ServerResponse res) { // <3> CreateKey.Request request = CreateKey.Request.builder() - .name(ENCRYPTION_KEY); - - secrets.createKey(request) - .flatMapSingle(ignored -> secrets.createKey(CreateKey.Request.builder() - .name(SIGNATURE_KEY) - .type("rsa-2048"))) - .forSingle(ignored -> res.send("Created keys")) - .exceptionally(res::send); -} + .name(ENCRYPTION_KEY); + + secrets.createKey(request); + secrets.createKey(CreateKey.Request.builder() + .name(SIGNATURE_KEY) + .type("rsa-2048")); -private void deleteKeys(ServerRequest req, ServerResponse res) { <4> + res.send("Created keys"); +} +private void deleteKeys(ServerRequest req, ServerResponse res) { // <4> secrets.updateKeyConfig(UpdateKeyConfig.Request.builder() - .name(ENCRYPTION_KEY) - .allowDeletion(true)) - .peek(ignored -> System.out.println("Updated key config")) - .flatMapSingle(ignored -> secrets.deleteKey(DeleteKey.Request.create(ENCRYPTION_KEY))) - .forSingle(ignored -> res.send("Deleted key.")) - .exceptionally(res::send); + .name(ENCRYPTION_KEY) + .allowDeletion(true)); + System.out.println("Updated key config"); + + secrets.deleteKey(DeleteKey.Request.create(ENCRYPTION_KEY)); + + res.send("Deleted key."); } -private void encryptSecret(ServerRequest req, ServerResponse res) { <5> - String secret = req.path().param("text"); - secrets.encrypt(Encrypt.Request.builder() - .encryptionKeyName(ENCRYPTION_KEY) - .data(Base64Value.create(secret))) - .forSingle(response -> res.send(response.encrypted().cipherText())) - .exceptionally(res::send); +private void encryptSecret(ServerRequest req, ServerResponse res) { // <5> + String secret = req.path().pathParameters().get("text"); + + Encrypt.Response encryptResponse = secrets.encrypt(Encrypt.Request.builder() + .encryptionKeyName(ENCRYPTION_KEY) + .data(Base64Value.create(secret))); + + res.send(encryptResponse.encrypted().cipherText()); } -private void decryptSecret(ServerRequest req, ServerResponse res) { <6> - String encrypted = req.path().param("text"); +private void decryptSecret(ServerRequest req, ServerResponse res) { // <6> + String encrypted = req.path().pathParameters().get("text"); - secrets.decrypt(Decrypt.Request.builder() - .encryptionKeyName(ENCRYPTION_KEY) - .cipherText(encrypted)) - .forSingle(response -> res.send(String.valueOf(response.decrypted().toDecodedString()))) - .exceptionally(res::send); + Decrypt.Response decryptResponse = secrets.decrypt(Decrypt.Request.builder() + .encryptionKeyName(ENCRYPTION_KEY) + .cipherText(encrypted)); + + res.send(String.valueOf(decryptResponse.decrypted().toDecodedString())); } -private void hmac(ServerRequest req, ServerResponse res) { <7> - secrets.hmac(Hmac.Request.builder() - .hmacKeyName(ENCRYPTION_KEY) - .data(SECRET_STRING)) - .forSingle(response -> res.send(response.hmac())) - .exceptionally(res::send); +private void hmac(ServerRequest req, ServerResponse res) { // <7> + Hmac.Response hmacResponse = secrets.hmac(Hmac.Request.builder() + .hmacKeyName(ENCRYPTION_KEY) + .data(SECRET_STRING)); + + res.send(hmacResponse.hmac()); } -private void sign(ServerRequest req, ServerResponse res) { <8> - secrets.sign(Sign.Request.builder() - .signatureKeyName(SIGNATURE_KEY) - .data(SECRET_STRING)) - .forSingle(response -> res.send(response.signature())) - .exceptionally(res::send); +private void sign(ServerRequest req, ServerResponse res) { // <8> + Sign.Response signResponse = secrets.sign(Sign.Request.builder() + .signatureKeyName(SIGNATURE_KEY) + .data(SECRET_STRING)); + + res.send(signResponse.signature()); } -private void verifyHmac(ServerRequest req, ServerResponse res) { <9> - String hmac = req.path().param("text"); +private void verifyHmac(ServerRequest req, ServerResponse res) { // <9> + String hmac = req.path().pathParameters().get("text"); - secrets.verify(Verify.Request.builder() - .digestKeyName(ENCRYPTION_KEY) - .data(SECRET_STRING) - .hmac(hmac)) - .forSingle(response -> res.send("Valid: " + response.isValid())) - .exceptionally(res::send); + Verify.Response verifyResponse = secrets.verify(Verify.Request.builder() + .digestKeyName(ENCRYPTION_KEY) + .data(SECRET_STRING) + .hmac(hmac)); + + res.send("Valid: " + verifyResponse.isValid()); } -private void verify(ServerRequest req, ServerResponse res) { <10> - String signature = req.path().param("text"); +private void verify(ServerRequest req, ServerResponse res) { // <10> + String signature = req.path().pathParameters().get("text"); + + Verify.Response verifyResponse = secrets.verify(Verify.Request.builder() + .digestKeyName(SIGNATURE_KEY) + .data(SECRET_STRING) + .signature(signature)); - secrets.verify(Verify.Request.builder() - .digestKeyName(SIGNATURE_KEY) - .data(SECRET_STRING) - .signature(signature)) - .forSingle(response -> res.send("Valid: " + response.isValid())) - .exceptionally(res::send); + res.send("Valid: " + verifyResponse.isValid()); } ---- @@ -491,68 +473,66 @@ class K8sExample { private final Vault tokenVault; private final String k8sAddress; private final Config config; - private final SysRx sys; + private final Sys sys; private Vault k8sVault; K8sExample(Vault tokenVault, Config config) { this.tokenVault = tokenVault; - this.sys = tokenVault.sys(SysRx.API); + this.sys = tokenVault.sys(Sys.API); this.k8sAddress = config.get("cluster-address").asString().get(); this.config = config; } - public Single run() { <1> + public String run() { // <1> /* The following tasks must be run before we authenticate */ - return enableK8sAuth() - // Now we can login using k8s - must run within a k8s cluster (or you need the k8s configuration files locally) - .flatMapSingle(ignored -> workWithSecrets()) - // Now back to token based Vault, as we will clean up - .flatMapSingle(ignored -> disableK8sAuth()) - .map(ignored -> "k8s example finished successfully."); + enableK8sAuth(); + // Now we can login using k8s - must run within a k8s cluster (or you need the k8s configuration files locally) + workWithSecrets(); + // Now back to token based Vault, as we will clean up + disableK8sAuth(); + return "k8s example finished successfully."; } - private Single workWithSecrets() { <2> - Kv2SecretsRx secrets = k8sVault.secrets(Kv2SecretsRx.ENGINE); - - return secrets.create(SECRET_PATH, Map.of("secret-key", "secretValue", - "secret-user", "username")) - .flatMapSingle(ignored -> secrets.get(SECRET_PATH)) - .peek(secret -> { - if (secret.isPresent()) { - Kv2Secret kv2Secret = secret.get(); - System.out.println("k8s first secret: " + kv2Secret.value("secret-key")); - System.out.println("k8s second secret: " + kv2Secret.value("secret-user")); - } else { - System.out.println("k8s secret not found"); - } - }).flatMapSingle(ignored -> secrets.deleteAll(SECRET_PATH)); + private void workWithSecrets() { // <2> + Kv2Secrets secrets = k8sVault.secrets(Kv2Secrets.ENGINE); + + secrets.create(SECRET_PATH, Map.of("secret-key", "secretValue", + "secret-user", "username")); + + Optional secret = secrets.get(SECRET_PATH); + if (secret.isPresent()) { + Kv2Secret kv2Secret = secret.get(); + System.out.println("k8s first secret: " + kv2Secret.value("secret-key")); + System.out.println("k8s second secret: " + kv2Secret.value("secret-user")); + } else { + System.out.println("k8s secret not found"); + } + secrets.deleteAll(SECRET_PATH); } - private Single disableK8sAuth() { <3> - return sys.deletePolicy(POLICY_NAME) - .flatMapSingle(ignored -> sys.disableAuth(K8sAuthRx.AUTH_METHOD.defaultPath())); + private void disableK8sAuth() { // <3> + sys.deletePolicy(POLICY_NAME); + sys.disableAuth(K8sAuth.AUTH_METHOD.defaultPath()); } - private Single enableK8sAuth() { <4> + private void enableK8sAuth() { // <4> // enable the method - return sys.enableAuth(K8sAuthRx.AUTH_METHOD) - // add policy - .flatMapSingle(ignored -> sys.createPolicy(POLICY_NAME, VaultPolicy.POLICY)) - .flatMapSingle(ignored -> tokenVault.auth(K8sAuthRx.AUTH_METHOD) - .configure(ConfigureK8s.Request.builder() - .address(k8sAddress))) - .flatMapSingle(ignored -> tokenVault.auth(K8sAuthRx.AUTH_METHOD) - // this must be the same role name as is defined in application.yaml - .createRole(CreateRole.Request.builder() - .roleName("my-role") - .addBoundServiceAccountName("*") - .addBoundServiceAccountNamespace("default") - .addTokenPolicy(POLICY_NAME))) - .peek(ignored -> k8sVault = Vault.create(config)) - .map(Function.identity()); + sys.enableAuth(K8sAuth.AUTH_METHOD); + sys.createPolicy(POLICY_NAME, VaultPolicy.POLICY); + tokenVault.auth(K8sAuth.AUTH_METHOD) + .configure(ConfigureK8s.Request.builder() + .address(k8sAddress)); + tokenVault.auth(K8sAuth.AUTH_METHOD) + // this must be the same role name as is defined in application.yaml + .createRole(CreateRole.Request.builder() + .roleName("my-role") + .addBoundServiceAccountName("*") + .addBoundServiceAccountNamespace("default") + .addTokenPolicy(POLICY_NAME)); + k8sVault = Vault.create(config); } } ---- diff --git a/docs/se/integrations/neo4j.adoc b/docs/se/integrations/neo4j.adoc index b2e2156b6c5..737b9559bb5 100644 --- a/docs/se/integrations/neo4j.adoc +++ b/docs/se/integrations/neo4j.adoc @@ -178,7 +178,7 @@ Movies can now be returned as JSON objects: [source,java] ---- -public class MovieService implements Service { +public class MovieService implements HttpService { private final MovieRepository movieRepository; public MovieService(MovieRepository movieRepository) { @@ -186,7 +186,7 @@ public class MovieService implements Service { } @Override - public void update(Routing.Rules rules) { + public void routing(HttpRules rules) { rules.get("/api/movies", this::findMoviesHandler); } diff --git a/docs/se/introduction.adoc b/docs/se/introduction.adoc index 03da9302b8c..722efef0dc1 100644 --- a/docs/se/introduction.adoc +++ b/docs/se/introduction.adoc @@ -24,7 +24,7 @@ include::{rootdir}/includes/se.adoc[] -Helidon SE is Helidon's foundational set of APIs and, as of Helidon 4, it uses virtual threads to enable these APIs to change from asynchronous to blocking. +Helidon SE is Helidon's foundational set of APIs and, as of Helidon 4, it uses virtual threads to enable these APIs to change from asynchronous to blocking. == Components @@ -62,7 +62,7 @@ Add support for CORS to your application using a Helidon module. .DB Client [icon=storage,link=dbclient.adoc] -- -Provides a unified, reactive API for working with databases in non-blocking way. +Unified API for working with databases. -- //GraphQL [CARD] @@ -134,7 +134,7 @@ Profile and monitor your applications across multiple services. .WebClient [icon=http,link=webclient.adoc] -- -HTTP client that handles responses to the HTTP requests in a reactive way. +A programmatic API to make HTTP requests. -- //WebServer @@ -142,7 +142,7 @@ HTTP client that handles responses to the HTTP requests in a reactive way. .WebServer [icon=settings_ethernet,link=webserver.adoc] -- -A programmatic HTTP API that uses virtual threads to handle nearly unlimited concurrent requests without blocking a platform thread or starving other requests. +A programmatic HTTP API that uses virtual threads to handle nearly unlimited concurrent requests without blocking a platform thread or starving other requests. // Each request runs in its own dedicated thread so it is free to perform blocking operations in a simple synchronous way without blocking a platform thread or starving other requests. -- diff --git a/docs/se/metrics/metrics.adoc b/docs/se/metrics/metrics.adoc index cf718573320..c08a28aada8 100644 --- a/docs/se/metrics/metrics.adoc +++ b/docs/se/metrics/metrics.adoc @@ -154,14 +154,14 @@ import org.eclipse.microprofile.metrics.Counter; import org.eclipse.microprofile.metrics.MetricRegistry; //... -public class MyService implements Service { +public class MyService implements HttpService { private final MetricRegistry registry = RegistryFactory.getInstance() .getRegistry(MetricRegistry.Type.APPLICATION); // <1> private final Counter accessCtr = registry.counter("accessctr"); // <2> @Override - public void update(Routing.Rules rules) { + public void routing(HttpRules rules) { rules .any(this::countAccess) .get("/", this::myGet); diff --git a/docs/se/metrics/micrometer.adoc b/docs/se/metrics/micrometer.adoc index cc24c573d8c..99e836c20e8 100644 --- a/docs/se/metrics/micrometer.adoc +++ b/docs/se/metrics/micrometer.adoc @@ -97,7 +97,7 @@ Routing.builder() ---- import io.micrometer.core.instrument.Counter; -public class MyService implements Service { +public class MyService implements HttpService { private final Counter requestCounter; @@ -106,7 +106,7 @@ public class MyService implements Service { } @Override - public void update(Routing.Rules rules) { + public void routing(HttpRules rules) { rules .any(this::countRequests) // <2> .get("/", this::myGet); diff --git a/docs/se/openapi/openapi-generator.adoc b/docs/se/openapi/openapi-generator.adoc index 6552205a720..788b7e2db23 100644 --- a/docs/se/openapi/openapi-generator.adoc +++ b/docs/se/openapi/openapi-generator.adoc @@ -36,7 +36,7 @@ Along with the `PetService` interface or abstract class which has methods such a [source,java] .Generated `PetService` abstract class ---- -public abstract class PetService implements Service { +public abstract class PetService implements HttpService { void addPet(ServerRequest request, ServerResponse response, Pet pet) { // ... } @@ -181,68 +181,45 @@ PetApi petApi = PetApiImpl.create(apiClient); ==== Invoking Remote Endpoints With the `petApi` object, your code can invoke any of the methods on the `PetApi` interface to contact the remote service. -The Helidon WebClient follows a reactive programming model, and the Helidon SE client generator creates an `ApiResponse` interface, also reactive. +The Helidon SE client generator creates an `ApiResponse` interface. Each generated `PetApi` method returns an `ApiResponse` where the `returnType` is the return type declared in the OpenAPI document for the corresponding operation. The `ApiResponse` interface exposes two methods your code can use to work with the response from the remote service invocation: -* `Single result()` +* `T result()` + -Provides reactive access to the value returned by the remote service in the response. +Provides access to the value returned by the remote service in the response. This method lets your code fetch the return value directly. -* `Single webClientResponse()` +* `HTTPClientResponse webClientResponse()` + -Provides reactive access to the Helidon `WebClientResponse` object. +Provides access to the Helidon `HTTPClientResponse` object. Your code can find out the HTTP return status, read headers in the response, and process the content (if any) in the response however it needs to. -In the reactive Helidon WebClient model, the first part of the response message can arrive (the status and headers are available) before the entity in the body of the response is readable. -So there are two reactive events associated with an incoming HTTP response: +In the Helidon WebClient model, the first part of the response message can arrive (the status and headers are available) before the entity in the body of the response is readable. +So there are two events associated with an incoming HTTP response: . when the response _excluding_ the entity content has arrived, and . when your code can begin consuming the entity content. You can adopt different styles of retrieving the results, depending on the specific needs of the code you are writing. -===== Synchronous access to the result -This example shows the simplest way to invoke the remote service and work with the result. +===== Access with status checking +The Helidon WebClient programming model includes a `HTTPClientResponse` interface which exposes all aspects of the HTTP response returned from the remote service. -[source,java] -.Synchronous access to the return value ----- -// Assumes the petApi field is initialized as above. -List availablePets = petApi.findPetsByStatus(List.of(Pet.StatusEnum.AVAILABLE.value())) // <1> - .result() // <2> - .await(4, TimeUnit.SECONDS); // <3> ----- -<1> Start the remote service invocation. -<2> Access the reactive result. -<3> Wait for the result to arrive subject to a four-second timeout. - -This code blocks the current thread, waiting up to four seconds for the response to arrive. -This approach might be adequate if you are developing a command-line client where the thread has no other meaningful work to do until it has the result. -This is _not_ an appropriate style for server code that uses the generated client to invoke another service. - -Note that this approach offers no access to the HTTP status for the response or any headers that might have been returned. - -===== Synchronous access with status checking -The Helidon WebClient programming model includes a `WebClientResponse` interface which exposes all aspects of the HTTP response returned from the remote service. - -The next example shows how your code can use the `WebClientResponse`. +The next example shows how your code can use the `HTTPClientResponse`. [source,java] -.Synchronous access with status checking +.Access with status checking ---- ApiResponse> apiResponse = petApi.findPetsByStatus(List.of(Pet.StatusEnum.AVAILABLE.value())); // <1> -WebClientResponse webClientResponse = apiResponse.webClientResponse() - .await(4, TimeUnit.SECONDS); // <2> +HTTPClientResponse webClientResponse = apiResponse.webClientResponse(); // <2> if (webClientResponse.status().code() != 200) { // <3> // Handle a non-successful status. } -List> availablePets = apiResponse.result() - .await(4, TimeUnit.SECONDS); // <4> +List> availablePets = apiResponse.result(); // <4> ---- <1> Start the remote service invocation. <2> Wait for the HTTP response status and headers to arrive. @@ -251,55 +228,6 @@ List> availablePets = apiResponse.result() This code also blocks the current thread, first to wait for the initial response and then to wait for the result content. -===== Fully-reactive access -The following example shows how your code might invoke the remote service and process the response using the reactive programming model. - -.Fully-reactive access with status checking -[source,java] ----- -// Assumes the petApi field is initialized as above. -ApiResponse> availablePetsResponse = - petApi.findPetsByStatus(List.of(Pet.StatusEnum.AVAILABLE.value())); // <1> - -availablePetsResponse.webClientResponse() - .thenAccept(resp -> { // <2> - if (resp.status().code() == 200) { - try { - availablePetsResponse.result() // <3> - .thenAccept(availablePets -> { - // Process the List of available pets. // <4> - - }) - .exceptionally(throwable -> { - // Handle whatever problem occurred in retrieving the results. // <5> - return null; - }); - } catch (ExecutionException | InterruptedException e) { - // Handle errors while waiting for the response content to arrive. // <6> - } - } else { - // Handle non-200 HTTP status. // <7> - }) - .exceptionally(throwable -> { - // Handle whatever problem occurred in receiving the response. // <8> - return null; - }); ----- -<1> Starts the remote service invocation. -<2> Reactively processes the first portion of the HTTP response. -<3> Reactively processes a successfully-received HTTP response. -<4> Reactively processes the successfully-returned list of available pets. -<5> Reactively handles any errors in retrieving the list of available pets. -<6> Handle problems that occurred while waiting for the response content to arrive. -<7> Handle a non-200 response status. -<8> Reactively handles any errors in receiving the HTTP response. - -The fully-reactive approach brings with it some complexity, but it lets your code completely avoid blocking the thread that initiates the outbound remote service access. -Avoiding blocking is especially important if the code which uses the generated client runs in a server. - -Some of this complexity enters because there are several ways this processing can fail. -Your code should handle each of them in whatever ways make sense for your application, and that might mean dealing with each different error scenario in a different way. - include::{gen-inc}[tag=common-references] * xref:{helidon-client-xref}[Helidon WebClient documentation] \ No newline at end of file diff --git a/docs/se/security/containers-integration.adoc b/docs/se/security/containers-integration.adoc index 381018e6a83..d6919c64ad8 100644 --- a/docs/se/security/containers-integration.adoc +++ b/docs/se/security/containers-integration.adoc @@ -16,30 +16,23 @@ /////////////////////////////////////////////////////////////////////////////// -= Cloud Security Container Integrations -:description: Helidon Security containers integration += WebServer Integration +:description: Helidon Security WebServer integration :keywords: helidon, security :rootdir: {docdir}/../.. include::{rootdir}/includes/se.adoc[] - -The following containers are integrated with Helidon Security: - -- <> -- <> - - == WebServer -To integrate xref:../webserver.adoc[reactive web server], add the following dependency to your project's pom.xml file: +To integrate xref:../webserver.adoc[web server], add the following dependency to your project's pom.xml file: [source,xml] .Maven Dependency ---- - io.helidon.security.integration - helidon-security-integration-webserver + io.helidon.webserver + helidon-webserver-security ---- @@ -94,94 +87,5 @@ security.web-server: include::{rootdir}/includes/security/helidon-endpoints.adoc[] -== Jersey - -Jersey (JAX-RS implementation) can be configured for both inbound and outbound security. - -[source,xml] -.Maven Dependency ----- - - io.helidon.security.integration - helidon-security-integration-jersey - ----- - -=== Inbound Security - -[source,java] -.Integrate with Jersey ----- -ResourceConfig resourceConfig = new ResourceConfig() - // register JAX-RS resource - .register(JaxRsResource.class) - // integrate security - .register(new io.helidon.security.jersey.SecurityFeature(security)); ----- - -=== Secure a Resource Method - -The current approach does not have a configuration option so security must be configured through annotations. -Security currently supports @Authenticated and @Authorized. When a resource is annotated with one of these - annotations (application class, resource class, or resource method), security will - be triggered. - -[source,java] -.Securing a resource method ----- -// this is sufficient for security to be triggered, see javadoc for further details -@Authenticated -@Path("/{name}") -@GET -@Produces(MediaType.TEXT_PLAIN) -// due to Jersey approach to path matching, we need two methods to match both the "root" and "root" + subpaths -public String getHelloName(@PathParam("name") String name) { - return "Hello " + name + ", your current subject: " + securityContext.getSubject(); -} ----- - -=== Access Context - -[source,java] -.Support in a JAX-RS resource ----- -// inject io.helidon.security.SecurityContext -@Context -private SecurityContext securityContext; - ----- - - -=== Outbound Security -Outbound security is automatically registered with Jersey client. -The provider must have outbound security configured for identity to be propagated. - -[source,xml] -.Maven Dependency ----- - - io.helidon.security.integration - helidon-security-integration-jersey-client - ----- - -[source,java] -.Call remote target with outbound security ----- -Client client = ClientBuilder.newClient(); - -try { - // call the resource, will propagate identity as configured in Security - String response = client.target("https://www.google.com") - .request() - // configure the security context for this request (as client and targets may be re-used) - .property(ClientSecurity.PROPERTY_CONTEXT, securityContext) - .get(String.class); -} finally { - client.close(); -} ----- - == Reference -* link:{security-integration-jersey-base-url}/module-summary.html[Helidon Jersey Security Integration] -* link:{security-integration-webserver-base-url}/module-summary.html[Helidon WebServer Security Integration] \ No newline at end of file +* link:{security-integration-webserver-base-url}/module-summary.html[Helidon WebServer Security Integration] diff --git a/docs/se/tracing.adoc b/docs/se/tracing.adoc index 83e02361e30..3121474d9d4 100644 --- a/docs/se/tracing.adoc +++ b/docs/se/tracing.adoc @@ -286,8 +286,7 @@ Parameters provided: === Span Propagation -Span propagation is supported with Helidon WebClient (and with Jersey client, though it is blocking and not suitable for -reactive implementations). +Span propagation is supported with Helidon WebClient. Tracing propagation is automatic as long as the current span context is available in Helidon Context (which is automatic when running within a WebServer request). diff --git a/docs/se/webclient.adoc b/docs/se/webclient.adoc index 10d165009dd..3b448ff5d37 100644 --- a/docs/se/webclient.adoc +++ b/docs/se/webclient.adoc @@ -18,7 +18,7 @@ = WebClient Introduction :description: Helidon WebClient -:keywords: helidon, se, rest, httpclient, webclient, reactive +:keywords: helidon, se, rest, httpclient, webclient :feature-name: WebClient :rootdir: {docdir}/.. @@ -35,13 +35,10 @@ include::{rootdir}/includes/se.adoc[] == Overview -WebClient is an HTTP client of Helidon SE. It handles the responses to the HTTP requests in a reactive way. +WebClient is an HTTP client of Helidon SE. It handles the responses to the HTTP requests in a programmatic way. Helidon WebClient provides the following features: -* *Reactive approach* + -Allows you to execute HTTP requests and handle the responses without having to wait for the server response. When the response is received, the client requests only the amount of data that it can handle at that time. So, there is no overflow of memory. - * *Builder-like setup and execution* + Creates every client and request as a builder pattern. This improves readability and code maintenance. diff --git a/docs/se/webserver.adoc b/docs/se/webserver.adoc index 0c931f41a44..a4da8b11545 100644 --- a/docs/se/webserver.adoc +++ b/docs/se/webserver.adoc @@ -17,8 +17,8 @@ /////////////////////////////////////////////////////////////////////////////// = WebServer Introduction -:description: Helidon Reactive WebServer Introduction -:keywords: helidon, reactive, reactive streams, reactive java, reactive webserver, tls +:description: Helidon WebServer Introduction +:keywords: helidon, java, webserver, tls :feature-name: WebServer :rootdir: {docdir}/.. :requested-uri-discovery-inc: {rootdir}/includes/server/requested-uri-discovery.adoc @@ -39,15 +39,13 @@ include::{rootdir}/includes/se.adoc[] - <> ** <> ** <> -** <> ** <> - <> - <> == Overview -WebServer provides an asynchronous and reactive API for creating web applications. -The API is inspired by popular NodeJS and Java frameworks. +WebServer provides an API for creating HTTP servers. It uses virtual threads and can handle nearly unlimited concurrent requests. include::{rootdir}/includes/dependencies.adoc[] @@ -213,9 +211,9 @@ or more services, each with its own path prefix and set of handlers. [source,java] .Service implementation ---- -public class HelloService implements Service { +public class HelloService implements HttpService { @Override - public void update(Routing.Rules rules) { + public void routing(HttpRules rules) { rules.get("/subpath", this::getHandler); } @@ -499,8 +497,8 @@ To enable Static Content Support add the following dependency to your project's [source,xml] ---- - io.helidon.reactive.webserver - helidon-reactive-webserver-static-content + io.helidon.webserver + helidon-webserver-static-content ---- @@ -527,93 +525,6 @@ A `StaticContentSupport` object can be created using `create(...)` factory metho `builder`. The `builder` lets you provide more configuration values, including _welcome file-name_ and mappings of filename extensions to media types. -== Jersey (JAX-RS) Support - -=== Maven Coordinates - -To enable Jersey (JAX-RS) Support add the following dependency to your project's `pom.xml`. - -[source,xml] ----- - - io.helidon.webserver - helidon-webserver-jersey - ----- - -=== JAX-RS Support -You can register a Jersey (JAX-RS) application at a _context root_ using the -`JerseySupport` class. - -==== Registering a Jersey Application -To register a *Jersey* application at a context root, use the -`JerseySupport` class and its `JerseySupport.Builder` builder. - -`JerseySupport` can register the JAX-RS resources directly. -[source,java] -.Jersey (JAX-RS) `HelloWorld` resource ----- -@Path("/") -public class HelloWorld { - - @GET - @Path("hello") - public Response hello() { - return Response.ok("Hello World!").build(); - } -} ----- - -[source,java] -.Registering the `HelloWorld` resource ----- -Routing.builder() - .register("/jersey", // <1> - JerseySupport.builder() - .register(HelloWorld.class) // <2> - .build()) - .build(); ----- -<1> Register the Jersey application at `/jersey` context root -<2> The Jersey `Application` stays hidden and consists of a single `HelloWorld` -resource class - -As a result, an HTTP GET request to `/jersey/hello` would yield a `Hello World!` -response string. - -===== Registering a JAX-RS Application -You can also register the JAX-RS `Application` object. -[source,java] -.Register the `HelloWorld` resource ----- -Routing.builder() - .register("/jersey", // <1> - JerseySupport.builder(new MyApplication()) // <2> - .build()) - .build(); ----- -<1> Register the Jersey application at `/jersey` context root -<2> `MyApplication` handles requests made to /jersey context root. - -===== Accessing WebServer Internals from a JAX-RS Application - -You can inject WebServer request and response objects into your -JAX-RS application using `@Context`. - -[source,java] -.Injection of WebServer internal objects ----- -@Path("/") -@RequestScoped -public class HelloWorld { - @Context - private ServerRequest request; - - @Context - private ServerResponse response; -} ----- - == JSON Support The WebServer supports JSON-P. When enabled, you can send and receive JSON-P objects transparently. @@ -625,29 +536,13 @@ To enable JSON Support add the following dependency to your project's `pom.xml`. [source,xml] ---- - io.helidon.reactive.media - helidon-reactive-media-jsonp + io.helidon.http.media + helidon-http-media-jsonp ---- === Usage -To enable JSON-P support, first register it with the web server. -Then you can add routes that handle and return JSON. - -[source,java] -.Configure JsonpSupport and use it for reading and writing of entities ----- -JsonpSupport jsonbSupport = JsonpSupport.create(); // <1> -WebServer webServer = WebServer.builder() - .addMediaSupport(jsonpSupport) // <2> - .build(); ----- -<1> Register JsonpSupport to enable transformation from and to `JsonObject` objects -<2> Register that JsonpSupport instance to enable automatic -deserialization of Java objects from and serialization of Java objects -to JSON. - [source,java] .Handler that receives and returns JSON objects ---- @@ -678,16 +573,6 @@ curl --noproxy '*' -X POST -H "Content-Type: application/json" \ {"message":"Hello Joe"} ---- -=== Configuring Json Reader/Writer Factories -To configure JSON-P `JsonReaderFactory` and `JsonWriterFactory` that are used by -the `JsonpSupport` instance, create the `JsonpSupport` object: - -[source,java] -.Create `JsonpSupport` with the provided configuration ----- -JsonpSupport.create(Map.of(JsonGenerator.PRETTY_PRINTING, false)); ----- - == JSON-B Support The WebServer supports the http://json-b.net/[JSON-B specification]. When this support is enabled, Java objects will be @@ -702,29 +587,12 @@ To enable JSON-B Support add the following dependency to your project's `pom.xml [source,xml] ---- - io.helidon.reactive.media - helidon-reactive-media-jsonp + io.helidon.http.media + helidon-http-media-jsonb ---- === Usage -To enable JSON-B support, first create and register a -link:{media-jsonb-javadoc-base-url}/io/helidon/reactive/media/jsonb/JsonbSupport.html[`JsonbSupport`] instance with a -link:{webserver-javadoc-base-url}/io/helidon/reactive/webserver/WebServer.Builder.html[`WebServer.Builder`]. - -[source,java] -.Registration of the `JsonbSupport` via `WebServer` ----- -JsonbSupport jsonbSupport = JsonbSupport.create(); // <1> -WebServer webServer = WebServer.builder() - .addMediaSupport(jsonbSupport) // <2> - .build(); ----- -<1> Create a `JsonbSupport` instance. This instance may be -reused freely. -<2> Register that `JsonbSupport` instance to enable automatic -deserialization of Java objects from and serialization of Java objects -to JSON. Now that automatic JSON serialization and deserialization facilities have been set up, you can register a `Handler` that works with Java @@ -794,30 +662,12 @@ To enable Jackson Support add the following dependency to your project's `pom.xm [source,xml] ---- - io.helidon.media - helidon-media-jackson + io.helidon.http.media + helidon-http-media-jackson ---- === Usage -To enable Jackson support, first create and register a -link:{media-jackson-javadoc-base-url}/io/helidon/media/jackson/JacksonSupport.html[`JacksonSupport`] -instance with a -link:link:{webserver-javadoc-base-url}/io/helidon/webserver/WebServer.Builder.html[`WebServer.Builder`]. - -[source,java] -.Registration of the `JacksonSupport` via `WebServer` ----- -JacksonSupport jacksonSupport = JacksonSupport.create(); // <1> -WebServer webServer = WebServer.builder() - .addMediaSupport(jacksonSupport) // <2> - .build(); ----- -<1> Create a `JacksonSupport` instance. This instance may be -reused freely. -<2> Register that `JacksonSupport` instance to enable automatic -deserialization of Java objects from and serialization of Java objects -to JSON. Now that automatic JSON serialization and deserialization facilities have been set up, you can register a `Handler` that works with Java diff --git a/docs/se/websocket.adoc b/docs/se/websocket.adoc index 9e612a1ee9e..cf04f732820 100644 --- a/docs/se/websocket.adoc +++ b/docs/se/websocket.adoc @@ -73,22 +73,18 @@ looking at `MessageQueueService`: [source,java] ---- -public class MessageQueueService implements Service { +public class MessageQueueService implements HttpService { private final MessageQueue messageQueue = MessageQueue.instance(); @Override - public void update(Routing.Rules routingRules) { + public void routing(HttpRules routingRules) { routingRules.post("/board", this::handlePost); } private void handlePost(ServerRequest request, ServerResponse response) { - request.content() - .as(String.class) - .thenAccept(it -> { - messageQueue.push(it); - response.status(204).send(); - }); + messageQueue.push(request.content().as(String.class)); + response.status(204).send(); } } ---- @@ -102,34 +98,23 @@ connection served by `MessageBoardEndpoint`: [source,java] ---- -public class MessageBoardEndpoint extends Endpoint { - +public class MessageBoardEndpoint implements WsListener { private final MessageQueue messageQueue = MessageQueue.instance(); @Override - public void onOpen(Session session, EndpointConfig endpointConfig) { - session.addMessageHandler(new MessageHandler.Whole() { - @Override - public void onMessage(String message) { - try { - // Send all messages in the queue - if (message.equals("SEND")) { - while (!messageQueue.isEmpty()) { - session.getBasicRemote().sendObject(messageQueue.pop()); - } - } - } catch (Exception e) { - // handle exception - } + public void onMessage(WsSession session, String text, boolean last) { + // Send all messages in the queue + if (text.equals("send")) { + while (!messageQueue.isEmpty()) { + session.send(messageQueue.pop(), last); } - }); + } } } ---- -This is an example of a programmatic endpoint that extends `Endpoint`. The method -`onOpen` will be invoked for every new connection. In this example, the application -registers a message handler for strings, and when the special `SEND` message +This is an example of a programmatic endpoint that extends `WsListener`. The method +`onMessage` will be invoked for every message. In this example, when the special `send` message is received, it empties the shared queue sending messages one at a time over the WebSocket connection. @@ -138,39 +123,20 @@ the web server. This is accomplished via a `Routing` builder: [source,java] ---- -List> encoders = - Collections.singletonList(UppercaseEncoder.class); - -WebServer server = WebServer.builder() - .port(8080) - .routing(r -> r - .register("/web", StaticContentSupport.builder("/WEB") - .welcomeFileName("index.html") - .build()) - .register("/rest", new MessageQueueService()) - ) - .addRouting(WebSocketRouting.builder() - .endpoint("/websocket", ServerEndpointConfig.Builder.create(MessageBoardEndpoint.class, "/board") - .encoders(encoders) - .build()) - .build() - ) - .build() +StaticContentService staticContent = StaticContentService.builder("/WEB") + .welcomeFileName("index.html") + .build(); +MessageQueueService messageQueueService = new MessageQueueService(); +server.routing(routing -> routing + .register("/web", staticContent) + .register("/rest", messageQueueService)) + .addRouting(WsRouting.builder() + .endpoint("/websocket/board", new MessageBoardEndpoint()) + .build()); ---- -This code snippet uses multiple builders for `Routing`, `WebSocketRouting` and `ServerEndpointConfig`. -In particular, it registers `MessageBoardEndpoint.class` at `"/websocket/board"` and associates -with it a _message encoder_. For more information on message encoders and decoders the -reader see the {jakarta-websocket-spec-url}[websocket specification]; in this - {helidon-github-tree-url}/examples/webserver/websocket[example], `UppercaseEncoder.class` simply uppercases every - message sent from the server. - -Endpoint methods in Helidon SE are executed in Netty's worker thread pool. Threads in this -pool are intended to be _non-blocking_, thus it is recommended for any blocking or -long-running operation triggered by an endpoint method to be executed using a separate -thread pool. See the documentation for `io.helidon.common.configurable.ThreadPoolSupplier`. +This code snippet registers `MessageBoardEndpoint` at `"/websocket/board"` and associates. == Reference -* link:{javadoc-base-url}/io.helidon.reactive.webserver.websocket/module-summary.html[Helidon WebSocket JavaDoc] -* link:{jakarta-websocket-spec-url}[Jakarta WebSocket Specification] +* link:{javadoc-base-url}/io.helidon.webserver.websocket/module-summary.html[Helidon WebSocket JavaDoc] diff --git a/docs/sitegen.yaml b/docs/sitegen.yaml index c6aede7e026..d4ac0c2802c 100644 --- a/docs/sitegen.yaml +++ b/docs/sitegen.yaml @@ -266,7 +266,7 @@ backend: - type: "PAGE" title: "Telemetry" source: "telemetry.adoc" - glyph: + glyph: type: "icon" value: "analytics" - type: "MENU" @@ -429,7 +429,7 @@ backend: - "engine.adoc" - "rsoperators.adoc" - type: "PAGE" - title: "Reactive Webserver" + title: "Webserver" source: "webserver.adoc" glyph: type: "icon"