Hi Tomcat-Team, since Apple released Safari 15 (both iOS and macOS) I am running into a strange issue related to Apache Tomcat, Safari/WebKit and certain sequences of messages received via a WebSocket. When the browser receives messages in this order, the connection gets closed.
The following sequence triggers the issue: 1. Connect from Safari to a Tomcat WebSocket server and use the 'permessage-deflate' extension 2. Receive a text message from the WebSocket server 3. Receive a large binary message from the server 4. The WebSocket connection is closed with the close code PROTOCOL_ERROR. Some further notes/observations: - I was able to reduce the error to a minimal example (see basic code at the end of this mail). - From my experiments, "large binary message" refers to messages with a byte size of at least roughly 8190 bytes. - If only binary messages (of any size) are received, no issue occurs. - If only text messages (of any size) are received, no issue occurs. - If the binary messages are smaller than the given size, no issue occurs. - Safari since version 15 sends the permessasge-deflate flag and Tomcat accepts it. If this flag is not accepted by the server, no issue occurs. However, no compression is used in this case (which is unfortunate). - The issue only seems to affect the combination of Tomcat and Safari. I was not able to reproduce the issue with a WebSocket server written in Python or with other browsers (Google Chrome/Mozilla Firefox). - The issue occurred for me with the latest Apache Tomcat versions as of 2022-03-05: 9.0.62 and 10.0.20. All versions of Safari 15 seem to be affected (even the recently released Safari Technology Preview). - While debugging happened mostly with the server running on Windows, our deployed Linux machines are also affected. Safari was running on macOS and on an iPad. - There are/were numerous bugs in Safari 15 related to WebSockets (e.g., https://bugs.webkit.org/show_bug.cgi?id=228296). However, the outlined issue does not occur if the sequence of messages is served by a WebSocket server written in Python. - The error thrown in Safari: "WebSocket connection to 'ws://hostname:port/WebSocket/ws' failed: The operation couldn't be completed. (kNWErrorDomainPOSIX error 100 - Protocol error)" Does anyone have an idea or is able to clarify if this is an issue with Tomcat or with Safari/WebKit? Or if there is a workaround to this issue? Best regards, Florian Example code Java: @ServerEndpoint(value = "/ws") public class WebSocketServer { @OnOpen public void onOpen(final Session session) throws IOException { System.out.println("WebSocket has been opened"); } @OnMessage public void onMessage(String message, Session session) { System.out.println("Received message: " + message); String[] parameters = message.split(","); if (parameters.length != 2) { return; } String type = parameters[0]; int length = Integer.parseInt(parameters[1]); switch (type) { case "text" -> session.getAsyncRemote().sendText(getRandomString(length)); case "binary" -> { byte[] randomBytes = new byte[length]; new Random().nextBytes(randomBytes); session.getAsyncRemote().sendBinary(ByteBuffer.wrap(randomBytes)); } default -> System.out.println("Unknown type " + type); } } private String getRandomString(int length) { String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; SecureRandom rnd = new SecureRandom(); StringBuilder sb = new StringBuilder(length); for(int i = 0; i < length; i++) sb.append(AB.charAt(rnd.nextInt(AB.length()))); return sb.toString(); } @OnClose public void onClose(final CloseReason closeReason){ System.out.println("Session closed; Reason: " + closeReason.getCloseCode() + ":" + closeReason.getReasonPhrase()); } @OnError public void onError(Session session, Throwable throwable) { throwable.printStackTrace(); } } Example Code HTML/Javascript: <!DOCTYPE html> <html> <button id="connectButton" type="button" onclick="connectToWebSocket()"> Connect to WebSocket </button> <br> <br> <button id="textMessageButton" disabled="true" onclick="textMessageTest()"> Test WebSocket text message </button> <br> <br> <button id="binaryMessageButton" disabled="true" onclick="binaryMessageTest()"> Test WebSocket binary message </button> <script> function connectToWebSocket() { const connectButton = document.getElementById('connectButton'); connectButton.disabled = true; try { this.ws = new WebSocket('ws://localhost:8080/webSocket/ws'); ws.binaryType = "arraybuffer"; } catch (error) { console.warn('Error during WebSocket initialization', error); } this.ws.onopen = function() { console.log('Successfully opened the WebSocket connection'); const textMessageButton = document.getElementById('textMessageButton'); textMessageButton.disabled = false; const binaryMessageButton = document.getElementById('binaryMessageButton'); binaryMessageButton.disabled = false; }; this.ws.onerror = function(error) { console.log('WebSocket error', error); connectButton.disabled = false; } this.ws.onmessage = function(messageEvent) { console.log('Received message from WebSocket:', messageEvent.data); } } function textMessageTest() { if (!this.ws) { console.warn('No WebSocket available'); return; } this.ws.send('text,1024'); } function binaryMessageTest() { if (!this.ws) { console.warn('No WebSocket available'); return; } this.ws.send('binary,16384'); } </script> </html> --------------------------------------------------------------------- To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org For additional commands, e-mail: users-h...@tomcat.apache.org