Skip to content

Commit

Permalink
Add support for Vert.x Web sessions
Browse files Browse the repository at this point in the history
  • Loading branch information
Ladicek committed Oct 31, 2023
1 parent e5d73a7 commit e6e973a
Show file tree
Hide file tree
Showing 42 changed files with 1,441 additions and 9 deletions.
20 changes: 20 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,16 @@
<artifactId>quarkus-infinispan-client-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-infinispan-client-sessions</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-infinispan-client-sessions-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jaeger</artifactId>
Expand Down Expand Up @@ -5977,6 +5987,11 @@
<artifactId>quarkus-redis-client-runtime-spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-client-sessions</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-cache</artifactId>
Expand All @@ -5993,6 +6008,11 @@
<artifactId>quarkus-redis-client-deployment-spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-client-sessions-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-cache-deployment</artifactId>
Expand Down
26 changes: 26 additions & 0 deletions devtools/bom-descriptor-json/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-infinispan-client-sessions</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-info</artifactId>
Expand Down Expand Up @@ -1838,6 +1851,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-client-sessions</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client</artifactId>
Expand Down
26 changes: 26 additions & 0 deletions docs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-infinispan-client-sessions-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-info-deployment</artifactId>
Expand Down Expand Up @@ -1854,6 +1867,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-client-sessions-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-deployment</artifactId>
Expand Down
146 changes: 145 additions & 1 deletion docs/src/main/asciidoc/http-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,150 @@ link:https://undertow.io/undertow-docs/undertow-docs-2.0.0/index.html#predicates

If you are using a `web.xml` file as your configuration file, you can place it in the `src/main/resources/META-INF` directory.

Check warning on line 547 in docs/src/main/asciidoc/http-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/http-reference.adoc", "range": {"start": {"line": 547, "column": 11}}}, "severity": "INFO"}

Check warning on line 547 in docs/src/main/asciidoc/http-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'.", "location": {"path": "docs/src/main/asciidoc/http-reference.adoc", "range": {"start": {"line": 547, "column": 35}}}, "severity": "INFO"}

=== Built-in route order values
[[vertx-web-sessions]]
== Sessions

Quarkus includes support for sessions, based on https://vertx.io/docs/vertx-web/java/#_handling_sessions[Vert.x Web sessions].

By default, sessions are disabled.
To enable them, set the `quarkus.http.sessions.mode` configuration property to:

`in-memory`:: for sessions stored in memory of the Quarkus application
`redis`:: for sessions stored in an external Redis server; requires using the Quarkus Redis Client extension

Check warning on line 558 in docs/src/main/asciidoc/http-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/http-reference.adoc", "range": {"start": {"line": 558, "column": 68}}}, "severity": "INFO"}
`infinispan`:: for sessions stored in an external Infinispan data grid; requires using the Quarkus Infinispan Client extension

Check warning on line 559 in docs/src/main/asciidoc/http-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/http-reference.adoc", "range": {"start": {"line": 559, "column": 81}}}, "severity": "INFO"}

This configuration property is fixed at build time and cannot be changed at runtime.

WARNING: Undertow includes its own support for sessions.
If Undertow is present, Vert.x Web sessions cannot be enabled.

Check warning on line 564 in docs/src/main/asciidoc/http-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/http-reference.adoc", "range": {"start": {"line": 564, "column": 48}}}, "severity": "INFO"}

Sessions require using a cookie, which holds the session identifier.
By default, the cookie name is `JSESSIONID`.

Check warning on line 567 in docs/src/main/asciidoc/http-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Cookieless'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'Cookieless'?", "location": {"path": "docs/src/main/asciidoc/http-reference.adoc", "range": {"start": {"line": 567, "column": 13}}}, "severity": "WARNING"}
Cookieless sessions are not supported.
Storing session data directly into the session cookie is not supported either.

=== Accessing sessions

Check warning on line 571 in docs/src/main/asciidoc/http-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.HeadingPunctuation] Do not use end punctuation in headings. Raw Output: {"message": "[Quarkus.HeadingPunctuation] Do not use end punctuation in headings.", "location": {"path": "docs/src/main/asciidoc/http-reference.adoc", "range": {"start": {"line": 571, "column": 1}}}, "severity": "INFO"}

When sessions are enabled, the Vert.x Web `Session` object may be obtained from the current `RoutingContext` using `RoutingContext.session()`.

Check warning on line 573 in docs/src/main/asciidoc/http-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/http-reference.adoc", "range": {"start": {"line": 573, "column": 27}}}, "severity": "WARNING"}
Alternatively, the `io.vertx.ext.web.Session` object may be injected.

Check warning on line 574 in docs/src/main/asciidoc/http-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/http-reference.adoc", "range": {"start": {"line": 574, "column": 21}}}, "severity": "WARNING"}

When using non-clustered in-memory sessions, arbitrary objects may be stored into a session.

Check warning on line 576 in docs/src/main/asciidoc/http-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/http-reference.adoc", "range": {"start": {"line": 576, "column": 5}}}, "severity": "INFO"}

With Redis, Infinispan, or cluster-wide in-memory sessions, the following data types may be stored into a session:

* primitive wrapper types
** `java.lang.Boolean`
** `java.lang.Byte`
** `java.lang.Short`
** `java.lang.Integer`
** `java.lang.Long`
** `java.lang.Float`
** `java.lang.Double`
** `java.lang.Character`
* `java.lang.String`
* `byte[]`
* `io.vertx.core.buffer.Buffer`
* `io.vertx.core.json.JsonObject`
* `io.vertx.core.json.JsonArray`

Session data are stored in a _session store_.
At the beginning of request processing, session data are loaded from the session store and put into the `Session` object.
The `Session` object contains an independent snapshot of session data and can be accessed freely during request processing.
When the response is being written, session data are stored back to the session store.

Session data are loaded and stored as an entire whole, which means that they are always internally consistent.
In case of concurrent access to the session, the initial session state (at the beginning of a request) is identical to the final session state of some previous request.
There is no guarantee that modifications will survive (another conflicting write may win the race).

[WARNING]
====
In case of non-clustered in-memory sessions, requests share the `Session` object and the instances stored in it.
It is your responsibility as an application programmer to properly synchronize access to these shared data.
With Redis or Infinispan, the `Session` objects are truly independent snapshots.
There are no objects shared between requests in this case.
====

Loading and storing session data in a fine-grained fashion (per individual attribute) is not supported, which makes reasoning about concurrent session access easier.
It also means you need to pay attention to the session size.

Persisting session data back to the session store is initiated when response headers are written, but the operation is otherwise asynchronous to response body writing.
It is possible that persisting the session to the session store takes longer than writing the entire response, especially in case of tiny responses.

=== General configuration

include::{generated-dir}/config/quarkus-vertx-http-config-group-sessions-build-time-config.adoc[leveloffset=+1, opts=optional]

include::{generated-dir}/config/quarkus-vertx-http-config-group-sessions-config.adoc[leveloffset=+1, opts=optional]

=== In-memory sessions

When `quarkus.http.sessions.mode` is set to `in-memory`, session data are stored in a Vert.x shared map.
By default, that shared map is _local_, which means that the session data are stored only in the JVM heap of the Quarkus application.

In this mode, if an application is deployed in multiple replicas fronted with a load balancer, it is necessary to enable sticky sessions (also known as session affinity) on the load balancer.
Still, losing a replica means losing all sessions stored on that replica.
In a multi-replica deployment, it is recommended to use an external session store (Redis or Infinispan).

Alternatively, if Vert.x clustering is configured, in-memory sessions may also be configured to be _cluster-wide_.
In this mode, a Vert.x _cluster-wide_ shared map is used to store session data, which means that sticky sessions are not necessary and losing one replica doesn't lead to session data loss.

include::{generated-dir}/config/quarkus-http-sessions-in-memory-sessions-in-memory-config.adoc[leveloffset=+1, opts=optional]

=== Redis sessions

When `quarkus.http.sessions.mode` is set to `redis`, session data are stored in an external Redis server.
The xref:./redis.adoc[Quarkus Redis Client] extension must be present and a connection to the Redis server used to store session data must be configured there.

By default, the default (unnamed) Redis connection is used.
To select a different (named) Redis connection, set the `quarkus.http.sessions.redis.client-name` configuration property.
For example:

[source,properties]
----
quarkus.http.sessions.mode=redis
quarkus.http.sessions.redis.client-name=web-sessions <1>
quarkus.redis.web-sessions.hosts=redis://localhost:6379/7 <2>
----
<1> Use the `web-sessions` Redis client for storing session data.
<2> Use database `7` on the Redis server at `localhost:6379`.

The Redis-based session store requires an entire Redis database for itself.
When using a standalone Redis server, you can use a https://redis.io/commands/select/[logical database] that is not used for other purposes.
If you want to store session data into a Redis cluster, you need to dedicate an entire cluster, because Redis cluster only supports database zero.

include::{generated-dir}/config/quarkus-redis-sessions.adoc[leveloffset=+1, opts=optional]

=== Infinispan sessions

When `quarkus.http.sessions.mode` is set to `infinispan`, session data are stored in an external Infinispan data grid.
The xref:./infinispan-client.adoc[Quarkus Infinispan Client] extension must be present and a connection to the Infinispan data grid used to store session data must be configured there.

By default, the default (unnamed) Infinispan connection is used.
To select a different (named) Infinispan connection, set the `quarkus.http.sessions.infinispan.client-name` configuration property.
For example:

[source,properties]
----
quarkus.http.sessions.mode=infinispan
quarkus.http.sessions.infinispan.client-name=web-sessions <1>
quarkus.infinispan-client.web-sessions.hosts=localhost:11222 <2>
----
<1> Use the `web-sessions` Infinispan client for storing session data.
<2> Use the Infinispan data grid at `localhost:11222`.

By default, the Infinispan cache used for storing session data is called `quarkus.sessions`.
To use a different cache, set the `quarkus.http.sessions.infinispan.cache-name` configuration property.

The Infinispan-based session store verifies if the configured cache exists.
If it does not, it is created automatically from the `DIST_SYNC` default template.
Therefore, the Infinispan client must be configured to connect as a user with permissions equivalent to at least the `deployer` Infinispan role.

include::{generated-dir}/config/quarkus-infinispan-sessions.adoc[leveloffset=+1, opts=optional]

== Built-in route order values

Route order values are the values that are specified via Vert.x route `io.vertx.ext.web.Route.order(int)` function.

Expand All @@ -565,6 +708,7 @@ Route order constants defined in `io.quarkus.vertx.http.runtime.RouteConstants`
| `Integer.MIN_VALUE` | `ROUTE_ORDER_BODY_HANDLER_MANAGEMENT` | Body handler for the management router.
| `Integer.MIN_VALUE` | `ROUTE_ORDER_HEADERS` | Handlers that add headers specified in the configuration.
| `Integer.MIN_VALUE` | `ROUTE_ORDER_CORS_MANAGEMENT` | CORS-Origin handler of the management router.
| `Integer.MIN_VALUE` | `ROUTE_ORDER_SESSION_HANDLER` | Session handler, if enabled in the configuration.
| `Integer.MIN_VALUE + 1` | `ROUTE_ORDER_BODY_HANDLER` | Body handler.
| `-2` | `ROUTE_ORDER_UPLOAD_LIMIT` | Route that enforces the upload body size limit.
| `0` | `ROUTE_ORDER_COMPRESSION` | Compression handler.
Expand Down
3 changes: 3 additions & 0 deletions extensions/infinispan-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,8 @@
<module>deployment-spi</module>
<module>runtime</module>
<module>runtime-spi</module>

<module>sessions/deployment</module>
<module>sessions/runtime</module>
</modules>
</project>
5 changes: 5 additions & 0 deletions extensions/infinispan-client/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@
<artifactId>quarkus-kubernetes-service-binding</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-infinispan-client-sessions</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
Expand Down
52 changes: 52 additions & 0 deletions extensions/infinispan-client/sessions/deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-infinispan-client-parent</artifactId>
<version>999-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

<artifactId>quarkus-infinispan-client-sessions-deployment</artifactId>

<name>Quarkus - Infinispan Client - Vert.x Web Sessions - Deployment</name>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-infinispan-client-sessions</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-infinispan-client-deployment-spi</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.quarkus.infinispan.sessions.deployment;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;

/**
* Configuration of Vert.x Web sessions stored in remote Infinispan cache.
*/
@ConfigRoot(name = "http.sessions.infinispan", phase = ConfigPhase.BUILD_TIME)
public class InfinispanSessionsBuildTimeConfig {
/**
* Name of the Infinispan client configured in the Quarkus Infinispan Client extension configuration.
* If not set, uses the default (unnamed) Infinispan client.
* <p>
* Note that the Infinispan client must be configured to connect as a user with the necessary permissions
* on the Infinispan server. The required minimum is equivalent to the Infinispan {@code deployer} role.
*/
@ConfigItem
public Optional<String> clientName;
}
Loading

0 comments on commit e6e973a

Please sign in to comment.