onCloseState in reactor.netty.http.server.WebsocketServerOperations#sendCloseNow should emit 1006 (NO_CLOSE_FRAME) instead of -1 if no close frame.
void sendCloseNow(@Nullable CloseWebSocketFrame frame, ChannelFutureListener listener) {
if (frame != null && !frame.isFinalFragment()) {
channel().writeAndFlush(frame);
return;
}
if (CLOSE_SENT.getAndSet(this, 1) == 0) {
if (frame != null) {
onCloseState.tryEmitValue(new WebSocketCloseStatus(frame.statusCode(), frame.reasonText()));
channel().writeAndFlush(frame)
.addListener(listener);
} else {
onCloseState.tryEmitValue(new WebSocketCloseStatus(-1, "")); // should emit 1006 instead of -1
channel().writeAndFlush(new CloseWebSocketFrame())
.addListener(listener);
}
}
else if (frame != null) {
frame.release();
}
}
org.springframework.web.reactive.socket.CloseStatus throw "java.lang.IllegalArgumentException: Invalid status code" because reactor.netty.http.server.WebsocketServerOperations#sendCloseNow tries to create an WebSocketCloseStatus with the status code -1.
The bug happens when a WebSocket session is closed without a close frame.
onCloseState in reactor.netty.http.server.WebsocketServerOperations#sendCloseNow should emit 1006 (NO_CLOSE_FRAME) instead of -1 if no close frame.
reactor-netty-http-1.0.1
spring-webflux-5.3.1
@JamesChenX Is it possible to provide a reproducible example?
I can create a new project to reproduce the bug but it's exaggerated.
The bug can always happen in the scenario when a TCP connection is closed by the peer without the WebSocket close frame and it's easy to understand.
The key point for the issue is: reactor-netty should not create a new WebSocketCloseStatus with the status code "-1" in any scenario because WebFlux will convert the WebSocketCloseStatus instance to CloseStatus while "-1" is an illegal close status code in org.springframework.web.reactive.socket.CloseStatus.
WebsocketServerOperations should use 1006 instead -1 for the disconnection without the WebSocket close frame as mentioned in RFC 6455: The WebSocket Protocol.
1006 is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting a status code to indicate that the connection was closed abnormally, e.g., without sending or receiving a Close control frame.
package org.springframework.web.reactive.socket;
public final class CloseStatus {
public CloseStatus(int code, @Nullable String reason) {
Assert.isTrue((code >= 1000 && code < 5000), "Invalid status code"); // So -1 is an illegal status code
this.code = code;
this.reason = reason;
}
}
@JamesChenX I was asking because we invoke that method with NULL only in two cases, so most probably your use case is related to onInboundCancel
I know you want to know why it happened. And I have mentioned the reason: The bug can always happen in the scenario when a TCP connection is closed by the peer without the WebSocket close frame and it's easy to understand.
I can reproduce the bug every time if I want by the following steps:
And the onInboundCancel in WebsocketServerOperations will be triggered. In this case, reactor-netty should create a WebSocket status code with 1006 instead of -1.
BTW: We cannot control the behavior of WebSocket clients because they are running on our customers' device (Android, iOS, etc)
@JamesChenX In order to proceed with a fix for this issue we need a new version of Netty - https://github.com/netty/netty/pull/10846
Very appreciate your support. I will keep an eye on the issue and test it again once the new version of reactor-netty published. Thanks!
@JamesChenX PTAL #1416
@JamesChenX This change is available from 1.0.3-SNAPSHOT and Spring's maven repository https://repo.spring.io/snapshot . If you can test it, it will be great. You need also to update Netty to version 4.1.55.Final.
@violetagg I have tested it with 1.0.3-SNAPSHOT and it works as expected now. Thanks for your support.