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 413a482  [CAMEL-16738] [CAMEL-16739] subprotocol & relative path 
support for camel-websocket (#5819)
413a482 is described below

commit 413a4826577723f07a69686f2fa6f93b569437c4
Author: Paul Galbraith <[email protected]>
AuthorDate: Fri Jul 9 03:26:27 2021 -0400

    [CAMEL-16738] [CAMEL-16739] subprotocol & relative path support for 
camel-websocket (#5819)
    
    * [CAMEL-16738] camel-websocket now supports websocket subprotocols
    
    * [CAMEL-16739] camel-websocket now adds header with URL relative path
---
 .../camel/catalog/docs/websocket-component.adoc    |  29 ++++-
 .../websocket/WebsocketComponentConfigurer.java    |   3 +
 .../websocket/WebsocketEndpointConfigurer.java     |   3 +
 .../websocket/WebsocketEndpointUriFactory.java     |   3 +-
 .../camel/component/websocket/websocket.json       |   2 +
 .../src/main/docs/websocket-component.adoc         |  29 ++++-
 .../component/websocket/DefaultWebsocket.java      |  16 ++-
 .../websocket/DefaultWebsocketFactory.java         |  10 +-
 .../component/websocket/WebSocketFactory.java      |   8 +-
 .../component/websocket/WebsocketComponent.java    |  28 ++++-
 .../websocket/WebsocketComponentServlet.java       | 100 ++++++++++++---
 .../component/websocket/WebsocketConstants.java    |   2 +
 .../component/websocket/WebsocketConsumer.java     |  16 ++-
 .../component/websocket/WebsocketEndpoint.java     |  25 ++++
 .../component/websocket/DefaultWebsocketTest.java  |   4 +-
 .../websocket/WebsocketComponentServletTest.java   |  11 +-
 .../component/websocket/WebsocketConsumerTest.java |   6 +-
 .../websocket/WebsocketRelativePathTest.java       |  85 +++++++++++++
 .../WebsocketSubprotocolComponentTest.java         | 117 +++++++++++++++++
 .../WebsocketSubprotocolEndpointTest.java          | 107 ++++++++++++++++
 .../WebsocketSubprotocolNegotiationTest.java       | 138 +++++++++++++++++++++
 .../dsl/WebsocketComponentBuilderFactory.java      |  25 ++++
 .../dsl/WebsocketEndpointBuilderFactory.java       |  23 ++++
 .../modules/ROOT/pages/websocket-component.adoc    |  29 ++++-
 24 files changed, 764 insertions(+), 55 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/websocket-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/websocket-component.adoc
index 86a443d..9ba6937 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/websocket-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/websocket-component.adoc
@@ -37,7 +37,7 @@ You can append query options to the URI in the following 
format,
 
 
 // component options: START
-The Jetty Websocket component supports 15 options, which are listed below.
+The Jetty Websocket component supports 16 options, which are listed below.
 
 
 
@@ -53,6 +53,7 @@ The Jetty Websocket component supports 15 options, which are 
listed below.
 | *enableJmx* (advanced) | If this option is true, Jetty JMX support will be 
enabled for this endpoint. See Jetty JMX support for more details. | false | 
boolean
 | *maxThreads* (advanced) | To set a value for maximum number of threads in 
server thread pool. MaxThreads/minThreads or threadPool fields are required due 
to switch to Jetty9. The default values for maxThreads is 1 2 noCores. |  | 
Integer
 | *minThreads* (advanced) | To set a value for minimum number of threads in 
server thread pool. MaxThreads/minThreads or threadPool fields are required due 
to switch to Jetty9. The default values for minThreads is 1. |  | Integer
+| *subprotocol* (advanced) | This is a comma-separated list of subprotocols 
that are supported by the application. The list is in priority order. The first 
subprotocol on this list that is proposed by the client is the one that will be 
accepted. If no subprotocol on this list is proposed by the client, then the 
websocket connection is refused. The special value 'any' means that any 
subprotocol is acceptable. 'any' can be used on its own, or as a failsafe at 
the end of a list of more spec [...]
 | *threadPool* (advanced) | To use a custom thread pool for the server. 
MaxThreads/minThreads or threadPool fields are required due to switch to 
Jetty9. |  | ThreadPool
 | *sslContextParameters* (security) | To configure security using 
SSLContextParameters |  | SSLContextParameters
 | *sslKeyPassword* (security) | The password for the keystore when using SSL. 
|  | String
@@ -92,7 +93,7 @@ with the following path and query parameters:
 |===
 
 
-=== Query Parameters (18 parameters):
+=== Query Parameters (19 parameters):
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
@@ -102,6 +103,7 @@ with the following path and query parameters:
 | *bridgeErrorHandler* (consumer) | Allows for bridging the consumer to the 
Camel routing Error Handler, which mean any exceptions occurred while the 
consumer is trying to pickup incoming messages, or the likes, will now be 
processed as a message and handled by the routing Error Handler. By default the 
consumer will use the org.apache.camel.spi.ExceptionHandler to deal with 
exceptions, that will be logged at WARN or ERROR level and ignored. | false | 
boolean
 | *sessionSupport* (consumer) | Whether to enable session support which 
enables HttpSession for each http request. | false | boolean
 | *staticResources* (consumer) | Set a resource path for static resources 
(such as .html files etc). The resources can be loaded from classpath, if you 
prefix with classpath:, otherwise the resources is loaded from file system or 
from JAR files. For example to load from root classpath use classpath:., or 
classpath:WEB-INF/static If not configured (eg null) then no static resource is 
in use. |  | String
+| *subprotocol* (consumer) | This is a comma-separated list of subprotocols 
that are supported by the application. The list is in priority order. The first 
subprotocol on this list that is proposed by the client is the one that will be 
accepted. If no subprotocol on this list is proposed by the client, then the 
websocket connection is refused. The special value 'any' means that any 
subprotocol is acceptable. 'any' can be used on its own, or as a failsafe at 
the end of a list of more spec [...]
 | *exceptionHandler* (consumer) | To let the consumer use a custom 
ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this 
option is not in use. By default the consumer will deal with exceptions, that 
will be logged at WARN or ERROR level and ignored. |  | ExceptionHandler
 | *exchangePattern* (consumer) | Sets the exchange pattern when the consumer 
creates an exchange. There are 3 enums and the value can be one of: InOnly, 
InOut, InOptionalOut |  | ExchangePattern
 | *lazyStartProducer* (producer) | 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 producer may otherwise fail during 
starting and cause the route to fail being started. By deferring this startup 
to be lazy then the startup failure can be handled during routing messages via 
Camel's routing error handlers. Beware that when the first message is processed 
then creating and [...]
@@ -126,18 +128,35 @@ with the following path and query parameters:
 
 == Message Headers
 
-The WebSocket component uses 2 headers to indicate to either send
-messages back to a single/current client, or to all clients.
+The WebSocket component uses headers to provide information about incoming 
messages from consumer endpoints, or as processing instructions for producer 
endpoints sending outgoing messages.
 
+=== Headers from Consumers
 [width="100%",cols="10%,90%",options="header",]
 |=======================================================================
+|Header Name |Description
+
+|`WebsocketConstants.CONNECTION_KEY` |Connection key identifying an individual 
client connection.  You can save this and specify it again when routing to a 
producer endpoing in order to direct messages to a specific connected client.
+
+|`WebsocketConstants.REMOTE_ADDRESS` |Remote address of the websocket session.
+
+|`WebsocketConstants.SUBPROTOCOL` |If a specific subprotocol was negotiated, 
it will be specfied in this header.  Note that if you specify the "any" 
subprotocol to be supported, and a client requests a specific subprotocol, the 
connection will be accepted without a specific subprotocol being used.  You 
need to specifically support a given protocol by name if you want it returned 
to the client and to show up in the message header.
+
+|`WebsocketConstants.RELATIVE_PATH` |If you specify a wildcard URI path for an 
endpoint, and a websocket client connects to that websocket endpoing, the 
relative path that the client specified will be provided in this header.
+
+For example, if you specified `websocket://0.0.0.0:80/api/*` as your endpoint 
URI, and a client connects to the server at 
`ws://host.com/api/specialized/apipath` then `specialized/apipath` is provided 
in the relative path header of all messages from that client.
+
+|=======================================================================
+
+=== Headers for Producers
+[width="100%",cols="10%,90%",options="header",]
+|=======================================================================
+|Header Name |Description
 
 |`WebsocketConstants.SEND_TO_ALL` |Sends the message to all clients which are 
currently connected. You can
 use the `sendToAll` option on the endpoint instead of using this header.
 
 |`WebsocketConstants.CONNECTION_KEY` |Sends the message to the client with the 
given connection key.
 
-|`WebsocketConstants.REMOTE_ADDRESS` |Remote address of the websocket session.
 |=======================================================================
 
 == Usage
diff --git 
a/components/camel-websocket/src/generated/java/org/apache/camel/component/websocket/WebsocketComponentConfigurer.java
 
b/components/camel-websocket/src/generated/java/org/apache/camel/component/websocket/WebsocketComponentConfigurer.java
index d75b61a..fe36fd6 100644
--- 
a/components/camel-websocket/src/generated/java/org/apache/camel/component/websocket/WebsocketComponentConfigurer.java
+++ 
b/components/camel-websocket/src/generated/java/org/apache/camel/component/websocket/WebsocketComponentConfigurer.java
@@ -45,6 +45,7 @@ public class WebsocketComponentConfigurer extends 
PropertyConfigurerSupport impl
         case "sslPassword": target.setSslPassword(property(camelContext, 
java.lang.String.class, value)); return true;
         case "staticresources":
         case "staticResources": 
target.setStaticResources(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "subprotocol": target.setSubprotocol(property(camelContext, 
java.lang.String.class, value)); return true;
         case "threadpool":
         case "threadPool": target.setThreadPool(property(camelContext, 
org.eclipse.jetty.util.thread.ThreadPool.class, value)); return true;
         case "useglobalsslcontextparameters":
@@ -80,6 +81,7 @@ public class WebsocketComponentConfigurer extends 
PropertyConfigurerSupport impl
         case "sslPassword": return java.lang.String.class;
         case "staticresources":
         case "staticResources": return java.lang.String.class;
+        case "subprotocol": return java.lang.String.class;
         case "threadpool":
         case "threadPool": return 
org.eclipse.jetty.util.thread.ThreadPool.class;
         case "useglobalsslcontextparameters":
@@ -116,6 +118,7 @@ public class WebsocketComponentConfigurer extends 
PropertyConfigurerSupport impl
         case "sslPassword": return target.getSslPassword();
         case "staticresources":
         case "staticResources": return target.getStaticResources();
+        case "subprotocol": return target.getSubprotocol();
         case "threadpool":
         case "threadPool": return target.getThreadPool();
         case "useglobalsslcontextparameters":
diff --git 
a/components/camel-websocket/src/generated/java/org/apache/camel/component/websocket/WebsocketEndpointConfigurer.java
 
b/components/camel-websocket/src/generated/java/org/apache/camel/component/websocket/WebsocketEndpointConfigurer.java
index 8babd92..42b42ef 100644
--- 
a/components/camel-websocket/src/generated/java/org/apache/camel/component/websocket/WebsocketEndpointConfigurer.java
+++ 
b/components/camel-websocket/src/generated/java/org/apache/camel/component/websocket/WebsocketEndpointConfigurer.java
@@ -57,6 +57,7 @@ public class WebsocketEndpointConfigurer extends 
PropertyConfigurerSupport imple
         case "sslContextParameters": 
target.setSslContextParameters(property(camelContext, 
org.apache.camel.support.jsse.SSLContextParameters.class, value)); return true;
         case "staticresources":
         case "staticResources": 
target.setStaticResources(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "subprotocol": target.setSubprotocol(property(camelContext, 
java.lang.String.class, value)); return true;
         default: return false;
         }
     }
@@ -100,6 +101,7 @@ public class WebsocketEndpointConfigurer extends 
PropertyConfigurerSupport imple
         case "sslContextParameters": return 
org.apache.camel.support.jsse.SSLContextParameters.class;
         case "staticresources":
         case "staticResources": return java.lang.String.class;
+        case "subprotocol": return java.lang.String.class;
         default: return null;
         }
     }
@@ -144,6 +146,7 @@ public class WebsocketEndpointConfigurer extends 
PropertyConfigurerSupport imple
         case "sslContextParameters": return target.getSslContextParameters();
         case "staticresources":
         case "staticResources": return target.getStaticResources();
+        case "subprotocol": return target.getSubprotocol();
         default: return null;
         }
     }
diff --git 
a/components/camel-websocket/src/generated/java/org/apache/camel/component/websocket/WebsocketEndpointUriFactory.java
 
b/components/camel-websocket/src/generated/java/org/apache/camel/component/websocket/WebsocketEndpointUriFactory.java
index 28b6633..bd6e9c5 100644
--- 
a/components/camel-websocket/src/generated/java/org/apache/camel/component/websocket/WebsocketEndpointUriFactory.java
+++ 
b/components/camel-websocket/src/generated/java/org/apache/camel/component/websocket/WebsocketEndpointUriFactory.java
@@ -20,7 +20,7 @@ public class WebsocketEndpointUriFactory extends 
org.apache.camel.support.compon
     private static final Set<String> PROPERTY_NAMES;
     private static final Set<String> SECRET_PROPERTY_NAMES;
     static {
-        Set<String> props = new HashSet<>(21);
+        Set<String> props = new HashSet<>(22);
         props.add("sendTimeout");
         props.add("minVersion");
         props.add("sendToAll");
@@ -32,6 +32,7 @@ public class WebsocketEndpointUriFactory extends 
org.apache.camel.support.compon
         props.add("sessionSupport");
         props.add("staticResources");
         props.add("filterPath");
+        props.add("subprotocol");
         props.add("lazyStartProducer");
         props.add("bridgeErrorHandler");
         props.add("allowedOrigins");
diff --git 
a/components/camel-websocket/src/generated/resources/org/apache/camel/component/websocket/websocket.json
 
b/components/camel-websocket/src/generated/resources/org/apache/camel/component/websocket/websocket.json
index b3099c1..0798a2d 100644
--- 
a/components/camel-websocket/src/generated/resources/org/apache/camel/component/websocket/websocket.json
+++ 
b/components/camel-websocket/src/generated/resources/org/apache/camel/component/websocket/websocket.json
@@ -31,6 +31,7 @@
     "enableJmx": { "kind": "property", "displayName": "Enable Jmx", "group": 
"advanced", "label": "advanced", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "description": "If this option is true, Jetty JMX 
support will be enabled for this endpoint. See Jetty JMX support for more 
details." },
     "maxThreads": { "kind": "property", "displayName": "Max Threads", "group": 
"advanced", "label": "advanced", "required": false, "type": "integer", 
"javaType": "java.lang.Integer", "deprecated": false, "autowired": false, 
"secret": false, "description": "To set a value for maximum number of threads 
in server thread pool. MaxThreads\/minThreads or threadPool fields are required 
due to switch to Jetty9. The default values for maxThreads is 1 2 noCores." },
     "minThreads": { "kind": "property", "displayName": "Min Threads", "group": 
"advanced", "label": "advanced", "required": false, "type": "integer", 
"javaType": "java.lang.Integer", "deprecated": false, "autowired": false, 
"secret": false, "description": "To set a value for minimum number of threads 
in server thread pool. MaxThreads\/minThreads or threadPool fields are required 
due to switch to Jetty9. The default values for minThreads is 1." },
+    "subprotocol": { "kind": "property", "displayName": "Subprotocol", 
"group": "advanced", "label": "advanced", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "any", "description": "This is a 
comma-separated list of subprotocols that are supported by the application. The 
list is in priority order. The first subprotocol on this list that is proposed 
by the client is the one that will be accept [...]
     "threadPool": { "kind": "property", "displayName": "Thread Pool", "group": 
"advanced", "label": "advanced", "required": false, "type": "object", 
"javaType": "org.eclipse.jetty.util.thread.ThreadPool", "deprecated": false, 
"autowired": false, "secret": false, "description": "To use a custom thread 
pool for the server. MaxThreads\/minThreads or threadPool fields are required 
due to switch to Jetty9." },
     "sslContextParameters": { "kind": "property", "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, "description": "To configure security 
using SSLContextParameters" },
     "sslKeyPassword": { "kind": "property", "displayName": "Ssl Key Password", 
"group": "security", "label": "security", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": true, "description": "The password for the keystore when using SSL." 
},
@@ -46,6 +47,7 @@
     "bridgeErrorHandler": { "kind": "parameter", "displayName": "Bridge Error 
Handler", "group": "consumer", "label": "consumer", "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 occurred 
while the consumer is trying to pickup incoming messages, or the likes, will 
now be processed as a m [...]
     "sessionSupport": { "kind": "parameter", "displayName": "Session Support", 
"group": "consumer", "label": "consumer", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "description": "Whether to enable session support 
which enables HttpSession for each http request." },
     "staticResources": { "kind": "parameter", "displayName": "Static 
Resources", "group": "consumer", "label": "consumer", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "Set a resource path for 
static resources (such as .html files etc). The resources can be loaded from 
classpath, if you prefix with classpath:, otherwise the resources is loaded 
from file system or from JAR files. For example t [...]
+    "subprotocol": { "kind": "parameter", "displayName": "Subprotocol", 
"group": "consumer", "label": "consumer", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "any", "description": "This is a 
comma-separated list of subprotocols that are supported by the application. The 
list is in priority order. The first subprotocol on this list that is proposed 
by the client is the one that will be accep [...]
     "exceptionHandler": { "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 default the 
con [...]
     "exchangePattern": { "kind": "parameter", "displayName": "Exchange 
Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", 
"required": false, "type": "object", "javaType": 
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut", 
"InOptionalOut" ], "deprecated": false, "autowired": false, "secret": false, 
"description": "Sets the exchange pattern when the consumer creates an 
exchange." },
     "lazyStartProducer": { "kind": "parameter", "displayName": "Lazy Start 
Producer", "group": "producer", "label": "producer", "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 producer 
may otherwise fail during sta [...]
diff --git a/components/camel-websocket/src/main/docs/websocket-component.adoc 
b/components/camel-websocket/src/main/docs/websocket-component.adoc
index 86a443d..9ba6937 100644
--- a/components/camel-websocket/src/main/docs/websocket-component.adoc
+++ b/components/camel-websocket/src/main/docs/websocket-component.adoc
@@ -37,7 +37,7 @@ You can append query options to the URI in the following 
format,
 
 
 // component options: START
-The Jetty Websocket component supports 15 options, which are listed below.
+The Jetty Websocket component supports 16 options, which are listed below.
 
 
 
@@ -53,6 +53,7 @@ The Jetty Websocket component supports 15 options, which are 
listed below.
 | *enableJmx* (advanced) | If this option is true, Jetty JMX support will be 
enabled for this endpoint. See Jetty JMX support for more details. | false | 
boolean
 | *maxThreads* (advanced) | To set a value for maximum number of threads in 
server thread pool. MaxThreads/minThreads or threadPool fields are required due 
to switch to Jetty9. The default values for maxThreads is 1 2 noCores. |  | 
Integer
 | *minThreads* (advanced) | To set a value for minimum number of threads in 
server thread pool. MaxThreads/minThreads or threadPool fields are required due 
to switch to Jetty9. The default values for minThreads is 1. |  | Integer
+| *subprotocol* (advanced) | This is a comma-separated list of subprotocols 
that are supported by the application. The list is in priority order. The first 
subprotocol on this list that is proposed by the client is the one that will be 
accepted. If no subprotocol on this list is proposed by the client, then the 
websocket connection is refused. The special value 'any' means that any 
subprotocol is acceptable. 'any' can be used on its own, or as a failsafe at 
the end of a list of more spec [...]
 | *threadPool* (advanced) | To use a custom thread pool for the server. 
MaxThreads/minThreads or threadPool fields are required due to switch to 
Jetty9. |  | ThreadPool
 | *sslContextParameters* (security) | To configure security using 
SSLContextParameters |  | SSLContextParameters
 | *sslKeyPassword* (security) | The password for the keystore when using SSL. 
|  | String
@@ -92,7 +93,7 @@ with the following path and query parameters:
 |===
 
 
-=== Query Parameters (18 parameters):
+=== Query Parameters (19 parameters):
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
@@ -102,6 +103,7 @@ with the following path and query parameters:
 | *bridgeErrorHandler* (consumer) | Allows for bridging the consumer to the 
Camel routing Error Handler, which mean any exceptions occurred while the 
consumer is trying to pickup incoming messages, or the likes, will now be 
processed as a message and handled by the routing Error Handler. By default the 
consumer will use the org.apache.camel.spi.ExceptionHandler to deal with 
exceptions, that will be logged at WARN or ERROR level and ignored. | false | 
boolean
 | *sessionSupport* (consumer) | Whether to enable session support which 
enables HttpSession for each http request. | false | boolean
 | *staticResources* (consumer) | Set a resource path for static resources 
(such as .html files etc). The resources can be loaded from classpath, if you 
prefix with classpath:, otherwise the resources is loaded from file system or 
from JAR files. For example to load from root classpath use classpath:., or 
classpath:WEB-INF/static If not configured (eg null) then no static resource is 
in use. |  | String
+| *subprotocol* (consumer) | This is a comma-separated list of subprotocols 
that are supported by the application. The list is in priority order. The first 
subprotocol on this list that is proposed by the client is the one that will be 
accepted. If no subprotocol on this list is proposed by the client, then the 
websocket connection is refused. The special value 'any' means that any 
subprotocol is acceptable. 'any' can be used on its own, or as a failsafe at 
the end of a list of more spec [...]
 | *exceptionHandler* (consumer) | To let the consumer use a custom 
ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this 
option is not in use. By default the consumer will deal with exceptions, that 
will be logged at WARN or ERROR level and ignored. |  | ExceptionHandler
 | *exchangePattern* (consumer) | Sets the exchange pattern when the consumer 
creates an exchange. There are 3 enums and the value can be one of: InOnly, 
InOut, InOptionalOut |  | ExchangePattern
 | *lazyStartProducer* (producer) | 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 producer may otherwise fail during 
starting and cause the route to fail being started. By deferring this startup 
to be lazy then the startup failure can be handled during routing messages via 
Camel's routing error handlers. Beware that when the first message is processed 
then creating and [...]
@@ -126,18 +128,35 @@ with the following path and query parameters:
 
 == Message Headers
 
-The WebSocket component uses 2 headers to indicate to either send
-messages back to a single/current client, or to all clients.
+The WebSocket component uses headers to provide information about incoming 
messages from consumer endpoints, or as processing instructions for producer 
endpoints sending outgoing messages.
 
+=== Headers from Consumers
 [width="100%",cols="10%,90%",options="header",]
 |=======================================================================
+|Header Name |Description
+
+|`WebsocketConstants.CONNECTION_KEY` |Connection key identifying an individual 
client connection.  You can save this and specify it again when routing to a 
producer endpoing in order to direct messages to a specific connected client.
+
+|`WebsocketConstants.REMOTE_ADDRESS` |Remote address of the websocket session.
+
+|`WebsocketConstants.SUBPROTOCOL` |If a specific subprotocol was negotiated, 
it will be specfied in this header.  Note that if you specify the "any" 
subprotocol to be supported, and a client requests a specific subprotocol, the 
connection will be accepted without a specific subprotocol being used.  You 
need to specifically support a given protocol by name if you want it returned 
to the client and to show up in the message header.
+
+|`WebsocketConstants.RELATIVE_PATH` |If you specify a wildcard URI path for an 
endpoint, and a websocket client connects to that websocket endpoing, the 
relative path that the client specified will be provided in this header.
+
+For example, if you specified `websocket://0.0.0.0:80/api/*` as your endpoint 
URI, and a client connects to the server at 
`ws://host.com/api/specialized/apipath` then `specialized/apipath` is provided 
in the relative path header of all messages from that client.
+
+|=======================================================================
+
+=== Headers for Producers
+[width="100%",cols="10%,90%",options="header",]
+|=======================================================================
+|Header Name |Description
 
 |`WebsocketConstants.SEND_TO_ALL` |Sends the message to all clients which are 
currently connected. You can
 use the `sendToAll` option on the endpoint instead of using this header.
 
 |`WebsocketConstants.CONNECTION_KEY` |Sends the message to the client with the 
given connection key.
 
-|`WebsocketConstants.REMOTE_ADDRESS` |Remote address of the websocket session.
 |=======================================================================
 
 == Usage
diff --git 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/DefaultWebsocket.java
 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/DefaultWebsocket.java
index 1a4f470..246fd3b 100644
--- 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/DefaultWebsocket.java
+++ 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/DefaultWebsocket.java
@@ -38,11 +38,23 @@ public class DefaultWebsocket implements Serializable {
     private Session session;
     private String connectionKey;
     private String pathSpec;
+    private String subprotocol;
+    private String relativePath;
 
     public DefaultWebsocket(NodeSynchronization sync, String pathSpec, 
WebsocketConsumer consumer) {
+        this(sync, pathSpec, consumer, null, null);
+    }
+
+    public DefaultWebsocket(NodeSynchronization sync,
+                            String pathSpec,
+                            WebsocketConsumer consumer,
+                            String subprotocol,
+                            String relativePath) {
         this.sync = sync;
         this.consumer = consumer;
         this.pathSpec = pathSpec;
+        this.subprotocol = subprotocol;
+        this.relativePath = relativePath;
     }
 
     @OnWebSocketClose
@@ -63,7 +75,7 @@ public class DefaultWebsocket implements Serializable {
     public void onMessage(String message) {
         LOG.debug("onMessage: {}", message);
         if (this.consumer != null) {
-            this.consumer.sendMessage(this.connectionKey, message, 
getRemoteAddress());
+            this.consumer.sendMessage(this.connectionKey, message, 
getRemoteAddress(), subprotocol, relativePath);
         } else {
             LOG.debug("No consumer to handle message received: {}", message);
         }
@@ -75,7 +87,7 @@ public class DefaultWebsocket implements Serializable {
         if (this.consumer != null) {
             byte[] message = new byte[length];
             System.arraycopy(data, offset, message, 0, length);
-            this.consumer.sendMessage(this.connectionKey, message, 
getRemoteAddress());
+            this.consumer.sendMessage(this.connectionKey, message, 
getRemoteAddress(), subprotocol, relativePath);
         } else {
             LOG.debug("No consumer to handle message received: byte[]");
         }
diff --git 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/DefaultWebsocketFactory.java
 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/DefaultWebsocketFactory.java
index 04bb137..aa8cb1b 100644
--- 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/DefaultWebsocketFactory.java
+++ 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/DefaultWebsocketFactory.java
@@ -25,8 +25,12 @@ public class DefaultWebsocketFactory implements 
WebSocketFactory {
 
     @Override
     public DefaultWebsocket newInstance(
-            ServletUpgradeRequest request, String protocol, String pathSpec, 
NodeSynchronization sync,
-            WebsocketConsumer consumer) {
-        return new DefaultWebsocket(sync, pathSpec, consumer);
+            ServletUpgradeRequest request,
+            String pathSpec,
+            NodeSynchronization sync,
+            WebsocketConsumer consumer,
+            String subprotocol,
+            String relativePath) {
+        return new DefaultWebsocket(sync, pathSpec, consumer, subprotocol, 
relativePath);
     }
 }
diff --git 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebSocketFactory.java
 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebSocketFactory.java
index df134d2..e377bf8 100644
--- 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebSocketFactory.java
+++ 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebSocketFactory.java
@@ -24,7 +24,11 @@ import 
org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
 public interface WebSocketFactory {
 
     DefaultWebsocket newInstance(
-            ServletUpgradeRequest request, String protocol, String pathSpec, 
NodeSynchronization sync,
-            WebsocketConsumer consumer);
+            ServletUpgradeRequest request,
+            String pathSpec,
+            NodeSynchronization sync,
+            WebsocketConsumer consumer,
+            String subprotocol,
+            String relativePath);
 
 }
diff --git 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketComponent.java
 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketComponent.java
index e3f7738..d54dd4d 100644
--- 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketComponent.java
+++ 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketComponent.java
@@ -92,6 +92,8 @@ public class WebsocketComponent extends DefaultComponent 
implements SSLContextPa
     protected String sslPassword;
     @Metadata(label = "security", secret = true)
     protected String sslKeystore;
+    @Metadata(label = "advanced", defaultValue = "any")
+    protected String subprotocol = "any";
 
     /**
      * Map for storing servlets. {@link WebsocketComponentServlet} is 
identified by pathSpec {@link String}.
@@ -130,7 +132,7 @@ public class WebsocketComponent extends DefaultComponent 
implements SSLContextPa
     public WebsocketComponent() {
         if (this.socketFactory == null) {
             this.socketFactory = new HashMap<>();
-            this.socketFactory.put("default", new DefaultWebsocketFactory());
+            
this.socketFactory.put(WebsocketComponentServlet.UNSPECIFIED_SUBPROTOCOL, new 
DefaultWebsocketFactory());
         }
     }
 
@@ -320,6 +322,7 @@ public class WebsocketComponent extends DefaultComponent 
implements SSLContextPa
         endpoint.setSslContextParameters(sslContextParameters);
         endpoint.setPort(port);
         endpoint.setHost(host);
+        endpoint.setSubprotocol(subprotocol);
 
         setProperties(endpoint, parameters);
         return endpoint;
@@ -763,6 +766,29 @@ public class WebsocketComponent extends DefaultComponent 
implements SSLContextPa
         this.useGlobalSslContextParameters = useGlobalSslContextParameters;
     }
 
+    /**
+     * See {@link #getSubprotocol()}
+     */
+    public String getSubprotocol() {
+        return subprotocol;
+    }
+
+    /**
+     * <p>
+     * This is a comma-separated list of subprotocols that are supported by 
the application. The list is in priority
+     * order. The first subprotocol on this list that is proposed by the 
client is the one that will be accepted. If no
+     * subprotocol on this list is proposed by the client, then the websocket 
connection is refused. The special value
+     * 'any' means that any subprotocol is acceptable. 'any' can be used on 
its own, or as a failsafe at the end of a
+     * list of more specific protocols. 'any' will also match the case where 
no subprotocol is proposed by the client.
+     * </p>
+     *
+     * @see <a 
href="https://www.iana.org/assignments/websocket/websocket.xml#subprotocol-name";>The
 official IANA list
+     *      of registered websocket subprotocols<a/>
+     */
+    public void setSubprotocol(String subprotocol) {
+        this.subprotocol = subprotocol;
+    }
+
     public Map<String, WebSocketFactory> getSocketFactory() {
         return socketFactory;
     }
diff --git 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketComponentServlet.java
 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketComponentServlet.java
index d7dd128..042c827 100644
--- 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketComponentServlet.java
+++ 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketComponentServlet.java
@@ -16,19 +16,24 @@
  */
 package org.apache.camel.component.websocket;
 
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Collectors;
 
 import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
 import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
-import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
 import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
 import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static 
org.eclipse.jetty.websocket.api.WebSocketConstants.SEC_WEBSOCKET_PROTOCOL;
+
 public class WebsocketComponentServlet extends WebSocketServlet {
+    public static final String UNSPECIFIED_SUBPROTOCOL = "default";
+    public static final String ANY_SUBPROTOCOL = "any";
+
     private static final long serialVersionUID = 1L;
     private final Logger log = LoggerFactory.getLogger(getClass());
 
@@ -63,19 +68,81 @@ public class WebsocketComponentServlet extends 
WebSocketServlet {
         consumers.remove(consumer.getPath());
     }
 
-    public DefaultWebsocket doWebSocketConnect(ServletUpgradeRequest request, 
String protocol) {
-        String protocolKey = protocol;
+    public DefaultWebsocket doWebSocketConnect(ServletUpgradeRequest request, 
ServletUpgradeResponse resp) {
+        String subprotocol = negotiateSubprotocol(request, consumer);
+        if (subprotocol == null) {
+            return null;       // no agreeable subprotocol was found, reject 
the connection
+        }
+
+        // now select the WebSocketFactory implementation based upon the 
agreed subprotocol
+        final WebSocketFactory factory;
+        if (socketFactory.containsKey(subprotocol)) {
+            factory = socketFactory.get(subprotocol);
+        } else {
+            log.debug("No factory found for the socket subprotocol: {}, using 
default implementation", subprotocol);
+            factory = socketFactory.get(UNSPECIFIED_SUBPROTOCOL);
+        }
+
+        if (subprotocol.equals(UNSPECIFIED_SUBPROTOCOL)) {
+            subprotocol = null;             // application clients should just 
see null if no subprotocol was actually negotiated
+        } else {
+            resp.setHeader(SEC_WEBSOCKET_PROTOCOL, subprotocol);    // confirm 
selected subprotocol to client
+        }
+
+        // if the websocket component was configured with a wildcard path, 
determine the releative path used by this client
+        final String relativePath;
+        if (pathSpec != null && pathSpec.endsWith("*")) {
+            final String prefix = pathSpec.substring(0, pathSpec.length() - 1);
+            final String reqPath = request.getRequestPath();
+            if (reqPath.startsWith(prefix) && reqPath.length() > 
prefix.length()) {
+                relativePath = reqPath.substring(prefix.length());
+            } else {
+                relativePath = null;
+            }
+        } else {
+            relativePath = null;
+        }
 
-        if (protocol == null || !socketFactory.containsKey(protocol)) {
-            log.debug("No factory found for the socket protocol: {}, returning 
default implementation", protocol);
-            protocolKey = "default";
+        return factory.newInstance(request, pathSpec, sync, consumer, 
subprotocol, relativePath);
+    }
+
+    private String negotiateSubprotocol(ServletUpgradeRequest request, 
WebsocketConsumer consumer) {
+        final String[] supportedSubprotocols = Optional.ofNullable(consumer)
+                .map(WebsocketConsumer::getEndpoint)
+                .map(WebsocketEndpoint::getSubprotocol)
+                .map(String::trim)
+                .filter(value -> !value.isEmpty())
+                .map(subprotocols -> subprotocols.split(","))
+                .orElse(new String[] { ANY_SUBPROTOCOL });         // default: 
all subprotocols are supported
+
+        final List<String> proposedSubprotocols = 
Optional.ofNullable(request.getHeaders(SEC_WEBSOCKET_PROTOCOL))
+                .map(list -> list.stream()
+                        .map(String::trim)
+                        .filter(value -> !value.isEmpty())
+                        .map(header -> header.split(","))
+                        .map(array -> Arrays.stream(array)
+                                .map(String::trim)
+                                .filter(value -> !value.isEmpty())
+                                .collect(Collectors.toList()))
+                        .flatMap(Collection::stream)
+                        .collect(Collectors.toList()))
+                .orElse(Collections.emptyList());               // default: no 
subprotocols are proposed
+
+        for (String s : supportedSubprotocols) {
+            final String supportedSubprotocol = s.trim();
+            if (supportedSubprotocol.equalsIgnoreCase(ANY_SUBPROTOCOL)) {
+                return UNSPECIFIED_SUBPROTOCOL;             // agree to use an 
unspecified subprotocol
+            } else {
+                if (proposedSubprotocols.contains(supportedSubprotocol)) {
+                    return supportedSubprotocol;                // accept this 
subprotocol
+                }
+            }
         }
 
-        WebSocketFactory factory = socketFactory.get(protocolKey);
-        return factory.newInstance(request, protocolKey,
-                (consumer != null && consumer.getEndpoint() != null)
-                        ? 
WebsocketComponent.createPathSpec(consumer.getEndpoint().getResourceUri()) : 
null,
-                sync, consumer);
+        log.debug("no agreeable subprotocol could be negotiated, server 
supports {} but client proposes {}",
+                supportedSubprotocols,
+                proposedSubprotocols);
+        return null;
     }
 
     public Map<String, WebSocketFactory> getSocketFactory() {
@@ -88,13 +155,6 @@ public class WebsocketComponentServlet extends 
WebSocketServlet {
 
     @Override
     public void configure(WebSocketServletFactory factory) {
-        factory.setCreator(new WebSocketCreator() {
-            @Override
-            public Object createWebSocket(ServletUpgradeRequest req, 
ServletUpgradeResponse resp) {
-                String protocolKey = "default";
-                WebSocketFactory factory = socketFactory.get(protocolKey);
-                return factory.newInstance(req, protocolKey, pathSpec, sync, 
consumer);
-            }
-        });
+        factory.setCreator(this::doWebSocketConnect);
     }
 }
diff --git 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketConstants.java
 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketConstants.java
index 9ff13f2..08473dd 100644
--- 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketConstants.java
+++ 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketConstants.java
@@ -24,6 +24,8 @@ public final class WebsocketConstants {
     public static final String CONNECTION_KEY = "websocket.connectionKey";
     public static final String SEND_TO_ALL = "websocket.sendToAll";
     public static final String REMOTE_ADDRESS = "websocket.remoteAddress";
+    public static final String SUBPROTOCOL = "websocket.subprotocol";
+    public static final String RELATIVE_PATH = "websocket.relativePath";
 
     public static final String WS_PROTOCOL = "ws";
     public static final String WSS_PROTOCOL = "wss";
diff --git 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketConsumer.java
 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketConsumer.java
index 4a7f1a4..2c431a3 100644
--- 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketConsumer.java
+++ 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketConsumer.java
@@ -56,20 +56,30 @@ public class WebsocketConsumer extends DefaultConsumer 
implements WebsocketProdu
     public void sendMessage(
             final String connectionKey,
             final String message,
-            final InetSocketAddress remote) {
-        sendMessage(connectionKey, (Object) message, remote);
+            final InetSocketAddress remote,
+            final String subprotocol,
+            final String relativePath) {
+        sendMessage(connectionKey, (Object) message, remote, subprotocol, 
relativePath);
     }
 
     public void sendMessage(
             final String connectionKey,
             final Object message,
-            final InetSocketAddress remote) {
+            final InetSocketAddress remote,
+            final String subprotocol,
+            final String relativePath) {
 
         final Exchange exchange = createExchange(true);
 
         // set header and body
         exchange.getIn().setHeader(WebsocketConstants.REMOTE_ADDRESS, remote);
         exchange.getIn().setHeader(WebsocketConstants.CONNECTION_KEY, 
connectionKey);
+        if (subprotocol != null) {
+            exchange.getIn().setHeader(WebsocketConstants.SUBPROTOCOL, 
subprotocol);
+        }
+        if (relativePath != null) {
+            exchange.getIn().setHeader(WebsocketConstants.RELATIVE_PATH, 
relativePath);
+        }
         exchange.getIn().setBody(message);
 
         // use default consumer callback
diff --git 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketEndpoint.java
 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketEndpoint.java
index 3661ef2..441eb76 100644
--- 
a/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketEndpoint.java
+++ 
b/components/camel-websocket/src/main/java/org/apache/camel/component/websocket/WebsocketEndpoint.java
@@ -81,6 +81,8 @@ public class WebsocketEndpoint extends DefaultEndpoint {
     private Integer maxBinaryMessageSize;
     @UriParam(label = "advanced", defaultValue = "13")
     private Integer minVersion;
+    @UriParam(label = "consumer", defaultValue = "any")
+    private String subprotocol;
 
     public WebsocketEndpoint(WebsocketComponent component, String uri, String 
resourceUri, Map<String, Object> parameters) {
         super(uri, component);
@@ -349,4 +351,27 @@ public class WebsocketEndpoint extends DefaultEndpoint {
     public void setResourceUri(String resourceUri) {
         this.resourceUri = resourceUri;
     }
+
+    /**
+     * See {@link #getSubprotocol()}
+     */
+    public String getSubprotocol() {
+        return subprotocol;
+    }
+
+    /**
+     * <p>
+     * This is a comma-separated list of subprotocols that are supported by 
the application. The list is in priority
+     * order. The first subprotocol on this list that is proposed by the 
client is the one that will be accepted. If no
+     * subprotocol on this list is proposed by the client, then the websocket 
connection is refused. The special value
+     * 'any' means that any subprotocol is acceptable. 'any' can be used on 
its own, or as a failsafe at the end of a
+     * list of more specific protocols. 'any' will also match the case where 
no subprotocol is proposed by the client.
+     * </p>
+     *
+     * @see <a 
href="https://www.iana.org/assignments/websocket/websocket.xml#subprotocol-name";>The
 official IANA list
+     *      of registered websocket subprotocols<a/>
+     */
+    public void setSubprotocol(String subprotocol) {
+        this.subprotocol = subprotocol;
+    }
 }
diff --git 
a/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/DefaultWebsocketTest.java
 
b/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/DefaultWebsocketTest.java
index a27ebfe..495de92 100644
--- 
a/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/DefaultWebsocketTest.java
+++ 
b/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/DefaultWebsocketTest.java
@@ -82,7 +82,7 @@ public class DefaultWebsocketTest {
         defaultWebsocket.setSession(session);
         defaultWebsocket.onMessage(MESSAGE);
         InOrder inOrder = inOrder(session, consumer, sync);
-        inOrder.verify(consumer, times(1)).sendMessage(CONNECTION_KEY, 
MESSAGE, ADDRESS);
+        inOrder.verify(consumer, times(1)).sendMessage(CONNECTION_KEY, 
MESSAGE, ADDRESS, null, null);
         inOrder.verifyNoMoreInteractions();
     }
 
@@ -92,7 +92,7 @@ public class DefaultWebsocketTest {
         defaultWebsocket.setConnectionKey(CONNECTION_KEY);
         defaultWebsocket.onMessage(MESSAGE);
         InOrder inOrder = inOrder(session, consumer, sync);
-        inOrder.verify(consumer, times(0)).sendMessage(CONNECTION_KEY, 
MESSAGE, ADDRESS);
+        inOrder.verify(consumer, times(0)).sendMessage(CONNECTION_KEY, 
MESSAGE, ADDRESS, null, null);
         inOrder.verifyNoMoreInteractions();
     }
 
diff --git 
a/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketComponentServletTest.java
 
b/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketComponentServletTest.java
index 8e41879..40a4da1 100644
--- 
a/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketComponentServletTest.java
+++ 
b/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketComponentServletTest.java
@@ -21,7 +21,9 @@ import java.util.HashMap;
 import java.util.Map;
 
 import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketConstants;
 import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.InOrder;
@@ -55,6 +57,8 @@ public class WebsocketComponentServletTest {
     private NodeSynchronization sync;
     @Mock
     private ServletUpgradeRequest request;
+    @Mock
+    private ServletUpgradeResponse response;
 
     private WebsocketComponentServlet websocketComponentServlet;
 
@@ -83,7 +87,7 @@ public class WebsocketComponentServletTest {
     @Test
     public void testDoWebSocketConnect() {
         websocketComponentServlet.setConsumer(consumer);
-        DefaultWebsocket webSocket = 
websocketComponentServlet.doWebSocketConnect(request, PROTOCOL);
+        DefaultWebsocket webSocket = 
websocketComponentServlet.doWebSocketConnect(request, response);
         assertNotNull(webSocket);
         assertEquals(DefaultWebsocket.class, webSocket.getClass());
         DefaultWebsocket defaultWebsocket = webSocket;
@@ -91,19 +95,20 @@ public class WebsocketComponentServletTest {
         defaultWebsocket.setSession(session);
         defaultWebsocket.onMessage(MESSAGE);
         InOrder inOrder = inOrder(consumer, sync, request);
-        inOrder.verify(consumer, times(1)).sendMessage(CONNECTION_KEY, 
MESSAGE, ADDRESS);
+        inOrder.verify(consumer, times(1)).sendMessage(CONNECTION_KEY, 
MESSAGE, ADDRESS, null, null);
         inOrder.verifyNoMoreInteractions();
     }
 
     @Test
     public void testDoWebSocketConnectConsumerIsNull() {
-        DefaultWebsocket webSocket = 
websocketComponentServlet.doWebSocketConnect(request, PROTOCOL);
+        DefaultWebsocket webSocket = 
websocketComponentServlet.doWebSocketConnect(request, response);
         assertNotNull(webSocket);
         assertEquals(DefaultWebsocket.class, webSocket.getClass());
         DefaultWebsocket defaultWebsocket = webSocket;
         defaultWebsocket.setConnectionKey(CONNECTION_KEY);
         defaultWebsocket.onMessage(MESSAGE);
         InOrder inOrder = inOrder(consumer, sync, request);
+        
inOrder.verify(request).getHeaders(WebSocketConstants.SEC_WEBSOCKET_PROTOCOL);
         inOrder.verifyNoMoreInteractions();
     }
 
diff --git 
a/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketConsumerTest.java
 
b/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketConsumerTest.java
index f52092f..6125a57 100644
--- 
a/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketConsumerTest.java
+++ 
b/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketConsumerTest.java
@@ -79,7 +79,7 @@ public class WebsocketConsumerTest {
     public void testSendExchange() throws Exception {
         when(exchange.getIn()).thenReturn(outMessage);
 
-        websocketConsumer.sendMessage(CONNECTION_KEY, MESSAGE, ADDRESS);
+        websocketConsumer.sendMessage(CONNECTION_KEY, MESSAGE, ADDRESS, null, 
null);
 
         InOrder inOrder = inOrder(endpoint, exceptionHandler, processor, 
exchange, outMessage);
         inOrder.verify(exchange, times(1)).getIn();
@@ -97,7 +97,7 @@ public class WebsocketConsumerTest {
         doThrow(exception).when(processor).process(exchange);
         when(exchange.getException()).thenReturn(exception);
 
-        websocketConsumer.sendMessage(CONNECTION_KEY, MESSAGE, ADDRESS);
+        websocketConsumer.sendMessage(CONNECTION_KEY, MESSAGE, ADDRESS, null, 
null);
 
         InOrder inOrder = inOrder(endpoint, exceptionHandler, processor, 
exchange, outMessage);
         inOrder.verify(exchange, times(1)).getIn();
@@ -116,7 +116,7 @@ public class WebsocketConsumerTest {
         doThrow(exception).when(processor).process(exchange);
         when(exchange.getException()).thenReturn(null);
 
-        websocketConsumer.sendMessage(CONNECTION_KEY, MESSAGE, ADDRESS);
+        websocketConsumer.sendMessage(CONNECTION_KEY, MESSAGE, ADDRESS, null, 
null);
 
         InOrder inOrder = inOrder(endpoint, exceptionHandler, processor, 
exchange, outMessage);
         inOrder.verify(exchange, times(1)).getIn();
diff --git 
a/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketRelativePathTest.java
 
b/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketRelativePathTest.java
new file mode 100644
index 0000000..b4ee10c
--- /dev/null
+++ 
b/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketRelativePathTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.websocket;
+
+import java.io.*;
+import java.net.*;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.AvailablePortFinder;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class WebsocketRelativePathTest extends CamelTestSupport {
+    @WebSocket
+    public static class TestWebSocket {
+        @OnWebSocketConnect
+        public void onConnect(Session session) throws IOException {
+            RemoteEndpoint endpoint = session.getRemote();
+            endpoint.sendString("Test Message");
+            session.close();
+        }
+    }
+
+    private int port;
+
+    @Override
+    @BeforeEach
+    public void setUp() throws Exception {
+        port = AvailablePortFinder.getNextAvailable();
+        super.setUp();
+    }
+
+    @Test
+    public void testRelativePathHeader() throws Exception {
+        URI uri = new URI("ws://localhost:" + port + "/test/relative/path");
+        TestWebSocket socket = new TestWebSocket();
+        WebSocketClient client = new WebSocketClient();
+        ClientUpgradeRequest request = new ClientUpgradeRequest();
+
+        MockEndpoint result = getMockEndpoint("mock:result");
+        result.expectedBodiesReceived("Test Message");
+        result.expectedHeaderReceived(WebsocketConstants.RELATIVE_PATH, 
"relative/path");
+
+        client.start();
+        client.connect(socket, uri, request).get(10, TimeUnit.SECONDS);
+        client.stop();
+
+        result.assertIsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            public void configure() {
+                from("websocket://localhost:" + port + "/test/*")
+                        .log(">>> Message received from WebSocket Client : 
${body}")
+                        .to("mock:result");
+            }
+        };
+    }
+
+}
diff --git 
a/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketSubprotocolComponentTest.java
 
b/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketSubprotocolComponentTest.java
new file mode 100644
index 0000000..15bfb1c
--- /dev/null
+++ 
b/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketSubprotocolComponentTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.websocket;
+
+import java.io.*;
+import java.net.*;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.AvailablePortFinder;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class WebsocketSubprotocolComponentTest extends CamelTestSupport {
+    @WebSocket
+    public static class TestWebSocket {
+        @OnWebSocketConnect
+        public void onConnect(Session session) throws IOException {
+            session.getRemote().sendString("This is not SOAP!");
+            session.close();
+        }
+    }
+
+    private int port;
+
+    @Override
+    @BeforeEach
+    public void setUp() throws Exception {
+        port = AvailablePortFinder.getNextAvailable();
+        super.setUp();
+    }
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+        WebsocketComponent soapServer = new WebsocketComponent();
+        soapServer.setSubprotocol("soap");
+        context.addComponent("soap", soapServer);
+        return context;
+    }
+
+    @Test
+    public void testSubprotocolAccepted() throws Exception {
+        URI uri = new URI("ws://localhost:" + port + "/test");
+        TestWebSocket socket = new TestWebSocket();
+        WebSocketClient client = new WebSocketClient();
+
+        ClientUpgradeRequest request = new ClientUpgradeRequest();
+        request.setSubProtocols("wamp", "chat,soap");       // three proposed 
subprotocols split across two headers
+
+        MockEndpoint result = getMockEndpoint("mock:result");
+        result.expectedBodiesReceived("This is not SOAP!");
+        result.expectedHeaderReceived(WebsocketConstants.SUBPROTOCOL, "soap");
+
+        client.start();
+        client.connect(socket, uri, request).get(10, TimeUnit.SECONDS);
+        client.stop();
+
+        result.assertIsSatisfied();
+    }
+
+    @Test
+    public void testSubprotocolRejected() throws Exception {
+        URI uri = new URI("ws://localhost:" + port + "/test");
+        TestWebSocket socket = new TestWebSocket();
+        WebSocketClient client = new WebSocketClient();
+
+        ClientUpgradeRequest request = new ClientUpgradeRequest();
+        request.setSubProtocols("wamp");
+
+        client.start();
+        Future<Session> future = client.connect(socket, uri, request);
+
+        // an exception should be thrown because the connection is rejected
+        assertThrows(ExecutionException.class, () -> future.get(10, 
TimeUnit.SECONDS));
+
+        client.stop();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            public void configure() {
+                from("soap://localhost:" + port + "/test")
+                        .log(">>> Message received from WebSocket Client : 
${body}")
+                        .to("mock:result");
+            }
+        };
+    }
+
+}
diff --git 
a/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketSubprotocolEndpointTest.java
 
b/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketSubprotocolEndpointTest.java
new file mode 100644
index 0000000..1e86014
--- /dev/null
+++ 
b/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketSubprotocolEndpointTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.websocket;
+
+import java.io.*;
+import java.net.*;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.AvailablePortFinder;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class WebsocketSubprotocolEndpointTest extends CamelTestSupport {
+    @WebSocket
+    public static class TestWebSocket {
+        @OnWebSocketConnect
+        public void onConnect(Session session) throws IOException {
+            session.getRemote().sendString("This is not SOAP!");
+            session.close();
+        }
+    }
+
+    private int port;
+
+    @Override
+    @BeforeEach
+    public void setUp() throws Exception {
+        port = AvailablePortFinder.getNextAvailable();
+        super.setUp();
+    }
+
+    @Test
+    public void testSubprotocolAccepted() throws Exception {
+        URI uri = new URI("ws://localhost:" + port + "/test");
+        TestWebSocket socket = new TestWebSocket();
+        WebSocketClient client = new WebSocketClient();
+
+        ClientUpgradeRequest request = new ClientUpgradeRequest();
+        request.setSubProtocols("wamp", "chat,soap");       // three proposed 
subprotocols split across two headers
+
+        MockEndpoint result = getMockEndpoint("mock:result");
+        result.expectedBodiesReceived("This is not SOAP!");
+        result.expectedHeaderReceived(WebsocketConstants.SUBPROTOCOL, "soap");
+
+        client.start();
+        client.connect(socket, uri, request).get(10, TimeUnit.SECONDS);
+        client.stop();
+
+        result.assertIsSatisfied();
+    }
+
+    @Test
+    public void testSubprotocolRejected() throws Exception {
+        URI uri = new URI("ws://localhost:" + port + "/test");
+        TestWebSocket socket = new TestWebSocket();
+        WebSocketClient client = new WebSocketClient();
+
+        ClientUpgradeRequest request = new ClientUpgradeRequest();
+        request.setSubProtocols("wamp");
+
+        client.start();
+        Future<Session> future = client.connect(socket, uri, request);
+
+        // an exception should be thrown because the connection is rejected
+        assertThrows(ExecutionException.class, () -> future.get(10, 
TimeUnit.SECONDS));
+
+        client.stop();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            public void configure() {
+                from("websocket://localhost:" + port + 
"/test?subprotocol=soap")
+                        .log(">>> Message received from WebSocket Client : 
${body}")
+                        .to("mock:result");
+            }
+        };
+    }
+
+}
diff --git 
a/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketSubprotocolNegotiationTest.java
 
b/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketSubprotocolNegotiationTest.java
new file mode 100644
index 0000000..dbb9452
--- /dev/null
+++ 
b/components/camel-websocket/src/test/java/org/apache/camel/component/websocket/WebsocketSubprotocolNegotiationTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.websocket;
+
+import java.util.*;
+import java.util.stream.Stream;
+
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static 
org.eclipse.jetty.websocket.api.WebSocketConstants.SEC_WEBSOCKET_PROTOCOL;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.mockito.Mockito.*;
+
+public class WebsocketSubprotocolNegotiationTest {
+    /*
+     * "selected" protocols are expected to be chosen
+     */
+    static Stream<Arguments> subprotocolNegotiationProvider() {
+        return Stream.of(
+                // server supported, client proposed, actually selected, 
reject connection?
+                arguments(null, null, null, false),                            
                         // select no specified protocol
+                arguments(null, "", null, false),                              
                         // select no specified protocol
+                arguments("", null, null, false),                              
                         // select no specified protocol
+                arguments("", "", null, false),                                
                         // select no specified protocol
+                arguments("any", null, null, false),                           
                         // select no specified protocol
+                arguments(null, "myProtocol", null, false),                    
                         // select no specified protocol
+                arguments("any", "protocol1,protocol2", null, false),          
                         // select no specified protocol
+                arguments("   protocol1   ", "   protocol2    ,    protocol1  
", "protocol1", false),   // strip whitespace
+                arguments("supported1,supported2", "supported2,supported1", 
"supported1", false),       // select first server supported
+                arguments("supported1,supported2", "supported1,supported2", 
"supported1", false),       // select first server supported
+                arguments("supported2,supported1", "supported1,supported2", 
"supported2", false),       // select first server supported
+                arguments("supported2,supported1", "supported2,supported1", 
"supported2", false),       // select first server supported
+                arguments("supported1,any", "supported1", "supported1", 
false),                         // select first server supported
+                arguments("supported1,any", "supported2", null, false),        
                         // select no specified protocol
+                arguments("supported1,supported2,supported3", "supported2", 
"supported2", false),       // select client proposed
+                arguments("supported2", "supported1,supported2,supported3", 
"supported2", false),       // select client proposed
+                arguments("supported1", "supported2", null, true),             
                         // should refuse connection
+                arguments("supported1,supported2", "supported3,supported4", 
null, true),                // should refuse connection
+                arguments("supported1,supported2,any", 
"supported3,supported4", null, false),           // select no specified protocol
+                arguments("supported1,supported2,any", 
"supported3,supported2", "supported2", false)    // select second server 
supported
+        );
+    }
+
+    /*
+     * "selected" protocols are NOT expected to be chosen even though the 
connection is accepted
+     */
+    static Stream<Arguments> subprotocolNegotiationNegativeProvider() {
+        return Stream.of(
+                // server supported, client proposed, actually selected
+                arguments(null, "myProtocol", null),                           
     // "myProtocol" should have been selected
+                arguments(null, "protocol1 ", "protocol1 "),                   
     // whitespace should have been stripped from selected protocol
+                arguments("serverProtocol", "clientProtocol", 
"serverProtocol"),    // no specific protocol should have been selected
+                arguments("serverProtocol", "clientProtocol", 
"clientProtocol")     // no specific protocol should have been selected
+        );
+    }
+
+    /*
+     * Test that the correct subprotocol is selected, based on the 
server-supported and client-proposed lists of subprotocols
+     */
+    @ParameterizedTest
+    @MethodSource("subprotocolNegotiationProvider")
+    void testSubprotocolNegotiation(
+            String supportedSubprotocols, String proposedSubprotocols, String 
expectedSelectedSubprotocol,
+            boolean expectRejectedConnection) {
+        // mock the test component inputs
+        NodeSynchronization sync = mock(NodeSynchronization.class);
+        Map<String, WebSocketFactory> factoryMap = mock(Map.class);
+        ServletUpgradeRequest req = mock(ServletUpgradeRequest.class);
+        ServletUpgradeResponse res = mock(ServletUpgradeResponse.class);
+        WebsocketConsumer consumer = mock(WebsocketConsumer.class);
+        WebsocketEndpoint endpoint = mock(WebsocketEndpoint.class);
+        DefaultWebsocket implementation = mock(DefaultWebsocket.class);
+
+        // return the server supported subprotocols to 
WebWebsocketComponentServlet, when they are asked for
+        when(consumer.getEndpoint()).thenReturn(endpoint);
+        when(endpoint.getSubprotocol()).thenReturn(supportedSubprotocols);
+
+        // return the client subprotocol proposal to 
WebsocketComponentServlet, when it is asked for
+        List<String> proposedList = proposedSubprotocols == null ? null : new 
ArrayList(Arrays.asList(proposedSubprotocols));
+        when(req.getHeaders(SEC_WEBSOCKET_PROTOCOL)).thenReturn(proposedList);
+
+        // mock the factory returned from factoryMap -- we don't care about 
this for this test, so just make it work
+        WebSocketFactory factory = mock(WebSocketFactory.class);
+        
when(factoryMap.get(WebsocketComponentServlet.UNSPECIFIED_SUBPROTOCOL)).thenReturn(factory);
+        when(factoryMap.get(expectedSelectedSubprotocol)).thenReturn(factory);
+        when(factory.newInstance(any(), any(), any(), any(), any(), 
any())).thenReturn(implementation);
+
+        // this is the core functionality we are testing
+        WebsocketComponentServlet wcs = new WebsocketComponentServlet(sync, 
"/anypath", factoryMap);
+        wcs.setConsumer(consumer);
+        DefaultWebsocket chosenImplementation = wcs.doWebSocketConnect(req, 
res);
+
+        // verify the connection was accepted/rejected as expected
+        assertEquals(chosenImplementation == null, expectRejectedConnection);
+
+        // verify the negotiated subprotocol
+        if (expectedSelectedSubprotocol == null) {
+            // verify that the response subprotocol header was never set
+            verify(res, never()).setHeader(eq(SEC_WEBSOCKET_PROTOCOL), 
anyString());
+        } else {
+            // verify that the subprotocol returned to the client was the one 
we expected
+            verify(res).setHeader(SEC_WEBSOCKET_PROTOCOL, 
expectedSelectedSubprotocol);
+        }
+    }
+
+    /*
+     * Test that the specified subprotocol is NOT selected even though the 
connection was accepted
+     */
+    @ParameterizedTest
+    @MethodSource("subprotocolNegotiationNegativeProvider")
+    void testSubprotocolNegotiationNegative(
+            String supportedSubprotocols, String proposedSubprotocols, String 
expectedNotSelectedSubprotocol) {
+        assertThrows(AssertionError.class, () -> {
+            testSubprotocolNegotiation(supportedSubprotocols, 
proposedSubprotocols, expectedNotSelectedSubprotocol, true);
+        });
+    }
+
+}
diff --git 
a/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/WebsocketComponentBuilderFactory.java
 
b/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/WebsocketComponentBuilderFactory.java
index 3c9c400..c04208f 100644
--- 
a/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/WebsocketComponentBuilderFactory.java
+++ 
b/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/WebsocketComponentBuilderFactory.java
@@ -218,6 +218,30 @@ public interface WebsocketComponentBuilderFactory {
             return this;
         }
         /**
+         * This is a comma-separated list of subprotocols that are supported by
+         * the application. The list is in priority order. The first 
subprotocol
+         * on this list that is proposed by the client is the one that will be
+         * accepted. If no subprotocol on this list is proposed by the client,
+         * then the websocket connection is refused. The special value 'any'
+         * means that any subprotocol is acceptable. 'any' can be used on its
+         * own, or as a failsafe at the end of a list of more specific
+         * protocols. 'any' will also match the case where no subprotocol is
+         * proposed by the client.
+         * 
+         * The option is a: &lt;code&gt;java.lang.String&lt;/code&gt; type.
+         * 
+         * Default: any
+         * Group: advanced
+         * 
+         * @param subprotocol the value to set
+         * @return the dsl builder
+         */
+        default WebsocketComponentBuilder subprotocol(
+                java.lang.String subprotocol) {
+            doSetProperty("subprotocol", subprotocol);
+            return this;
+        }
+        /**
          * To use a custom thread pool for the server. MaxThreads/minThreads or
          * threadPool fields are required due to switch to Jetty9.
          * 
@@ -338,6 +362,7 @@ public interface WebsocketComponentBuilderFactory {
             case "enableJmx": ((WebsocketComponent) 
component).setEnableJmx((boolean) value); return true;
             case "maxThreads": ((WebsocketComponent) 
component).setMaxThreads((java.lang.Integer) value); return true;
             case "minThreads": ((WebsocketComponent) 
component).setMinThreads((java.lang.Integer) value); return true;
+            case "subprotocol": ((WebsocketComponent) 
component).setSubprotocol((java.lang.String) value); return true;
             case "threadPool": ((WebsocketComponent) 
component).setThreadPool((org.eclipse.jetty.util.thread.ThreadPool) value); 
return true;
             case "sslContextParameters": ((WebsocketComponent) 
component).setSslContextParameters((org.apache.camel.support.jsse.SSLContextParameters)
 value); return true;
             case "sslKeyPassword": ((WebsocketComponent) 
component).setSslKeyPassword((java.lang.String) value); return true;
diff --git 
a/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/WebsocketEndpointBuilderFactory.java
 
b/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/WebsocketEndpointBuilderFactory.java
index 6bf7e9e..ceb3824 100644
--- 
a/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/WebsocketEndpointBuilderFactory.java
+++ 
b/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/WebsocketEndpointBuilderFactory.java
@@ -179,6 +179,29 @@ public interface WebsocketEndpointBuilderFactory {
             return this;
         }
         /**
+         * This is a comma-separated list of subprotocols that are supported by
+         * the application. The list is in priority order. The first 
subprotocol
+         * on this list that is proposed by the client is the one that will be
+         * accepted. If no subprotocol on this list is proposed by the client,
+         * then the websocket connection is refused. The special value 'any'
+         * means that any subprotocol is acceptable. 'any' can be used on its
+         * own, or as a failsafe at the end of a list of more specific
+         * protocols. 'any' will also match the case where no subprotocol is
+         * proposed by the client.
+         * 
+         * The option is a: &lt;code&gt;java.lang.String&lt;/code&gt; type.
+         * 
+         * Default: any
+         * Group: consumer
+         * 
+         * @param subprotocol the value to set
+         * @return the dsl builder
+         */
+        default WebsocketEndpointConsumerBuilder subprotocol(String 
subprotocol) {
+            doSetProperty("subprotocol", subprotocol);
+            return this;
+        }
+        /**
          * The CORS allowed origins. Use to allow all.
          * 
          * The option is a: &lt;code&gt;java.lang.String&lt;/code&gt; type.
diff --git a/docs/components/modules/ROOT/pages/websocket-component.adoc 
b/docs/components/modules/ROOT/pages/websocket-component.adoc
index 90c46fa..c400e28 100644
--- a/docs/components/modules/ROOT/pages/websocket-component.adoc
+++ b/docs/components/modules/ROOT/pages/websocket-component.adoc
@@ -39,7 +39,7 @@ You can append query options to the URI in the following 
format,
 
 
 // component options: START
-The Jetty Websocket component supports 15 options, which are listed below.
+The Jetty Websocket component supports 16 options, which are listed below.
 
 
 
@@ -55,6 +55,7 @@ The Jetty Websocket component supports 15 options, which are 
listed below.
 | *enableJmx* (advanced) | If this option is true, Jetty JMX support will be 
enabled for this endpoint. See Jetty JMX support for more details. | false | 
boolean
 | *maxThreads* (advanced) | To set a value for maximum number of threads in 
server thread pool. MaxThreads/minThreads or threadPool fields are required due 
to switch to Jetty9. The default values for maxThreads is 1 2 noCores. |  | 
Integer
 | *minThreads* (advanced) | To set a value for minimum number of threads in 
server thread pool. MaxThreads/minThreads or threadPool fields are required due 
to switch to Jetty9. The default values for minThreads is 1. |  | Integer
+| *subprotocol* (advanced) | This is a comma-separated list of subprotocols 
that are supported by the application. The list is in priority order. The first 
subprotocol on this list that is proposed by the client is the one that will be 
accepted. If no subprotocol on this list is proposed by the client, then the 
websocket connection is refused. The special value 'any' means that any 
subprotocol is acceptable. 'any' can be used on its own, or as a failsafe at 
the end of a list of more spec [...]
 | *threadPool* (advanced) | To use a custom thread pool for the server. 
MaxThreads/minThreads or threadPool fields are required due to switch to 
Jetty9. |  | ThreadPool
 | *sslContextParameters* (security) | To configure security using 
SSLContextParameters |  | SSLContextParameters
 | *sslKeyPassword* (security) | The password for the keystore when using SSL. 
|  | String
@@ -94,7 +95,7 @@ with the following path and query parameters:
 |===
 
 
-=== Query Parameters (18 parameters):
+=== Query Parameters (19 parameters):
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
@@ -104,6 +105,7 @@ with the following path and query parameters:
 | *bridgeErrorHandler* (consumer) | Allows for bridging the consumer to the 
Camel routing Error Handler, which mean any exceptions occurred while the 
consumer is trying to pickup incoming messages, or the likes, will now be 
processed as a message and handled by the routing Error Handler. By default the 
consumer will use the org.apache.camel.spi.ExceptionHandler to deal with 
exceptions, that will be logged at WARN or ERROR level and ignored. | false | 
boolean
 | *sessionSupport* (consumer) | Whether to enable session support which 
enables HttpSession for each http request. | false | boolean
 | *staticResources* (consumer) | Set a resource path for static resources 
(such as .html files etc). The resources can be loaded from classpath, if you 
prefix with classpath:, otherwise the resources is loaded from file system or 
from JAR files. For example to load from root classpath use classpath:., or 
classpath:WEB-INF/static If not configured (eg null) then no static resource is 
in use. |  | String
+| *subprotocol* (consumer) | This is a comma-separated list of subprotocols 
that are supported by the application. The list is in priority order. The first 
subprotocol on this list that is proposed by the client is the one that will be 
accepted. If no subprotocol on this list is proposed by the client, then the 
websocket connection is refused. The special value 'any' means that any 
subprotocol is acceptable. 'any' can be used on its own, or as a failsafe at 
the end of a list of more spec [...]
 | *exceptionHandler* (consumer) | To let the consumer use a custom 
ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this 
option is not in use. By default the consumer will deal with exceptions, that 
will be logged at WARN or ERROR level and ignored. |  | ExceptionHandler
 | *exchangePattern* (consumer) | Sets the exchange pattern when the consumer 
creates an exchange. There are 3 enums and the value can be one of: InOnly, 
InOut, InOptionalOut |  | ExchangePattern
 | *lazyStartProducer* (producer) | 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 producer may otherwise fail during 
starting and cause the route to fail being started. By deferring this startup 
to be lazy then the startup failure can be handled during routing messages via 
Camel's routing error handlers. Beware that when the first message is processed 
then creating and [...]
@@ -128,18 +130,35 @@ with the following path and query parameters:
 
 == Message Headers
 
-The WebSocket component uses 2 headers to indicate to either send
-messages back to a single/current client, or to all clients.
+The WebSocket component uses headers to provide information about incoming 
messages from consumer endpoints, or as processing instructions for producer 
endpoints sending outgoing messages.
 
+=== Headers from Consumers
 [width="100%",cols="10%,90%",options="header",]
 |=======================================================================
+|Header Name |Description
+
+|`WebsocketConstants.CONNECTION_KEY` |Connection key identifying an individual 
client connection.  You can save this and specify it again when routing to a 
producer endpoing in order to direct messages to a specific connected client.
+
+|`WebsocketConstants.REMOTE_ADDRESS` |Remote address of the websocket session.
+
+|`WebsocketConstants.SUBPROTOCOL` |If a specific subprotocol was negotiated, 
it will be specfied in this header.  Note that if you specify the "any" 
subprotocol to be supported, and a client requests a specific subprotocol, the 
connection will be accepted without a specific subprotocol being used.  You 
need to specifically support a given protocol by name if you want it returned 
to the client and to show up in the message header.
+
+|`WebsocketConstants.RELATIVE_PATH` |If you specify a wildcard URI path for an 
endpoint, and a websocket client connects to that websocket endpoing, the 
relative path that the client specified will be provided in this header.
+
+For example, if you specified `websocket://0.0.0.0:80/api/*` as your endpoint 
URI, and a client connects to the server at 
`ws://host.com/api/specialized/apipath` then `specialized/apipath` is provided 
in the relative path header of all messages from that client.
+
+|=======================================================================
+
+=== Headers for Producers
+[width="100%",cols="10%,90%",options="header",]
+|=======================================================================
+|Header Name |Description
 
 |`WebsocketConstants.SEND_TO_ALL` |Sends the message to all clients which are 
currently connected. You can
 use the `sendToAll` option on the endpoint instead of using this header.
 
 |`WebsocketConstants.CONNECTION_KEY` |Sends the message to the client with the 
given connection key.
 
-|`WebsocketConstants.REMOTE_ADDRESS` |Remote address of the websocket session.
 |=======================================================================
 
 == Usage

Reply via email to