diff --git a/documentation/jetty-documentation/pom.xml b/documentation/jetty-documentation/pom.xml index bc7e62058411..eb6358c97ed1 100644 --- a/documentation/jetty-documentation/pom.xml +++ b/documentation/jetty-documentation/pom.xml @@ -209,6 +209,7 @@ org.eclipse.jetty jetty-slf4j-impl ${project.version} + runtime org.eclipse.jetty.memcached @@ -225,5 +226,15 @@ websocket-jetty-client ${project.version} + + org.eclipse.jetty.websocket + websocket-javax-server + ${project.version} + + + org.eclipse.jetty.websocket + websocket-jetty-server + ${project.version} + diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-filter.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-filter.adoc new file mode 100644 index 000000000000..bcd2ad62bfbd --- /dev/null +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-filter.adoc @@ -0,0 +1,82 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[pg-server-websocket-configure-filter]] +==== Advanced `WebSocketUpgradeFilter` Configuration + +The `WebSocketUpgradeFilter` that handles the HTTP requests that upgrade to WebSocket is installed in these cases: + +* Either by the `JavaxWebSocketServletContainerInitializer`, as described in xref:pg-server-websocket-standard[this section]. +* Or by a call to `JettyWebSocketServerContainer.addMapping(\...)`, as described in xref:pg-server-websocket-jetty[this section]. + +Typically, the `WebSocketUpgradeFilter` is not present in the `web.xml` configuration, and therefore the mechanisms above create a new `WebSocketUpgradeFilter` and install it _before_ any other Filter declared in `web.xml`, under the default name of `"org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter"` and with path mapping `/*`. + +However, if the `WebSocketUpgradeFilter` is already present in `web.xml` under the default name, then the ``ServletContainerInitializer``s will use that declared in `web.xml` instead of creating a new one. + +This allows you to customize: + +* The filter order; for example, by configuring the `CrossOriginFilter` (or other filters) for increased security or authentication _before_ the `WebSocketUpgradeFilter`. +* The `WebSocketUpgradeFilter` configuration via ``init-param``s, that affects all `Session` instances created by this filter. +* The `WebSocketUpgradeFilter` path mapping. Rather than the default mapping of `+/*+`, you can map the `WebSocketUpgradeFilter` to a more specific path such as `+/ws/*+`. +* The possibility to have multiple ``WebSocketUpgradeFilter``s, mapped to different paths, each with its own configuration. + +For example: + +[source,xml,subs=verbatim] +---- + + + + My WebSocket WebApp + + + + cross-origin + org.eclipse.jetty.servlets.CrossOriginFilter + true + + + cross-origin + /* + + + + + + org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter + org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter + + + maxTextMessageSize + 1048576 + + true + + + org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter + + /ws/* + + + +---- +<1> The `CrossOriginFilter` is the first to protect against link:https://owasp.org/www-community/attacks/csrf[cross-site request forgery attacks]. +<2> The configuration for the _default_ `WebSocketUpgradeFilter`. +<3> Note the use of the _default_ `WebSocketUpgradeFilter` name. +<4> Specific configuration for `WebSocketUpgradeFilter` parameters. +<5> Use a more specific path mapping for `WebSocketUpgradeFilter`. + +Note that using a more specific path mapping for WebSocket requests is also beneficial to the performance of normal HTTP requests: they do not go through the `WebSocketUpgradeFilter` (as they will not match its path mapping), saving the cost of analyzing them to see whether they are WebSocket upgrade requests or not. diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-jetty.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-jetty.adoc new file mode 100644 index 000000000000..d04e3fc84485 --- /dev/null +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-jetty.adoc @@ -0,0 +1,177 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[pg-server-websocket-jetty]] +==== Jetty APIs Implementation + +When you write a WebSocket application using the Jetty WebSocket APIs, your code typically need to depend on just the Jetty WebSocket APIs to compile your application. +However, at runtime you need to have the _implementation_ of the Jetty WebSocket APIs in your class-path (or module-path). + +Jetty's WebSocket APIs are provided by the following Maven artifact: + +[source,xml,subs=normal] +---- + + org.eclipse.jetty.websocket + websocket-jetty-api + {version} + +---- + +Jetty's implementation of the Jetty WebSocket APIs is provided by the following Maven artifact (and its transitive dependencies): + +[source,xml,subs=normal] +---- + + org.eclipse.jetty.websocket + websocket-jetty-server + {version} + +---- + +[NOTE] +==== +The `websocket-jetty-api` artifact and the `websocket-jetty-server` artifact (and its transitive dependencies) should be present in the server class-path (or module-path), and never in the web application's `/WEB-INF/lib` directory. +==== + +To configure correctly your WebSocket application based on the Jetty WebSocket APIs, you need two steps: + +. Make sure that Jetty xref:pg-server-websocket-jetty-container[sets up] an instance of `JettyWebSocketServerContainer`. +. Use the `JettyWebSocketServerContainer` APIs in your applications to xref:pg-server-websocket-jetty-endpoints[register your WebSocket endpoints] that implement your application logic. + +[[pg-server-websocket-jetty-container]] +===== Setting up `JettyWebSocketServerContainer` + +Jetty sets up a `JettyWebSocketServerContainer` instance using `JettyWebSocketServletContainerInitializer`. + +When you deploy web applications using xref:pg-server-http-handler-use-webapp-context[`WebAppContext`], then `JettyWebSocketServletContainerInitializer` is automatically discovered and initialized by Jetty when the web application starts, so that it sets up the `JettyWebSocketServerContainer`. +In this way, you do not need to write any additional code: + +[source,java,indent=0] +---- +include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=standardContainerWebAppContext] +---- + +On the other hand, when you deploy web applications using xref:pg-server-http-handler-use-servlet-context[`ServletContextHandler`], you have to write the code to ensure that the `JettyWebSocketServletContainerInitializer` is initialized, so that it sets up the `JettyWebSocketServerContainer`: + +[source,java,indent=0] +---- +include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=jettyContainerServletContextHandler] +---- + +Calling `JettyWebSocketServletContainerInitializer.configure(\...)` must be done _before_ the `ServletContextHandler` is started, and configures the Jetty WebSocket implementation for that web application context. + +[[pg-server-websocket-jetty-endpoints]] +===== Configuring Endpoints + +Once you have xref:pg-server-websocket-jetty-container[setup] the `JettyWebSocketServerContainer`, you can configure your xref:pg-websocket-endpoints[WebSocket endpoints]. + +Differently from the xref:pg-server-websocket-standard-endpoints[configuration of standard WebSocket endpoints], WebSocket endpoint classes may be annotated with Jetty WebSocket API annotations, or extend the `org.eclipse.jetty.websocket.api.WebSocketListener` interface, but they are not automatically discovered, not even when deploying web applications using xref:pg-server-http-handler-use-webapp-context[`WebAppContext`]. + +[IMPORTANT] +==== +When using the Jetty WebSocket APIs, WebSocket endpoints must always be explicitly configured. +==== + +There are two ways of configuring WebSocket endpoints when using the Jetty WebSocket APIs: + +* xref:pg-server-websocket-jetty-endpoints-container[Using `JettyWebSocketServerContainer`], which is very similar to how WebSocket endpoints are configured when using the xref:pg-server-websocket-standard-endpoints[standard `javax.websocket` APIs], but also provides APIs to perform a direct, programmatic, WebSocket upgrade. +* xref:pg-server-websocket-jetty-endpoints-servlet[Using `JettyWebSocketServlet`], which may configured in `web.xml`, rather than in Java code. + +[[pg-server-websocket-jetty-endpoints-container]] +====== Using `JettyWebSocketServerContainer` + +To register WebSocket endpoints using the Jetty WebSocket APIs you need to access the `JettyWebSocketServerContainer` APIs. + +The `JettyWebSocketServerContainer` instance is stored in the `ServletContext`, so it can be retrieved when the `ServletContext` is initialized, either from a `ServletContextListener` or from a `HttpServlet`: + +[source,java,indent=0] +---- +include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=jettyEndpointsInitialization] +---- + +[source,java,indent=0] +---- +include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=jettyWebSocketInitializerServlet] +---- + +You can also use this variant to set up the `JettyWebSocketServerContainer` and configure the WebSocket endpoints in one step: + +[source,java,indent=0] +---- +include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=jettyContainerAndEndpoints] +---- + +When the `ServletContextHandler` is started, the `Configurator` lambda (the second parameter passed to `JettyWebSocketServletContainerInitializer.configure(\...)`) is invoked and allows you to explicitly configure the WebSocket endpoints using the Jetty WebSocket APIs provided by `JettyWebSocketServerContainer`. + +Under the hood, the call to `JettyWebSocketServerContainer.addMapping(\...)` installs the `org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter`, which is the component that intercepts HTTP requests to upgrade to WebSocket, described in xref:pg-server-websocket-standard-upgrade[this section]. +For more information about the `WebSocketUpgradeFilter` see also xref:pg-server-websocket-configure-filter[this section]. + +One last alternative to register your WebSocket endpoints is to use a programmatic WebSocket upgrade via `JettyWebSocketServerContainer.upgrade(\...)`, which allows you to use a standard `HttpServlet` subclass (rather than a `JettyWebSocketServlet` as explained in xref:pg-server-websocket-jetty-endpoints-servlet[this section]) to perform a direct WebSocket upgrade when your application logic demands so: + +[source,java,indent=0] +---- +include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=jettyContainerServletContextHandler] +---- + +[source,java,indent=0] +---- +include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=jettyContainerUpgrade] +---- + +When using `JettyWebSocketServerContainer.upgrade(\...)`, the `WebSocketUpgradeFilter` is not installed, since the WebSocket upgrade is performed programmatically. + +[[pg-server-websocket-jetty-endpoints-servlet]] +====== Using `JettyWebSocketServlet` + +An alternative way to register WebSocket endpoints using the Jetty WebSocket APIs is to use a `JettyWebSocketServlet` subclass (or even many different `JettyWebSocketServlet` subclasses). + +This method has the advantage that it does not install the `WebSocketUpgradeFilter` under the hood, because the WebSocket upgrade is handled directly by your `JettyWebSocketServlet` subclass. +This may also have a performance benefit for non-WebSocket HTTP requests (as they will not pass through the `WebSocketUpgradeFilter`). + +Your `JettyWebSocketServlet` subclass may be declared and configured either in code or in `web.xml`. +Declaring your `JettyWebSocketServlet` subclass explicitly in code or in `web.xml` also simplifies the declaration and configuration of other web components such as other Servlets and/or Filters (for example, it is easier to configure the `CrossOriginFilter`, see also xref:pg-server-websocket-configure-filter[this section] for more information). + +For example, your `JettyWebSocketServlet` subclass may be declared in code in this way: + +[source,java,indent=0] +---- +include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=jettyWebSocketServletMain] +---- + +[source,java,indent=0] +---- +include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=jettyWebSocketServlet] +---- + +Note how in the call to `JettyWebSocketServletContainerInitializer.configure(\...)` the second parameter is `null`, because WebSocket endpoints are not created here, but instead by one (or more) `JettyWebSocketServlet` subclasses. +Yet the call is necessary to create other WebSocket implementation components that are necessary also when using `JettyWebSocketServlet` subclasses. + +An HTTP upgrade request to WebSocket that matches your `JettyWebSocketServlet` subclass path mapping (specified above via `ServletContextHandler.addServlet(\...)`) arrives at the Servlet and is inspected to verify whether it is a valid upgrade to WebSocket. + +If the HTTP request is a valid upgrade to WebSocket, `JettyWebSocketServlet` calls `configure(JettyWebSocketServletFactory factory)` that you have overridden in your subclass, so that your application can instantiate and return the WebSocket endpoint. +After having obtained the WebSocket endpoint, `JettyWebSocketServlet` performs the WebSocket upgrade. +From this point on, the communication happens with the WebSocket protocol, and HTTP components such as Filters and Servlets are not relevant anymore. + +If the HTTP request is not an upgrade to WebSocket, `JettyWebSocketServlet` delegates the processing to the superclass, `javax.servlet.HttpServlet`, which in turn invokes methods such as `doGet(\...)` or `doPost(\...)` depending on the HTTP method. +If your `JettyWebSocketServlet` subclass did not override the `doXYZ(\...)` method corresponding to the HTTP request, a `405 Method Not Allowed` response is returned to the client, as per the standard `HttpServlet` class implementation. + +[NOTE] +==== +It is possible to use both `JettyWebSocketServerContainer` and `JettyWebSocketServlet`. + +However, it is typically best to avoid mixing the use of `JettyWebSocketServerContainer` with the use of `JettyWebSocketServlet`, so that all your WebSocket endpoints are initialized by the same code in one place only. +==== + +Using `JettyWebSocketServerContainer.addMapping(\...)` will install the `WebSocketUpgradeFilter` under the hood, which by default will intercepts all HTTP requests to upgrade to WebSocket. +However, as explained in xref:pg-server-websocket-standard-upgrade[this section], if `WebSocketUpgradeFilter` does not find a matching WebSocket endpoint for the request URI path, then the HTTP request is passed to the Filter chain of your web application and may arrive to your `JettyWebSocketServlet` subclass, where it would be processed and possibly result in a WebSocket upgrade. diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-standard.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-standard.adoc new file mode 100644 index 000000000000..3d69d07cdf7c --- /dev/null +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-standard.adoc @@ -0,0 +1,141 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +[[pg-server-websocket-standard]] +==== Standard APIs Implementation + +When you write a WebSocket application using the standard `javax.websocket` APIs, your code typically need to depend on just the APIs to compile your application. +However, at runtime you need to have an implementation of the standard APIs in your class-path (or module-path). + +The standard `javax.websocket` APIs are provided by the following Maven artifact: + +[source,xml,subs=normal] +---- + + javax.websocket + javax.websocket-api + 1.1 + +---- + +However, the artifact above lacks a proper JPMS `module-info.class` file, and therefore it is a little more difficult to use if you want to use of JPMS for your application. + +If you want to use JPMS for your application, you can use this Maven artifact instead: + +[source,xml,subs=normal] +---- + + org.eclipse.jetty.toolchain + jetty-javax-websocket-api + 1.1.2 + +---- + +This artifact is nothing more than the `javax.websocket:javax.websocket-api:1.1` artifact repackaged with a proper `module-info.class` file. + +At runtime, you also need an implementation of the standard `javax.websocket` APIs. + +Jetty's implementation of the standard `javax.websocket` APIs is provided by the following Maven artifact (and its transitive dependencies): + +[source,xml,subs=normal] +---- + + org.eclipse.jetty.websocket + websocket-javax-server + {version} + +---- + +[NOTE] +==== +The `javax.websocket-api` artifact and the `websocket-javax-server` artifact (and its transitive dependencies) should be present in the server class-path (or module-path), and never in the web application's `/WEB-INF/lib` directory. +==== + +To configure correctly your WebSocket application based on the standard `javax.websocket` APIs, you need two steps: + +. Make sure that Jetty xref:pg-server-websocket-standard-container[sets up] an instance of `javax.websocket.server.ServerContainer`. +. xref:pg-server-websocket-standard-endpoints[Configure] the WebSocket endpoints that implement your application logic, either by annotating their classes with the standard `javax.websocket` annotations, or by using the `ServerContainer` APIs to register them in your code. + +[[pg-server-websocket-standard-container]] +===== Setting Up `ServerContainer` + +Jetty sets up a `ServerContainer` instance using `JavaxWebSocketServletContainerInitializer`. + +When you deploy web applications using xref:pg-server-http-handler-use-webapp-context[`WebAppContext`], then `JavaxWebSocketServletContainerInitializer` is automatically discovered and initialized by Jetty when the web application starts, so that it sets up the `ServerContainer`. +In this way, you do not need to write any additional code: + +[source,java,indent=0] +---- +include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=standardContainerWebAppContext] +---- + +On the other hand, when you deploy web applications using xref:pg-server-http-handler-use-servlet-context[`ServletContextHandler`], you have to write the code to ensure that the `JavaxWebSocketServletContainerInitializer` is initialized, so that it sets up the `ServerContainer`: + +[source,java,indent=0] +---- +include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=standardContainerServletContextHandler] +---- + +Calling `JavaxWebSocketServletContainerInitializer.configure(\...)` must be done _before_ the `ServletContextHandler` is started, and configures the `javax.websocket` implementation for that web application context. + +[[pg-server-websocket-standard-endpoints]] +===== Configuring Endpoints + +Once you have xref:pg-server-websocket-standard-container[setup] the `ServerContainer`, you can configure your xref:pg-websocket-endpoints[WebSocket endpoints]. + +The WebSocket endpoints classes may be either annotated with the standard `javax.websocket` annotations, extend the `javax.websocket.Endpoint` abstract class, or implement the `javax.websocket.server.ServerApplicationConfig` interface. + +When you deploy web applications using xref:pg-server-http-handler-use-webapp-context[`WebAppContext`], then annotated WebSocket endpoint classes are automatically discovered and registered. +In this way, you do not need to write any additional code; you just need to ensure that your WebSocket endpoint classes are present in the web application's `/WEB-INF/classes` directory, or in a `*.jar` file in `/WEB-INF/lib`. + +On the other hand, when you deploy web applications using xref:pg-server-http-handler-use-webapp-context[`WebAppContext`] but you need to perform more advanced configuration of the `ServerContainer` or of the WebSocket endpoints, or when you deploy web applications using xref:pg-server-http-handler-use-servlet-context[`ServletContextHandler`], you need to access the `ServerContainer` APIs. + +The `ServerContainer` instance is stored as a `ServletContext` attribute, so it can be retrieved when the `ServletContext` is initialized, either from a `ServletContextListener` or from a `HttpServlet`: + +[source,java,indent=0] +---- +include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=standardEndpointsInitialization] +---- + +[source,java,indent=0] +---- +include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=standardWebSocketInitializerServlet] +---- + +When you deploy web applications using xref:pg-server-http-handler-use-servlet-context[`ServletContextHandler`], you can also use this variant to set up the `ServerContainer` and configure the WebSocket endpoints in one step: + +[source,java,indent=0] +---- +include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=standardContainerAndEndpoints] +---- + +When the `ServletContextHandler` is started, the `Configurator` lambda (the second parameter passed to `JavaxWebSocketServletContainerInitializer.configure(\...)`) is invoked and allows you to explicitly configure the WebSocket endpoints using the standard APIs provided by `ServerContainer`. + +[[pg-server-websocket-standard-upgrade]] +====== Upgrade to WebSocket + +Under the hood, `JavaxWebSocketServletContainerInitializer` installs the `org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter`, which is the component that intercepts HTTP requests to upgrade to WebSocket, and performs the upgrade from the HTTP protocol to the WebSocket protocol. + +[NOTE] +==== +The `WebSocketUpgradeFilter` is installed under the filter name corresponding to its class name (that is, the string `"org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter"`) and with a filter mapping of `/*`. + +Refer to the xref:pg-server-websocket-configure-filter[advanced `WebSocketUpgradeFilter` configuration section] for more information. +==== + +With the default configuration, every HTTP request flows first through the `WebSocketUpgradeFilter`. + +If the HTTP request is a valid upgrade to WebSocket, then `WebSocketUpgradeFilter` tries to find a matching WebSocket endpoint for the request URI path; if the match is found, `WebSocketUpgradeFilter` performs the upgrade and does not invoke any other Filter or Servlet. +From this point on, the communication happens with the WebSocket protocol, and HTTP components such as Filters and Servlets are not relevant anymore. + +If the HTTP request is not an upgrade to WebSocket, or `WebSocketUpgradeFilter` did not find a matching WebSocket endpoint for the request URI path, then the request is passed to the Filter chain of your web application, and eventually the request arrives to a Servlet to be processed (otherwise a `404 Not Found` response is returned to client). diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket.adoc index 2bc35d4b8683..6db4ab470669 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket.adoc @@ -12,6 +12,32 @@ // [[pg-server-websocket]] -=== WebSocket Server Libraries +=== WebSocket Server -TODO +Jetty provides two API implementations of the WebSocket protocol: + +* An implementation for the standard `javax.websocket` APIs provided by link:https://www.jcp.org/en/jsr/detail?id=356[JSR 356], described in xref:pg-server-websocket-standard[this section]. +* An implementation for Jetty-specific WebSocket APIs, described in xref:pg-server-websocket-jetty[this section]. + +Using the standard `javax.websocket` APIs allows your applications to depend only on standard APIs, and your applications may be deployed in any compliant WebSocket Container that supports JSR 356. + +The standard APIs provide few features that are not present in the Jetty WebSocket APIs: + +* Encoders and Decoders for automatic conversion of text or binary messages to objects. +* `Reader` and `InputStream` for simple, blocking, message streaming. +* Simple URI template matching. + +On the other hand, the Jetty WebSocket APIs are more efficient and offer greater and more fine-grained control, and provide features that are not present in the standard APIs: + +* Suspend/resume to control backpressure. +* Remote socket address (IP address and port) information. +* WebSocket upgrade handling via Filter or Servlet. +* Advanced URI matching with Servlet WebSocket upgrade. +* Control of the idle timeout. +* Configuration of the network buffer capacity. + +If your application needs specific features that are not provided by the standard APIs, the Jetty WebSocket APIs may provide such features -- and if they do not, you may ask for these features by submitting an issue to the Jetty Project without waiting for the standard process to approve them. + +include::server-websocket-standard.adoc[] +include::server-websocket-jetty.adoc[] +include::server-websocket-filter.adoc[] diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java new file mode 100644 index 000000000000..51b24422c22c --- /dev/null +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java @@ -0,0 +1,351 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.docs.programming.server.websocket; + +import java.io.IOException; +import java.util.List; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpoint; +import javax.websocket.server.ServerEndpointConfig; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; +import org.eclipse.jetty.websocket.server.JettyWebSocketCreator; +import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; +import org.eclipse.jetty.websocket.server.JettyWebSocketServlet; +import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory; +import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; + +@SuppressWarnings("unused") +public class WebSocketServerDocs +{ + public void standardContainerWebAppContext() throws Exception + { + // tag::standardContainerWebAppContext[] + // Create a Server with a ServerConnector listening on port 8080. + Server server = new Server(8080); + + // Create a WebAppContext with the given context path. + WebAppContext handler = new WebAppContext("/path/to/webapp", "/ctx"); + server.setHandler(handler); + + // Starting the Server will start the WebAppContext. + server.start(); + // end::standardContainerWebAppContext[] + } + + public void standardContainerServletContextHandler() throws Exception + { + // tag::standardContainerServletContextHandler[] + // Create a Server with a ServerConnector listening on port 8080. + Server server = new Server(8080); + + // Create a ServletContextHandler with the given context path. + ServletContextHandler handler = new ServletContextHandler(server, "/ctx"); + server.setHandler(handler); + + // Ensure that JavaxWebSocketServletContainerInitializer is initialized, + // to setup the ServerContainer for this web application context. + JavaxWebSocketServletContainerInitializer.configure(handler, null); + + // Starting the Server will start the ServletContextHandler. + server.start(); + // end::standardContainerServletContextHandler[] + } + + public void standardEndpointsInitialization() throws Exception + { + // tag::standardEndpointsInitialization[] + // Create a Server with a ServerConnector listening on port 8080. + Server server = new Server(8080); + + // Create a ServletContextHandler with the given context path. + ServletContextHandler handler = new ServletContextHandler(server, "/ctx"); + server.setHandler(handler); + + // Ensure that JavaxWebSocketServletContainerInitializer is initialized, + // to setup the ServerContainer for this web application context. + JavaxWebSocketServletContainerInitializer.configure(handler, null); + + // Add a WebSocket-initializer Servlet to register WebSocket endpoints. + handler.addServlet(MyJavaxWebSocketInitializerServlet.class, "/*"); + + // Starting the Server will start the ServletContextHandler. + server.start(); + // end::standardEndpointsInitialization[] + } + + @SuppressWarnings("InnerClassMayBeStatic") + // tag::standardWebSocketInitializerServlet[] + public class MyJavaxWebSocketInitializerServlet extends HttpServlet + { + @Override + public void init() throws ServletException + { + try + { + // Retrieve the ServerContainer from the ServletContext attributes. + ServerContainer container = (ServerContainer)getServletContext().getAttribute(ServerContainer.class.getName()); + + // Configure the ServerContainer. + container.setDefaultMaxTextMessageBufferSize(128 * 1024); + + // Simple registration of your WebSocket endpoints. + container.addEndpoint(MyJavaxWebSocketEndPoint.class); + + // Advanced registration of your WebSocket endpoints. + container.addEndpoint( + ServerEndpointConfig.Builder.create(MyJavaxWebSocketEndPoint.class, "/ws") + .subprotocols(List.of("my-ws-protocol")) + .build() + ); + } + catch (DeploymentException x) + { + throw new ServletException(x); + } + } + } + // end::standardWebSocketInitializerServlet[] + + public void standardContainerAndEndpoints() throws Exception + { + // tag::standardContainerAndEndpoints[] + // Create a Server with a ServerConnector listening on port 8080. + Server server = new Server(8080); + + // Create a ServletContextHandler with the given context path. + ServletContextHandler handler = new ServletContextHandler(server, "/ctx"); + server.setHandler(handler); + + // Setup the ServerContainer and the WebSocket endpoints for this web application context. + JavaxWebSocketServletContainerInitializer.configure(handler, (servletContext, container) -> + { + // Configure the ServerContainer. + container.setDefaultMaxTextMessageBufferSize(128 * 1024); + + // Simple registration of your WebSocket endpoints. + container.addEndpoint(MyJavaxWebSocketEndPoint.class); + + // Advanced registration of your WebSocket endpoints. + container.addEndpoint( + ServerEndpointConfig.Builder.create(MyJavaxWebSocketEndPoint.class, "/ws") + .subprotocols(List.of("my-ws-protocol")) + .build() + ); + }); + + // Starting the Server will start the ServletContextHandler. + server.start(); + // end::standardContainerAndEndpoints[] + } + + public void jettyContainerServletContextHandler() throws Exception + { + // tag::jettyContainerServletContextHandler[] + // Create a Server with a ServerConnector listening on port 8080. + Server server = new Server(8080); + + // Create a ServletContextHandler with the given context path. + ServletContextHandler handler = new ServletContextHandler(server, "/ctx"); + server.setHandler(handler); + + // Ensure that JettyWebSocketServletContainerInitializer is initialized, + // to setup the JettyWebSocketServerContainer for this web application context. + JettyWebSocketServletContainerInitializer.configure(handler, null); + + // Starting the Server will start the ServletContextHandler. + server.start(); + // end::jettyContainerServletContextHandler[] + } + + public void jettyEndpointsInitialization() throws Exception + { + // tag::jettyEndpointsInitialization[] + // Create a Server with a ServerConnector listening on port 8080. + Server server = new Server(8080); + + // Create a ServletContextHandler with the given context path. + ServletContextHandler handler = new ServletContextHandler(server, "/ctx"); + server.setHandler(handler); + + // Ensure that JettyWebSocketServletContainerInitializer is initialized, + // to setup the JettyWebSocketServerContainer for this web application context. + JettyWebSocketServletContainerInitializer.configure(handler, null); + + // Add a WebSocket-initializer Servlet to register WebSocket endpoints. + handler.addServlet(MyJettyWebSocketInitializerServlet.class, "/*"); + + // Starting the Server will start the ServletContextHandler. + server.start(); + // end::jettyEndpointsInitialization[] + } + + @SuppressWarnings("InnerClassMayBeStatic") + // tag::jettyWebSocketInitializerServlet[] + public class MyJettyWebSocketInitializerServlet extends HttpServlet + { + @Override + public void init() throws ServletException + { + // Retrieve the JettyWebSocketServerContainer. + JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(getServletContext()); + + // Configure the JettyWebSocketServerContainer. + container.setMaxTextMessageSize(128 * 1024); + + // Simple registration of your WebSocket endpoints. + container.addMapping("/ws/myURI", MyJettyWebSocketEndPoint.class); + + // Advanced registration of your WebSocket endpoints. + container.addMapping("/ws/myOtherURI", (upgradeRequest, upgradeResponse) -> + new MyOtherJettyWebSocketEndPoint() + ); + } + } + // end::jettyWebSocketInitializerServlet[] + + public void jettyContainerAndEndpoints() throws Exception + { + // tag::jettyContainerAndEndpoints[] + // Create a Server with a ServerConnector listening on port 8080. + Server server = new Server(8080); + + // Create a ServletContextHandler with the given context path. + ServletContextHandler handler = new ServletContextHandler(server, "/ctx"); + server.setHandler(handler); + + // Setup the JettyWebSocketServerContainer and the WebSocket endpoints for this web application context. + JettyWebSocketServletContainerInitializer.configure(handler, (servletContext, container) -> + { + // Configure the ServerContainer. + container.setMaxTextMessageSize(128 * 1024); + + // Add your WebSocket endpoint(s) to the JettyWebSocketServerContainer. + container.addMapping("/ws/myURI", MyJettyWebSocketEndPoint.class); + + // Use JettyWebSocketCreator to have more control on the WebSocket endpoint creation. + container.addMapping("/ws/myOtherURI", (upgradeRequest, upgradeResponse) -> + { + // Possibly inspect the upgrade request and modify the upgrade response. + upgradeResponse.setAcceptedSubProtocol("my-ws-protocol"); + + // Create the new WebSocket endpoint. + return new MyOtherJettyWebSocketEndPoint(); + }); + }); + + // Starting the Server will start the ServletContextHandler. + server.start(); + // end::jettyContainerAndEndpoints[] + } + + @SuppressWarnings("InnerClassMayBeStatic") + // tag::jettyContainerUpgrade[] + public class ProgrammaticWebSocketUpgradeServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException + { + if (requiresWebSocketUpgrade(request)) + { + // Retrieve the JettyWebSocketServerContainer. + JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(getServletContext()); + + // Use a JettyWebSocketCreator to inspect the upgrade request, + // possibly modify the upgrade response, and create the WebSocket endpoint. + JettyWebSocketCreator creator = (upgradeRequest, upgradeResponse) -> new MyJettyWebSocketEndPoint(); + + // Perform the direct WebSocket upgrade. + container.upgrade(creator, request, response); + } + else + { + // Normal handling of the HTTP request/response. + } + } + } + // end::jettyContainerUpgrade[] + + private boolean requiresWebSocketUpgrade(HttpServletRequest request) + { + return false; + } + + public void jettyWebSocketServletMain() throws Exception + { + // tag::jettyWebSocketServletMain[] + // Create a Server with a ServerConnector listening on port 8080. + Server server = new Server(8080); + + // Create a ServletContextHandler with the given context path. + ServletContextHandler handler = new ServletContextHandler(server, "/ctx"); + server.setHandler(handler); + + // Setup the JettyWebSocketServerContainer to initialize WebSocket components. + JettyWebSocketServletContainerInitializer.configure(handler, null); + + // Add your WebSocketServlet subclass to the ServletContextHandler. + handler.addServlet(MyJettyWebSocketServlet.class, "/ws/*"); + + // Starting the Server will start the ServletContextHandler. + server.start(); + // end::jettyWebSocketServletMain[] + } + + @SuppressWarnings("InnerClassMayBeStatic") + // tag::jettyWebSocketServlet[] + public class MyJettyWebSocketServlet extends JettyWebSocketServlet + { + @Override + protected void configure(JettyWebSocketServletFactory factory) + { + // At most 1 MiB text messages. + factory.setMaxTextMessageSize(1048576); + + // Add the WebSocket endpoint. + factory.addMapping("/ws/someURI", (upgradeRequest, upgradeResponse) -> + { + // Possibly inspect the upgrade request and modify the upgrade response. + + // Create the new WebSocket endpoint. + return new MyJettyWebSocketEndPoint(); + }); + } + } + // end::jettyWebSocketServlet[] + + @ServerEndpoint("/ws") + private static class MyJavaxWebSocketEndPoint + { + } + + @WebSocket + private static class MyJettyWebSocketEndPoint + { + } + + @WebSocket + private static class MyOtherJettyWebSocketEndPoint + { + } +} diff --git a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketMappings.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketMappings.java index 7f1a6be3df44..dc72d911fb30 100644 --- a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketMappings.java +++ b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketMappings.java @@ -26,6 +26,7 @@ import org.eclipse.jetty.http.pathmap.ServletPathSpec; import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.websocket.core.Configuration; @@ -223,17 +224,15 @@ public WebSocketNegotiator getMatchedNegotiator(String target, Consumer { - // Store PathSpec resource mapping as request attribute, for WebSocketCreator - // implementors to use later if they wish + // Store PathSpec resource mapping as request attribute, + // for WebSocketCreator implementors to use later if they wish. request.setAttribute(PathSpec.class.getName(), pathSpec); }); diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketUpgradeFilter.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketUpgradeFilter.java index 810049d3f5d1..e945491746e2 100644 --- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketUpgradeFilter.java +++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketUpgradeFilter.java @@ -143,7 +143,7 @@ public void lifeCycleStopping(LifeCycle event) } private final Configuration.ConfigurationCustomizer defaultCustomizer = new Configuration.ConfigurationCustomizer(); - private WebSocketMappings mapping; + private WebSocketMappings mappings; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException @@ -151,7 +151,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha HttpServletRequest httpreq = (HttpServletRequest)request; HttpServletResponse httpresp = (HttpServletResponse)response; - if (mapping.upgrade(httpreq, httpresp, defaultCustomizer)) + if (mappings.upgrade(httpreq, httpresp, defaultCustomizer)) return; // If we reach this point, it means we had an incoming request to upgrade @@ -173,13 +173,13 @@ public String dump() @Override public void dump(Appendable out, String indent) throws IOException { - Dumpable.dumpObjects(out, indent, this, mapping); + Dumpable.dumpObjects(out, indent, this, mappings); } @Override public void init(FilterConfig config) throws ServletException { - mapping = WebSocketMappings.ensureMappings(config.getServletContext()); + mappings = WebSocketMappings.ensureMappings(config.getServletContext()); String max = config.getInitParameter("idleTimeout"); if (max == null)