Affects: Spring Framework >= v5.2.6
We've observed an issue, using spring-cloud-gateway, where websocket handshakes fail if different clients, with different websocket protocols, are connecting in succession:
1. wsClientA connects without any (sub-)protocol -> success
2. wsClientB connects with protocol v10.stomp
-> success
3. wsClientA connects without any protocol -> failure
The bug first appeared in spring-webflux v5.2.6, with the newly introduced ReactorNettyRequestUpgradeStrategy (#24959 -> 0520ee0, ping @rstoyanchev).
The global WebsocketServerSpec.Builder
is mutated for each request, persisting the client-specific protocol across requests, and thereby potentially breaking subsequent requests for clients with different (or null
) subprotocols (see ReactorNettyRequestUpgradeStrategy.java#L89).
In the example above, the third handshake request is silently aborted in HttpServerOperations.java#L649, because websocketServerSpec.protocols()
is set (by the second request), but ops.selectedSubprotocol()
is null
.
I created a simple WebSocketIntegration testcase to reproduce the issue: https://github.com/Croissong/spring-framework/blob/ReactorNetty-websocket-protocol-bug/spring-webflux/src/test/java/org/springframework/web/reactive/socket/ReactorNettyWebsocketProtocolBugTests.java. The test fails for all combinations using the ReactorHttpServer. However, the ReactorNettyWebSocketClient uses the same logic: ReactorNettyWebSocketClient.java#L111.
Comment From: rstoyanchev
This should be addressed now. It's only the default constructor of ReactorNettyRequestUpgradeStrategy
that was affected because it uses WebsocketServerSpect.builder()
as the the Supplier
but that returns the same instance always.
The other constructor receives an external Supplier
and it's the responsibility of the caller to ensure that supplier returns a difference builder each time, so no change needed there. ReactorNettyWebSocketClient
only has one constructor with an external Supplier and therefore is not affected by this.