This is an automated email from the ASF dual-hosted git repository. davsclaus 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 da0124046a4 Add handshakeHeaders uri param in vertx-websocket component (#13960) da0124046a4 is described below commit da0124046a4c861e6e02fd2f0711965acca6925e Author: Alexis SEGURA <alex.segur...@gmail.com> AuthorDate: Fri Apr 26 12:43:48 2024 +0200 Add handshakeHeaders uri param in vertx-websocket component (#13960) Some websocket apis require authentication and various header during the handshake process in order to upgrade to websocket. It was not supported by the vertx-component. So I added an uri param to set a map of headers that will be passed as header in the HTTP handshake request. It is important to note that it only works when the endpoint is a producer, or a consumer of a remote host (consumeAsClient true). Use multiValue mecanism on UriParam. Co-authored-by: Alexis SEGURA <alexis.seg...@akt.io> --- .../camel/catalog/components/vertx-websocket.json | 29 +-- .../VertxWebsocketEndpointConfigurer.java | 15 ++ .../VertxWebsocketEndpointUriFactory.java | 7 +- .../component/vertx/websocket/vertx-websocket.json | 29 +-- .../vertx/websocket/VertxWebsocketComponent.java | 4 + .../websocket/VertxWebsocketConfiguration.java | 15 ++ .../vertx/websocket/VertxWebsocketEndpoint.java | 5 + .../VertxWebsocketEndpointConfigurationTest.java | 18 ++ .../VertxWebsocketHandshakeHeadersTest.java | 226 +++++++++++++++++++++ .../kotlin/components/VertxWebsocketUriDsl.kt | 8 + 10 files changed, 326 insertions(+), 30 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/vertx-websocket.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/vertx-websocket.json index efe52316058..1841d9b9b2e 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/vertx-websocket.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/vertx-websocket.json @@ -49,19 +49,20 @@ "allowOriginHeader": { "index": 4, "kind": "parameter", "displayName": "Allow Origin Header", "group": "consumer", "label": "producer,consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Whether the WebSocket client should add the Origin [...] "consumeAsClient": { "index": 5, "kind": "parameter", "displayName": "Consume As Client", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "When set to true, the consumer acts as a WebSocket client, crea [...] "fireWebSocketConnectionEvents": { "index": 6, "kind": "parameter", "displayName": "Fire Web Socket Connection Events", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Whether the server consumer will [...] - "maxReconnectAttempts": { "index": 7, "kind": "parameter", "displayName": "Max Reconnect Attempts", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "When consumeAsClient is set to true this sets the maximum num [...] - "originHeaderUrl": { "index": 8, "kind": "parameter", "displayName": "Origin Header Url", "group": "consumer", "label": "producer,consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "The value of the Origin header that the WebSocket client should use o [...] - "reconnectInitialDelay": { "index": 9, "kind": "parameter", "displayName": "Reconnect Initial Delay", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "When consumeAsClient is set to true this sets the initial d [...] - "reconnectInterval": { "index": 10, "kind": "parameter", "displayName": "Reconnect Interval", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1000, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "When consumeAsClient is set to true this sets the interval in mi [...] - "router": { "index": 11, "kind": "parameter", "displayName": "Router", "group": "consumer", "label": "consumer", "required": false, "type": "object", "javaType": "io.vertx.ext.web.Router", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To use an existing vertx router for the HTTP server" }, - "serverOptions": { "index": 12, "kind": "parameter", "displayName": "Server Options", "group": "consumer", "label": "consumer", "required": false, "type": "object", "javaType": "io.vertx.core.http.HttpServerOptions", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Sets customized options for configuring the HTTP server hostin [...] - "bridgeErrorHandler": { "index": 13, "kind": "parameter", "displayName": "Bridge Error Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming [...] - "exceptionHandler": { "index": 14, "kind": "parameter", "displayName": "Exception Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", "deprecated": false, "autowired": false, "secret": false, "description": "To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By de [...] - "exchangePattern": { "index": 15, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the exchange pattern when the consumer creates an exchange." }, - "clientOptions": { "index": 16, "kind": "parameter", "displayName": "Client Options", "group": "producer", "label": "producer", "required": false, "type": "object", "javaType": "io.vertx.core.http.HttpClientOptions", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Sets customized options for configuring the WebSocket client u [...] - "clientSubProtocols": { "index": 17, "kind": "parameter", "displayName": "Client Sub Protocols", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Comma separated list of WebSocket subprotocols that the client should u [...] - "sendToAll": { "index": 18, "kind": "parameter", "displayName": "Send To All", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To send to all websocket subscribers. Can be used to configure at the endp [...] - "lazyStartProducer": { "index": 19, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produ [...] - "sslContextParameters": { "index": 20, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To configure security using SSLCo [...] + "handshakeHeaders": { "index": 7, "kind": "parameter", "displayName": "Handshake Headers", "group": "consumer", "label": "producer,consumer", "required": false, "type": "object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>", "prefix": "handshake.", "multiValue": true, "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "descri [...] + "maxReconnectAttempts": { "index": 8, "kind": "parameter", "displayName": "Max Reconnect Attempts", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "When consumeAsClient is set to true this sets the maximum num [...] + "originHeaderUrl": { "index": 9, "kind": "parameter", "displayName": "Origin Header Url", "group": "consumer", "label": "producer,consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "The value of the Origin header that the WebSocket client should use o [...] + "reconnectInitialDelay": { "index": 10, "kind": "parameter", "displayName": "Reconnect Initial Delay", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "When consumeAsClient is set to true this sets the initial [...] + "reconnectInterval": { "index": 11, "kind": "parameter", "displayName": "Reconnect Interval", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1000, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "When consumeAsClient is set to true this sets the interval in mi [...] + "router": { "index": 12, "kind": "parameter", "displayName": "Router", "group": "consumer", "label": "consumer", "required": false, "type": "object", "javaType": "io.vertx.ext.web.Router", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To use an existing vertx router for the HTTP server" }, + "serverOptions": { "index": 13, "kind": "parameter", "displayName": "Server Options", "group": "consumer", "label": "consumer", "required": false, "type": "object", "javaType": "io.vertx.core.http.HttpServerOptions", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Sets customized options for configuring the HTTP server hostin [...] + "bridgeErrorHandler": { "index": 14, "kind": "parameter", "displayName": "Bridge Error Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming [...] + "exceptionHandler": { "index": 15, "kind": "parameter", "displayName": "Exception Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", "deprecated": false, "autowired": false, "secret": false, "description": "To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By de [...] + "exchangePattern": { "index": 16, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the exchange pattern when the consumer creates an exchange." }, + "clientOptions": { "index": 17, "kind": "parameter", "displayName": "Client Options", "group": "producer", "label": "producer", "required": false, "type": "object", "javaType": "io.vertx.core.http.HttpClientOptions", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Sets customized options for configuring the WebSocket client u [...] + "clientSubProtocols": { "index": 18, "kind": "parameter", "displayName": "Client Sub Protocols", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Comma separated list of WebSocket subprotocols that the client should u [...] + "sendToAll": { "index": 19, "kind": "parameter", "displayName": "Send To All", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To send to all websocket subscribers. Can be used to configure at the endp [...] + "lazyStartProducer": { "index": 20, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produ [...] + "sslContextParameters": { "index": 21, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To configure security using SSLCo [...] } } diff --git a/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointConfigurer.java b/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointConfigurer.java index 9d90229200d..1fea496c4e8 100644 --- a/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointConfigurer.java +++ b/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointConfigurer.java @@ -41,6 +41,8 @@ public class VertxWebsocketEndpointConfigurer extends PropertyConfigurerSupport case "exchangePattern": target.setExchangePattern(property(camelContext, org.apache.camel.ExchangePattern.class, value)); return true; case "firewebsocketconnectionevents": case "fireWebSocketConnectionEvents": target.getConfiguration().setFireWebSocketConnectionEvents(property(camelContext, boolean.class, value)); return true; + case "handshakeheaders": + case "handshakeHeaders": target.getConfiguration().setHandshakeHeaders(property(camelContext, java.util.Map.class, value)); return true; case "lazystartproducer": case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; case "maxreconnectattempts": @@ -83,6 +85,8 @@ public class VertxWebsocketEndpointConfigurer extends PropertyConfigurerSupport case "exchangePattern": return org.apache.camel.ExchangePattern.class; case "firewebsocketconnectionevents": case "fireWebSocketConnectionEvents": return boolean.class; + case "handshakeheaders": + case "handshakeHeaders": return java.util.Map.class; case "lazystartproducer": case "lazyStartProducer": return boolean.class; case "maxreconnectattempts": @@ -126,6 +130,8 @@ public class VertxWebsocketEndpointConfigurer extends PropertyConfigurerSupport case "exchangePattern": return target.getExchangePattern(); case "firewebsocketconnectionevents": case "fireWebSocketConnectionEvents": return target.getConfiguration().isFireWebSocketConnectionEvents(); + case "handshakeheaders": + case "handshakeHeaders": return target.getConfiguration().getHandshakeHeaders(); case "lazystartproducer": case "lazyStartProducer": return target.isLazyStartProducer(); case "maxreconnectattempts": @@ -146,5 +152,14 @@ public class VertxWebsocketEndpointConfigurer extends PropertyConfigurerSupport default: return null; } } + + @Override + public Object getCollectionValueType(Object target, String name, boolean ignoreCase) { + switch (ignoreCase ? name.toLowerCase() : name) { + case "handshakeheaders": + case "handshakeHeaders": return java.lang.Object.class; + default: return null; + } + } } diff --git a/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointUriFactory.java b/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointUriFactory.java index 49d740af4ed..47f7fac1be1 100644 --- a/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointUriFactory.java +++ b/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointUriFactory.java @@ -23,7 +23,7 @@ public class VertxWebsocketEndpointUriFactory extends org.apache.camel.support.c private static final Set<String> SECRET_PROPERTY_NAMES; private static final Set<String> MULTI_VALUE_PREFIXES; static { - Set<String> props = new HashSet<>(21); + Set<String> props = new HashSet<>(22); props.add("allowOriginHeader"); props.add("allowedOriginPattern"); props.add("bridgeErrorHandler"); @@ -33,6 +33,7 @@ public class VertxWebsocketEndpointUriFactory extends org.apache.camel.support.c props.add("exceptionHandler"); props.add("exchangePattern"); props.add("fireWebSocketConnectionEvents"); + props.add("handshakeHeaders"); props.add("host"); props.add("lazyStartProducer"); props.add("maxReconnectAttempts"); @@ -47,7 +48,9 @@ public class VertxWebsocketEndpointUriFactory extends org.apache.camel.support.c props.add("sslContextParameters"); PROPERTY_NAMES = Collections.unmodifiableSet(props); SECRET_PROPERTY_NAMES = Collections.emptySet(); - MULTI_VALUE_PREFIXES = Collections.emptySet(); + Set<String> prefixes = new HashSet<>(1); + prefixes.add("handshake."); + MULTI_VALUE_PREFIXES = Collections.unmodifiableSet(prefixes); } @Override diff --git a/components/camel-vertx/camel-vertx-websocket/src/generated/resources/META-INF/org/apache/camel/component/vertx/websocket/vertx-websocket.json b/components/camel-vertx/camel-vertx-websocket/src/generated/resources/META-INF/org/apache/camel/component/vertx/websocket/vertx-websocket.json index efe52316058..1841d9b9b2e 100644 --- a/components/camel-vertx/camel-vertx-websocket/src/generated/resources/META-INF/org/apache/camel/component/vertx/websocket/vertx-websocket.json +++ b/components/camel-vertx/camel-vertx-websocket/src/generated/resources/META-INF/org/apache/camel/component/vertx/websocket/vertx-websocket.json @@ -49,19 +49,20 @@ "allowOriginHeader": { "index": 4, "kind": "parameter", "displayName": "Allow Origin Header", "group": "consumer", "label": "producer,consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Whether the WebSocket client should add the Origin [...] "consumeAsClient": { "index": 5, "kind": "parameter", "displayName": "Consume As Client", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "When set to true, the consumer acts as a WebSocket client, crea [...] "fireWebSocketConnectionEvents": { "index": 6, "kind": "parameter", "displayName": "Fire Web Socket Connection Events", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Whether the server consumer will [...] - "maxReconnectAttempts": { "index": 7, "kind": "parameter", "displayName": "Max Reconnect Attempts", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "When consumeAsClient is set to true this sets the maximum num [...] - "originHeaderUrl": { "index": 8, "kind": "parameter", "displayName": "Origin Header Url", "group": "consumer", "label": "producer,consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "The value of the Origin header that the WebSocket client should use o [...] - "reconnectInitialDelay": { "index": 9, "kind": "parameter", "displayName": "Reconnect Initial Delay", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "When consumeAsClient is set to true this sets the initial d [...] - "reconnectInterval": { "index": 10, "kind": "parameter", "displayName": "Reconnect Interval", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1000, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "When consumeAsClient is set to true this sets the interval in mi [...] - "router": { "index": 11, "kind": "parameter", "displayName": "Router", "group": "consumer", "label": "consumer", "required": false, "type": "object", "javaType": "io.vertx.ext.web.Router", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To use an existing vertx router for the HTTP server" }, - "serverOptions": { "index": 12, "kind": "parameter", "displayName": "Server Options", "group": "consumer", "label": "consumer", "required": false, "type": "object", "javaType": "io.vertx.core.http.HttpServerOptions", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Sets customized options for configuring the HTTP server hostin [...] - "bridgeErrorHandler": { "index": 13, "kind": "parameter", "displayName": "Bridge Error Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming [...] - "exceptionHandler": { "index": 14, "kind": "parameter", "displayName": "Exception Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", "deprecated": false, "autowired": false, "secret": false, "description": "To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By de [...] - "exchangePattern": { "index": 15, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the exchange pattern when the consumer creates an exchange." }, - "clientOptions": { "index": 16, "kind": "parameter", "displayName": "Client Options", "group": "producer", "label": "producer", "required": false, "type": "object", "javaType": "io.vertx.core.http.HttpClientOptions", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Sets customized options for configuring the WebSocket client u [...] - "clientSubProtocols": { "index": 17, "kind": "parameter", "displayName": "Client Sub Protocols", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Comma separated list of WebSocket subprotocols that the client should u [...] - "sendToAll": { "index": 18, "kind": "parameter", "displayName": "Send To All", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To send to all websocket subscribers. Can be used to configure at the endp [...] - "lazyStartProducer": { "index": 19, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produ [...] - "sslContextParameters": { "index": 20, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To configure security using SSLCo [...] + "handshakeHeaders": { "index": 7, "kind": "parameter", "displayName": "Handshake Headers", "group": "consumer", "label": "producer,consumer", "required": false, "type": "object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>", "prefix": "handshake.", "multiValue": true, "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "descri [...] + "maxReconnectAttempts": { "index": 8, "kind": "parameter", "displayName": "Max Reconnect Attempts", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "When consumeAsClient is set to true this sets the maximum num [...] + "originHeaderUrl": { "index": 9, "kind": "parameter", "displayName": "Origin Header Url", "group": "consumer", "label": "producer,consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "The value of the Origin header that the WebSocket client should use o [...] + "reconnectInitialDelay": { "index": 10, "kind": "parameter", "displayName": "Reconnect Initial Delay", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "When consumeAsClient is set to true this sets the initial [...] + "reconnectInterval": { "index": 11, "kind": "parameter", "displayName": "Reconnect Interval", "group": "consumer", "label": "consumer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1000, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "When consumeAsClient is set to true this sets the interval in mi [...] + "router": { "index": 12, "kind": "parameter", "displayName": "Router", "group": "consumer", "label": "consumer", "required": false, "type": "object", "javaType": "io.vertx.ext.web.Router", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To use an existing vertx router for the HTTP server" }, + "serverOptions": { "index": 13, "kind": "parameter", "displayName": "Server Options", "group": "consumer", "label": "consumer", "required": false, "type": "object", "javaType": "io.vertx.core.http.HttpServerOptions", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Sets customized options for configuring the HTTP server hostin [...] + "bridgeErrorHandler": { "index": 14, "kind": "parameter", "displayName": "Bridge Error Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming [...] + "exceptionHandler": { "index": 15, "kind": "parameter", "displayName": "Exception Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", "deprecated": false, "autowired": false, "secret": false, "description": "To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By de [...] + "exchangePattern": { "index": 16, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the exchange pattern when the consumer creates an exchange." }, + "clientOptions": { "index": 17, "kind": "parameter", "displayName": "Client Options", "group": "producer", "label": "producer", "required": false, "type": "object", "javaType": "io.vertx.core.http.HttpClientOptions", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Sets customized options for configuring the WebSocket client u [...] + "clientSubProtocols": { "index": 18, "kind": "parameter", "displayName": "Client Sub Protocols", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Comma separated list of WebSocket subprotocols that the client should u [...] + "sendToAll": { "index": 19, "kind": "parameter", "displayName": "Send To All", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To send to all websocket subscribers. Can be used to configure at the endp [...] + "lazyStartProducer": { "index": 20, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produ [...] + "sslContextParameters": { "index": 21, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To configure security using SSLCo [...] } } diff --git a/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketComponent.java b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketComponent.java index f29295371c7..9b057cb8c8a 100644 --- a/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketComponent.java +++ b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketComponent.java @@ -30,6 +30,7 @@ import org.apache.camel.spi.Metadata; import org.apache.camel.spi.annotations.Component; import org.apache.camel.support.DefaultComponent; import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.PropertiesHelper; import org.apache.camel.util.URISupport; import org.apache.camel.util.UnsafeUriCharactersEncoder; @@ -76,9 +77,12 @@ public class VertxWebsocketComponent extends DefaultComponent implements SSLCont } } + Map<String, Object> handshakeHeaders = PropertiesHelper.extractProperties(parameters, "handshake."); + VertxWebsocketConfiguration configuration = new VertxWebsocketConfiguration(); configuration.setAllowOriginHeader(isAllowOriginHeader()); configuration.setOriginHeaderUrl(getOriginHeaderUrl()); + configuration.setHandshakeHeaders(handshakeHeaders); VertxWebsocketEndpoint endpoint = createEndpointInstance(uri, configuration); setProperties(endpoint, parameters); diff --git a/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketConfiguration.java b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketConfiguration.java index f91631fe0a8..a474089902c 100644 --- a/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketConfiguration.java +++ b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketConfiguration.java @@ -17,6 +17,7 @@ package org.apache.camel.component.vertx.websocket; import java.net.URI; +import java.util.Map; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpServerOptions; @@ -68,6 +69,8 @@ public class VertxWebsocketConfiguration { private boolean allowOriginHeader = true; @UriParam(label = "producer,consumer") private String originHeaderUrl; + @UriParam(label = "producer,consumer", prefix = "handshake.", multiValue = true) + private Map<String, Object> handshakeHeaders; /** * The WebSocket URI address to use. @@ -277,4 +280,16 @@ public class VertxWebsocketConfiguration { public void setOriginHeaderUrl(String originHeaderUrl) { this.originHeaderUrl = originHeaderUrl; } + + public Map<String, Object> getHandshakeHeaders() { + return handshakeHeaders; + } + + /** + * Headers to send in the HTTP handshake request. When the endpoint is a consumer, it only works when it consumes a + * remote host as a client (i.e. consumeAsClient is true). + */ + public void setHandshakeHeaders(final Map<String, Object> handshakeHeaders) { + this.handshakeHeaders = handshakeHeaders; + } } diff --git a/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpoint.java b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpoint.java index e160b4204b4..4c162867bce 100644 --- a/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpoint.java +++ b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpoint.java @@ -189,6 +189,11 @@ public class VertxWebsocketEndpoint extends DefaultEndpoint { connectOptions.addHeader(ORIGIN_HTTP_HEADER_NAME, defaultOriginHeader); } + if (ObjectHelper.isNotEmpty(configuration.getHandshakeHeaders())) { + configuration.getHandshakeHeaders() + .forEach((headerName, headerValue) -> connectOptions.addHeader(headerName, headerValue.toString())); + } + return connectOptions; } diff --git a/components/camel-vertx/camel-vertx-websocket/src/test/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointConfigurationTest.java b/components/camel-vertx/camel-vertx-websocket/src/test/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointConfigurationTest.java index 810a5edfdf5..cd467566de1 100644 --- a/components/camel-vertx/camel-vertx-websocket/src/test/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointConfigurationTest.java +++ b/components/camel-vertx/camel-vertx-websocket/src/test/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointConfigurationTest.java @@ -114,6 +114,24 @@ public class VertxWebsocketEndpointConfigurationTest extends VertxWebSocketTestS assertEquals(testQueryParam, websocketURI.getQuery(), "Query parameters are not correctly set in the in websocketURI."); } + @Test + void testHandshakeHeaders() { + String handshakeHeaders + = "handshake.Authorization=Bearer token&handshake.ApiSign=-u-4tjFSE=&handshake.Timestamp=2024-04-23T15:22:16.000000Z"; + String endpointParams = "consumeAsClient=true"; + + VertxWebsocketEndpoint endpoint + = context.getEndpoint("vertx-websocket:foo.bar.com/test?" + endpointParams + "&" + handshakeHeaders, + VertxWebsocketEndpoint.class); + assertNotNull(endpoint.getConfiguration().getHandshakeHeaders().get("Authorization"), + "Handshake headers Authorization is not correctly configured."); + assertNotNull(endpoint.getConfiguration().getHandshakeHeaders().get("ApiSign"), + "Handshake headers ApiSign is not correctly configured."); + assertNotNull(endpoint.getConfiguration().getHandshakeHeaders().get("Timestamp"), + "Handshake headers Timestamp is not correctly configured."); + + } + @Override protected RoutesBuilder createRouteBuilder() { return new RouteBuilder() { diff --git a/components/camel-vertx/camel-vertx-websocket/src/test/java/org/apache/camel/component/vertx/websocket/VertxWebsocketHandshakeHeadersTest.java b/components/camel-vertx/camel-vertx-websocket/src/test/java/org/apache/camel/component/vertx/websocket/VertxWebsocketHandshakeHeadersTest.java new file mode 100644 index 00000000000..6bf034c0cb1 --- /dev/null +++ b/components/camel-vertx/camel-vertx-websocket/src/test/java/org/apache/camel/component/vertx/websocket/VertxWebsocketHandshakeHeadersTest.java @@ -0,0 +1,226 @@ +/* + * 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.vertx.websocket; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import io.vertx.core.Handler; +import io.vertx.core.Vertx; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.http.HttpServerOptions; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.http.ServerWebSocket; +import io.vertx.ext.web.Route; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class VertxWebsocketHandshakeHeadersTest extends VertxWebSocketTestSupport { + + private static final Logger LOG = LoggerFactory.getLogger(VertxWebsocketHandshakeHeadersTest.class); + + @Test + public void testHandshakeHeadersAsProducer() throws Exception { + CountDownLatch latch = new CountDownLatch(2); + Vertx vertx = Vertx.vertx(); + Router router = Router.router(vertx); + Route route = router.route("/ws"); + route.handler(new Handler<RoutingContext>() { + @Override + public void handle(RoutingContext context) { + HttpServerRequest request = context.request(); + + String authorizationHeader = request.getHeader("Authorization"); + assertNotNull(authorizationHeader, "Authorization header is not passed in the request."); + assertFalse(authorizationHeader.isBlank(), "Authorization header is blank."); + + String apiSignHeader = request.getHeader("ApiSign"); + assertNotNull(apiSignHeader, "ApiSign header is not passed in the request."); + assertFalse(apiSignHeader.isBlank(), "ApiSign header is blank."); + + String connectionHeader = request.headers().get(HttpHeaders.CONNECTION); + if (connectionHeader == null || !connectionHeader.toLowerCase().contains("upgrade")) { + context.response().setStatusCode(400); + context.response().end("Can \"Upgrade\" only to \"WebSocket\"."); + } else { + // we're about to upgrade the connection, which means an asynchronous + // operation. We have to pause the request otherwise we will loose the + // body of the request once the upgrade completes + final boolean parseEnded = request.isEnded(); + if (!parseEnded) { + request.pause(); + } + // upgrade + request.toWebSocket(toWebSocket -> { + if (toWebSocket.succeeded()) { + // resume the parsing + if (!parseEnded) { + request.resume(); + } + // handle the websocket session as usual + ServerWebSocket webSocket = toWebSocket.result(); + webSocket.textMessageHandler(new Handler<String>() { + @Override + public void handle(String message) { + latch.countDown(); + } + }); + } else { + // the upgrade failed + context.fail(toWebSocket.cause()); + } + }); + } + } + }); + + HttpServerOptions options = new HttpServerOptions(); + + VertxWebsocketHostConfiguration configuration = new VertxWebsocketHostConfiguration(vertx, router, options, null); + VertxWebsocketHostKey key = new VertxWebsocketHostKey("localhost", 0); + VertxWebsocketHost host = new VertxWebsocketHost(context, configuration, key); + host.start(); + + String handshakeHeaders = "handshake.Authorization=Bearer token&handshake.ApiSign=-u-4tjFSE="; + + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + from("direct:start") + .toD("vertx-websocket:localhost:${header.port}/ws?" + handshakeHeaders); + + } + }); + + context.start(); + try { + ProducerTemplate template = context.createProducerTemplate(); + template.sendBodyAndHeader("direct:start", "Hello world", "port", host.getPort()); + template.sendBodyAndHeader("direct:start", "Hello world after handshake", "port", host.getPort()); + + assertTrue(latch.await(10, TimeUnit.SECONDS)); + } finally { + try { + host.stop(); + } catch (Exception e) { + LOG.warn("Failed to stop Vert.x server", e); + } + context.stop(); + } + } + + @Test + public void testHandshakeHeadersAsConsumer() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + Vertx vertx = Vertx.vertx(); + Router router = Router.router(vertx); + Route route = router.route("/ws"); + route.handler(new Handler<RoutingContext>() { + @Override + public void handle(RoutingContext context) { + HttpServerRequest request = context.request(); + + String authorizationHeader = request.getHeader("Authorization"); + assertNotNull(authorizationHeader, "Authorization header is not passed in the request."); + assertFalse(authorizationHeader.isBlank(), "Authorization header is blank."); + + String apiSignHeader = request.getHeader("ApiSign"); + assertNotNull(apiSignHeader, "ApiSign header is not passed in the request."); + assertFalse(apiSignHeader.isBlank(), "ApiSign header is blank."); + + String connectionHeader = request.headers().get(HttpHeaders.CONNECTION); + if (connectionHeader == null || !connectionHeader.toLowerCase().contains("upgrade")) { + context.response().setStatusCode(400); + context.response().end("Can \"Upgrade\" only to \"WebSocket\"."); + } else { + // we're about to upgrade the connection, which means an asynchronous + // operation. We have to pause the request otherwise we will loose the + // body of the request once the upgrade completes + final boolean parseEnded = request.isEnded(); + if (!parseEnded) { + request.pause(); + } + + // upgrade + request.toWebSocket(toWebSocket -> { + if (toWebSocket.succeeded()) { + // resume the parsing + if (!parseEnded) { + request.resume(); + } + // Send a text message to consumer + ServerWebSocket webSocket = toWebSocket.result(); + webSocket.writeTextMessage("Hello World"); + webSocket.writeTextMessage("Ping").onComplete(event -> latch.countDown()); + } else { + // the upgrade failed + context.fail(toWebSocket.cause()); + } + }); + } + } + }); + + HttpServerOptions options = new HttpServerOptions(); + + VertxWebsocketHostConfiguration configuration = new VertxWebsocketHostConfiguration(vertx, router, options, null); + VertxWebsocketHostKey key = new VertxWebsocketHostKey("localhost", 0); + VertxWebsocketHost host = new VertxWebsocketHost(context, configuration, key); + host.start(); + + String handshakeHeaders = "handshake.Authorization=Bearer token&handshake.ApiSign=-u-4tjFSE="; + + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + fromF("vertx-websocket:localhost:%d/ws?consumeAsClient=true&" + handshakeHeaders, host.getPort()) + .log("Consume websocket message ${body}") + .to("mock:result"); + } + }); + + context.start(); + try { + assertTrue(latch.await(10, TimeUnit.SECONDS)); + + MockEndpoint mockEndpoint = context.getEndpoint("mock:result", MockEndpoint.class); + mockEndpoint.expectedBodiesReceivedInAnyOrder("Hello World", "Ping"); + mockEndpoint.assertIsSatisfied(); + } finally { + try { + host.stop(); + } catch (Exception e) { + LOG.warn("Failed to stop Vert.x server", e); + } + context.stop(); + } + } + + @Override + protected void startCamelContext() { + } +} diff --git a/dsl/camel-kotlin-api/src/generated/kotlin/org/apache/camel/kotlin/components/VertxWebsocketUriDsl.kt b/dsl/camel-kotlin-api/src/generated/kotlin/org/apache/camel/kotlin/components/VertxWebsocketUriDsl.kt index 7d283d96e1e..44b06090c03 100644 --- a/dsl/camel-kotlin-api/src/generated/kotlin/org/apache/camel/kotlin/components/VertxWebsocketUriDsl.kt +++ b/dsl/camel-kotlin-api/src/generated/kotlin/org/apache/camel/kotlin/components/VertxWebsocketUriDsl.kt @@ -132,6 +132,14 @@ public class VertxWebsocketUriDsl( it.property("fireWebSocketConnectionEvents", fireWebSocketConnectionEvents.toString()) } + /** + * Headers to send in the HTTP handshake request. When the endpoint is a consumer, it only works + * when it consumes a remote host as a client (i.e. consumeAsClient is true). + */ + public fun handshakeHeaders(handshakeHeaders: String) { + it.property("handshakeHeaders", handshakeHeaders) + } + /** * When consumeAsClient is set to true this sets the maximum number of allowed reconnection * attempts to a previously closed WebSocket. A value of 0 (the default) will attempt to reconnect