diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 6ca2d3dd93b414..25358c78f98ef3 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -1223,6 +1223,16 @@ quarkus-infinispan-client-deployment ${project.version} + + io.quarkus + quarkus-infinispan-client-sessions + ${project.version} + + + io.quarkus + quarkus-infinispan-client-sessions-deployment + ${project.version} + io.quarkus quarkus-jaeger @@ -1840,6 +1850,16 @@ quarkus-vertx-http ${project.version} + + io.quarkus + quarkus-vertx-http-sessions-deployment-spi + ${project.version} + + + io.quarkus + quarkus-vertx-http-sessions-runtime-spi + ${project.version} + io.quarkus quarkus-vertx-http-dev-console-spi @@ -5946,6 +5966,11 @@ quarkus-redis-client ${project.version} + + io.quarkus + quarkus-redis-client-sessions + ${project.version} + io.quarkus quarkus-redis-cache @@ -5962,6 +5987,11 @@ quarkus-redis-client-deployment-spi ${project.version} + + io.quarkus + quarkus-redis-client-sessions-deployment + ${project.version} + io.quarkus quarkus-redis-cache-deployment diff --git a/extensions/infinispan-client/pom.xml b/extensions/infinispan-client/pom.xml index c2e49097946b7f..f35e7f790ed88e 100644 --- a/extensions/infinispan-client/pom.xml +++ b/extensions/infinispan-client/pom.xml @@ -17,5 +17,8 @@ deployment deployment-spi runtime + + sessions/deployment + sessions/runtime diff --git a/extensions/infinispan-client/runtime/pom.xml b/extensions/infinispan-client/runtime/pom.xml index d65278237f3dca..b125041a0a8fd8 100644 --- a/extensions/infinispan-client/runtime/pom.xml +++ b/extensions/infinispan-client/runtime/pom.xml @@ -143,6 +143,11 @@ quarkus-kubernetes-service-binding true + + io.quarkus + quarkus-infinispan-client-sessions + true + io.quarkus quarkus-junit5-internal diff --git a/extensions/infinispan-client/sessions/deployment/pom.xml b/extensions/infinispan-client/sessions/deployment/pom.xml new file mode 100644 index 00000000000000..fe1744c4ee63ea --- /dev/null +++ b/extensions/infinispan-client/sessions/deployment/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + + io.quarkus + quarkus-infinispan-client-parent + 999-SNAPSHOT + ../../pom.xml + + + quarkus-infinispan-client-sessions-deployment + + Quarkus - Infinispan Client - Vert.x Web Sessions - Deployment + + + io.quarkus + quarkus-infinispan-client-sessions + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-infinispan-client-deployment-spi + + + io.quarkus + quarkus-vertx-http-sessions-deployment-spi + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/infinispan-client/sessions/deployment/src/main/java/io/quarkus/infinispan/sessions/deployment/InfinispanSessionsProcessor.java b/extensions/infinispan-client/sessions/deployment/src/main/java/io/quarkus/infinispan/sessions/deployment/InfinispanSessionsProcessor.java new file mode 100644 index 00000000000000..fe8a20a8aa68b3 --- /dev/null +++ b/extensions/infinispan-client/sessions/deployment/src/main/java/io/quarkus/infinispan/sessions/deployment/InfinispanSessionsProcessor.java @@ -0,0 +1,44 @@ +package io.quarkus.infinispan.sessions.deployment; + +import java.util.List; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.infinispan.client.deployment.spi.InfinispanClientBuildItem; +import io.quarkus.infinispan.client.deployment.spi.InfinispanClientNameBuildItem; +import io.quarkus.infinispan.client.runtime.InfinispanClientUtil; +import io.quarkus.infinispan.sessions.runtime.InfinispanSessionsRecorder; +import io.quarkus.vertx.http.sessions.deployment.spi.SessionStoreRequestBuildItem; +import io.quarkus.vertx.http.sessions.deployment.spi.SessionStoreResponseBuildItem; +import io.quarkus.vertx.http.sessions.spi.SessionStoreKind; + +public class InfinispanSessionsProcessor { + @BuildStep + public void infinispanClients(SessionStoreRequestBuildItem request, + BuildProducer infinispanRequest) { + if (request.is(SessionStoreKind.INFINISPAN)) { + String clientName = request.clientName().orElse(InfinispanClientUtil.DEFAULT_INFINISPAN_CLIENT_NAME); + infinispanRequest.produce(new InfinispanClientNameBuildItem(clientName)); + } + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + public void infinispanSessions(SessionStoreRequestBuildItem request, + BuildProducer response, + List infinispanClients, + InfinispanSessionsRecorder recorder) { + if (request.is(SessionStoreKind.INFINISPAN)) { + String clientName = request.clientName().orElse(InfinispanClientUtil.DEFAULT_INFINISPAN_CLIENT_NAME); + for (InfinispanClientBuildItem infinispanClient : infinispanClients) { + if (clientName.equals(infinispanClient.getName())) { + response.produce(new SessionStoreResponseBuildItem(recorder.create(infinispanClient.getClient()))); + return; + } + } + throw new IllegalStateException("Unknown Infinispan client: " + clientName); + } + } +} diff --git a/extensions/infinispan-client/sessions/runtime/pom.xml b/extensions/infinispan-client/sessions/runtime/pom.xml new file mode 100644 index 00000000000000..e9ab7c14eaa3b5 --- /dev/null +++ b/extensions/infinispan-client/sessions/runtime/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + + io.quarkus + quarkus-infinispan-client-parent + 999-SNAPSHOT + ../../pom.xml + + + quarkus-infinispan-client-sessions + + Quarkus - Infinispan Client - Vert.x Web Sessions - Runtime + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-vertx-http-sessions-runtime-spi + + + io.vertx + vertx-web-sstore-infinispan + + + org.infinispan + infinispan-client-hotrod + + + io.reactivex.rxjava3 + rxjava + + + + + org.infinispan + infinispan-client-hotrod-jakarta + + + org.infinispan + infinispan-jboss-marshalling + + + org.jboss.spec.javax.transaction + jboss-transaction-api_1.2_spec + + + io.netty + netty-transport-native-epoll + + + + + + + + + io.quarkus + quarkus-extension-maven-plugin + + + io.quarkus:quarkus-vertx-http + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/infinispan-client/sessions/runtime/src/main/java/io/quarkus/infinispan/sessions/runtime/InfinispanSessionsRecorder.java b/extensions/infinispan-client/sessions/runtime/src/main/java/io/quarkus/infinispan/sessions/runtime/InfinispanSessionsRecorder.java new file mode 100644 index 00000000000000..49be1d18ffaade --- /dev/null +++ b/extensions/infinispan-client/sessions/runtime/src/main/java/io/quarkus/infinispan/sessions/runtime/InfinispanSessionsRecorder.java @@ -0,0 +1,31 @@ +package io.quarkus.infinispan.sessions.runtime; + +import java.time.Duration; +import java.util.Map; + +import org.infinispan.client.hotrod.RemoteCacheManager; + +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; +import io.quarkus.vertx.http.sessions.spi.SessionStoreProvider; +import io.vertx.core.Vertx; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.sstore.SessionStore; +import io.vertx.ext.web.sstore.infinispan.InfinispanSessionStore; + +@Recorder +public class InfinispanSessionsRecorder { + public SessionStoreProvider create(RuntimeValue client) { + return new SessionStoreProvider() { + @Override + public SessionStore create(Vertx vertx, Map config) { + String cacheName = (String) config.get("cacheName"); + Duration retryTimeout = (Duration) config.get("retryTimeout"); + JsonObject options = new JsonObject() + .put("cacheName", cacheName) + .put("retryTimeout", retryTimeout.toMillis()); + return InfinispanSessionStore.create(vertx, options, client.getValue()); + } + }; + } +} diff --git a/extensions/infinispan-client/sessions/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/infinispan-client/sessions/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 00000000000000..476e398efc5ae4 --- /dev/null +++ b/extensions/infinispan-client/sessions/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,13 @@ +--- +artifact: ${project.groupId}:${project.artifactId}:${project.version} +name: "Infinispan Client - Vert.x Web Sessions" +metadata: + keywords: + - "infinispan" + - "vertx" + - "sessions" + guide: "https://quarkus.io/guides/http-reference#vertx-web-sessions" + categories: + - "web" + status: "preview" + unlisted: true diff --git a/extensions/redis-client/pom.xml b/extensions/redis-client/pom.xml index d3f800372cbdf7..01d24ecdd6c34d 100644 --- a/extensions/redis-client/pom.xml +++ b/extensions/redis-client/pom.xml @@ -20,6 +20,9 @@ deployment deployment-spi runtime + + sessions/deployment + sessions/runtime diff --git a/extensions/redis-client/runtime/pom.xml b/extensions/redis-client/runtime/pom.xml index f00b8e2c6715e2..a96546e13c4e78 100644 --- a/extensions/redis-client/runtime/pom.xml +++ b/extensions/redis-client/runtime/pom.xml @@ -33,6 +33,11 @@ quarkus-smallrye-health true + + io.quarkus + quarkus-redis-client-sessions + true + org.assertj assertj-core diff --git a/extensions/redis-client/sessions/deployment/pom.xml b/extensions/redis-client/sessions/deployment/pom.xml new file mode 100644 index 00000000000000..45c001014b45bc --- /dev/null +++ b/extensions/redis-client/sessions/deployment/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + + io.quarkus + quarkus-redis-client-parent + 999-SNAPSHOT + ../../pom.xml + + + quarkus-redis-client-sessions-deployment + + Quarkus - Redis Client - Vert.x Web Sessions - Deployment + + + io.quarkus + quarkus-redis-client-sessions + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-redis-client-deployment-spi + + + io.quarkus + quarkus-vertx-http-sessions-deployment-spi + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/redis-client/sessions/deployment/src/main/java/io/quarkus/redis/sessions/deployment/RedisSessionsProcessor.java b/extensions/redis-client/sessions/deployment/src/main/java/io/quarkus/redis/sessions/deployment/RedisSessionsProcessor.java new file mode 100644 index 00000000000000..93d1d7619d2f4c --- /dev/null +++ b/extensions/redis-client/sessions/deployment/src/main/java/io/quarkus/redis/sessions/deployment/RedisSessionsProcessor.java @@ -0,0 +1,44 @@ +package io.quarkus.redis.sessions.deployment; + +import java.util.List; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.redis.deployment.client.spi.RedisClientBuildItem; +import io.quarkus.redis.deployment.client.spi.RequestedRedisClientBuildItem; +import io.quarkus.redis.runtime.client.config.RedisConfig; +import io.quarkus.redis.sessions.runtime.RedisSessionsRecorder; +import io.quarkus.vertx.http.sessions.deployment.spi.SessionStoreRequestBuildItem; +import io.quarkus.vertx.http.sessions.deployment.spi.SessionStoreResponseBuildItem; +import io.quarkus.vertx.http.sessions.spi.SessionStoreKind; + +public class RedisSessionsProcessor { + @BuildStep + public void redisClients(SessionStoreRequestBuildItem request, + BuildProducer redisRequest) { + if (request.is(SessionStoreKind.REDIS)) { + String clientName = request.clientName().orElse(RedisConfig.DEFAULT_CLIENT_NAME); + redisRequest.produce(new RequestedRedisClientBuildItem(clientName)); + } + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + public void redisSessions(SessionStoreRequestBuildItem request, + BuildProducer response, + List redisClients, + RedisSessionsRecorder recorder) { + if (request.is(SessionStoreKind.REDIS)) { + String clientName = request.clientName().orElse(RedisConfig.DEFAULT_CLIENT_NAME); + for (RedisClientBuildItem redisClient : redisClients) { + if (clientName.equals(redisClient.getName())) { + response.produce(new SessionStoreResponseBuildItem(recorder.create(redisClient.getClient()))); + return; + } + } + throw new IllegalStateException("Unknown Redis client: " + clientName); + } + } +} diff --git a/extensions/redis-client/sessions/runtime/pom.xml b/extensions/redis-client/sessions/runtime/pom.xml new file mode 100644 index 00000000000000..5377749e4df589 --- /dev/null +++ b/extensions/redis-client/sessions/runtime/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + + io.quarkus + quarkus-redis-client-parent + 999-SNAPSHOT + ../../pom.xml + + + quarkus-redis-client-sessions + + Quarkus - Redis Client - Vert.x Web Sessions - Runtime + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-vertx-http-sessions-runtime-spi + + + io.vertx + vertx-web-sstore-redis + + + io.smallrye.reactive + smallrye-mutiny-vertx-redis-client + + + + + + + io.quarkus + quarkus-extension-maven-plugin + + + io.quarkus:quarkus-vertx-http + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/redis-client/sessions/runtime/src/main/java/io/quarkus/redis/sessions/runtime/RedisSessionsRecorder.java b/extensions/redis-client/sessions/runtime/src/main/java/io/quarkus/redis/sessions/runtime/RedisSessionsRecorder.java new file mode 100644 index 00000000000000..eb237820d9ae7b --- /dev/null +++ b/extensions/redis-client/sessions/runtime/src/main/java/io/quarkus/redis/sessions/runtime/RedisSessionsRecorder.java @@ -0,0 +1,25 @@ +package io.quarkus.redis.sessions.runtime; + +import java.time.Duration; +import java.util.Map; +import java.util.function.Supplier; + +import io.quarkus.runtime.annotations.Recorder; +import io.quarkus.vertx.http.sessions.spi.SessionStoreProvider; +import io.vertx.core.Vertx; +import io.vertx.ext.web.sstore.SessionStore; +import io.vertx.ext.web.sstore.redis.RedisSessionStore; +import io.vertx.mutiny.redis.client.Redis; + +@Recorder +public class RedisSessionsRecorder { + public SessionStoreProvider create(Supplier client) { + return new SessionStoreProvider() { + @Override + public SessionStore create(Vertx vertx, Map config) { + Duration retryTimeout = (Duration) config.get("retryTimeout"); + return RedisSessionStore.create(vertx, retryTimeout.toMillis(), client.get().getDelegate()); + } + }; + } +} diff --git a/extensions/redis-client/sessions/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/redis-client/sessions/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 00000000000000..4f4ef6cbe8e9b4 --- /dev/null +++ b/extensions/redis-client/sessions/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,13 @@ +--- +artifact: ${project.groupId}:${project.artifactId}:${project.version} +name: "Redis Client - Vert.x Web Sessions" +metadata: + keywords: + - "redis" + - "vertx" + - "sessions" + guide: "https://quarkus.io/guides/http-reference#vertx-web-sessions" + categories: + - "web" + status: "preview" + unlisted: true diff --git a/extensions/vertx-http/deployment/pom.xml b/extensions/vertx-http/deployment/pom.xml index ed3dd67a09031b..31746d40c8e943 100644 --- a/extensions/vertx-http/deployment/pom.xml +++ b/extensions/vertx-http/deployment/pom.xml @@ -33,7 +33,11 @@ io.quarkus quarkus-kubernetes-spi - + + io.quarkus + quarkus-vertx-http-sessions-deployment-spi + + io.quarkus diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java index fd8ec2b3ce36e4..879640d2c62f1f 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java @@ -60,6 +60,7 @@ import io.quarkus.vertx.http.runtime.CurrentVertxRequest; import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; import io.quarkus.vertx.http.runtime.HttpConfiguration; +import io.quarkus.vertx.http.runtime.SessionsBuildTimeConfig; import io.quarkus.vertx.http.runtime.VertxConfigBuilder; import io.quarkus.vertx.http.runtime.VertxHttpRecorder; import io.quarkus.vertx.http.runtime.attribute.ExchangeAttributeBuilder; @@ -67,11 +68,15 @@ import io.quarkus.vertx.http.runtime.filters.Filter; import io.quarkus.vertx.http.runtime.filters.GracefulShutdownFilter; import io.quarkus.vertx.http.runtime.management.ManagementInterfaceBuildTimeConfig; +import io.quarkus.vertx.http.sessions.deployment.spi.SessionStoreRequestBuildItem; +import io.quarkus.vertx.http.sessions.deployment.spi.SessionStoreResponseBuildItem; +import io.quarkus.vertx.http.sessions.spi.SessionStoreKind; import io.vertx.core.Handler; import io.vertx.core.http.impl.Http1xServerRequest; import io.vertx.core.impl.VertxImpl; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.sstore.SessionStore; class VertxHttpProcessor { @@ -186,6 +191,19 @@ public KubernetesPortBuildItem kubernetesForManagement( managementInterfaceBuildTimeConfig.enabled); } + @BuildStep + public void sessionStoreRequest(HttpBuildTimeConfig config, BuildProducer request) { + if (config.sessions.mode == SessionsBuildTimeConfig.SessionsMode.REDIS) { + request.produce( + new SessionStoreRequestBuildItem(SessionStoreKind.REDIS, config.sessions.redis.clientName)); + } else if (config.sessions.mode == SessionsBuildTimeConfig.SessionsMode.INFINISPAN) { + request.produce( + new SessionStoreRequestBuildItem(SessionStoreKind.INFINISPAN, config.sessions.infinispan.clientName)); + } else { + request.produce(new SessionStoreRequestBuildItem(SessionStoreKind.NONE, Optional.empty())); + } + } + @BuildStep void notFoundRoutes( List routes, @@ -314,7 +332,9 @@ ServiceStartBuildItem finalizeRouter( ShutdownConfig shutdownConfig, LiveReloadConfig lrc, CoreVertxBuildItem core, // Injected to be sure that Vert.x has been produced before calling this method. - ExecutorBuildItem executorBuildItem) + ExecutorBuildItem executorBuildItem, + List sessionStoreProvider, + Capabilities capabilities) throws BuildException, IOException { Optional defaultRoute; @@ -366,6 +386,35 @@ ServiceStartBuildItem finalizeRouter( } } + if (httpBuildTimeConfig.sessions.mode != SessionsBuildTimeConfig.SessionsMode.DISABLED + && capabilities.isPresent(Capability.SERVLET)) { + throw new IllegalStateException("Vert.x Web sessions may not be enabled together with Undertow; " + + "use Undertow (servlet) sessions instead"); + } + + RuntimeValue sessionStore = null; + switch (httpBuildTimeConfig.sessions.mode) { + case DISABLED: + break; + case IN_MEMORY: + sessionStore = recorder.createInMemorySessionStore(); + break; + case REDIS: + if (sessionStoreProvider.isEmpty()) { + throw new IllegalStateException("Redis-based session store was configured, " + + "but the Quarkus Redis Client extension is missing"); + } + sessionStore = recorder.createRedisSessionStore(sessionStoreProvider.get(0).getProvider()); + break; + case INFINISPAN: + if (sessionStoreProvider.isEmpty()) { + throw new IllegalStateException("Infinispan-based session store was configured, " + + "but the Quarkus Infinispan Client extension is missing"); + } + sessionStore = recorder.createInfinispanSessionStore(sessionStoreProvider.get(0).getProvider()); + break; + } + recorder.finalizeRouter(beanContainer.getValue(), defaultRoute.map(DefaultRouteBuildItem::getRoute).orElse(null), listOfFilters, listOfManagementInterfaceFilters, @@ -376,7 +425,8 @@ ServiceStartBuildItem finalizeRouter( nonApplicationRootPathBuildItem.getNonApplicationRootPath(), launchMode.getLaunchMode(), !requireBodyHandlerBuildItems.isEmpty(), bodyHandler, gracefulShutdownFilter, - shutdownConfig, executorBuildItem.getExecutorProxy()); + shutdownConfig, executorBuildItem.getExecutorProxy(), + sessionStore); return new ServiceStartBuildItem("vertx-http"); } diff --git a/extensions/vertx-http/pom.xml b/extensions/vertx-http/pom.xml index f370261e6feeea..4d0779bd0f459f 100644 --- a/extensions/vertx-http/pom.xml +++ b/extensions/vertx-http/pom.xml @@ -16,6 +16,8 @@ deployment runtime + sessions-deployment-spi + sessions-runtime-spi dev-console-spi dev-console-runtime-spi deployment-spi diff --git a/extensions/vertx-http/runtime/pom.xml b/extensions/vertx-http/runtime/pom.xml index 6f0fd6ea1fdf57..435c501feb9910 100644 --- a/extensions/vertx-http/runtime/pom.xml +++ b/extensions/vertx-http/runtime/pom.xml @@ -61,6 +61,10 @@ + + io.quarkus + quarkus-vertx-http-sessions-runtime-spi + org.graalvm.sdk graal-sdk diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/CurrentVertxRequest.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/CurrentVertxRequest.java index 217c01185a875a..85d077e91ae4fa 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/CurrentVertxRequest.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/CurrentVertxRequest.java @@ -4,6 +4,7 @@ import jakarta.enterprise.inject.Produces; import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.Session; @RequestScoped public class CurrentVertxRequest { @@ -17,6 +18,16 @@ public RoutingContext getCurrent() { return current; } + @Produces + @RequestScoped + public Session getCurrentSession() { + Session result = current.session(); + if (result == null) { + throw new UnsupportedOperationException("No active session or support for sessions disabled"); + } + return result; + } + public CurrentVertxRequest setCurrent(RoutingContext current) { this.current = current; return this; diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java index c1a2819bd3a88e..99d2f1e6a8ebc6 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java @@ -98,4 +98,10 @@ public class HttpBuildTimeConfig { */ @ConfigItem public OptionalInt compressionLevel; + + /** + * Configuration of Vert.x Web sessions. + */ + @ConfigItem + public SessionsBuildTimeConfig sessions; } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpConfiguration.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpConfiguration.java index 89ffdf53d0c198..749c01bad42c69 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpConfiguration.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpConfiguration.java @@ -265,6 +265,11 @@ public class HttpConfiguration { @ConfigItem public Map filter; + /** + * Configuration of Vert.x Web sessions. + */ + public SessionsConfig sessions; + public ProxyConfig proxy; public int determinePort(LaunchMode launchMode) { diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsBuildTimeConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsBuildTimeConfig.java new file mode 100644 index 00000000000000..cacdc647a76fd1 --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsBuildTimeConfig.java @@ -0,0 +1,58 @@ +package io.quarkus.vertx.http.runtime; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +/** + * Configuration of Vert.x Web sessions. + */ +@ConfigGroup +public class SessionsBuildTimeConfig { + /** + * Whether Vert.x Web support for sessions is enabled (the {@code SessionHandler} is added to the router) + * and if so, which session store is used. For the {@code redis} and {@code infinispan} modes, the corresponding + * Quarkus extension must be present and a connection to the data store must be configured there. + */ + @ConfigItem(defaultValue = "disabled") + public SessionsMode mode; + + /** + * Configuration of sessions stored in Redis. + */ + public SessionsRedisBuildTimeConfig redis; + + /** + * Configuration of sessions stored in remote Infinispan cache. + */ + public SessionsInfinispanBuildTimeConfig infinispan; + + public enum SessionsMode { + /** + * Support for Vert.x Web sessions is disabled. + */ + DISABLED, + /** + * Support for Vert.x Web sessions is enabled and sessions are stored in memory. + *

+ * 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 enabled ({@code quarkus.vertx.cluster}), in-memory sessions + * may be clustered ({@code quarkus.http.sessions.in-memory.clustered}), which also makes sticky sessions + * not necessary and prevents session data loss (depending on the Vert.x cluster manager configuration). + */ + IN_MEMORY, + /** + * Support for Vert.x Web sessions is enabled and sessions are stored in a remote Redis server. + * The Quarkus Redis Client extension must be present and a Redis connection must be configured. + */ + REDIS, + /** + * Support for Vert.x Web sessions is enabled and sessions are stored in a remote Infinispan cache. + * The Quarkus Infinispan Client extension must be present and an Infinispan connection must be configured. + */ + INFINISPAN, + } +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsConfig.java new file mode 100644 index 00000000000000..b096f79cc486d3 --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsConfig.java @@ -0,0 +1,114 @@ +package io.quarkus.vertx.http.runtime; + +import java.time.Duration; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; +import io.vertx.core.http.CookieSameSite; + +/** + * Configuration of Vert.x Web sessions. + */ +@ConfigGroup +public class SessionsConfig { + /** + * The session timeout. + */ + @ConfigItem(defaultValue = "30M") + public Duration timeout; + + /** + * The requested length of the session identifier. + */ + @ConfigItem(defaultValue = "16") + public int idLength; + + /** + * The path on which the Vert.x Web {@code SessionHandler} is installed in the router, + * as well as the session cookie path. The value is relative to {@code quarkus.http.root-path}. + */ + @ConfigItem(defaultValue = "/") + public String path; + + /** + * The name of the session cookie. + */ + @ConfigItem(defaultValue = "JSESSIONID") + public String cookieName; + + /** + * Whether the session cookie has the {@code HttpOnly} attribute. + */ + @ConfigItem(defaultValue = "true") + public boolean cookieHttpOnly; + + /** + * Whether the session cookie has the {@code Secure} attribute. + *

+ */ + @ConfigItem(defaultValue = "auto") + public SessionCookieSecure cookieSecure; + + /** + * The {@code SameSite} attribute of the session cookie. + */ + @ConfigItem + public Optional cookieSameSite; // TODO maybe not `Optional` and default to `strict`? + + /** + * The {@code Max-Age} attribute of the session cookie. Note that setting this option turns the session cookie + * into a persistent cookie. + */ + @ConfigItem + public Optional cookieMaxAge; + + /** + * Configuration of sessions stored in memory. + */ + public SessionsInMemoryConfig inMemory; + + /** + * Configuration of sessions stored in Redis. + */ + public SessionsRedisConfig redis; + + /** + * Configuration of sessions stored in remote Infinispan cache. + */ + public SessionsInfinispanConfig infinispan; + + public enum SessionCookieSecure { + /** + * The session cookie only has the {@code Secure} attribute when {@code quarkus.http.insecure-requests} + * is {@code redirect} or {@code disabled}. If {@code insecure-requests} is {@code enabled}, the session cookie + * does not have the {@code Secure} attribute. + */ + AUTO, + /** + * The session cookie always has the {@code Secure} attribute. + */ + ALWAYS, + /** + * The session cookie never has the {@code Secure} attribute. + */ + NEVER; + + boolean isEnabled(HttpConfiguration.InsecureRequests insecureRequests) { + if (this == ALWAYS) { + return true; + } else if (this == NEVER) { + return false; + } else { + return insecureRequests != HttpConfiguration.InsecureRequests.ENABLED; + } + } + } + +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsInMemoryConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsInMemoryConfig.java new file mode 100644 index 00000000000000..abaee2dada4a4f --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsInMemoryConfig.java @@ -0,0 +1,36 @@ +package io.quarkus.vertx.http.runtime; + +import java.time.Duration; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +/** + * Configuration of Vert.x Web sessions stored in memory. + */ +@ConfigGroup +public class SessionsInMemoryConfig { + /** + * Name of the Vert.x local map or cluster-wide map to store the session data. + */ + @ConfigItem(defaultValue = "quarkus.sessions") + public String mapName; + + /** + * Whether in-memory sessions are clustered. + *

+ * Ignored when Vert.x clustering is not enabled. + */ + @ConfigItem(defaultValue = "false") + public boolean clustered; + + /** + * Maximum time to retry when retrieving session data from the cluster-wide map. + * The Vert.x session handler retries when the session data are not found, because + * distributing data across the cluster may take time. + *

+ * Ignored when in-memory sessions are not clustered. + */ + @ConfigItem(defaultValue = "5s") + public Duration retryTimeout; +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsInfinispanBuildTimeConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsInfinispanBuildTimeConfig.java new file mode 100644 index 00000000000000..ad71048fa7a7cd --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsInfinispanBuildTimeConfig.java @@ -0,0 +1,22 @@ +package io.quarkus.vertx.http.runtime; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +/** + * Configuration of Vert.x Web sessions stored in remote Infinispan cache. + */ +@ConfigGroup +public class SessionsInfinispanBuildTimeConfig { + /** + * Name of the Infinispan client configured in the Quarkus Infinispan Client extension configuration. + * If not set, uses the default (unnamed) Infinispan client. + *

+ * Note that the Infinispan client must be configured to so that the user has necessary permissions + * on the Infinispan server. The required minimum is the Infinispan {@code deployer} role. + */ + @ConfigItem + public Optional clientName; +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsInfinispanConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsInfinispanConfig.java new file mode 100644 index 00000000000000..a708b8e51422ac --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsInfinispanConfig.java @@ -0,0 +1,27 @@ +package io.quarkus.vertx.http.runtime; + +import java.time.Duration; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +/** + * Configuration of Vert.x Web sessions stored in remote Infinispan cache. + */ +@ConfigGroup +public class SessionsInfinispanConfig { + /** + * Name of the Infinispan cache used to store session data. If it does not exist, it is created + * automatically from Infinispan's default template {@code DIST_SYNC}. + */ + @ConfigItem(defaultValue = "quarkus.sessions") + public String cacheName; + + /** + * Maximum time to retry when retrieving session data from the Infinispan cache. + * The Vert.x session handler retries when the session data are not found, because + * distributing data across an Infinispan cluster may take time. + */ + @ConfigItem(defaultValue = "5s") + public Duration retryTimeout; +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsRedisBuildTimeConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsRedisBuildTimeConfig.java new file mode 100644 index 00000000000000..e7a2b7f3150d5d --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsRedisBuildTimeConfig.java @@ -0,0 +1,19 @@ +package io.quarkus.vertx.http.runtime; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +/** + * Configuration of Vert.x Web sessions stored in Redis. + */ +@ConfigGroup +public class SessionsRedisBuildTimeConfig { + /** + * Name of the Redis client configured in the Quarkus Redis extension configuration. + * If not set, uses the default (unnamed) Redis client. + */ + @ConfigItem + public Optional clientName; +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsRedisConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsRedisConfig.java new file mode 100644 index 00000000000000..38314ce1f3bb39 --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/SessionsRedisConfig.java @@ -0,0 +1,20 @@ +package io.quarkus.vertx.http.runtime; + +import java.time.Duration; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +/** + * Configuration of Vert.x Web sessions stored in Redis. + */ +@ConfigGroup +public class SessionsRedisConfig { + /** + * Maximum time to retry when retrieving session data from the Redis server. + * The Vert.x session handler retries when the session data are not found, because + * distributing data across a potential Redis cluster may take some time. + */ + @ConfigItem(defaultValue = "2s") + public Duration retryTimeout; +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java index fbcb893f48b9dc..e21c0cd58c9ec3 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java @@ -7,9 +7,11 @@ import java.net.BindException; import java.net.URI; import java.net.URISyntaxException; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -74,6 +76,7 @@ import io.quarkus.vertx.http.runtime.management.ManagementInterfaceConfiguration; import io.quarkus.vertx.http.runtime.options.HttpServerCommonHandlers; import io.quarkus.vertx.http.runtime.options.HttpServerOptionsUtils; +import io.quarkus.vertx.http.sessions.spi.SessionStoreProvider; import io.smallrye.common.vertx.VertxContext; import io.vertx.core.AbstractVerticle; import io.vertx.core.AsyncResult; @@ -104,6 +107,10 @@ import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.BodyHandler; import io.vertx.ext.web.handler.CorsHandler; +import io.vertx.ext.web.handler.SessionHandler; +import io.vertx.ext.web.sstore.ClusteredSessionStore; +import io.vertx.ext.web.sstore.LocalSessionStore; +import io.vertx.ext.web.sstore.SessionStore; @Recorder public class VertxHttpRecorder { @@ -191,14 +198,18 @@ private boolean uriValid(HttpServerRequest httpServerRequest) { final RuntimeValue managementConfiguration; private static volatile Handler managementRouter; + final RuntimeValue vertxConfiguration; + public VertxHttpRecorder(HttpBuildTimeConfig httpBuildTimeConfig, ManagementInterfaceBuildTimeConfig managementBuildTimeConfig, RuntimeValue httpConfiguration, - RuntimeValue managementConfiguration) { + RuntimeValue managementConfiguration, + RuntimeValue vertxConfiguration) { this.httpBuildTimeConfig = httpBuildTimeConfig; this.httpConfiguration = httpConfiguration; this.managementBuildTimeConfig = managementBuildTimeConfig; this.managementConfiguration = managementConfiguration; + this.vertxConfiguration = vertxConfiguration; } public static void setHotReplacement(Handler handler, HotReplacementContext hrc) { @@ -346,6 +357,34 @@ public void mountFrameworkRouter(RuntimeValue mainRouter, RuntimeValue createInMemorySessionStore() { + Vertx vertx = VertxCoreRecorder.getVertx().get(); + if (httpConfiguration.getValue().sessions.inMemory.clustered + && vertxConfiguration.getValue().cluster() != null + && vertxConfiguration.getValue().cluster().clustered()) { + return new RuntimeValue<>(ClusteredSessionStore.create(vertx, + httpConfiguration.getValue().sessions.inMemory.mapName, + httpConfiguration.getValue().sessions.inMemory.retryTimeout.toMillis())); + } else { + // TODO maybe make reaper interval also configurable? + return new RuntimeValue<>(LocalSessionStore.create(vertx, + httpConfiguration.getValue().sessions.inMemory.mapName)); + } + } + + public RuntimeValue createRedisSessionStore(SessionStoreProvider provider) { + Map config = new HashMap<>(); + config.put("retryTimeout", httpConfiguration.getValue().sessions.redis.retryTimeout); + return new RuntimeValue<>(provider.create(VertxCoreRecorder.getVertx().get(), config)); + } + + public RuntimeValue createInfinispanSessionStore(SessionStoreProvider provider) { + Map config = new HashMap<>(); + config.put("cacheName", httpConfiguration.getValue().sessions.infinispan.cacheName); + config.put("retryTimeout", httpConfiguration.getValue().sessions.infinispan.retryTimeout); + return new RuntimeValue<>(provider.create(VertxCoreRecorder.getVertx().get(), config)); + } + public void finalizeRouter(BeanContainer container, Consumer defaultRouteHandler, List filterList, List managementInterfaceFilterList, Supplier vertx, LiveReloadConfig liveReloadConfig, Optional> mainRouterRuntimeValue, @@ -355,7 +394,7 @@ public void finalizeRouter(BeanContainer container, Consumer defaultRoute LaunchMode launchMode, boolean requireBodyHandler, Handler bodyHandler, GracefulShutdownFilter gracefulShutdownFilter, ShutdownConfig shutdownConfig, - Executor executor) { + Executor executor, RuntimeValue sessionStore) { HttpConfiguration httpConfiguration = this.httpConfiguration.getValue(); // install the default route at the end Router httpRouteRouter = httpRouterRuntimeValue.getValue(); @@ -413,6 +452,23 @@ public void handle(RoutingContext routingContext) { // Headers sent on any request, regardless of the response HttpServerCommonHandlers.applyHeaders(httpConfiguration.header, httpRouteRouter); + if (sessionStore != null) { + SessionsConfig sessions = httpConfiguration.sessions; + // TODO probably need to normalize and relativize the path here + String path = sessions.path; + SessionHandler sessionHandler = SessionHandler.create(sessionStore.getValue()) + .setSessionTimeout(sessions.timeout.toMillis()) + .setMinLength(sessions.idLength) + .setSessionCookiePath(path) + .setSessionCookieName(sessions.cookieName) + .setCookieHttpOnlyFlag(sessions.cookieHttpOnly) + .setCookieSecureFlag(sessions.cookieSecure.isEnabled(httpConfiguration.insecureRequests)) + .setCookieSameSite(sessions.cookieSameSite.orElse(null)) + .setCookieMaxAge(sessions.cookieMaxAge.map(Duration::toMillis).orElse(-1L)); + // TODO verify if this is the correct router to which the session handler should be installed + httpRouteRouter.route(path).handler(sessionHandler); + } + Handler root; if (rootPath.equals("/")) { if (hotReplacementHandler != null) { diff --git a/extensions/vertx-http/sessions-deployment-spi/pom.xml b/extensions/vertx-http/sessions-deployment-spi/pom.xml new file mode 100644 index 00000000000000..fe00089c8c9778 --- /dev/null +++ b/extensions/vertx-http/sessions-deployment-spi/pom.xml @@ -0,0 +1,27 @@ + + + + quarkus-vertx-http-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-vertx-http-sessions-deployment-spi + Quarkus - Vert.x - HTTP - Sessions Deployment SPI + + + + io.quarkus + quarkus-vertx-http-sessions-runtime-spi + + + io.quarkus + quarkus-core-deployment + + + + diff --git a/extensions/vertx-http/sessions-deployment-spi/src/main/java/io/quarkus/vertx/http/sessions/deployment/spi/SessionStoreRequestBuildItem.java b/extensions/vertx-http/sessions-deployment-spi/src/main/java/io/quarkus/vertx/http/sessions/deployment/spi/SessionStoreRequestBuildItem.java new file mode 100644 index 00000000000000..8caeba6fbffd79 --- /dev/null +++ b/extensions/vertx-http/sessions-deployment-spi/src/main/java/io/quarkus/vertx/http/sessions/deployment/spi/SessionStoreRequestBuildItem.java @@ -0,0 +1,25 @@ +package io.quarkus.vertx.http.sessions.deployment.spi; + +import java.util.Objects; +import java.util.Optional; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.vertx.http.sessions.spi.SessionStoreKind; + +public final class SessionStoreRequestBuildItem extends SimpleBuildItem { + private final SessionStoreKind kind; + private final Optional clientName; // may be empty for the unnamed client + + public SessionStoreRequestBuildItem(SessionStoreKind kind, Optional clientName) { + this.kind = Objects.requireNonNull(kind); + this.clientName = Objects.requireNonNull(clientName); + } + + public boolean is(SessionStoreKind kind) { + return this.kind == kind; + } + + public Optional clientName() { + return clientName; + } +} diff --git a/extensions/vertx-http/sessions-deployment-spi/src/main/java/io/quarkus/vertx/http/sessions/deployment/spi/SessionStoreResponseBuildItem.java b/extensions/vertx-http/sessions-deployment-spi/src/main/java/io/quarkus/vertx/http/sessions/deployment/spi/SessionStoreResponseBuildItem.java new file mode 100644 index 00000000000000..1d72b982b15a99 --- /dev/null +++ b/extensions/vertx-http/sessions-deployment-spi/src/main/java/io/quarkus/vertx/http/sessions/deployment/spi/SessionStoreResponseBuildItem.java @@ -0,0 +1,23 @@ +package io.quarkus.vertx.http.sessions.deployment.spi; + +import java.util.Objects; + +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.vertx.http.sessions.spi.SessionStoreProvider; + +/** + * This is a {@code MultiBuildItem} so that multiple producers may exist + * among the set of currently present extensions. However, at most one item + * of this type may be produced. + */ +public final class SessionStoreResponseBuildItem extends MultiBuildItem { + private final SessionStoreProvider provider; + + public SessionStoreResponseBuildItem(SessionStoreProvider provider) { + this.provider = Objects.requireNonNull(provider); + } + + public SessionStoreProvider getProvider() { + return provider; + } +} diff --git a/extensions/vertx-http/sessions-runtime-spi/pom.xml b/extensions/vertx-http/sessions-runtime-spi/pom.xml new file mode 100644 index 00000000000000..dd0a7ae7e7d0f7 --- /dev/null +++ b/extensions/vertx-http/sessions-runtime-spi/pom.xml @@ -0,0 +1,23 @@ + + + + quarkus-vertx-http-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-vertx-http-sessions-runtime-spi + Quarkus - Vert.x - HTTP - Sessions Runtime SPI + + + + io.vertx + vertx-web + + + + diff --git a/extensions/vertx-http/sessions-runtime-spi/src/main/java/io/quarkus/vertx/http/sessions/spi/SessionStoreKind.java b/extensions/vertx-http/sessions-runtime-spi/src/main/java/io/quarkus/vertx/http/sessions/spi/SessionStoreKind.java new file mode 100644 index 00000000000000..2f48d5e00fd917 --- /dev/null +++ b/extensions/vertx-http/sessions-runtime-spi/src/main/java/io/quarkus/vertx/http/sessions/spi/SessionStoreKind.java @@ -0,0 +1,7 @@ +package io.quarkus.vertx.http.sessions.spi; + +public enum SessionStoreKind { + NONE, + REDIS, + INFINISPAN, +} diff --git a/extensions/vertx-http/sessions-runtime-spi/src/main/java/io/quarkus/vertx/http/sessions/spi/SessionStoreProvider.java b/extensions/vertx-http/sessions-runtime-spi/src/main/java/io/quarkus/vertx/http/sessions/spi/SessionStoreProvider.java new file mode 100644 index 00000000000000..9599413b606aa3 --- /dev/null +++ b/extensions/vertx-http/sessions-runtime-spi/src/main/java/io/quarkus/vertx/http/sessions/spi/SessionStoreProvider.java @@ -0,0 +1,10 @@ +package io.quarkus.vertx.http.sessions.spi; + +import java.util.Map; + +import io.vertx.core.Vertx; +import io.vertx.ext.web.sstore.SessionStore; + +public interface SessionStoreProvider { + SessionStore create(Vertx vertx, Map config); +}