# 5. The limits: origin, size, and which one actually fires Previous: [4. STOMP](04-stomp.md) · Next: [6. Choosing](06-choosing.md) --- ## Origin: checked by default, and the check is real The WebSocket handshake is an ordinary HTTP GET, and the browser's same-origin policy **does not apply to it**. Without a server-side check, a page on any site could open a WebSocket to your server with the user's cookies attached. Spring checks `Origin` by default. [`WebSocketHandshakeOriginTest`](../src/test/java/com/ankurm/ssews/WebSocketHandshakeOriginTest.java) varies only that header ([`handshake-origin.txt`](output/handshake-origin.txt)): ``` no Origin header -> HTTP/1.1 101 Origin: http://localhost -> HTTP/1.1 101 Origin: https://evil... -> HTTP/1.1 403 ``` The middle row is the policy working. The **first** row is the one to understand: a client that sends no `Origin` at all is allowed through, which is why every Java test in this module connects without ceremony — and why `Origin` is a browser control, not an authentication mechanism. For a real cross-origin front end use `setAllowedOriginPatterns("https://app.example.com")`; `setAllowedOrigins("*")` is rejected outright when credentials are allowed. ## Size: two limits, and the documented one is not the one that fires `WebSocketTransportRegistration.setMessageSizeLimit` defaults to 64 KB and is what every guide tells you to raise. Underneath it sits the servlet container's own WebSocket buffer, which `/diag` reports as: ``` "ws.container.defaultMaxTextMessageBufferSize":8192 ``` Spring's `SubProtocolWebSocketHandler` does not support partial messages, so the container has to assemble each text message whole before Spring sees it. Cross the container's ceiling and the connection is **closed** — WebSocket status 1009, `The decoded text message was too big for the output buffer and the endpoint does not support partial messages` — before Spring's limit is ever consulted. [`StompSizeLimitDefaultBufferTest`](../src/test/java/com/ankurm/ssews/StompSizeLimitDefaultBufferTest.java) binary-searches for the exact ceiling ([`websocket-size-limits.txt`](output/websocket-size-limits.txt)): | container buffer | largest body delivered | smallest rejected | |---|---|---| | 8 192 (Tomcat default) | 16 459 | 16 522 | | 16 384 | 16 446 | 16 496 | | 32 768 | 32 615 | 32 665 | | 65 536 | 65 350 | 65 400 | Body length is the JSON string field; the STOMP frame adds about 90 bytes of headers on top, so each row is a frame just under 16 KB, 16 KB, 32 KB and 64 KB respectively. Read that table honestly: **the ceiling behaves like `max(16 KB, buffer)`**. Setting the buffer to 16 KB changes nothing from the default; above 16 KB the ceiling follows it. I have not reduced the 16 KB floor to a line in Tomcat's source and will not claim a mechanism I have not read. What is certain, and is the point: > **Spring's 64 KB `messageSizeLimit` is not reached on a stock Tomcat.** The real ceiling is > about 16 KB, it is enforced by the container, and raising Spring's limit alone does > nothing whatsoever. The fix is a `ServletServerContainerFactoryBean` ([`WebSocketBufferConfig`](../src/main/java/com/ankurm/ssews/WebSocketBufferConfig.java), active under the `bigframes` profile): ```java @Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); container.setMaxTextMessageBufferSize(256 * 1024); container.setMaxBinaryMessageBufferSize(256 * 1024); return container; } ``` ### The two failures look different, and only one is diagnosable With the buffer at 256 KB, Spring's limit finally decides — and it fails *better*: ``` 60 KB body -> delivered, body length 61440 80 KB body -> NOT delivered, connection dropped org.springframework.messaging.simp.stomp.StompConversionException: The configured STOMP buffer size limit of 65536 bytes has been exceeded ``` Spring logs the exception, names the limit, and sends a STOMP `ERROR` frame the client can see. The container's refusal does none of that. So the practical advice is not just "raise the buffer" — it is **raise the container buffer above Spring's limit deliberately, so that the limit that fires is the one that tells you it fired.** ### The client has the same limit A `WebSocketStompClient` built on `StandardWebSocketClient` inherits the same 8 KB default, and hits it on its *own* session first. That produced a false positive while writing these tests: a message that looked rejected by the server was actually refused by the client. [`SizeProbe`](../src/test/java/com/ankurm/ssews/SizeProbe.java) raises the client side to 1 MB before measuring anything. ## Send buffer and send time Two limits that bite in production rather than in tests, both set in `WebSocketConfig`: - `setSendBufferSizeLimit` (512 KB here) — how much undelivered output Spring will hold for one slow client before closing its session. Without it, one client on hotel wifi can grow the heap until the process dies. - `setSendTimeLimit` (20 s here) — how long a single send may take before the session is closed. Both exist because a broadcast is only as fast as its slowest subscriber, and a WebSocket session has no equivalent of TCP's back-pressure reaching your application code. This is precisely the gap RSocket closes, and the reason the [next article](06-choosing.md) exists. --- Previous: [4. STOMP](04-stomp.md) · Next: [6. Choosing](06-choosing.md)