This is an automated email from the ASF dual-hosted git repository.
cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git
The following commit(s) were added to refs/heads/master by this push:
new 0444ac4f73 FELIX-6692 jetty 12.x websockets new approach (based on
#309) (#310)
0444ac4f73 is described below
commit 0444ac4f73a3cb876511b00a2df49e7f16efee0c
Author: Paul <[email protected]>
AuthorDate: Wed May 1 09:04:05 2024 +0200
FELIX-6692 jetty 12.x websockets new approach (based on #309) (#310)
* Revert "Add jetty websocket support to Jetty12 (#298)"
This reverts commit 6d95c936dfc7767f1cea23d1db98a8c7d7810378.
* FELIX-6692 Add Jetty WebSocket support for jetty 12.x
- Apply 11.x approach to jetty12 bundle
- Add two new classifiers 'with-jetty-ee10-websockets' and
'with-jakarta-ee10-websockets' to have a fat jar containing the appropriate
websocket classes
* Working example base on previous code.
Do note that the workaround in FelixJettyWebSocketServlet are still
required; the initialization code in the Jetty12 bundle doesn't seem to work
* * Enable cross context support to allow WebSockets to be registered in
Jetty 12.
See
https://github.com/jetty/jetty.project/blob/jetty-12.0.x/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java#L510
and https://github.com/jetty/jetty.project/issues/9774
* Add example based on registering the websocket to the main servlet
context instead of the per-bundle one
-
* Added paxweb unit tests based on the Jetty11 work from #309
* Fix version
* Fix message
* Test casing
* Update README.md
* Make client optional
* Add Jetty12 bundle to pom
* Store the WebSocket container reference and set it on the shared servlet
context once available
This removes the need to set setCrossContextDispatchSupported, as the WS
container is available on the proper servlet context itself
* Update example to no longer use the root context.
Removed other WebSocket example, as this is no longer needed with the new
approach.
Updated documentation.
* Add servlet based example again, as it shows another example of how to
register a WebSocket endpoint that abides to the servlet context it's
registered to.
* Rename class
* Comment
* Comments
* Remove classloader code as it now also works without
* Rename test class to EE10
* Small changes to README.md
---
http/README.md | 123 +++----
http/jetty12/pom.xml | 377 +++++++++++++++++++--
.../jetty/internal/ConfigMetaTypeProvider.java | 11 +
.../felix/http/jetty/internal/JettyConfig.java | 22 ++
.../felix/http/jetty/internal/JettyService.java | 86 ++++-
.../http/jetty/it/AbstractJettyTestSupport.java | 185 ++++++++++
.../jetty/it/JakartaEE10SpecificWebsocketIT.java | 211 ++++++++++++
.../jetty/it/JettyEE10SpecificWebsocketIT.java | 209 ++++++++++++
.../jetty/it/MissingWebsocketDependenciesIT.java | 91 +++++
http/pom.xml | 1 +
http/samples/whiteboard/pom.xml | 20 +-
.../felix/http/samples/whiteboard/Activator.java | 31 +-
.../whiteboard/FelixJettyWebSocketServlet.java | 109 +-----
.../samples/whiteboard/TestWebSocketServlet.java | 32 +-
...t.java => TestWebSocketServletAlternative.java} | 24 +-
15 files changed, 1331 insertions(+), 201 deletions(-)
diff --git a/http/README.md b/http/README.md
index 875fcce940..10f8fd4d87 100644
--- a/http/README.md
+++ b/http/README.md
@@ -6,8 +6,8 @@ This is an implementation of the [R8.1 Whiteboard Specification
for Jakarta Serv
* Standard OSGi Http Whiteboard implementation
* Run either with Jetty (version 11 or 12) bundle or inside your own
application server using the servlet bridge
* [Felix HTTP Jetty
12](https://mvnrepository.com/artifact/org.apache.felix/org.apache.felix.http.jetty12)
is the preferred bundle of choice as it supports JavaEE 8 and JakartaEE 8 with
the `javax` namespace, JakartaEE 9/10/11/future versions with the `jakarta`
namespace.
- * [Jetty WebSocket
support](https://github.com/apache/felix-dev/pull/298), see example code
[here](https://github.com/apache/felix-dev/blob/master/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServlet.java).
- * [Felix HTTP Jetty
11](https://mvnrepository.com/artifact/org.apache.felix/org.apache.felix.http.jetty)
is the predecessor of the Jetty 12 bundle, which shipped with [Jetty
9.4.x](https://mvnrepository.com/artifact/org.apache.felix/org.apache.felix.http.jetty/4.2.26)
in the 4.x range, [Jetty
11.x](https://mvnrepository.com/artifact/org.apache.felix/org.apache.felix.http.jetty/5.1.10)
in the 5.x range.
+ * [Jetty WebSocket
support](https://github.com/apache/felix-dev/pull/310), see example code
[here](https://github.com/apache/felix-dev/blob/master/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServlet.java).
+ * [Felix HTTP Jetty
11](https://mvnrepository.com/artifact/org.apache.felix/org.apache.felix.http.jetty)
is the predecessor of the Jetty 12 bundle, which shipped with [Jetty
9.4.x](https://mvnrepository.com/artifact/org.apache.felix/org.apache.felix.http.jetty/4.2.26)
in the 4.x range, [Jetty
11.x](https://mvnrepository.com/artifact/org.apache.felix/org.apache.felix.http.jetty/5.1.10)
in the 5.x range.
* Correctly versioned Servlet API.
## Installing
@@ -27,12 +27,17 @@ Note that as of version **3.x**, the Servlet APIs are **no
longer** packaged wit
`org.apache.felix.http.servlet-api` (or any other compatible Serlvet API
bundle) to your
classpath and deployment!
-### Light bundle
-If you would like to use your own Jetty jar instead of the one packaged with
the Felix Jetty bundles, you can use the `light` variant.
-When building the Felix Jetty bundle with Maven (`mvn clean install`), the
`light` bundle will be created in the `target` directory, postfixed with
`-light.jar`.
-This jar can be deployed to your Felix OSGi environment, along with a
compatible Jetty jar.
+### Using classifiers: `light`, `with-jetty-ee10-websockets` and
`with-jakarta-ee10-websockets` bundle
+If you would like to use your own Jetty jars instead of the one packaged with
the Felix Jetty bundles, you can use the variants with the following
classifiers:
+* `light` - A light version of the bundle that does not include the Jetty
classes. This is useful when you want to use your own Jetty jars. Available for
both Jetty bundles.
+* `with-jetty-ee10-websockets` - A bundle that includes the classes required
for Jetty WebSocket support for Jakarta EE10. Jetty12 bundle only.
+* `with-jakarta-ee10-websockets` - A bundle that includes the classes required
for Jakarta WebSocket support for Jakarta EE10. Jetty12 bundle only.
-Or just use maven to include the dependency with the `light` classifier.
+When building the Felix Jetty bundle with Maven (`mvn clean install`), the
additional bundles will be created in the `target` directory, postfixed with
classifier.
+This jar can be deployed to your Felix OSGi environment, along with a
compatible Jetty jars.
+See the unit tests for the required bundles and versions that need to be
deployed.
+
+Or just use maven to include the dependency with the proper classifier.
```
<dependency>
<groupId>org.apache.felix</groupId>
@@ -381,57 +386,59 @@ The service can both be configured using OSGi environment
properties and using C
this service is `"org.apache.felix.http"`. If you use both methods,
Configuration Admin takes precedence. The following
properties can be used (some legacy property names still exist but are not
documented here on purpose). As properties might change over time, the actual
list of properties can be found [here for the Jetty 12
bundle](https://github.com/apache/felix-dev/blob/master/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java)
and [here for the Jetty 11
bundle](https://github.com/apache/felix-dev/blob/master/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/J
[...]
-| Property | Description |
-|--|--|
-| `org.apache.felix.http.host` | Host name or IP Address of the interface to
listen on. The default is `null` causing Jetty to listen on all interfaces. |
-| `org.osgi.service.http.port` | The port used for servlets and resources
available via HTTP. The default is `8080`. See [port settings
below](#http-port-settings) for additional information. A negative port number
has the same effect as setting `org.apache.felix.http.enable` to `false`. |
-| `org.osgi.service.http.port.secure` | The port used for servlets and
resources available via HTTPS. The default is `8443`. See [port settings
below](#http-port-settings) for additional information. A negative port number
has the same effect as setting `org.apache.felix.https.enable` to `false`. |
-| `org.apache.felix.http.context_path` | The servlet Context Path to use for
the Http Service. If this property is not configured it defaults to "/". This
must be a valid path starting with a slash and not ending with a slash (unless
it is the root context). |
-| `org.apache.felix.http.timeout` | Connection timeout in milliseconds. The
default is `60000` (60 seconds). |
-| `org.apache.felix.http.session.timeout` | Allows for the specification of
the Session life time as a number of minutes. This property serves the same
purpose as the `session-timeout` element in a Web Application descriptor. The
default is "0" (zero) for no timeout at all. |
-| `org.apache.felix.http.enable` | Flag to enable the use of HTTP. The default
is `true`. |
-| `org.apache.felix.https.enable` | Flag to enable the user of HTTPS. The
default is `false`. |
-| `org.apache.felix.https.keystore` | The name of the file containing the
keystore. |
-| `org.apache.felix.https.keystore.password` | The password for the keystore. |
-| `org.apache.felix.https.keystore.key.password` | The password for the key in
the keystore. |
-| `org.apache.felix.https.truststore` | The name of the file containing the
truststore. |
-| `org.apache.felix.https.truststore.type` | The type of truststore to use.
The default is `JKS`. |
-| `org.apache.felix.https.truststore.password` | The password for the
truststore. |
-| `org.apache.felix.https.jetty.ciphersuites.excluded` | Configures
comma-separated list of SSL cipher suites to *exclude*. Default is `null`,
meaning that no cipher suite is excluded. |
-| `org.apache.felix.https.jetty.ciphersuites.included` | Configures
comma-separated list of SSL cipher suites to *include*. Default is `null`,
meaning that the default cipher suites are used. |
-| `org.apache.felix.https.jetty.protocols.excluded` | Configures
comma-separated list of SSL protocols (e.g. SSLv3, TLSv1.0, TLSv1.1, TLSv1.2)
to *exclude*. Default is `null`, meaning that no protocol is excluded. |
-| `org.apache.felix.https.jetty.protocols.included` | Configures
comma-separated list of SSL protocols to *include*. Default is `null`, meaning
that the default protocols are used. |
-| `org.apache.felix.https.clientcertificate` | Flag to determine if the HTTPS
protocol requires, wants or does not use client certificates. Legal values are
`needs`, `wants` and `none`. The default is `none`. |
-| `org.apache.felix.http.jetty.headerBufferSize` | Size of the buffer for
request and response headers, in bytes. Default is 16 KB. |
-| `org.apache.felix.http.jetty.requestBufferSize` | Size of the buffer for
requests not fitting the header buffer, in bytes. Default is 8 KB. |
-| `org.apache.felix.http.jetty.responseBufferSize` | Size of the buffer for
responses, in bytes. Default is 24 KB. |
-| `org.apache.felix.http.jetty.maxFormSize` | The maximum size accepted for a
form post, in bytes. Defaults to 200 KB. |
-| `org.apache.felix.http.mbeans` | If `true`, enables the MBean server
functionality. The default is `false`. |
-| `org.apache.felix.http.jetty.sendServerHeader` | If `false`, the `Server`
HTTP header is no longer included in responses. The default is `false`. |
-| `org.eclipse.jetty.servlet.SessionCookie` | Name of the cookie used to
transport the Session ID. The default is `JSESSIONID`. |
-| `org.eclipse.jetty.servlet.SessionURL` | Name of the request parameter to
transport the Session ID. The default is `jsessionid`. |
-| `org.eclipse.jetty.servlet.SessionDomain` | Domain to set on the session
cookie. The default is `null`. |
-| `org.eclipse.jetty.servlet.SessionPath` | The path to set on the session
cookie. The default is the configured session context path ("/"). |
-| `org.eclipse.jetty.servlet.MaxAge` | The maximum age value to set on the
cookie. The default is "-1". |
-| `org.eclipse.jetty.UriComplianceMode` | The URI compliance mode to set. The
default is
[DEFAULT](https://eclipse.dev/jetty/javadoc/jetty-12/org/eclipse/jetty/http/UriCompliance.html#DEFAULT).
See
[documentation](https://eclipse.dev/jetty/documentation/jetty-12/programming-guide/index.html#pg-server-compliance-uri.)
and [possible
modes](https://github.com/jetty/jetty.project/blob/jetty-12.0.x/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/UriCompliance.java#L186C107-L186C113).
|
-| `org.apache.felix.proxy.load.balancer.connection.enable` | Set this to
`true` when running Felix HTTP behind a (offloading) proxy or load balancer
which rewrites the requests. The default is `false`. |
-| `org.apache.felix.http.runtime.init.` | Properties starting with this prefix
are added as service registration properties to the HttpServiceRuntime service.
The prefix is removed for the property name. |
-| `org.apache.felix.jetty.gziphandler.enable` | Whether the server should use
a server-wide gzip handler. Default is false. |
-| `org.apache.felix.jetty.gzip.minGzipSize` | The minimum response size to
trigger dynamic compression. Default is GzipHandler.DEFAULT_MIN_GZIP_SIZE. |
-| `org.apache.felix.jetty.gzip.inflateBufferSize` | The size in bytes of the
buffer to inflate compressed request, or <= 0 for no inflation. Default is -1. |
-| `org.apache.felix.jetty.gzip.syncFlush` | True if Deflater#SYNC_FLUSH should
be used, else Deflater#NO_FLUSH will be used. Default is false. |
-| `org.apache.felix.jetty.gzip.includedMethods` | The additional http methods
to include in compression. Default is none. |
-| `org.apache.felix.jetty.gzip.excludedMethods` | The additional http methods
to exclude in compression. Default is none. |
-| `org.apache.felix.jetty.gzip.includedPaths` | The additional path specs to
include. Inclusion takes precedence over exclusion. Default is none. |
-| `org.apache.felix.jetty.gzip.excludedPaths` | The additional path specs to
exclude. Inclusion takes precedence over exclusion. Default is none. |
-| `org.apache.felix.jetty.gzip.includedMimeTypes` | The included mime types.
Inclusion takes precedence over exclusion. Default is none. |
-| `org.apache.felix.jetty.gzip.excludedMimeTypes` | The excluded mime types.
Inclusion takes precedence over exclusion. Default is none. |
-| `org.apache.felix.http2.enable` | Whether to enable HTTP/2. Default is
false. |
-| `org.apache.felix.jetty.http2.maxConcurrentStreams` | The max number of
concurrent streams per connection. Default is 128. |
-| `org.apache.felix.jetty.http2.initialStreamRecvWindow` | The initial stream
receive window (client to server). Default is 524288. |
-| `org.apache.felix.jetty.http2.initialSessionRecvWindow` | The initial
session receive window (client to server). Default is 1048576. |
-| `org.apache.felix.jetty.alpn.protocols` | The ALPN protocols to consider.
Default is h2, http/1.1. |
-| `org.apache.felix.jetty.alpn.defaultProtocol` | The default protocol when
negotiation fails. Default is http/1.1. |
+| Property | Description
[...]
+|--|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `org.apache.felix.http.host` | Host name or IP
Address of the interface to listen on. The default is `null` causing Jetty to
listen on all interfaces.
[...]
+| `org.osgi.service.http.port` | The port used for
servlets and resources available via HTTP. The default is `8080`. See [port
settings below](#http-port-settings) for additional information. A negative
port number has the same effect as setting `org.apache.felix.http.enable` to
`false`.
[...]
+| `org.osgi.service.http.port.secure` | The port used for
servlets and resources available via HTTPS. The default is `8443`. See [port
settings below](#http-port-settings) for additional information. A negative
port number has the same effect as setting `org.apache.felix.https.enable` to
`false`.
[...]
+| `org.apache.felix.http.context_path` | The servlet
Context Path to use for the Http Service. If this property is not configured it
defaults to "/". This must be a valid path starting with a slash and not
ending with a slash (unless it is the root context).
[...]
+| `org.apache.felix.http.timeout` | Connection
timeout in milliseconds. The default is `60000` (60 seconds).
[...]
+| `org.apache.felix.http.session.timeout` | Allows for the
specification of the Session life time as a number of minutes. This property
serves the same purpose as the `session-timeout` element in a Web Application
descriptor. The default is "0" (zero) for no timeout at all.
[...]
+| `org.apache.felix.http.enable` | Flag to enable
the use of HTTP. The default is `true`.
[...]
+| `org.apache.felix.https.enable` | Flag to enable
the user of HTTPS. The default is `false`.
[...]
+| `org.apache.felix.https.keystore` | The name of the
file containing the keystore.
[...]
+| `org.apache.felix.https.keystore.password` | The password for
the keystore.
[...]
+| `org.apache.felix.https.keystore.key.password` | The password for
the key in the keystore.
[...]
+| `org.apache.felix.https.truststore` | The name of the
file containing the truststore.
[...]
+| `org.apache.felix.https.truststore.type` | The type of
truststore to use. The default is `JKS`.
[...]
+| `org.apache.felix.https.truststore.password` | The password for
the truststore.
[...]
+| `org.apache.felix.https.jetty.ciphersuites.excluded` | Configures
comma-separated list of SSL cipher suites to *exclude*. Default is `null`,
meaning that no cipher suite is excluded.
[...]
+| `org.apache.felix.https.jetty.ciphersuites.included` | Configures
comma-separated list of SSL cipher suites to *include*. Default is `null`,
meaning that the default cipher suites are used.
[...]
+| `org.apache.felix.https.jetty.protocols.excluded` | Configures
comma-separated list of SSL protocols (e.g. SSLv3, TLSv1.0, TLSv1.1, TLSv1.2)
to *exclude*. Default is `null`, meaning that no protocol is excluded.
[...]
+| `org.apache.felix.https.jetty.protocols.included` | Configures
comma-separated list of SSL protocols to *include*. Default is `null`, meaning
that the default protocols are used.
[...]
+| `org.apache.felix.https.clientcertificate` | Flag to determine
if the HTTPS protocol requires, wants or does not use client certificates.
Legal values are `needs`, `wants` and `none`. The default is `none`.
[...]
+| `org.apache.felix.http.jetty.headerBufferSize` | Size of the
buffer for request and response headers, in bytes. Default is 16 KB.
[...]
+| `org.apache.felix.http.jetty.requestBufferSize` | Size of the
buffer for requests not fitting the header buffer, in bytes. Default is 8 KB.
[...]
+| `org.apache.felix.http.jetty.responseBufferSize` | Size of the
buffer for responses, in bytes. Default is 24 KB.
[...]
+| `org.apache.felix.http.jetty.maxFormSize` | The maximum size
accepted for a form post, in bytes. Defaults to 200 KB.
[...]
+| `org.apache.felix.http.mbeans` | If `true`,
enables the MBean server functionality. The default is `false`.
[...]
+| `org.apache.felix.http.jetty.sendServerHeader` | If `false`, the
`Server` HTTP header is no longer included in responses. The default is
`false`.
[...]
+| `org.eclipse.jetty.servlet.SessionCookie` | Name of the
cookie used to transport the Session ID. The default is `JSESSIONID`.
[...]
+| `org.eclipse.jetty.servlet.SessionURL` | Name of the
request parameter to transport the Session ID. The default is `jsessionid`.
[...]
+| `org.eclipse.jetty.servlet.SessionDomain` | Domain to set on
the session cookie. The default is `null`.
[...]
+| `org.eclipse.jetty.servlet.SessionPath` | The path to set
on the session cookie. The default is the configured session context path
("/").
[...]
+| `org.eclipse.jetty.servlet.MaxAge` | The maximum age
value to set on the cookie. The default is "-1".
[...]
+| `org.eclipse.jetty.UriComplianceMode` | The URI
compliance mode to set. The default is
[DEFAULT](https://eclipse.dev/jetty/javadoc/jetty-12/org/eclipse/jetty/http/UriCompliance.html#DEFAULT).
See
[documentation](https://eclipse.dev/jetty/documentation/jetty-12/programming-guide/index.html#pg-server-compliance-uri.)
and [possible
modes](https://github.com/jetty/jetty.project/blob/jetty-12.0.x/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/UriCompliance.jav
[...]
+| `org.apache.felix.proxy.load.balancer.connection.enable` | Set this to
`true` when running Felix HTTP behind a (offloading) proxy or load balancer
which rewrites the requests. The default is `false`.
[...]
+| `org.apache.felix.http.runtime.init.` | Properties
starting with this prefix are added as service registration properties to the
HttpServiceRuntime service. The prefix is removed for the property name.
[...]
+| `org.apache.felix.jetty.gziphandler.enable` | Whether the
server should use a server-wide gzip handler. Default is false.
[...]
+| `org.apache.felix.jetty.gzip.minGzipSize` | The minimum
response size to trigger dynamic compression. Default is
GzipHandler.DEFAULT_MIN_GZIP_SIZE.
[...]
+| `org.apache.felix.jetty.gzip.inflateBufferSize` | The size in bytes
of the buffer to inflate compressed request, or <= 0 for no inflation. Default
is -1.
[...]
+| `org.apache.felix.jetty.gzip.syncFlush` | True if
Deflater#SYNC_FLUSH should be used, else Deflater#NO_FLUSH will be used.
Default is false.
[...]
+| `org.apache.felix.jetty.gzip.includedMethods` | The additional
http methods to include in compression. Default is none.
[...]
+| `org.apache.felix.jetty.gzip.excludedMethods` | The additional
http methods to exclude in compression. Default is none.
[...]
+| `org.apache.felix.jetty.gzip.includedPaths` | The additional
path specs to include. Inclusion takes precedence over exclusion. Default is
none.
[...]
+| `org.apache.felix.jetty.gzip.excludedPaths` | The additional
path specs to exclude. Inclusion takes precedence over exclusion. Default is
none.
[...]
+| `org.apache.felix.jetty.gzip.includedMimeTypes` | The included mime
types. Inclusion takes precedence over exclusion. Default is none.
[...]
+| `org.apache.felix.jetty.gzip.excludedMimeTypes` | The excluded mime
types. Inclusion takes precedence over exclusion. Default is none.
[...]
+| `org.apache.felix.http2.enable` | Whether to enable
HTTP/2. Default is false.
[...]
+| `org.apache.felix.jetty.http2.maxConcurrentStreams` | The max number of
concurrent streams per connection. Default is 128.
[...]
+| `org.apache.felix.jetty.http2.initialStreamRecvWindow` | The initial
stream receive window (client to server). Default is 524288.
[...]
+| `org.apache.felix.jetty.http2.initialSessionRecvWindow` | The initial
session receive window (client to server). Default is 1048576.
[...]
+| `org.apache.felix.jetty.alpn.protocols` | The ALPN
protocols to consider. Default is h2, http/1.1.
[...]
+| `org.apache.felix.jetty.alpn.defaultProtocol` | The default
protocol when negotiation fails. Default is http/1.1.
[...]
+| `org.apache.felix.jakarta.ee10.websocket.enable` | Enables Jakarta
EE10 websocket support. Default is false. Jetty12 only.
[...]
+| `org.apache.felix.jetty.ee10.websocket.enable` | Enables Jetty
EE10 websocket support. Default is false. Jetty12 only.
[...]
### Multiple Servers
diff --git a/http/jetty12/pom.xml b/http/jetty12/pom.xml
index 0267fdb641..450d6ed274 100644
--- a/http/jetty12/pom.xml
+++ b/http/jetty12/pom.xml
@@ -43,6 +43,9 @@
<felix.java.version>17</felix.java.version>
<jetty.version>12.0.8</jetty.version>
<baseline.skip>true</baseline.skip>
+ <org.ops4j.pax.exam.version>4.13.3</org.ops4j.pax.exam.version>
+ <!-- To debug the pax process, override this with -D -->
+ <pax.vm.options>-Xmx512M</pax.vm.options>
</properties>
<build>
@@ -69,7 +72,12 @@
// scan each of the artifacts to preserve the
information found in any META-INF/services/* files
project.artifacts.each() { artifact ->
- if
(artifact.getArtifactHandler().isAddedToClasspath() &&
!org.apache.maven.artifact.Artifact.SCOPE_TEST.equals( artifact.getScope() )) {
+ if
(artifact.getArtifactHandler().isAddedToClasspath() &&
!org.apache.maven.artifact.Artifact.SCOPE_TEST.equals( artifact.getScope() )
+ &&
!"org.eclipse.jetty.websocket".equals(artifact.getGroupId()) // skip the
optional websocket artifacts
+ &&
!"org.eclipse.jetty.ee10.websocket".equals(artifact.getGroupId()) // skip the
optional websocket artifacts
+ &&
!"jetty-annotations".equals(artifact.getArtifactId()) // skip the transitive
artifacts from the optional websocket artifacts
+ &&
!"jetty-plus".equals(artifact.getArtifactId())
+ &&
!"jetty-webapp".equals(artifact.getArtifactId())) {
def jar;
try {
jar = new
java.util.jar.JarFile(artifact.file)
@@ -164,9 +172,17 @@
org.osgi.service.servlet.runtime,
org.osgi.service.servlet.runtime.dto,
org.osgi.service.servlet.whiteboard,
- !org.eclipse.jetty,
- !org.eclipse.jetty.version,
- org.eclipse.jetty.*,
+ org.eclipse.jetty.alpn.server,
+ org.eclipse.jetty.http.*,
+ org.eclipse.jetty.http2.*,
+ org.eclipse.jetty.io.*,
+ org.eclipse.jetty.jmx.*,
+ org.eclipse.jetty.security.*,
+ org.eclipse.jetty.session.*,
+ org.eclipse.jetty.server.*,
+ org.eclipse.jetty.util.*,
+ !org.eclipse.jetty.ee10.websocket.*,
+ org.eclipse.jetty.ee10.servlet.*,
org.apache.felix.http.jetty,
org.apache.felix.http.jakartawrappers,
org.apache.felix.http.javaxwrappers
@@ -180,9 +196,6 @@
org.apache.commons.*
</Conditional-Package>
<Import-Package>
- jakarta.annotation.*;resolution:=optional,
- jakarta.transaction.*;resolution:=optional,
- org.objectweb.asm.*;resolution:=optional,
sun.misc;resolution:=optional,
sun.nio.ch;resolution:=optional,
javax.imageio;resolution:=optional,
@@ -319,8 +332,257 @@
</instructions>
</configuration>
</execution>
+ <execution>
+ <id>with-jetty-ee10-websockets</id>
+ <goals>
+ <goal>bundle</goal>
+ </goals>
+ <configuration>
+ <classifier>with-jetty-ee10-websockets</classifier>
+ <instructions>
+
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+
<Bundle-Version>${project.version}</Bundle-Version>
+ <X-Jetty-Version>
+ ${jetty.version}
+ </X-Jetty-Version>
+ <Bundle-Activator>
+
org.apache.felix.http.jetty.internal.JettyActivator
+ </Bundle-Activator>
+ <Export-Package>
+ org.osgi.service.http,
+ org.osgi.service.http.context,
+ org.osgi.service.http.runtime,
+ org.osgi.service.http.runtime.dto,
+ org.osgi.service.http.whiteboard,
+ org.osgi.service.servlet.context,
+ org.osgi.service.servlet.runtime,
+ org.osgi.service.servlet.runtime.dto,
+ org.osgi.service.servlet.whiteboard,
+ org.eclipse.jetty.alpn.server,
+ org.eclipse.jetty.http.*,
+ org.eclipse.jetty.http2.*,
+ org.eclipse.jetty.io.*,
+ org.eclipse.jetty.jmx.*,
+ org.eclipse.jetty.security.*,
+ org.eclipse.jetty.session.*,
+ org.eclipse.jetty.server.*,
+ org.eclipse.jetty.util.*,
+ org.eclipse.jetty.ee10.servlet.*,
+
!org.eclipse.jetty.ee10.websocket.jakarta.*,
+ org.eclipse.jetty.ee10.websocket.*,
+ org.eclipse.jetty.websocket.*,
+ org.apache.felix.http.jetty,
+ org.apache.felix.http.jakartawrappers,
+ org.apache.felix.http.javaxwrappers
+ </Export-Package>
+ <Private-Package>
+ org.apache.felix.http.base.*,
+ org.apache.felix.http.jetty.*,
+ org.eclipse.jetty.version
+ </Private-Package>
+ <Conditional-Package>
+ org.apache.commons.*
+ </Conditional-Package>
+ <Import-Package>
+
org.eclipse.jetty.client;resolution:=optional,
+ sun.misc;resolution:=optional,
+ sun.nio.ch;resolution:=optional,
+ javax.imageio;resolution:=optional,
+ javax.sql;resolution:=optional,
+ org.ietf.jgss;resolution:=optional,
+
org.osgi.service.cm;resolution:=optional;version="[1.3,2)",
+
org.osgi.service.event;resolution:=optional;version="[1.2,2)",
+
org.osgi.service.log;resolution:=optional;version="[1.3,2)",
+
org.osgi.service.metatype;resolution:=optional;version="[1.1,2)",
+
org.osgi.service.useradmin;resolution:=optional;version="[1.1,2)",
+
org.osgi.service.http;version="[1.2.1,1.3)",
+
org.osgi.service.http.context;version="[1.1,1.2)",
+
org.osgi.service.http.runtime;version="[1.1,1.2)",
+
org.osgi.service.http.runtime.dto;version="[1.1,1.2)",
+ org.slf4j;version="[1.0,3.0)",
+ *
+ </Import-Package>
+ <DynamicImport-Package>
+ org.osgi.service.cm;version="[1.3,2)",
+ org.osgi.service.event;version="[1.2,2)",
+ org.osgi.service.log;version="[1.3,2)",
+ org.osgi.service.metatype;version="[1.4,2)"
+ </DynamicImport-Package>
+ <Provide-Capability>
+
osgi.implementation;osgi.implementation="osgi.http";version:Version="1.1";
+
uses:="javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard",
+
osgi.implementation;osgi.implementation="osgi.http";version:Version="2.0";
+
uses:="jakarta.servlet,jakarta.servlet.http,org.osgi.service.servlet.context,org.osgi.service.servlet.whiteboard",
+
osgi.service;objectClass:List<String>="org.osgi.service.servlet.runtime.HttpServiceRuntime";
+
uses:="org.osgi.service.servlet.runtime,org.osgi.service.servlet.runtime.dto",
+
osgi.service;objectClass:List<String>="org.osgi.service.http.runtime.HttpServiceRuntime";
+
uses:="org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto",
+
osgi.service;objectClass:List<String>="org.osgi.service.http.HttpService";
+ uses:="org.osgi.service.http",
+
osgi.serviceloader;osgi.serviceloader="org.eclipse.jetty.http.HttpFieldPreEncoder"
+ </Provide-Capability>
+ <Require-Capability>
+
osgi.contract;filter:="(&(osgi.contract=JavaServlet)(version=4.0))",
+
osgi.contract;filter:="(&(osgi.contract=JakartaServlet)(version=6.0))",
+
osgi.extender;filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional,
+
osgi.extender;filter:="(osgi.extender=osgi.serviceloader.processor)";resolution:=optional,
+
osgi.serviceloader;filter:="(osgi.serviceloader=org.eclipse.jetty.http.HttpFieldPreEncoder)";resolution:=optional;cardinality:=multiple,
+
osgi.serviceloader;filter:="(osgi.serviceloader=org.eclipse.jetty.io.ssl.ALPNProcessor$Server)";resolution:=optional;cardinality:=multiple
+ </Require-Capability>
+ <Include-Resource>
+
{maven-resources},${project.build.directory}/serviceloader-resources
+ </Include-Resource>
+ <_removeheaders>
+
Private-Package,Conditional-Package,Include-Resource
+ </_removeheaders>
+ </instructions>
+ </configuration>
+ </execution>
+ <execution>
+ <id>with-jakarta-ee10-websockets</id>
+ <goals>
+ <goal>bundle</goal>
+ </goals>
+ <configuration>
+
<classifier>with-jakarta-ee10-websockets</classifier>
+ <instructions>
+
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+
<Bundle-Version>${project.version}</Bundle-Version>
+ <X-Jetty-Version>
+ ${jetty.version}
+ </X-Jetty-Version>
+ <Bundle-Activator>
+
org.apache.felix.http.jetty.internal.JettyActivator
+ </Bundle-Activator>
+ <Export-Package>
+ org.osgi.service.http,
+ org.osgi.service.http.context,
+ org.osgi.service.http.runtime,
+ org.osgi.service.http.runtime.dto,
+ org.osgi.service.http.whiteboard,
+ org.osgi.service.servlet.context,
+ org.osgi.service.servlet.runtime,
+ org.osgi.service.servlet.runtime.dto,
+ org.osgi.service.servlet.whiteboard,
+ org.eclipse.jetty.alpn.server,
+ org.eclipse.jetty.http.*,
+ org.eclipse.jetty.http2.*,
+ org.eclipse.jetty.io.*,
+ org.eclipse.jetty.jmx.*,
+ org.eclipse.jetty.security.*,
+ org.eclipse.jetty.session.*,
+ org.eclipse.jetty.server.*,
+ org.eclipse.jetty.util.*,
+ !org.eclipse.jetty.ee10.websocket.server.*,
+
!org.eclipse.jetty.ee10.websocket.servlet.*,
+ org.eclipse.jetty.ee10.websocket.jakarta.*,
+ org.eclipse.jetty.ee10.servlet.*,
+ org.eclipse.jetty.websocket.*,
+ org.apache.felix.http.jetty,
+ org.apache.felix.http.jakartawrappers,
+ org.apache.felix.http.javaxwrappers
+ </Export-Package>
+ <Private-Package>
+ org.apache.felix.http.base.*,
+ org.apache.felix.http.jetty.*,
+ org.eclipse.jetty.version
+ </Private-Package>
+ <Conditional-Package>
+ org.apache.commons.*
+ </Conditional-Package>
+ <Import-Package>
+
org.eclipse.jetty.client;resolution:=optional,
+ sun.misc;resolution:=optional,
+ sun.nio.ch;resolution:=optional,
+ javax.imageio;resolution:=optional,
+ javax.sql;resolution:=optional,
+ org.ietf.jgss;resolution:=optional,
+
org.osgi.service.cm;resolution:=optional;version="[1.3,2)",
+
org.osgi.service.event;resolution:=optional;version="[1.2,2)",
+
org.osgi.service.log;resolution:=optional;version="[1.3,2)",
+
org.osgi.service.metatype;resolution:=optional;version="[1.1,2)",
+
org.osgi.service.useradmin;resolution:=optional;version="[1.1,2)",
+
org.osgi.service.http;version="[1.2.1,1.3)",
+
org.osgi.service.http.context;version="[1.1,1.2)",
+
org.osgi.service.http.runtime;version="[1.1,1.2)",
+
org.osgi.service.http.runtime.dto;version="[1.1,1.2)",
+ org.slf4j;version="[1.0,3.0)",
+ *
+ </Import-Package>
+ <DynamicImport-Package>
+ org.osgi.service.cm;version="[1.3,2)",
+ org.osgi.service.event;version="[1.2,2)",
+ org.osgi.service.log;version="[1.3,2)",
+ org.osgi.service.metatype;version="[1.4,2)"
+ </DynamicImport-Package>
+ <Provide-Capability>
+
osgi.implementation;osgi.implementation="osgi.http";version:Version="1.1";
+
uses:="javax.servlet,javax.servlet.http,org.osgi.service.http.context,org.osgi.service.http.whiteboard",
+
osgi.implementation;osgi.implementation="osgi.http";version:Version="2.0";
+
uses:="jakarta.servlet,jakarta.servlet.http,org.osgi.service.servlet.context,org.osgi.service.servlet.whiteboard",
+
osgi.service;objectClass:List<String>="org.osgi.service.servlet.runtime.HttpServiceRuntime";
+
uses:="org.osgi.service.servlet.runtime,org.osgi.service.servlet.runtime.dto",
+
osgi.service;objectClass:List<String>="org.osgi.service.http.runtime.HttpServiceRuntime";
+
uses:="org.osgi.service.http.runtime,org.osgi.service.http.runtime.dto",
+
osgi.service;objectClass:List<String>="org.osgi.service.http.HttpService";
+ uses:="org.osgi.service.http",
+
osgi.serviceloader;osgi.serviceloader="org.eclipse.jetty.http.HttpFieldPreEncoder"
+ </Provide-Capability>
+ <Require-Capability>
+
osgi.contract;filter:="(&(osgi.contract=JavaServlet)(version=4.0))",
+
osgi.contract;filter:="(&(osgi.contract=JakartaServlet)(version=6.0))",
+
osgi.extender;filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional,
+
osgi.extender;filter:="(osgi.extender=osgi.serviceloader.processor)";resolution:=optional,
+
osgi.serviceloader;filter:="(osgi.serviceloader=org.eclipse.jetty.http.HttpFieldPreEncoder)";resolution:=optional;cardinality:=multiple,
+
osgi.serviceloader;filter:="(osgi.serviceloader=org.eclipse.jetty.io.ssl.ALPNProcessor$Server)";resolution:=optional;cardinality:=multiple
+ </Require-Capability>
+ <Include-Resource>
+
{maven-resources},${project.build.directory}/serviceloader-resources
+ </Include-Resource>
+ <_removeheaders>
+
Private-Package,Conditional-Package,Include-Resource
+ </_removeheaders>
+ </instructions>
+ </configuration>
+ </execution>
</executions>
</plugin>
+
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <redirectTestOutputToFile>true</redirectTestOutputToFile>
+ </configuration>
+ </plugin>
+ <!-- plugins for paxexam integration tests -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>integration-test</id>
+ <phase>integration-test</phase>
+ <goals>
+ <goal>integration-test</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>verify</id>
+ <phase>integration-test</phase>
+ <goals>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <redirectTestOutputToFile>true</redirectTestOutputToFile>
+ <systemPropertyVariables>
+ <jetty.version>${jetty.version}</jetty.version>
+
<bundle.filename>${basedir}/target/${project.build.finalName}.jar</bundle.filename>
+ <pax.vm.options>${pax.vm.options}</pax.vm.options>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
</plugins>
</build>
@@ -366,11 +628,6 @@
<artifactId>jetty-ee10-servlet</artifactId>
<version>${jetty.version}</version>
</dependency>
- <dependency>
- <groupId>org.eclipse.jetty.ee10.websocket</groupId>
- <artifactId>jetty-ee10-websocket-jetty-server</artifactId>
- <version>${jetty.version}</version>
- </dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
@@ -417,19 +674,27 @@
<version>${jetty.version}</version>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty</groupId>
- <artifactId>jetty-session</artifactId>
+ <groupId>org.eclipse.jetty.ee10.websocket</groupId>
+ <artifactId>jetty-ee10-websocket-jakarta-server</artifactId>
+ <version>${jetty.version}</version>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.ee10.websocket</groupId>
+ <artifactId>jetty-ee10-websocket-jetty-server</artifactId>
<version>${jetty.version}</version>
+ <optional>true</optional>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.websocket</groupId>
- <artifactId>jetty-websocket-jetty-api</artifactId>
- <version>${jetty.version}</version>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>jetty-websocket-jetty-server</artifactId>
+ <version>${jetty.version}</version>
+ <optional>true</optional>
</dependency>
<dependency>
- <groupId>org.eclipse.jetty.websocket</groupId>
- <artifactId>jetty-websocket-jetty-server</artifactId>
- <version>${jetty.version}</version>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-session</artifactId>
+ <version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
@@ -463,6 +728,7 @@
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.5</version>
+ <optional>true</optional>
</dependency>
<dependency>
<groupId>commons-io</groupId>
@@ -488,5 +754,76 @@
<version>1.3.0</version>
<scope>test</scope>
</dependency>
+
+ <!-- an OSGi framework -->
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <version>7.0.5</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.inject</groupId>
+ <artifactId>javax.inject</artifactId>
+ <version>1</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- Pax Exam -->
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam</artifactId>
+ <version>${org.ops4j.pax.exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-cm</artifactId>
+ <version>${org.ops4j.pax.exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-container-forked</artifactId>
+ <version>${org.ops4j.pax.exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-junit4</artifactId>
+ <version>${org.ops4j.pax.exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-link-mvn</artifactId>
+ <version>${org.ops4j.pax.exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-client</artifactId>
+ <version>${jetty.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.websocket</groupId>
+ <artifactId>jetty-websocket-jetty-client</artifactId>
+ <version>${jetty.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <version>4.2.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>2.0.13</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git
a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java
b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java
index 917e334132..f12c62641c 100644
---
a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java
+++
b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java
@@ -489,6 +489,17 @@ class ConfigMetaTypeProvider implements MetaTypeProvider
"The format of the request log entries. Only relevant if
'Enable SLF4J Request Logging' is checked. Valid placeholders are described in
https://www.eclipse.org/jetty/documentation/jetty-11/operations-guide/index.html#og-module-requestlog",
CustomRequestLog.NCSA_FORMAT,
bundle.getBundleContext().getProperty(JettyConfig.FELIX_HTTP_REQUEST_LOG_FORMAT)));
+
+ adList.add(new
AttributeDefinitionImpl(JettyConfig.FELIX_JAKARTA_EE10_WEBSOCKET_ENABLE,
+ "Enable Jakarta EE10 standard WebSocket support",
+ "Whether to enable jakarta EE10 standard WebSocket support.
Default is false.",
+ false,
+
bundle.getBundleContext().getProperty(JettyConfig.FELIX_JAKARTA_EE10_WEBSOCKET_ENABLE)));
+ adList.add(new
AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_EE10_WEBSOCKET_ENABLE,
+ "Enable Jetty EE10 specific WebSocket support",
+ "Whether to enable jetty EE10 specific WebSocket support.
Default is false.",
+ false,
+
bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_EE10_WEBSOCKET_ENABLE)));
return new ObjectClassDefinition()
{
diff --git
a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
index 05e6c7f941..cb61ae0b8b 100644
---
a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
+++
b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
@@ -271,6 +271,12 @@ public final class JettyConfig
/** Felix specific property to specify the default protocol when
negotiation fails */
public static final String FELIX_JETTY_ALPN_DEFAULT_PROTOCOL =
"org.apache.felix.jetty.alpn.defaultProtocol";
+ /** Felix specific property to control whether to enable the standard
jakarta.websocket EE10 APIs provided by Jakarta WebSocket 2.1 */
+ public static final String FELIX_JAKARTA_EE10_WEBSOCKET_ENABLE =
"org.apache.felix.jakarta.ee10.websocket.enable";
+
+ /** Felix specific property to control whether to enable they
Jetty-specific EE10 WebSocket APIs */
+ public static final String FELIX_JETTY_EE10_WEBSOCKET_ENABLE =
"org.apache.felix.jetty.ee10.websocket.enable";
+
private static String validateContextPath(String ctxPath)
{
// undefined, empty, or root context path
@@ -677,6 +683,22 @@ public final class JettyConfig
return getLongProperty(FELIX_JETTY_STOP_TIMEOUT, -1l);
}
+ /**
+ * Returns <code>true</code> if jakarta EE10 websocket is configured to be
used (
+ * {@link #FELIX_JAKARTA_EE10_WEBSOCKET_ENABLE})
+ */
+ public boolean isUseJakartaEE10Websocket() {
+ return getBooleanProperty(FELIX_JAKARTA_EE10_WEBSOCKET_ENABLE, false);
+ }
+
+ /**
+ * Returns <code>true</code> if jetty websocket is configured to be used (
+ * {@link #FELIX_JETTY_EE10_WEBSOCKET_ENABLE})
+ */
+ public boolean isUseJettyEE10Websocket() {
+ return getBooleanProperty(FELIX_JETTY_EE10_WEBSOCKET_ENABLE, false);
+ }
+
public void reset()
{
update(null);
diff --git
a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
index f75a6af3d8..ea2eca3d32 100644
---
a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
+++
b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
@@ -253,7 +253,7 @@ public final class JettyService
loginService.setUserStore(new UserStore());
this.server.addBean(loginService);
- ServletContextHandler context = new
ServletContextHandler(this.config.getContextPath(),
+ ServletContextHandler context = new
ServletContextHandler(this.config.getContextPath(),
ServletContextHandler.SESSIONS);
this.parent = new ContextHandlerCollection(context);
@@ -309,8 +309,18 @@ public final class JettyService
this.server.setStopTimeout(this.config.getStopTimeout());
}
+ if (this.config.isUseJettyEE10Websocket()) {
+ maybeInitializeJettyEE10Websocket(context);
+ }
+
+ if (this.config.isUseJakartaEE10Websocket()) {
+ maybeInitializeJakartaEE10Websocket(context);
+ }
+
this.server.start();
+ maybeStoreWebSocketContainerAttributes(context);
+
// session id manager is only available after server is started
context.getSessionHandler().getSessionIdManager().getSessionHouseKeeper().setIntervalSec(
this.config.getLongProperty(JettyConfig.FELIX_JETTY_SESSION_SCAVENGING_INTERVAL,
@@ -478,6 +488,80 @@ public final class JettyService
return startConnector(connector);
}
+ /**
+ * Initialize the jakarta EE10 websocket support for the servlet context
handler.
+ * If the optional initializer class is not present then a warning will be
logged.
+ *
+ * @param handler the sevlet context handler to initialize
+ */
+ private void maybeInitializeJakartaEE10Websocket(ServletContextHandler
handler) {
+ if
(isClassNameVisible("org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer"))
{
+ // Ensure that JakartaWebSocketServletContainerInitializer is
initialized,
+ // to setup the ServerContainer for this web application context.
+
org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer.configure(handler,
null);
+ SystemLogger.LOGGER.info("Jakarta WebSocket EE10 servlet container
initialized");
+ } else {
+ SystemLogger.LOGGER.warn("Failed to initialize jakarta EE10
standard websocket support since the initializer class was not found. "
+ + "Check if the jetty-ee10-websocket-jakarta-server bundle
is deployed.");
+ }
+ }
+
+ /**
+ * Initialize the jetty EE10 websocket support for the servlet context
handler.
+ * If the optional initializer class is not present then a warning will be
logged.
+ *
+ * @param handler the sevlet context handler to initialize
+ */
+ private void maybeInitializeJettyEE10Websocket(ServletContextHandler
handler) {
+ if
(isClassNameVisible("org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer"))
{
+ // Ensure that JettyWebSocketServletContainerInitializer is
initialized,
+ // to setup the JettyWebSocketServerContainer for this web
application context.
+
org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer.configure(handler,
null);
+ SystemLogger.LOGGER.info("Jetty WebSocket EE10 servlet container
initialized");
+ } else {
+ SystemLogger.LOGGER.warn("Failed to initialize jetty EE10 specific
websocket support since the initializer class was not found. "
+ + "Check if the jetty-ee10-websocket-jetty-server bundle
is deployed.");
+ }
+ }
+
+ /**
+ * Based on the configuration, store the WebSocket container attributes
for the shared servlet context.
+ *
+ * @param context the context
+ */
+ private void maybeStoreWebSocketContainerAttributes(ServletContextHandler
context) {
+ // when the server is started, retrieve the container attribute and
+ // set it on the shared servlet context once available
+ if (this.config.isUseJettyEE10Websocket() &&
+
isClassNameVisible("org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer"))
{
+ String attribute =
org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer.JETTY_WEBSOCKET_CONTAINER_ATTRIBUTE;
+ this.controller.setAttributeSharedServletContext(attribute,
context.getServletContext().getAttribute(attribute));
+ }
+ if (this.config.isUseJakartaEE10Websocket() &&
+
isClassNameVisible("org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer"))
{
+ String attribute =
org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer.ATTR_JAKARTA_SERVER_CONTAINER;
+ this.controller.setAttributeSharedServletContext(attribute,
context.getServletContext().getAttribute(attribute));
+ }
+ }
+
+ /**
+ * Checks if an optional class name is visible to the bundle classloader
+ *
+ * @param className the class name to check
+ * @return true if the class is visible, false otherwise
+ */
+ private boolean isClassNameVisible(String className) {
+ boolean visible;
+ try {
+ // check if the class is visible to our classloader
+ getClass().getClassLoader().loadClass(className);
+ visible = true;
+ } catch (ClassNotFoundException e) {
+ visible = false;
+ }
+ return visible;
+ }
+
private void configureSslContextFactory(final SslContextFactory.Server
connector)
{
if (this.config.getKeystoreType() != null)
diff --git
a/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/AbstractJettyTestSupport.java
b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/AbstractJettyTestSupport.java
new file mode 100644
index 0000000000..5da84b6d4f
--- /dev/null
+++
b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/AbstractJettyTestSupport.java
@@ -0,0 +1,185 @@
+/*
+ * 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.felix.http.jetty.it;
+
+import static org.ops4j.pax.exam.CoreOptions.bundle;
+import static org.ops4j.pax.exam.CoreOptions.composite;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.keepCaches;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+import static org.ops4j.pax.exam.CoreOptions.vmOption;
+import static org.ops4j.pax.exam.CoreOptions.when;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.UUID;
+
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.options.ModifiableCompositeOption;
+import org.ops4j.pax.exam.options.OptionalCompositeOption;
+import org.ops4j.pax.exam.options.SystemPropertyOption;
+import org.ops4j.pax.exam.options.UrlProvisionOption;
+import org.ops4j.pax.exam.options.extra.VMOption;
+import org.ops4j.pax.exam.util.PathUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractJettyTestSupport {
+
+ protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+ private final String workingDirectory =
String.format("%s/target/paxexam/%s/%s", PathUtils.getBaseDir(),
getClass().getSimpleName(), UUID.randomUUID());
+
+ /**
+ * Provides a random path for a working directory below Maven's build
target directory.
+ *
+ * @return the absolute path for working directory
+ */
+ protected String workingDirectory() {
+ return workingDirectory;
+ }
+
+ @Configuration
+ public Option[] configuration() throws IOException {
+ final String vmOpt = System.getProperty("pax.vm.options");
+ VMOption vmOption = null;
+ if (vmOpt != null && !vmOpt.isEmpty()) {
+ vmOption = new VMOption(vmOpt);
+ }
+
+ final int httpPort = findFreePort();
+
+ return options(
+ composite(
+ when(vmOption != null).useOptions(vmOption),
+ failOnUnresolvedBundles(),
+ keepCaches(),
+ localMavenRepo(),
+ CoreOptions.workingDirectory(workingDirectory()),
+ optionalRemoteDebug(),
+
mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.http.servlet-api").version("3.0.0"),
+ testBundle("bundle.filename"),
+ junitBundles(),
+ awaitility(),
+
+ config(),
+ felixHttpConfig(httpPort)
+ ).add(
+ additionalOptions()
+ )
+ );
+ }
+
+ public static ModifiableCompositeOption awaitility() {
+ return composite(
+
mavenBundle().groupId("org.awaitility").artifactId("awaitility").version("4.2.1"),
+
mavenBundle().groupId("org.hamcrest").artifactId("hamcrest").version("2.2")
+ );
+ }
+
+ public static ModifiableCompositeOption config() {
+ return composite(
+
mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.configadmin").version("1.9.26")
+ );
+ }
+
+ protected Option felixHttpConfig(final int httpPort) {
+ return newConfiguration("org.apache.felix.http")
+ .put("org.osgi.service.http.port", httpPort)
+ .asOption();
+ }
+
+ protected Option[] additionalOptions() throws IOException { // NOSONAR
+ return new Option[]{};
+ }
+
+ /**
+ * Finds a free local port.
+ *
+ * @return the free local port
+ */
+ public static int findFreePort() {
+ try (ServerSocket serverSocket = new ServerSocket(0)) {
+ return serverSocket.getLocalPort();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Provides an option to set the System property {@code
pax.exam.osgi.unresolved.fail} to {@code "true"}.
+ *
+ * @return the property option
+ */
+ public static SystemPropertyOption failOnUnresolvedBundles() {
+ return systemProperty("pax.exam.osgi.unresolved.fail").value("true");
+ }
+
+ /**
+ * Reads the System property {@code maven.repo.local} and provides an
option to set the System property {@code org.ops4j.pax.url.mvn.localRepository}
when former is not empty.
+ *
+ * @return the property option
+ */
+ public static OptionalCompositeOption localMavenRepo() {
+ final String localRepository = System.getProperty("maven.repo.local",
""); // PAXEXAM-543
+ return when(!localRepository.isBlank()).useOptions(
+
systemProperty("org.ops4j.pax.url.mvn.localRepository").value(localRepository)
+ );
+ }
+
+ /**
+ * Reads the pathname of the test bundle from the given System property
and provides a provisioning option.
+ *
+ * @param systemProperty the System property which contains the pathname
of the test bundle
+ * @return the provisioning option
+ */
+ public static UrlProvisionOption testBundle(final String systemProperty) {
+ final String pathname = System.getProperty(systemProperty);
+ final File file = new File(pathname);
+ return bundle(file.toURI().toString());
+ }
+
+ /**
+ * Optionally configure remote debugging on the port supplied by the
"debugPort"
+ * system property.
+ */
+ protected ModifiableCompositeOption optionalRemoteDebug() {
+ VMOption option = null;
+ String property = System.getProperty("debugPort");
+ if (property != null) {
+ option =
vmOption(String.format("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=%s",
property));
+ }
+ return composite(option);
+ }
+
+ public static ModifiableCompositeOption spifly() {
+ return composite(
+
mavenBundle().groupId("org.apache.aries.spifly").artifactId("org.apache.aries.spifly.dynamic.bundle").version("1.3.7"),
+
mavenBundle().groupId("org.ow2.asm").artifactId("asm-analysis").version("9.7"),
+
mavenBundle().groupId("org.ow2.asm").artifactId("asm-commons").version("9.7"),
+
mavenBundle().groupId("org.ow2.asm").artifactId("asm-tree").version("9.7"),
+
mavenBundle().groupId("org.ow2.asm").artifactId("asm-util").version("9.7"),
+
mavenBundle().groupId("org.ow2.asm").artifactId("asm").version("9.7")
+ );
+ }
+}
\ No newline at end of file
diff --git
a/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JakartaEE10SpecificWebsocketIT.java
b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JakartaEE10SpecificWebsocketIT.java
new file mode 100644
index 0000000000..1f19a4ad39
--- /dev/null
+++
b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JakartaEE10SpecificWebsocketIT.java
@@ -0,0 +1,211 @@
+/*
+ * 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.felix.http.jetty.it;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
+
+import java.io.IOException;
+import java.net.URI;
+import java.time.Duration;
+import java.util.Hashtable;
+import java.util.Map;
+
+import javax.inject.Inject;
+import jakarta.servlet.Servlet;
+import jakarta.servlet.ServletConfig;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.websocket.ClientEndpoint;
+import jakarta.websocket.DeploymentException;
+import jakarta.websocket.OnMessage;
+import jakarta.websocket.OnOpen;
+import jakarta.websocket.Session;
+import jakarta.websocket.WebSocketContainer;
+import jakarta.websocket.server.ServerContainer;
+import jakarta.websocket.server.ServerEndpoint;
+
+import org.awaitility.Awaitility;
+import
org.eclipse.jetty.ee10.websocket.jakarta.client.JakartaWebSocketClientContainerProvider;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.servlet.whiteboard.HttpWhiteboardConstants;
+
+/**
+ *
+ */
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class JakartaEE10SpecificWebsocketIT extends AbstractJettyTestSupport {
+
+ @Inject
+ protected BundleContext bundleContext;
+
+ @Override
+ protected Option[] additionalOptions() throws IOException {
+ String jettyVersion = System.getProperty("jetty.version", "12.0.8");
+ return new Option[]{
+ spifly(),
+
+ // bundles for the server side
+
mavenBundle().groupId("jakarta.websocket").artifactId("jakarta.websocket-api").version("2.1.1"),
+
mavenBundle().groupId("jakarta.websocket").artifactId("jakarta.websocket-client-api").version("2.1.1"),
+
mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-client").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-client").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.ee10").artifactId("jetty-ee10-webapp").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("jetty-websocket-core-client").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("jetty-websocket-core-common").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("jetty-websocket-core-server").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.ee10.websocket").artifactId("jetty-ee10-websocket-jakarta-client").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.ee10.websocket").artifactId("jetty-ee10-websocket-jakarta-common").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.ee10.websocket").artifactId("jetty-ee10-websocket-jakarta-server").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.ee10.websocket").artifactId("jetty-ee10-websocket-servlet").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-xml").version(jettyVersion)
+ };
+ }
+
+ @Override
+ protected Option felixHttpConfig(int httpPort) {
+ return newConfiguration("org.apache.felix.http")
+ .put("org.osgi.service.http.port", httpPort)
+ .put("org.apache.felix.jakarta.ee10.websocket.enable", true)
+ .asOption();
+ }
+
+ @Test
+ public void testWebSocketConversation() throws Exception {
+ assertNotNull(bundleContext);
+ bundleContext.registerService(Servlet.class, new
MyWebSocketInitServlet(), new Hashtable<>(Map.of(
+ HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN,
"/mywebsocket1"
+ )));
+
+ WebSocketContainer container =
JakartaWebSocketClientContainerProvider.getContainer(null);
+
+ // Create client side endpoint
+ MyClientWebSocket clientEndpoint = new MyClientWebSocket();
+
+ // Attempt Connect
+ Object value =
bundleContext.getServiceReference(HttpService.class).getProperty("org.osgi.service.http.port");
+ int httpPort = Integer.parseInt((String) value);
+ URI destUri = new URI(String.format("ws://localhost:%d/mywebsocket1",
httpPort));
+ try (Session session = container.connectToServer(clientEndpoint,
destUri)) {
+
+ // send a message from the client to the server
+ clientEndpoint.sendMessage("Hello WebSocket");
+
+ // wait for the async response from the server
+ Awaitility.await("waitForResponse")
+ .atMost(Duration.ofSeconds(30))
+ .pollDelay(Duration.ofMillis(200))
+ .until(() -> clientEndpoint.getLastMessage() != null);
+ assertEquals("Hello WebSocket", clientEndpoint.getLastMessage());
+ }
+ }
+
+ /**
+ * A servlet that declares the websocket during init
+ */
+ private static final class MyWebSocketInitServlet extends HttpServlet {
+ private static final long serialVersionUID = -6893620059263229183L;
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ super.init(config);
+
+ // Lookup the ServletContext for the context path where the
websocket server is attached.
+ ServletContext servletContext = config.getServletContext();
+
+ // Retrieve the ServerContainer from the ServletContext attributes.
+ ServerContainer container = (ServerContainer)
servletContext.getAttribute(ServerContainer.class.getName());
+
+ // Configure the ServerContainer.
+ container.setDefaultMaxTextMessageBufferSize(128 * 1024);
+
+ // Simple registration of your WebSocket endpoints.
+ try {
+ container.addEndpoint(MyServerWebSocket.class);
+ } catch (DeploymentException e) {
+ throw new ServletException(e);
+ }
+ }
+ }
+
+ /**
+ * WebSocket handler for the client side
+ */
+ @ClientEndpoint
+ public static class MyClientWebSocket {
+ private Session session;
+ private String lastMessage;
+
+ public String getLastMessage() {
+ return lastMessage;
+ }
+
+ @OnOpen
+ public void onConnect(Session session) {
+ this.session = session;
+ }
+
+ /**
+ * Send a message to the server side
+ *
+ * @param msg the message to send
+ */
+ public void sendMessage(String msg) throws IOException {
+ this.session.getBasicRemote().sendText(msg);
+ }
+
+ /**
+ * Receive a message from the server side
+ *
+ * @param msg the message
+ */
+ @OnMessage
+ public void onMessage(String msg) {
+ lastMessage = msg;
+ }
+ }
+
+ /**
+ * WebSocket handler for the server side
+ */
+ @ServerEndpoint(value = "/mywebsocket1")
+ public static class MyServerWebSocket {
+ /**
+ * Receive message sent from the client
+ *
+ * @param session the session
+ * @param message the message
+ */
+ @OnMessage
+ public void onText(Session session, String message) throws IOException
{
+ // echo a response back to the client
+ session.getBasicRemote().sendText(message);
+ }
+ }
+
+}
\ No newline at end of file
diff --git
a/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettyEE10SpecificWebsocketIT.java
b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettyEE10SpecificWebsocketIT.java
new file mode 100644
index 0000000000..72fe774fcf
--- /dev/null
+++
b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettyEE10SpecificWebsocketIT.java
@@ -0,0 +1,209 @@
+/*
+ * 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.felix.http.jetty.it;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
+
+import java.io.IOException;
+import java.net.URI;
+import java.time.Duration;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import javax.inject.Inject;
+import jakarta.servlet.Servlet;
+import jakarta.servlet.ServletConfig;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+
+import org.awaitility.Awaitility;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
+import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer;
+import org.eclipse.jetty.websocket.api.Callback;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen;
+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.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.servlet.whiteboard.HttpWhiteboardConstants;
+
+/**
+ *
+ */
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class JettyEE10SpecificWebsocketIT extends AbstractJettyTestSupport {
+
+ @Inject
+ protected BundleContext bundleContext;
+
+ @Override
+ protected Option[] additionalOptions() throws IOException {
+ String jettyVersion = System.getProperty("jetty.version", "12.0.8");
+ return new Option[] {
+ spifly(),
+
+ // bundles for the server side
+
mavenBundle().groupId("org.eclipse.jetty.ee10").artifactId("jetty-ee10-webapp").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("jetty-websocket-core-common").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("jetty-websocket-core-server").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("jetty-websocket-jetty-api").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("jetty-websocket-jetty-common").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("jetty-websocket-jetty-server").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.ee10.websocket").artifactId("jetty-ee10-websocket-servlet").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.ee10.websocket").artifactId("jetty-ee10-websocket-jetty-server").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-xml").version(jettyVersion),
+
+ // additional bundles for the client side
+
mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-client").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-client").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("jetty-websocket-core-client").version(jettyVersion),
+
mavenBundle().groupId("org.eclipse.jetty.websocket").artifactId("jetty-websocket-jetty-client").version(jettyVersion)
+ };
+ }
+
+ @Override
+ protected Option felixHttpConfig(int httpPort) {
+ return newConfiguration("org.apache.felix.http")
+ .put("org.osgi.service.http.port", httpPort)
+ .put("org.apache.felix.jetty.ee10.websocket.enable", true)
+ .asOption();
+ }
+
+
+ @Test
+ public void testWebSocketConversation() throws Exception {
+ assertNotNull(bundleContext);
+ bundleContext.registerService(Servlet.class, new
MyWebSocketInitServlet(), new Hashtable<>(Map.of(
+ HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN,
"/mywebsocket1"
+ )));
+
+ HttpClientTransportOverHTTP transport = new
HttpClientTransportOverHTTP();
+ HttpClient httpClient = new
org.eclipse.jetty.client.HttpClient(transport);
+ WebSocketClient webSocketClient = new WebSocketClient(httpClient);
+ webSocketClient.start();
+
+ Object value =
bundleContext.getServiceReference(HttpService.class).getProperty("org.osgi.service.http.port");
+ int httpPort = Integer.parseInt((String)value);
+ URI destUri = new URI(String.format("ws://localhost:%d/mywebsocket1",
httpPort));
+
+ MyClientWebSocket clientWebSocket = new MyClientWebSocket();
+ ClientUpgradeRequest request = new ClientUpgradeRequest();
+ CompletableFuture<Session> future =
webSocketClient.connect(clientWebSocket, destUri, request);
+ Session session = future.get();
+ assertNotNull(session);
+
+ // send a message from the client to the server
+ clientWebSocket.sendMessage("Hello WebSocket");
+
+ // wait for the async response from the server
+ Awaitility.await("waitForResponse")
+ .atMost(Duration.ofSeconds(30))
+ .pollDelay(Duration.ofMillis(200))
+ .until(() -> clientWebSocket.getLastMessage() != null);
+ assertEquals("Hello WebSocket", clientWebSocket.getLastMessage());
+ }
+
+ /**
+ * A servlet that declares the websocket during init
+ */
+ private static final class MyWebSocketInitServlet extends HttpServlet {
+ private static final long serialVersionUID = -6893620059263229183L;
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ super.init(config);
+
+ // Lookup the ServletContext for the context path where the
websocket server is attached.
+ ServletContext servletContext = config.getServletContext();
+
+ // Retrieve the JettyWebSocketServerContainer.
+ JettyWebSocketServerContainer container =
JettyWebSocketServerContainer.getContainer(servletContext);
+ assertNotNull(container);
+ container.addMapping("/mywebsocket1", (upgradeRequest,
upgradeResponse) -> new MyServerWebSocket());
+ }
+ }
+
+ /**
+ * WebSocket handler for the client side
+ */
+ @WebSocket()
+ public static class MyClientWebSocket {
+ private Session session;
+ private String lastMessage;
+
+ public String getLastMessage() {
+ return lastMessage;
+ }
+
+ @OnWebSocketOpen
+ public void onConnect(Session session) {
+ this.session = session;
+ }
+
+ /**
+ * Send a message to the server side
+ * @param msg the message to send
+ */
+ public void sendMessage(String msg) {
+ this.session.sendText(msg, Callback.NOOP);
+ }
+
+ /**
+ * Receive a message from the server side
+ * @param msg the message
+ */
+ @OnWebSocketMessage
+ public void onMessage(String msg) {
+ lastMessage = msg;
+ }
+ }
+
+ /**
+ * WebSocket handler for the server side
+ */
+ @WebSocket()
+ public static class MyServerWebSocket {
+ /**
+ * Receive message sent from the client
+ *
+ * @param session the session
+ * @param message the message
+ */
+ @OnWebSocketMessage
+ public void onText(Session session, String message) {
+ // echo a response back to the client
+ session.sendText(message, Callback.NOOP);
+ }
+ }
+
+}
\ No newline at end of file
diff --git
a/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/MissingWebsocketDependenciesIT.java
b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/MissingWebsocketDependenciesIT.java
new file mode 100644
index 0000000000..7bb6bf43fc
--- /dev/null
+++
b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/MissingWebsocketDependenciesIT.java
@@ -0,0 +1,91 @@
+/*
+ * 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.felix.http.jetty.it;
+
+import static org.junit.Assert.assertTrue;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.time.Duration;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+
+import org.awaitility.Awaitility;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+import org.osgi.framework.BundleContext;
+
+/**
+ *
+ */
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class MissingWebsocketDependenciesIT extends AbstractJettyTestSupport {
+
+ @Inject
+ protected BundleContext bundleContext;
+
+ @Override
+ protected Option felixHttpConfig(int httpPort) {
+ return newConfiguration("org.apache.felix.http")
+ .put("org.osgi.service.http.port", httpPort)
+ .put("org.apache.felix.jetty.ee10.websocket.enable", true)
+ .put("org.apache.felix.jakarta.ee10.websocket.enable", true)
+ .asOption();
+ }
+
+ @Test
+ public void testMissingDepencencyWarningLogs() throws Exception {
+ // should have warnings in the log file output
+ File logFile = new
File("target/failsafe-reports/org.apache.felix.http.jetty.it.MissingWebsocketDependenciesIT-output.txt");
+ assertTrue(logFile.exists());
+
+ // wait for the log buffer to be written to the file
+ Awaitility.await("waitForLogs")
+ .atMost(Duration.ofSeconds(50))
+ .pollDelay(Duration.ofMillis(200))
+ .until(() -> containsString(logFile,
"org.apache.felix.http.jetty12[org.apache.felix.http]"));
+
+ assertTrue(containsString(logFile,
"org.apache.felix.http.jetty12[org.apache.felix.http] : Failed to "
+ + "initialize jetty EE10 specific websocket support since the
initializer class was not found. "
+ + "Check if the jetty-ee10-websocket-jetty-server bundle is
deployed."));
+ assertTrue(containsString(logFile,
"org.apache.felix.http.jetty12[org.apache.felix.http] : Failed to "
+ + "initialize jakarta EE10 standard websocket support since
the initializer class was not found. "
+ + "Check if the jetty-ee10-websocket-jakarta-server bundle is
deployed."));
+ }
+
+ /**
+ * Checks if the text is present in the file
+ *
+ * @param file the file to check
+ * @param expected the text to look for
+ * @return true if the text was found, false otherwise
+ */
+ private boolean containsString(File file, String expected) throws
IOException {
+ try (Stream<String> stream = Files.lines(file.toPath())) {
+ return stream.anyMatch(line -> line.contains(expected));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/http/pom.xml b/http/pom.xml
index 8684e405ff..97f623fe7b 100644
--- a/http/pom.xml
+++ b/http/pom.xml
@@ -44,6 +44,7 @@
<module>inventoryprinter</module>
<module>itest</module>
<module>jetty</module>
+ <module>jetty12</module>
<module>proxy</module>
<module>samples/whiteboard</module>
<module>servlet-api</module>
diff --git a/http/samples/whiteboard/pom.xml b/http/samples/whiteboard/pom.xml
index 4f40bff0ce..4f7b63d825 100644
--- a/http/samples/whiteboard/pom.xml
+++ b/http/samples/whiteboard/pom.xml
@@ -32,16 +32,16 @@
<version>3.0.0-SNAPSHOT</version>
<packaging>bundle</packaging>
- <properties>
- <jetty.version>12.0.8</jetty.version>
- </properties>
-
<scm>
<connection>scm:git:https://github.com/apache/felix-dev.git</connection>
<developerConnection>scm:git:https://github.com/apache/felix-dev.git</developerConnection>
<url>https://gitbox.apache.org/repos/asf?p=felix-dev.git</url>
</scm>
+ <properties>
+ <jetty.version>12.0.8</jetty.version>
+ </properties>
+
<build>
<plugins>
<plugin>
@@ -105,6 +105,18 @@
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${jetty.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.ee10</groupId>
+ <artifactId>jetty-ee10-servlet</artifactId>
+ <version>${jetty.version}</version>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>org.eclipse.jetty.ee10.websocket</groupId>
<artifactId>jetty-ee10-websocket-jetty-server</artifactId>
diff --git
a/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/Activator.java
b/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/Activator.java
index 8e4346641c..6963791264 100644
---
a/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/Activator.java
+++
b/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/Activator.java
@@ -77,21 +77,40 @@ public final class Activator
context.registerService(Filter.class, filter2, filter2Props);
/**
- * Register a WebSocket servlet on /filtersample/websocket/*.
+ * Register WebSocket servlet on /websocketservlet/*.
+ * Do note that the path the servlet is registered to is not reflected
in the WebSocket URL.
+ * This is due to the way of registering the WebSocket code.
* In the Chrome Console, this snippet can be used to send a message
to the WebSocket:
*
- * const websocket = new
WebSocket("ws://localhost:8080/filtersample/websocket/example");
- * websocket.send("test");
+ * const websocket = new
WebSocket("ws://localhost:8080/websocket/example");
+ * websocket.send("test from websocket");
*
- * This will log "test" to the stdout.
+ * This will log "test from websocket" to the stdout.
*/
- final TestWebSocketServlet webSocketServlet = new
TestWebSocketServlet("websocketservlet1");
+ final TestWebSocketServlet webSocketServlet = new
TestWebSocketServlet("websocket1");
final Dictionary<String, Object> webSocketServletProps = new
Hashtable<>();
-
webSocketServletProps.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN,
"/websocket/*");
+
webSocketServletProps.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN,
"/websocketservlet/*");
webSocketServletProps.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
"(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME +
"=filtersample)");
context.registerService(Servlet.class, webSocketServlet,
webSocketServletProps);
+ /**
+ * Register another WebSocket servlet on /websocketservlet2/*.
+ * Do note that the path the servlet is registered to _is_ reflected
in the WebSocket URL.
+ * This is due to the way of registering the WebSocket code.
+ * In the Chrome Console, this snippet can be used to send a message
to the WebSocket:
+ *
+ * const websocket = new
WebSocket("ws://localhost:8080/filtersample/websocketservlet2/example");
+ * websocket.send("test from websocket");
+ *
+ * This will log "test from websocket" to the stdout.
+ */
+ final TestWebSocketServletAlternative webSocketServlet2 = new
TestWebSocketServletAlternative("websocket2");
+ final Dictionary<String, Object> webSocketServletProps2 = new
Hashtable<>();
+
webSocketServletProps2.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN,
"/websocketservlet2/*");
+
webSocketServletProps2.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
+ "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME +
"=filtersample)");
+ context.registerService(Servlet.class, webSocketServlet2,
webSocketServletProps2);
}
@Override
diff --git
a/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/FelixJettyWebSocketServlet.java
b/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/FelixJettyWebSocketServlet.java
index a8ed768291..55af0f1863 100644
---
a/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/FelixJettyWebSocketServlet.java
+++
b/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/FelixJettyWebSocketServlet.java
@@ -17,82 +17,32 @@
package org.apache.felix.http.samples.whiteboard;
import java.io.IOException;
-import java.lang.reflect.Proxy;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
-import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
-import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
-import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer;
import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServlet;
-import
org.eclipse.jetty.ee10.websocket.server.internal.JettyServerFrameHandlerFactory;
-import org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter;
-import org.eclipse.jetty.websocket.core.server.WebSocketMappings;
-import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents;
/**
* Abstract class that hides all Jetty Websocket specifics and provides a way
for the developer to focus on the actual WebSocket implementation.
- * @author paulrutters
*/
public abstract class FelixJettyWebSocketServlet extends JettyWebSocketServlet
{
-
private final AtomicBoolean myFirstInitCall = new AtomicBoolean(true);
private final CountDownLatch myInitBarrier = new CountDownLatch(1);
- private ServletContext myProxiedContext;
- private ServletContextHandler myServletContextHandler;
- @Override
- public void init() throws ServletException {
- // Init, delaying init call until service method is called...
+ public final void init() {
+ // nothing, see delayed init below in service method
+ // this is a workaround as stated in
https://issues.apache.org/jira/browse/FELIX-5310
}
@Override
- public void destroy() {
- // only call destroy when the servlet has been initialized
- if (!myFirstInitCall.get()) {
- // This is required because WebSocketServlet needs to have it's
destroy() method called as well
- // Causes NPE otherwise when calling an WS endpoint
- super.destroy();
- }
- }
-
-
- // This is a workaround required for WebSockets to work in Jetty12, see
- // https://www.eclipse.org/forums/index.php/t/1110140/
- @Override
- public synchronized ServletContext getServletContext() {
- if (myProxiedContext == null) {
- myProxiedContext = (ServletContext)
Proxy.newProxyInstance(JettyWebSocketServlet.class.getClassLoader(),
- new Class[]{ServletContext.class}, (proxy, method,
methodArgs) -> {
- final ServletContext osgiServletContext =
super.getServletContext();
- if (!"getAttribute".equals(method.getName())) {
- return method.invoke(osgiServletContext,
methodArgs);
- }
-
- final String name = (String) methodArgs[0];
- Object value = osgiServletContext.getAttribute(name);
- if (value == null && myProxiedContext != null) {
- final ServletContext jettyServletContext =
myServletContextHandler.getServletContext();
- value = jettyServletContext.getAttribute(name);
- }
- return value;
- });
- }
-
- return myProxiedContext;
- }
-
- @Override
- protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
+ public void service(final ServletRequest req, final ServletResponse res)
throws ServletException, IOException {
if (myFirstInitCall.compareAndSet(true, false)) {
try {
- delayedInit();
- } catch (Exception e) {
- System.err.println("Error delayed init: " + e.getMessage());
+ super.init();
} finally {
myInitBarrier.countDown();
}
@@ -104,42 +54,19 @@ public abstract class FelixJettyWebSocketServlet extends
JettyWebSocketServlet {
}
}
- // Call JettyWebSocketServlet service method to handle upgrade requests
- super.service(req, resp);
- }
-
- private void delayedInit() throws ServletException {
- // Make sure WebSockets are enabled in Jetty12
- ensureWebSocketsInitialized();
-
- // Overide the TCCL so that the internal factory can be found
- // Jetty tries to use ServiceLoader, and their fallback is to
- // use TCCL, it would be better if we could provide a loader...
- final Thread currentThread = Thread.currentThread();
- final ClassLoader tccl = currentThread.getContextClassLoader();
-
currentThread.setContextClassLoader(JettyWebSocketServlet.class.getClassLoader());
- try {
- super.init();
- } finally {
- currentThread.setContextClassLoader(tccl);
- }
+ super.service(req, res);
}
- private void ensureWebSocketsInitialized() {
- final ServletContext osgiServletContext = getServletContext();
- myServletContextHandler =
ServletContextHandler.getServletContextHandler(osgiServletContext,
"WebSockets");
-
- final JettyWebSocketServerContainer serverContainer =
JettyWebSocketServerContainer
- .getContainer(osgiServletContext);
- if (serverContainer == null) {
- // Ensure WebSocket components are initialized in Jetty12
- final ServletContext jettyServletContext =
myServletContextHandler.getServletContext();
-
WebSocketServerComponents.ensureWebSocketComponents(myServletContextHandler.getServer(),
- myServletContextHandler);
- WebSocketUpgradeFilter.ensureFilter(jettyServletContext);
- WebSocketMappings.ensureMappings(myServletContextHandler);
- JettyServerFrameHandlerFactory.getFactory(jettyServletContext);
- JettyWebSocketServerContainer.ensureContainer(jettyServletContext);
+ /**
+ * Cleanup method.
+ */
+ @Override
+ public final void destroy() {
+ // only call destroy when the servlet has been initialized
+ if (!myFirstInitCall.get()) {
+ // This is required because WebSocketServlet needs to have it's
destroy() method called as well
+ // Causes NPE otherwise when calling an WS endpoint
+ super.destroy();
}
}
}
diff --git
a/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServlet.java
b/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServlet.java
index b09b7f6bbb..7408c319db 100644
---
a/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServlet.java
+++
b/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServlet.java
@@ -17,9 +17,11 @@
package org.apache.felix.http.samples.whiteboard;
import jakarta.servlet.ServletConfig;
+import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
-import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServletFactory;
+import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer;
import org.eclipse.jetty.websocket.api.Callback;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
@@ -28,29 +30,37 @@ import
org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
-public class TestWebSocketServlet extends FelixJettyWebSocketServlet {
+/**
+ * Example of a WebSocket servlet that uses the Jetty WebSocket API.
+ * It does not respect the path this servlet is registered to, but requires no
further workarounds.
+ * Setting `org.apache.felix.jetty.ee10.websocket.enable=true` is enough.
+ */
+public class TestWebSocketServlet extends HttpServlet {
private final String name;
public TestWebSocketServlet(String name) {
this.name = name;
}
+ private void doLog(String message) {
+ System.out.println("## [" + this.name + "] " + message);
+ }
+
+
@Override
public void init(ServletConfig config) throws ServletException {
doLog("Init with config [" + config + "]");
super.init(config);
- }
- private void doLog(String message) {
- System.out.println("## [" + this.name + "] " + message);
- }
+ // Lookup the ServletContext for the context path where the websocket
server is attached.
+ ServletContext servletContext = config.getServletContext();
- @Override
- protected void configure(JettyWebSocketServletFactory
jettyWebSocketServletFactory) {
- doLog("Configuring WebSocket factory");
- jettyWebSocketServletFactory.register(TestWebSocket.class);
+ // Retrieve the JettyWebSocketServerContainer.
+ JettyWebSocketServerContainer container =
JettyWebSocketServerContainer.getContainer(servletContext);
+ container.addMapping("/websocket/*", (upgradeRequest, upgradeResponse)
-> new TestWebSocket());
}
+
@WebSocket
public static class TestWebSocket {
@OnWebSocketMessage
@@ -80,4 +90,4 @@ public class TestWebSocketServlet extends
FelixJettyWebSocketServlet {
System.out.println("## [" + this.getClass() + "] " + message);
}
}
-}
+}
\ No newline at end of file
diff --git
a/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServlet.java
b/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServletAlternative.java
similarity index 82%
copy from
http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServlet.java
copy to
http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServletAlternative.java
index b09b7f6bbb..df980c4ea2 100644
---
a/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServlet.java
+++
b/http/samples/whiteboard/src/main/java/org/apache/felix/http/samples/whiteboard/TestWebSocketServletAlternative.java
@@ -28,27 +28,31 @@ import
org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
-public class TestWebSocketServlet extends FelixJettyWebSocketServlet {
+/**
+ * Example of a WebSocket servlet that uses the Jetty WebSocket API, and is
registered by extending JettyWebSocketServlet.
+ * It does respect the path this servlet is registered to, but requires a
further workaround. See FelixJettyWebSocketServlet.
+ * Requires setting `org.apache.felix.jetty.ee10.websocket.enable=true`.
+ */
+public class TestWebSocketServletAlternative extends
FelixJettyWebSocketServlet {
private final String name;
- public TestWebSocketServlet(String name) {
+ public TestWebSocketServletAlternative(String name) {
this.name = name;
}
+ private void doLog(String message) {
+ System.out.println("## [" + this.name + "] " + message);
+ }
+
@Override
public void init(ServletConfig config) throws ServletException {
doLog("Init with config [" + config + "]");
super.init(config);
}
- private void doLog(String message) {
- System.out.println("## [" + this.name + "] " + message);
- }
-
@Override
- protected void configure(JettyWebSocketServletFactory
jettyWebSocketServletFactory) {
- doLog("Configuring WebSocket factory");
- jettyWebSocketServletFactory.register(TestWebSocket.class);
+ protected void configure(JettyWebSocketServletFactory factory) {
+ factory.register(TestWebSocket.class);
}
@WebSocket
@@ -80,4 +84,4 @@ public class TestWebSocketServlet extends
FelixJettyWebSocketServlet {
System.out.println("## [" + this.getClass() + "] " + message);
}
}
-}
+}
\ No newline at end of file