Skip to content

Commit

Permalink
Issue #5229 - WebSocket documentation.
Browse files Browse the repository at this point in the history
Updates after review.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
  • Loading branch information
sbordet committed Sep 6, 2021
1 parent 9fb6f99 commit cd75747
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 28 deletions.
Expand Up @@ -14,9 +14,12 @@
[[pg-server-websocket-configure-filter]]
==== Advanced `WebSocketUpgradeFilter` Configuration

The WebSocket ``ServletContainerInitializer``s, namely `JavaxWebSocketServletContainerInitializer` and `JettyWebSocketServletContainerInitializer`, install the `WebSocketUpgradeFilter` to handle HTTP requests that upgrade to WebSocket.
The `WebSocketUpgradeFilter` that handles the HTTP requests that upgrade to WebSocket is installed in these cases:

Typically, the `WebSocketUpgradeFilter` is not present in the `web.xml` configuration, and therefore the WebSocket ``ServletContainerInitializer``s 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 `/*`.
* 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.

Expand Down
Expand Up @@ -14,8 +14,8 @@
[[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 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).
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:

Expand Down Expand Up @@ -46,8 +46,8 @@ The `websocket-jetty-api` artifact and the `websocket-jetty-server` artifact (an

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`, so that WebSocket applications can use it to register xref:pg-websocket-endpoints[WebSocket endpoints] that implement your application logic.
. Use the `JettyWebSocketServerContainer` APIs to xref:pg-server-websocket-jetty-endpoints[register your WebSocket endpoints].
. 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`
Expand Down Expand Up @@ -78,15 +78,15 @@ Once you have xref:pg-server-websocket-jetty-container[setup] the `JettyWebSocke

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`].

[NOTE]
[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 using the standard `javax.websocket` APIs (see xref:pg-server-websocket-standard-endpoints[here]).
* xref:pg-server-websocket-jetty-endpoints-servlet[Using `JettyWebSocketServlet`], which may be more straightforward to configure in `web.xml`.
* 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`
Expand Down Expand Up @@ -114,19 +114,35 @@ include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/We

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, `JettyWebSocketServerContainer` 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].
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.
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 hoods, because the WebSocket upgrade is handled directly by your `JettyWebSocketServlet` subclass.
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`).

This may also have a performance benefit for non-WebSocket HTTP requests (as they will not pass through the `WebSocketUpgradeFilter`), and be simpler to configure in `web.xml` (for example, it is easier to configure the `CrossOriginFilter`, see also xref:pg-server-websocket-configure-filter[this section] for more information).
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:
For example, your `JettyWebSocketServlet` subclass may be declared in code in this way:

[source,java,indent=0]
----
Expand All @@ -141,21 +157,21 @@ include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/We
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.

[TIP]
====
It is 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.
====

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 it is a valid upgrade to WebSocket.
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 an 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.
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.
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 that it is possible, although not recommended, to use both `JettyWebSocketServerContainer` and `JettyWebSocketServlet`.
[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` will install the `WebSocketUpgradeFilter` under the hoods, which by default will intercepts all HTTP requests to upgrade to WebSocket.
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.
Expand Up @@ -63,8 +63,8 @@ The `javax.websocket-api` artifact and the `websocket-javax-server` artifact (an

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`, so that WebSocket applications can use it to register xref:pg-websocket-endpoints[WebSocket endpoints] that implement your application logic.
. Use the `ServerContainer` APIs to xref:pg-server-websocket-standard-endpoints[register your WebSocket endpoints].
. Make sure that Jetty xref:pg-server-websocket-standard-container[sets up] an instance of `javax.websocket.server.ServerContainer`.
. Use the `ServerContainer` APIs in your applications to xref:pg-server-websocket-standard-endpoints[register your WebSocket endpoints] that implement your application logic.

[[pg-server-websocket-standard-container]]
===== Setting Up `ServerContainer`
Expand Down Expand Up @@ -98,7 +98,7 @@ The WebSocket endpoints classes may be either annotated with the standard `javax
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-servlet-context[`ServletContextHandler`], or you need to perform more advanced configuration of the `ServerContainer` or of the WebSocket endpoints, you need to access the `ServerContainer` APIs.
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`:

Expand Down
Expand Up @@ -13,9 +13,12 @@

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;
Expand All @@ -26,6 +29,7 @@
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;
Expand Down Expand Up @@ -255,6 +259,38 @@ public void jettyContainerAndEndpoints() throws Exception
// 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[]
Expand Down

0 comments on commit cd75747

Please sign in to comment.