Chapter 8. Tyrus proprietary configuration

Following settings do have influence on Tyrus behaviour and are NOT part of WebSocket specification. If you are using following configurable options, your application might not be easily transferable to other WebSocket API implementation.

8.1. Client-side SSL configuration

When accessing "wss" URLs, Tyrus client will pick up whatever keystore and truststore is actually set for current JVM instance, but that might not be always convenient. WebSocket API does not have this feature (yet, seeWEBSOCKET_SPEC-210), so Tyrus exposed SSLEngineConfigurator class from Grizzly which can be used for specifying all SSL parameters to be used with current client instance. Additionally, WebSocket API does not have anything like a client, only WebSocketContainer and it does not have any properties, so you need to use Tyrus specific class - ClientManager.

final ClientManager client = ClientManager.createClient();

System.getProperties().put("javax.net.debug", "all");
System.getProperties().put(SSLContextConfigurator.KEY_STORE_FILE, "...");
System.getProperties().put(SSLContextConfigurator.TRUST_STORE_FILE, "...");
System.getProperties().put(SSLContextConfigurator.KEY_STORE_PASSWORD, "...");
System.getProperties().put(SSLContextConfigurator.TRUST_STORE_PASSWORD, "...");
final SSLContextConfigurator defaultConfig = new SSLContextConfigurator();

defaultConfig.retrieve(System.getProperties());
    // or setup SSLContextConfigurator using its API.

SSLEngineConfigurator sslEngineConfigurator =
    new SSLEngineConfigurator(defaultConfig, true, false, false);
client.getProperties().put(GrizzlyEngine.SSL_ENGINE_CONFIGURATOR,
    sslEngineConfigurator);
client.connectToServer(... , ClientEndpointConfig.Builder.create().build(),
    new URI("wss://localhost:8181/sample-echo/echo"));
}

8.2. Asynchronous connectToServer methods

WebSocketContainer.connectToServer(...) methods are by definition blocking - declared exceptions needs to be thrown after connection attempt is made and it returns Session instance, which needs to be ready for sending messages and invoking other methods, which require already estabilished connection.

Existing connectToServer methods are fine for lots of uses, but it might cause issue when you are designing application with highly responsible user interface. Tyrus introduces asynchronous variants to each connectToServer method (prefixed with "async"), which returns Future<Session>. These methods do only simple check for provided URL and the rest is executed in separate thread. All exceptions thrown during this phase are reported as cause of ExecutionException thrown when calling Future<Session>.get().

Asynchronous connect methods are declared on Tyrus implementation of WebSocketContainer called ClientManager.

ClientManager client = ClientManager.createClient();
  final Future<Session> future = client.asyncConnectToServer(ClientEndpoint.class, URI.create("..."));
  try {
    future.get();
  } catch (...) {
}

ClientManager contains async alternative to each connectToServer method.

8.3. Optimized broadcast

One of the typical usecases we've seen so far for WebSocket server-side endpoints is broadcasting messages to all connected clients, something like:

@OnMessage
public void onMessage(Session session, String message) throws IOException {
  for (Session s : session.getOpenSessions()) {
    s.getBasicRemote().sendText(message);
  }
}

Executing this code might cause serious load increase on your application server. Tyrus provides optimized broadcast implementation, which takes advantage of the fact, that we are sending exactly same message to all clients, so dataframe can be created and serialized only once. Furthermore, Tyrus can iterate over set of opened connections faster than Session.getOpenSession().

@OnMessage
public void onMessage(Session session, String message) {
  ((TyrusSession) session).broadcast(message);
}

Unfortunately, WebSocket API forbids anything else than Session in @OnMessage annotated method parameter, so you cannot use TyrusSession there directly and you might need to perform instanceof check.

8.4. Incoming buffer size

Sevlet container buffers incoming WebSocket frames and there must a size limit to precede OutOfMemory Exception and potentially DDoS attacks.

Configuration property is named "org.glassfish.tyrus.servlet.incoming-buffer-size" and you can set it in web.xml (this particular snipped sets the buffer size to 17000000 bytes (~16M payload):

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

  <context-param>
    <param-name>org.glassfish.tyrus.servlet.incoming-buffer-size</param-name>
    <param-value>17000000</param-value>
  </context-param>
</web-app>

Default value is 4194315, which correspond to 4M plus few bytes to frame headers, so you should be able to receive up to 4M long message without the need to care about this property.

Same issue is present on client side. There you can set this property via ClientManager:

ClientManager client = ClientManager.createClient();
client.getProperties().put("org.glassfish.tyrus.incomingBufferSize", 6000000); // sets the incoming buffer size to 6000000 bytes.
client.connectToServer( ... )