You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tomcat.apache.org by "Hagenauer, Florian" <fl...@siemens-healthineers.com> on 2022/05/03 13:12:02 UTC

Tomcat + Safari WebSocket issue

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-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


RE: Tomcat + Safari WebSocket issue

Posted by "Hagenauer, Florian" <fl...@siemens-healthineers.com>.
Hi Pawel,

> Try capturing the traffic (with Wireshark, etc) for the broken connection against Tomcat, and then also to the "working" Python server, at least what are the differences on the wire.
> That would probably help identify the problem a lot faster.

I attached screenshots from Wireshark capturing three scenarios:
* tomcat-safari.png (triggers the issue)
* tomcat-chrome.png (does not trigger the issue)
* python-safari.png (does not trigger the issue)

I am no expert in low-level  WebSocket communication, but it looks like Safari has an issue with Tomcat splitting the message into frames.

Beste regards,
Florian



-----Original Message-----
From: Pawel Veselov <pa...@gmail.com> 
Sent: Dienstag, 3. Mai 2022 18:20
To: Hagenauer, Florian <fl...@siemens-healthineers.com>
Cc: Tomcat Users List <us...@tomcat.apache.org>
Subject: Re: Tomcat + Safari WebSocket issue

Florian,

On Tue, May 3, 2022 at 3:12 PM Hagenauer, Florian <fl...@siemens-healthineers.com> wrote:
> 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.

Try capturing the traffic (with Wireshark, etc) for the broken connection against Tomcat, and then also to the "working" Python server, at least what are the differences on the wire.
That would probably help identify the problem a lot faster.

Re: Tomcat + Safari WebSocket issue

Posted by Pawel Veselov <pa...@gmail.com>.
Florian,

On Tue, May 3, 2022 at 3:12 PM Hagenauer, Florian
<fl...@siemens-healthineers.com> wrote:
> 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.

Try capturing the traffic (with Wireshark, etc) for the broken
connection against Tomcat, and then also to the "working" Python
server, at least what are the differences on the wire.
That would probably help identify the problem a lot faster.

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Tomcat + Safari WebSocket issue

Posted by Mark Thomas <ma...@apache.org>.
On 03/05/2022 14:12, Hagenauer, Florian wrote:

<snip/>

> 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?

I've just run Safari on a fully updated macOS Monterey against the 
Autobahn|Testsuite for the WebSocket protocol.

Safari failed multiple tests, mostly in the compression sections. Safari 
also exhibited multiple "non-strict" results which indicate things like 
closing a connection with the wrong close code.

Tomcat fully passes the Autobahn|Testsuite.

Given the above, it seems reasonable to conclude that there are still a 
few bugs in Safari's WebSocket implementation that need fixing.

Mark

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org