Issue #7121 - Add documentation for websocket configuration. (#8931)

* Improvements to WebSocket documentation.
* Documented configuration parameters.
* Documented and added a test case for PathSpec usage.

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
Co-authored-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Lachlan 2023-03-29 04:42:44 +11:00 committed by GitHub
parent 925109f364
commit dde6e04731
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 182 additions and 9 deletions

View File

@ -54,7 +54,12 @@ You may create multiple instances of `WebSocketClient`, but typically one instan
Creating multiple instances may be necessary for example when you need to specify different configuration parameters for different instances.
For example, you may need different instances when you need to configure the `HttpClient` differently: different transports, different proxies, different cookie stores, different authentications, etc.
WebSocket specific configuration may be typically be given a default value in `WebSocketClient` and then overridden more specifically, see for example xref:pg-websocket-session-configure[this section].
The configuration that is not WebSocket specific (such as idle timeout, etc.) should be directly configured on the associated `HttpClient` instance.
The WebSocket specific configuration can be configured directly on the `WebSocketClient` instance.
Configuring the `WebSocketClient` allows to give default values to various parameters, whose values may be overridden more specifically, as described in xref:pg-websocket-session-configure[this section].
Refer to the `WebSocketClient` link:{javadoc-url}/org/eclipse/jetty/websocket/client/WebSocketClient.html[javadocs] for the setter methods available to customize the WebSocket specific configuration.
[[pg-client-websocket-stop]]
==== Stopping WebSocketClient

View File

@ -49,6 +49,8 @@ To configure correctly your WebSocket application based on the Jetty WebSocket A
. 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.
You can read more about the xref:pg-websocket-architecture[Jetty WebSocket architecture], which is common to both client-side and server-side, to get familiar with the terminology used in the following sections.
[[pg-server-websocket-jetty-container]]
===== Setting up `JettyWebSocketServerContainer`
@ -112,10 +114,12 @@ You can also use this variant to set up the `JettyWebSocketServerContainer` and
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=jettyContainerAndEndpoints]
----
In the call to `JettyWebSocketServerContainer.addMapping(\...)`, you can specify a _path spec_ (the first parameter) that can be configured as specified in xref:pg-server-websocket-jetty-pathspec[this section].
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].
For more information about the configuration of `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:
@ -175,3 +179,22 @@ However, it is typically best to avoid mixing the use of `JettyWebSocketServerCo
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.
[[pg-server-websocket-jetty-pathspec]]
====== Custom PathSpec Mappings
The `JettyWebSocketServerContainer.addMapping(\...)` API maps a _path spec_ to a `JettyWebSocketCreator` instance (typically a lambda expression).
The path spec is matched against the WebSocket upgrade request URI to select the correspondent `JettyWebSocketCreator` to invoke.
The path spec can have these forms:
* Servlet syntax, specified with `servlet|<path spec>`, where the `servlet|` 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 the `regex|` 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 example `+uri-template|/ws/chat/{room}+`).
Within the `JettyWebSocketCreator`, it is possible to access the path spec and, for example in case of URI templates, extract additional information in the following way:
[source,java,indent=0]
----
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java[tags=uriTemplatePathSpec]
----

View File

@ -91,7 +91,7 @@ Calling `JavaxWebSocketServletContainerInitializer.configure(\...)` must be done
[[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].
Once you have xref:pg-server-websocket-standard-container[setup] the `ServerContainer`, you can configure your 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.

View File

@ -21,19 +21,16 @@ Jetty provides two API implementations of the WebSocket protocol:
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:
The standard 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.
* `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:
On the other hand, the Jetty WebSocket APIs are more efficient and offer greater and more fine-grained control, and provide these 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.

View File

@ -14,7 +14,7 @@
// Snippets of WebSocket documentation that are common between client and server.
[[pg-websocket-architecture]]
==== WebSocket Architecture
==== Jetty WebSocket Architecture
The Jetty WebSocket architecture is organized around the concept of a logical _connection_ between the client and the server.
@ -152,6 +152,29 @@ You want to do this as soon as you have access to the `Session` object, typicall
include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=sessionConfigure]
----
The settings that can be configured include:
maxBinaryMessageSize::
the maximum size in bytes of a binary message (which may be composed of multiple frames) that can be received.
maxTextMessageSize::
the maximum size in bytes of a text message (which may be composed of multiple frames) that can be received.
maxFrameSize::
the maximum payload size in bytes of any WebSocket frame that can be received.
inputBufferSize::
the input (read from network/transport layer) buffer size in bytes; it has no relationship with the WebSocket frame size or message size.
outputBufferSize::
the output (write to network/transport layer) buffer size in bytes; it has no relationship to the WebSocket frame size or message size.
autoFragment::
whether WebSocket frames are automatically fragmented to respect the maximum frame size.
idleTimeout::
the duration that a WebSocket connection may remain idle (that is, there is no network traffic, neither in read nor in write) before being closed by the implementation.
Please refer to the `Session` link:{javadoc-url}/org/eclipse/jetty/websocket/api/Session.html[javadocs] for the complete list of configuration APIs.
[[pg-websocket-session-send]]

View File

@ -15,6 +15,7 @@ package org.eclipse.jetty.docs.programming.server.websocket;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@ -24,6 +25,8 @@ import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.webapp.WebAppContext;
@ -348,4 +351,38 @@ public class WebSocketServerDocs
private static class MyOtherJettyWebSocketEndPoint
{
}
public void uriTemplatePathSpec()
{
Server server = new Server(8080);
// tag::uriTemplatePathSpec[]
ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
// Configure the JettyWebSocketServerContainer.
JettyWebSocketServletContainerInitializer.configure(handler, (servletContext, container) ->
{
container.addMapping("/ws/chat/{room}", (upgradeRequest, upgradeResponse) ->
{
// Retrieve the URI template.
UriTemplatePathSpec pathSpec = (UriTemplatePathSpec)upgradeRequest.getServletAttribute(PathSpec.class.getName());
// Match the URI template.
Map<String, String> params = pathSpec.getPathParams(upgradeRequest.getRequestPath());
String room = params.get("room");
// Create the new WebSocket endpoint with the URI template information.
return new MyWebSocketRoomEndPoint(room);
});
});
// end::uriTemplatePathSpec[]
}
@WebSocket
private static class MyWebSocketRoomEndPoint
{
public MyWebSocketRoomEndPoint(String room)
{
}
}
}

View File

@ -22,14 +22,49 @@ public final class WebSocketConstants
public static final int SPEC_VERSION = 13;
public static final String SPEC_VERSION_STRING = Integer.toString(SPEC_VERSION);
/**
* Max Binary Message Size - The maximum size of a binary message which can be received.
*/
public static final int DEFAULT_MAX_BINARY_MESSAGE_SIZE = 64 * 1024;
/**
* Max Text Message Size - The maximum size of a text message which can be received.
*/
public static final int DEFAULT_MAX_TEXT_MESSAGE_SIZE = 64 * 1024;
/**
* Max Frame Size - The maximum payload size of any WebSocket Frame which can be received.
*/
public static final int DEFAULT_MAX_FRAME_SIZE = 64 * 1024;
/**
* Output Buffer Size - The output (write to network layer) buffer size. This is the raw write operation buffer size and has no relationship to the websocket frame.
*/
public static final int DEFAULT_INPUT_BUFFER_SIZE = 4 * 1024;
/**
* Input Buffer Size - The input (read from network layer) buffer size. This is the raw read operation buffer size, before the parsing of the websocket frames.
*/
public static final int DEFAULT_OUTPUT_BUFFER_SIZE = 4 * 1024;
/**
* Max Outgoing Frames - Set the maximum number of data frames allowed to be waiting to be sent at any one time.
*/
public static final int DEFAULT_MAX_OUTGOING_FRAMES = -1;
/**
* Auto Fragment - If set to true, frames are automatically fragmented to respect the maximum frame size.
*/
public static final boolean DEFAULT_AUTO_FRAGMENT = true;
/**
* Idle Timeout - The duration that a websocket may be idle before being closed by the implementation.
*/
public static final Duration DEFAULT_IDLE_TIMEOUT = Duration.ofSeconds(30);
/**
* Write Timeout - The maximum time a frame may be waiting to be sent.
*/
public static final Duration DEFAULT_WRITE_TIMEOUT = Duration.ZERO;
/**

View File

@ -13,12 +13,20 @@
package org.eclipse.jetty.websocket.tests;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.server.JettyServerUpgradeRequest;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
@ -28,6 +36,8 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class JettyWebSocketServletAttributeTest
@ -87,4 +97,47 @@ public class JettyWebSocketServletAttributeTest
clientEndpoint.session.close();
assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testPathSpecAttribute() throws Exception
{
start((context, container) -> container.addMapping("uri-template|/{path}", (req, resp) ->
{
UriTemplatePathSpec pathSpec = (UriTemplatePathSpec)req.getServletAttribute(PathSpec.class.getName());
assertNotNull(pathSpec);
Map<String, String> params = pathSpec.getPathParams(req.getRequestPath());
String path = params.get("path");
assertNotNull(path);
return new ParamWebSocketEndPoint(path);
}));
String param = "world";
URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/" + param);
EventSocket clientEndpoint = new EventSocket();
try (Session session = client.connect(clientEndpoint, uri).get(5, TimeUnit.SECONDS))
{
session.getRemote().sendString("hello", WriteCallback.NOOP);
String path = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS);
assertEquals(param, path);
}
}
@WebSocket
public static class ParamWebSocketEndPoint
{
private final String param;
public ParamWebSocketEndPoint(String param)
{
this.param = param;
}
@OnWebSocketMessage
public void onText(Session session, String text) throws IOException
{
session.getRemote().sendString(param);
}
}
}