WebSocket Server
Jetty provides different implementations of the WebSocket protocol:
-
A Jakarta EE 8 (
javax.websocket
) implementation, based on the Jakarta WebSocket 1.1 Specification. -
A Jakarta EE 9 (
jakarta.websocket
) implementation, based on the Jakarta WebSocket 2.0 Specification. -
A Jakarta EE 10 (
jakarta.websocket
) implementation, based on the Jakarta WebSocket 2.1 Specification. -
A Jetty specific implementation, based on the Jetty WebSocket APIs, that does not depend on any Jakarta EE APIs.
The Jakarta EE implementations and APIs are described in this section.
Using the standard Jakarta EE WebSocket APIs allows your applications to depend only on standard APIs, and your applications may be deployed in any compliant WebSocket Container that supports Jakarta WebSocket. The standard Jakarta EE WebSocket APIs provide these features that are not present in the Jetty WebSocket APIs:
-
Encoders and Decoders for automatic conversion of text or binary messages to objects.
The Jetty specific WebSocket implementation and APIs are described in this section.
Using the Jetty WebSocket APIs allows your applications to be more efficient and offer greater and more fine-grained control, and provide these features that are not present in the Jakarta EE WebSocket APIs:
-
A demand mechanism to control backpressure.
-
Remote socket address (IP address and port) information.
-
Advanced request URI matching with regular expressions, in addition to Servlet patterns and URI template patterns.
-
More configuration options, for example the network buffer capacity.
-
Programmatic WebSocket upgrade, in addition to WebSocket upgrade based on URI matching, for maximum flexibility.
If your application needs specific features that are not provided by the standard APIs, the Jetty WebSocket APIs may provide such features.
If the feature you are looking for is not present, you may ask for these features by submitting an issue to the Jetty Project without waiting for the standard Jakarta EE process to approve them and release a new version of the Jakarta EE WebSocket specification. |
Standard APIs Implementation
When you write a WebSocket application using the standard jakarta.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 jakarta.websocket
APIs, for example for Jakarta EE 10, are provided by the following Maven artifact:
<dependency>
<groupId>jakarta.websocket</groupId>
<artifactId>jakarta.websocket-api</artifactId>
<version>2.1.0</version>
</dependency>
At runtime, you also need an implementation of the standard Jakarta EE 10 WebSocket APIs, that Jetty provides with the following Maven artifact (and its transitive dependencies):
<dependency>
<groupId>org.eclipse.jetty.ee10.websocket</groupId>
<artifactId>jetty-ee10-websocket-jakarta-server</artifactId>
<version>12.0.16-SNAPSHOT</version>
</dependency>
The |
To configure correctly your WebSocket application based on the standard Jakarta EE 10 WebSocket APIs, you need two steps:
-
Make sure that Jetty sets up an instance of
jakarta.websocket.server.ServerContainer
, described in this section. -
Configure the WebSocket endpoints that implement your application logic, either by annotating their classes with the standard
jakarta.websocket
annotations, or by using theServerContainer
APIs to register them in your code, described in this section.
Setting Up ServerContainer
Jetty sets up a ServerContainer
instance using JakartaWebSocketServletContainerInitializer
.
When you deploy web applications using WebAppContext
, then JakartaWebSocketServletContainerInitializer
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:
// 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();
On the other hand, when you deploy web applications using ServletContextHandler
, you have to write the code to ensure that the JakartaWebSocketServletContainerInitializer
is initialized, so that it sets up the ServerContainer
:
// 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("/ctx");
server.setHandler(handler);
// Ensure that JavaxWebSocketServletContainerInitializer is initialized,
// to setup the ServerContainer for this web application context.
JakartaWebSocketServletContainerInitializer.configure(handler, null);
// Starting the Server will start the ServletContextHandler.
server.start();
Calling JakartaWebSocketServletContainerInitializer.configure(...)
must be done before the ServletContextHandler
is started, and configures the Jakarta EE 10 WebSocket implementation for that web application context, making ServerContainer
available to web applications.
Configuring Endpoints
Once you have setup the ServerContainer
, you can configure your WebSocket endpoints.
The WebSocket endpoints classes may be either annotated with the standard jakarta.websocket
annotations, extend the jakarta.websocket.Endpoint
abstract class, or implement the jakarta.websocket.server.ServerApplicationConfig
interface.
When you deploy web applications using 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 WebAppContext
but you need to perform more advanced configuration of the ServerContainer
or of the WebSocket endpoints, or when you deploy web applications using 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 Servlet Filter
, or from an HttpServlet
:
// 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("/ctx");
server.setHandler(handler);
// Ensure that JavaxWebSocketServletContainerInitializer is initialized,
// to setup the ServerContainer for this web application context.
JakartaWebSocketServletContainerInitializer.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();
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);
}
}
}
When you deploy web applications using ServletContextHandler
, you can alternatively use the code below to set up the ServerContainer
and configure the WebSocket endpoints in one step:
// 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("/ctx");
server.setHandler(handler);
// Setup the ServerContainer and the WebSocket endpoints for this web application context.
JakartaWebSocketServletContainerInitializer.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();
When the ServletContextHandler
is started, the Configurator
lambda (the second parameter passed to JakartaWebSocketServletContainerInitializer.configure(...)
) is invoked and allows you to explicitly configure the WebSocket endpoints using the standard APIs provided by ServerContainer
.
Upgrade to WebSocket
Under the hood, JakartaWebSocketServletContainerInitializer
installs the org.eclipse.jetty.ee10.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.
The Refer to the advanced |
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).
Advanced WebSocketUpgradeFilter
Configuration
The WebSocketUpgradeFilter
that handles the HTTP requests that upgrade to WebSocket is installed by the JakartaWebSocketServletContainerInitializer
, as described in 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.ee10.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 filters for increased security or authentication before the
WebSocketUpgradeFilter
. -
The
WebSocketUpgradeFilter
configuration viainit-param
s, that affects allSession
instances created by this filter. -
The
WebSocketUpgradeFilter
path mapping. Rather than the default mapping of/*
, you can map theWebSocketUpgradeFilter
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:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<display-name>My WebSocket WebApp</display-name>
<!-- The SecurityFilter *must* be the first --> (1)
<filter>
<filter-name>security</filter-name>
<filter-class>com.acme.SecurityFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>security</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Configure the default WebSocketUpgradeFilter --> (2)
<filter>
<!-- The filter name must be the default WebSocketUpgradeFilter name -->
<filter-name>org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter</filter-name> (3)
<filter-class>org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter</filter-class>
<!-- Configure at most 1 MiB text messages -->
<init-param> (4)
<param-name>maxTextMessageSize</param-name>
<param-value>1048576</param-value>
</init-param>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter</filter-name>
<!-- Use a more specific path mapping for WebSocket requests -->
<url-pattern>/ws/*</url-pattern> (5)
</filter-mapping>
</web-app>
1 | The custom SecurityFilter is the first, to apply custom security. |
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.
Jetty APIs Implementation
When you write a WebSocket application using the Jetty WebSocket APIs, your code typically needs 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:
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>jetty-websocket-jetty-api</artifactId>
<version>12.0.16-SNAPSHOT</version>
</dependency>
Jetty’s implementation of the Jetty WebSocket APIs is provided by the following Maven artifact (and its transitive dependencies):
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>jetty-websocket-jetty-server</artifactId>
<version>12.0.16-SNAPSHOT</version>
</dependency>
The |
To configure correctly your WebSocket application based on the Jetty WebSocket APIs, you need two steps:
-
Make sure to set up an instance of
org.eclipse.jetty.websocket.server.ServerWebSocketContainer
. -
Use the
ServerWebSocketContainer
APIs in your applications to register the WebSocket endpoints that implement your application logic.
You can read more about the Jetty WebSocket architecture, which is common to both client-side and server-side, to get familiar with the terminology used in the following sections.
Setting up ServerWebSocketContainer
You need Jetty to set up a ServerWebSocketContainer
instance to make your WebSocket applications based on the Jetty WebSocket APIs work.
Your WebSocket web application is represented by a ContextHandler
.
The WebSocket upgrade is performed in a descendant (typically the only child) of the ContextHandler
, either by the org.eclipse.jetty.websocket.server.WebSocketUpgradeHandler
, or by a custom Handler
that you write and is part of your web application.
In both cases, you need to set up a ServerWebSocketContainer
, and this can be done implicitly by using WebSocketUpgradeHandler
, or explicitly by creating the ServerWebSocketContainer
instance.
Implicit setup using WebSocketUpgradeHandler
Using WebSocketUpgradeHandler
is the most common way to set up your WebSocket applications.
You can use the WebSocketUpgradeHandler
and the ServerWebSocketContainer
APIs to map HTTP request URIs to WebSocket endpoints.
When an HTTP request arrives, WebSocketUpgradeHandler
tests whether it is a WebSocket upgrade request, whether it matches a mapped URI, and if so upgrades the protocol to WebSocket.
From this point on, the communication on the upgraded connection happens with the WebSocket protocol.
This is very similar to what WebSocketUpgradeFilter
does when using the Jakarta EE WebSocket APIs.
Once you have set up the WebSocketUpgradeHandler
, you can use the ServerWebSocketContainer
APIs to configure the WebSocket endpoints.
The example below shows how to set up the WebSocketUpgradeHandler
and use the ServerWebSocketContainer
APIs:
// Create a Server with a ServerConnector listening on port 8080.
Server server = new Server(8080);
// Create a ContextHandler with the given context path.
ContextHandler contextHandler = new ContextHandler("/ctx");
server.setHandler(contextHandler);
// Create a WebSocketUpgradeHandler that implicitly creates a ServerWebSocketContainer.
WebSocketUpgradeHandler webSocketHandler = WebSocketUpgradeHandler.from(server, contextHandler, container ->
{
// Configure the ServerWebSocketContainer.
container.setMaxTextMessageSize(128 * 1024);
// Map a request URI to a WebSocket endpoint, for example using a regexp.
container.addMapping("regex|/ws/v\\d+/echo", (rq, rs, cb) -> new EchoEndPoint());
// Advanced registration of a WebSocket endpoint.
container.addMapping("/ws/adv", (rq, rs, cb) ->
{
List<String> subProtocols = rq.getSubProtocols();
if (subProtocols.contains("my-ws-protocol"))
return new MyJettyWebSocketEndPoint();
return null;
});
});
contextHandler.setHandler(webSocketHandler);
// Starting the Server will start the ContextHandler and the WebSocketUpgradeHandler,
// which would run the configuration of the ServerWebSocketContainer.
server.start();
The mapping of request URIs to WebSocket endpoints is further explained in this section.
Explicit setup using ServerWebSocketContainer
A more advanced way to set up your WebSocket applications is to explicitly create the ServerWebSocketContainer
instance programmatically.
This gives you more flexibility when deciding whether an HTTP request should be upgraded to WebSocket, because you do not need to match request URIs (although you can), nor you need to use WebSocketUpgradeHandler
(although you can).
Once you have created the ServerWebSocketContainer
, you can use its APIs to configure the WebSocket endpoints as shown in the example below.
// Create a Server with a ServerConnector listening on port 8080.
Server server = new Server(8080);
// Create a ContextHandler with the given context path.
ContextHandler contextHandler = new ContextHandler("/ctx");
server.setHandler(contextHandler);
// Create a ServerWebSocketContainer, which is also stored as an attribute in the context.
ServerWebSocketContainer container = ServerWebSocketContainer.ensure(server, contextHandler);
// You can use WebSocketUpgradeHandler if you want, but it is not necessary.
// You can ignore the line below, it is shown only for reference.
WebSocketUpgradeHandler webSocketHandler = new WebSocketUpgradeHandler(container);
// You can directly use ServerWebSocketContainer from any Handler.
contextHandler.setHandler(new Handler.Abstract()
{
@Override
public boolean handle(Request request, Response response, Callback callback)
{
// Retrieve the ServerWebSocketContainer.
ServerWebSocketContainer container = ServerWebSocketContainer.get(request.getContext());
// Verify special conditions for which a request should be upgraded to WebSocket.
String pathInContext = Request.getPathInContext(request);
if (pathInContext.startsWith("/ws/echo") && request.getHeaders().contains("X-WS", "true"))
{
try
{
// This is a WebSocket upgrade request, perform a direct upgrade.
boolean upgraded = container.upgrade((rq, rs, cb) -> new EchoEndPoint(), request, response, callback);
if (upgraded)
return true;
// This was supposed to be a WebSocket upgrade request, but something went wrong.
Response.writeError(request, response, callback, HttpStatus.UPGRADE_REQUIRED_426);
return true;
}
catch (Exception x)
{
Response.writeError(request, response, callback, HttpStatus.UPGRADE_REQUIRED_426, "failed to upgrade", x);
return true;
}
}
else
{
// Handle a normal HTTP request.
response.setStatus(HttpStatus.OK_200);
callback.succeeded();
return true;
}
}
});
// Starting the Server will start the ContextHandler.
server.start();
Note how the call to ServerWebSocketContainer.upgrade(...)
allows you to perform a direct WebSocket upgrade programmatically.
WebSocket Endpoints
When using the Jetty WebSocket APIs, the WebSocket endpoint classes must be either annotated with the Jetty WebSocket annotations from the org.eclipse.jetty.websocket.api.annotations
package, or implement the org.eclipse.jetty.websocket.api.Session.Listener
interface.
In the case you want to implement the Session.Listener
interface, remember that you have to explicitly demand to receive the next WebSocket event.
Use Session.Listener.AutoDemanding
to automate the demand for simple use cases.
Refer to the Jetty WebSocket architecture section for more information about Jetty WebSocket endpoints and how to correctly deal with the demand for WebSocket events.
There is no automatic discovery of WebSocket endpoints; all the WebSocket endpoints of your application must be returned by a org.eclipse.jetty.websocket.server.WebSocketCreator
that is either mapped to a request URI via ServerWebSocketContainer.addMapping(...)
, or directly upgraded via ServerWebSocketContainer.upgrade(...)
.
In the call to ServerWebSocketContainer.addMapping(...)
, you can specify a path spec (the first parameter) that can specified as discussed in this section.
When the Server
is started, the lambda passed to ServerWebSocketContainer.configure(...)
) is invoked and allows you to explicitly configure the WebSocket endpoints using the Jetty WebSocket APIs provided by ServerWebSocketContainer
.
Custom PathSpec Mappings
The ServerWebSocketContainer.addMapping(...)
API maps a path spec to a WebSocketCreator
instance (typically a lambda expression).
The path spec is matched against the WebSocket upgrade request URI to select the correspondent WebSocketCreator
to invoke.
The path spec can have these forms:
-
Servlet syntax, specified with
servlet|<path spec>
, where theservlet|
prefix can be omitted if the path spec begins with/
or*.
(for example,/ws
,/ws/chat
or*.ws
). -
Regex syntax, specified with
regex|<path spec>
, where theregex|
prefix can be omitted if the path spec begins with^
(for example,^/ws/[0-9]+
). -
URI template syntax, specified with
uri-template|<path spec>
(for exampleuri-template|/ws/chat/{room}
).
Within the WebSocketCreator
, it is possible to access the path spec and, for example in case of URI templates, extract additional information in the following way:
Server server = new Server(8080);
ContextHandler contextHandler = new ContextHandler("/ctx");
server.setHandler(contextHandler);
// Create a WebSocketUpgradeHandler.
WebSocketUpgradeHandler webSocketHandler = WebSocketUpgradeHandler.from(server, contextHandler, container ->
{
container.addMapping("/ws/chat/{room}", (upgradeRequest, upgradeResponse, callback) ->
{
// Retrieve the URI template.
UriTemplatePathSpec pathSpec = (UriTemplatePathSpec)upgradeRequest.getAttribute(PathSpec.class.getName());
// Match the URI template.
String pathInContext = Request.getPathInContext(upgradeRequest);
Map<String, String> params = pathSpec.getPathParams(pathInContext);
String room = params.get("room");
// Create the new WebSocket endpoint with the URI template information.
return new MyWebSocketRoomEndPoint(room);
});
});
contextHandler.setHandler(webSocketHandler);