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: <code>java.lang.String</code> 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: <code>java.lang.String</code> 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: <code>java.lang.String</code> 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