You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by jo...@apache.org on 2023/12/27 09:18:51 UTC
(camel) branch main updated: CAMEL-20019: camel-platform-http-vertx implement session handler (#12530)
This is an automated email from the ASF dual-hosted git repository.
jono pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new f79496b51dc CAMEL-20019: camel-platform-http-vertx implement session handler (#12530)
f79496b51dc is described below
commit f79496b51dc8278950b641f4f07ad140de3b375a
Author: Jono Morris <jo...@apache.org>
AuthorDate: Wed Dec 27 22:18:45 2023 +1300
CAMEL-20019: camel-platform-http-vertx implement session handler (#12530)
* CAMEL-20019: demo session handler implementation
* CAMEL-20019: updates in response to review comments
---
.../http/vertx/VertxPlatformHttpServer.java | 5 +
.../VertxPlatformHttpServerConfiguration.java | 137 +++++++++++
.../http/vertx/VertxPlatformHttpSessionTest.java | 256 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_4.adoc | 10 +
4 files changed, 408 insertions(+)
diff --git a/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpServer.java b/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpServer.java
index c81e3e8d5b0..132c3c6139a 100644
--- a/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpServer.java
+++ b/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpServer.java
@@ -183,6 +183,11 @@ public class VertxPlatformHttpServer extends ServiceSupport implements CamelCont
subRouter.route().handler(createCorsHandler(configuration));
}
+ if (configuration.getSessionConfig().isEnabled()) {
+ subRouter.route().handler(
+ configuration.getSessionConfig().createSessionHandler(vertx));
+ }
+
router.route(configuration.getPath() + "*").subRouter(subRouter);
context.getRegistry().bind(
diff --git a/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpServerConfiguration.java b/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpServerConfiguration.java
index 3720b250b59..b2bcc078e4e 100644
--- a/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpServerConfiguration.java
+++ b/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpServerConfiguration.java
@@ -19,6 +19,12 @@ package org.apache.camel.component.platform.http.vertx;
import java.time.Duration;
import java.util.List;
+import io.vertx.core.Vertx;
+import io.vertx.core.http.CookieSameSite;
+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;
import org.apache.camel.support.jsse.SSLContextParameters;
/**
@@ -39,6 +45,7 @@ public class VertxPlatformHttpServerConfiguration {
private BodyHandler bodyHandler = new BodyHandler();
private Cors cors = new Cors();
+ private SessionConfig sessionConfig = new SessionConfig();
public int getPort() {
return getBindPort();
@@ -112,6 +119,14 @@ public class VertxPlatformHttpServerConfiguration {
this.cors = corsConfiguration;
}
+ public SessionConfig getSessionConfig() {
+ return sessionConfig;
+ }
+
+ public void setSessionConfig(SessionConfig sessionConfig) {
+ this.sessionConfig = sessionConfig;
+ }
+
public BodyHandler getBodyHandler() {
return bodyHandler;
}
@@ -120,6 +135,128 @@ public class VertxPlatformHttpServerConfiguration {
this.bodyHandler = bodyHandler;
}
+ public static class SessionConfig {
+ private boolean enabled;
+ private SessionStoreType storeType = SessionStoreType.LOCAL;
+ private String sessionCookieName = SessionHandler.DEFAULT_SESSION_COOKIE_NAME;
+ private String sessionCookiePath = SessionHandler.DEFAULT_SESSION_COOKIE_PATH;
+ private long sessionTimeOut = SessionHandler.DEFAULT_SESSION_TIMEOUT;
+ private boolean cookieSecure = SessionHandler.DEFAULT_COOKIE_SECURE_FLAG;
+ private boolean cookieHttpOnly = SessionHandler.DEFAULT_COOKIE_HTTP_ONLY_FLAG;
+ private int sessionIdMinLength = SessionHandler.DEFAULT_SESSIONID_MIN_LENGTH;
+ private CookieSameSite cookieSameSite = CookieSameSite.STRICT;
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public SessionStoreType getStoreType() {
+ return this.storeType;
+ }
+
+ public void setStoreType(SessionStoreType storeType) {
+ this.storeType = storeType;
+ }
+
+ public String getSessionCookieName() {
+ return this.sessionCookieName;
+ }
+
+ public void setSessionCookieName(String sessionCookieName) {
+ this.sessionCookieName = sessionCookieName;
+ }
+
+ public String getSessionCookiePath() {
+ return this.sessionCookiePath;
+ }
+
+ public void setSessionCookiePath(String sessionCookiePath) {
+ this.sessionCookiePath = sessionCookiePath;
+ }
+
+ public long getSessionTimeOut() {
+ return this.sessionTimeOut;
+ }
+
+ public void setSessionTimeout(long timeout) {
+ this.sessionTimeOut = timeout;
+ }
+
+ public boolean isCookieSecure() {
+ return this.cookieSecure;
+ }
+
+ // Instructs browsers to only send the cookie over HTTPS when set.
+ public void setCookieSecure(boolean cookieSecure) {
+ this.cookieSecure = cookieSecure;
+ }
+
+ public boolean isCookieHttpOnly() {
+ return this.cookieHttpOnly;
+ }
+
+ // Instructs browsers to prevent Javascript access to the cookie.
+ // Defends against XSS attacks.
+ public void setCookieHttpOnly(boolean cookieHttpOnly) {
+ this.cookieHttpOnly = cookieHttpOnly;
+ }
+
+ public int getSessionIdMinLength() {
+ return this.sessionIdMinLength;
+ }
+
+ public void setSessionIdMinLength(int sessionIdMinLength) {
+ this.sessionIdMinLength = sessionIdMinLength;
+ }
+
+ public CookieSameSite getCookieSameSite() {
+ return this.cookieSameSite;
+ }
+
+ public void setCookieSameSite(CookieSameSite cookieSameSite) {
+ this.cookieSameSite = cookieSameSite;
+ }
+
+ public SessionHandler createSessionHandler(Vertx vertx) {
+ SessionStore sessionStore = storeType.create(vertx);
+ SessionHandler handler = SessionHandler.create(sessionStore);
+ configure(handler);
+ return handler;
+ }
+
+ private void configure(SessionHandler handler) {
+ handler.setSessionTimeout(this.sessionTimeOut)
+ .setSessionCookieName(this.sessionCookieName)
+ .setSessionCookiePath(this.sessionCookiePath)
+ .setSessionTimeout(this.sessionTimeOut)
+ .setCookieHttpOnlyFlag(this.cookieHttpOnly)
+ .setCookieSecureFlag(this.cookieSecure)
+ .setMinLength(this.sessionIdMinLength)
+ .setCookieSameSite(this.cookieSameSite);
+ }
+ }
+
+ public enum SessionStoreType {
+ LOCAL {
+ @Override
+ public SessionStore create(Vertx vertx) {
+ return LocalSessionStore.create(vertx);
+ }
+ },
+ CLUSTERED {
+ @Override
+ public SessionStore create(Vertx vertx) {
+ return ClusteredSessionStore.create(vertx);
+ }
+ };
+
+ public abstract SessionStore create(Vertx vertx);
+ }
+
public static class Cors {
private boolean enabled;
private List<String> origins;
diff --git a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpSessionTest.java b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpSessionTest.java
new file mode 100644
index 00000000000..7ef4d3d8862
--- /dev/null
+++ b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpSessionTest.java
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.platform.http.vertx;
+
+import java.util.Arrays;
+
+import io.restassured.RestAssured;
+import io.vertx.core.Handler;
+import io.vertx.core.http.CookieSameSite;
+import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.web.Session;
+import io.vertx.ext.web.handler.SessionHandler;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.platform.http.PlatformHttpComponent;
+import org.apache.camel.component.platform.http.PlatformHttpConstants;
+import org.apache.camel.http.base.cookie.InstanceCookieHandler;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.test.AvailablePortFinder;
+import org.junit.jupiter.api.Test;
+
+import static io.restassured.RestAssured.given;
+import static io.restassured.matcher.RestAssuredMatchers.detailedCookie;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class VertxPlatformHttpSessionTest {
+
+ @Test
+ public void testSessionDisabled() throws Exception {
+ CamelContext context = createCamelContext(sessionConfig -> {
+ // session handling disabled by default
+ });
+
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("platform-http:/disabled")
+ .setBody().constant("disabled");
+ }
+ });
+
+ try {
+ context.start();
+
+ given()
+ .when()
+ .get("/disabled")
+ .then()
+ .statusCode(200)
+ .header("set-cookie", nullValue())
+ .header("cookie", nullValue())
+ .body(equalTo("disabled"));
+ } finally {
+ context.stop();
+ }
+ }
+
+ @Test
+ public void testCookeForDefaultSessionConfig() throws Exception {
+ CamelContext context = createCamelContext(sessionConfig -> {
+ sessionConfig.setEnabled(true);
+ });
+
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("platform-http:/session")
+ .setBody().constant("session");
+ }
+ });
+
+ try {
+ context.start();
+
+ String sessionCookieValue = given()
+ .when()
+ .get("/session")
+ .then()
+ .statusCode(200)
+ .cookie("vertx-web.session",
+ detailedCookie()
+ .path("/").value(notNullValue())
+ .httpOnly(false)
+ .secured(false)
+ .sameSite("Strict"))
+ .header("cookie", nullValue())
+ .body(equalTo("session"))
+ .extract().cookie("vertx-web.session");
+
+ assertTrue(sessionCookieValue.length() >= SessionHandler.DEFAULT_SESSIONID_MIN_LENGTH);
+
+ } finally {
+ context.stop();
+ }
+ }
+
+ @Test
+ public void testCookieForModifiedSessionConfig() throws Exception {
+ CamelContext context = createCamelContext(sessionConfig -> {
+ sessionConfig.setSessionCookieName("vertx-session");
+ sessionConfig.setEnabled(true);
+ sessionConfig.setSessionCookiePath("/session");
+ sessionConfig.setCookieSecure(true);
+ sessionConfig.setCookieHttpOnly(true);
+ sessionConfig.setCookieSameSite(CookieSameSite.LAX);
+ sessionConfig.setSessionIdMinLength(64);
+ });
+
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("platform-http:/session")
+ .setBody().constant("session");
+ }
+ });
+
+ try {
+ context.start();
+
+ String sessionCookieValue = given()
+ .when()
+ .get("/session")
+ .then()
+ .statusCode(200)
+ .cookie("vertx-session",
+ detailedCookie()
+ .path("/session").value(notNullValue())
+ .httpOnly(true)
+ .secured(true)
+ .sameSite("Lax"))
+ .header("cookie", nullValue())
+ .body(equalTo("session"))
+ .extract().cookie("vertx-session");
+
+ assertTrue(sessionCookieValue.length() >= 64);
+
+ } finally {
+ context.stop();
+ }
+ }
+
+ @Test
+ public void testSessionHandling() throws Exception {
+ int port = AvailablePortFinder.getNextAvailable();
+ CamelContext context = createCamelContext(port,
+ sessionConfig -> {
+ sessionConfig.setEnabled(true);
+ });
+ addPlatformHttpEngineHandler(context, new HitCountHandler());
+ context.getRegistry().bind("instanceCookieHander", new InstanceCookieHandler());
+
+ try {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("platform-http:/session")
+ .setBody().constant("session");
+
+ from("direct:session")
+ .toF("http://localhost:%d/session?cookieHandler=#instanceCookieHander",
+ port);
+ }
+ });
+
+ context.start();
+
+ // initial call establishes session
+ ProducerTemplate template = context.createProducerTemplate();
+ Exchange exchange = template.request("direct:session", null);
+ // 'set-cookie' header for new session, e.g. 'vertx-web.session=735944d69685aaf63421fb5b3c116b84; Path=/; SameSite=Strict'
+ String sessionCookie = getHeader("set-cookie", exchange);
+ assertNotNull(getHeader("set-cookie", exchange));
+ assertEquals(getHeader("hitcount", exchange), "1");
+
+ // subsequent call reuses session
+ exchange = template.request("direct:session", null);
+ // 'cookie' header for existing session, e.g. 'vertx-web.session=735944d69685aaf63421fb5b3c116b84'
+ String cookieHeader = getHeader("cookie", exchange);
+ assertEquals(cookieHeader, sessionCookie.substring(0, sessionCookie.indexOf(';')));
+ assertNull(getHeader("set-cookie", exchange));
+ assertEquals(getHeader("hitcount", exchange), "2");
+
+ } finally {
+ context.stop();
+ }
+ }
+
+ private String getHeader(String header, Exchange exchange) {
+ return (String) exchange.getMessage().getHeader(header);
+ }
+
+ private CamelContext createCamelContext(SessionConfigCustomizer customizer)
+ throws Exception {
+ int bindPort = AvailablePortFinder.getNextAvailable();
+ RestAssured.port = bindPort;
+ return createCamelContext(bindPort, customizer);
+ }
+
+ private CamelContext createCamelContext(int bindPort, SessionConfigCustomizer customizer)
+ throws Exception {
+ VertxPlatformHttpServerConfiguration conf = new VertxPlatformHttpServerConfiguration();
+ conf.setBindPort(bindPort);
+
+ VertxPlatformHttpServerConfiguration.SessionConfig sessionConfig
+ = new VertxPlatformHttpServerConfiguration.SessionConfig();
+ customizer.customize(sessionConfig);
+ conf.setSessionConfig(sessionConfig);
+
+ CamelContext camelContext = new DefaultCamelContext();
+ camelContext.addService(new VertxPlatformHttpServer(conf));
+ return camelContext;
+ }
+
+ private void addPlatformHttpEngineHandler(CamelContext camelContext, Handler<RoutingContext> handler) {
+ VertxPlatformHttpEngine platformEngine = new VertxPlatformHttpEngine();
+ platformEngine.setHandlers(Arrays.asList(handler));
+ PlatformHttpComponent component = new PlatformHttpComponent(camelContext);
+ component.setEngine(platformEngine);
+ camelContext.getRegistry().bind(PlatformHttpConstants.PLATFORM_HTTP_COMPONENT_NAME, component);
+ }
+
+ private class HitCountHandler implements Handler<RoutingContext> {
+ @Override
+ public void handle(RoutingContext routingContext) {
+ Session session = routingContext.session();
+ Integer cnt = session.get("hitcount");
+ cnt = (cnt == null ? 0 : cnt) + 1;
+ session.put("hitcount", cnt);
+ routingContext.response().putHeader("hitcount", Integer.toString(cnt));
+ routingContext.next();
+ }
+ }
+
+ interface SessionConfigCustomizer {
+ void customize(VertxPlatformHttpServerConfiguration.SessionConfig sessionConfig);
+ }
+}
diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_4.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_4.adoc
index 2549c8f3911..4fea0c6c367 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_4.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_4.adoc
@@ -123,3 +123,13 @@ The route controller configuration has been moved from general main to its own g
All keys started with `camel.springboot.routesController` should be renamed to `camel.routecontroller.`, for example
`camel.springboot.routeControllerBackOffDelay` should be renamed to `camel.routecontroller.backOffDelay`.
And the option `camel.springboot.routeControllerSuperviseEnabled` has been renamed to `camel.routecontroller.enabled`.
+
+=== camel-platform-http-vertx
+
+Added configuration to enable Vert.x session handling.
+Sessions are disabled by default, but can be enabled by setting the `enabled` property on `VertxPlatformHttpServerConfiguration.SessionConfig`
+to `true`.
+Other properties include `sessionCookieName`, `sessionCookiePath`, `sessionTimeout`, `cookieSecure`, `cookieHttpOnly`
+`cookieSameSite` and `storeType`.
+The session `storeType` defaults to the Vert.x `LocalSessionStore` and `cookieSameSite` to `Strict`. The remainder
+of the properties are configured with Vert.x defaults if not set.