dart-lang / web_socket_channel

StreamChannel wrappers for WebSockets.
https://pub.dev/packages/web_socket_channel
BSD 3-Clause "New" or "Revised" License
412 stars 107 forks source link

Unexpected Behavior and Premature Closure when Using web_socket_channel to Connect to WebSocket Server #365

Closed vfiruz97 closed 1 month ago

vfiruz97 commented 1 month ago

Issue Description: When attempting to use the web_socket_channel package in Dart to establish a WebSocket connection to the server wss://mob-backdev.dentro.ru:444, unexpected behavior occurs similar to what was experienced with the Dart WebSocket class. Although the server is expected to echo the sent messages, the received data is inconsistent, and sometimes the connection closes prematurely with a SocketException.

Steps to Reproduce:

  1. Run the provided Dart code to establish a WebSocket connection to the server.
  2. Send five messages sequentially to the server with a delay of 1 second between each message.
  3. Listen for incoming messages and track the count.
  4. Observe the received data and the behavior of the connection.

Expected Behavior:

  1. All five messages are successfully sent to the server.
  2. The server echoes each message back to the client.
  3. The connection remains open until all messages have been received.
  4. The connection closes gracefully after receiving all echoed messages.

Actual Behavior:

  1. The received data is inconsistent and sometimes includes unexpected characters.
  2. The connection occasionally closes prematurely with a SocketException.
  3. The observed behavior varies between runs.

Additional Information:

Code Snippet:

import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/status.dart' as status;

void main() async {
  final channel = IOWebSocketChannel.connect(
    'wss://mob-backdev.dentro.ru:444',
    headers: {'dentro-token': '86F1A241876A2B46BFEFED38C88936D8'},
  );

  await channel.ready;

  int messageCount = 0;
  channel.stream.listen(
    (message) {
      print('Received: $message');
      messageCount++;
      if (messageCount == 5) {
        channel.sink.close(status.normalClosure);
      }
    },
    onError: (error) {
      print('Error: $error');
    },
    onDone: () {
      print(channel.closeReason);
      print('Connection closed by server!');
    },
  );

  Future.delayed(Duration(seconds: 1)).then((_) {
    channel.sink.add('Message 1');
    Future.delayed(Duration(seconds: 1)).then((_) {
      channel.sink.add('Message 2');
      Future.delayed(Duration(seconds: 1)).then((_) {
        channel.sink.add('Message 3');
        Future.delayed(Duration(seconds: 1)).then((_) {
          channel.sink.add('Message 4');
          Future.delayed(Duration(seconds: 1)).then((_) {
            channel.sink.add('Message 5');
          });
        });
      });
    });
  });
}

Outputs:

➜  ws git:(main) ✗ dart lib/test.dart 
Received: Message 0
Received: Message 1
Connection closed by server!

➜  ws git:(main) ✗ dart lib/test.dart
Received: 5gn5gn5
Connection closed by server!

➜  ws git:(main) ✗ dart lib/test2.dart
Received: Message 0
Connection closed by server!
Unhandled exception:
SocketException: Reading from a closed socket
#0      _RawSecureSocket.read (dart:io/secure_socket.dart:779:7)
#1      _Socket._onData (dart:io-patch/socket_patch.dart:2448:28)
#2      _RootZone.runUnaryGuarded (dart:async/zone.dart:1594:10)
#3      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:365:11)
#4      _BufferingStreamSubscription._add (dart:async/stream_impl.dart:297:7)
#5      _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:784:19)
#6      _StreamController._add (dart:async/stream_controller.dart:658:7)
#7      _StreamController.add (dart:async/stream_controller.dart:606:5)
#8      _RawSecureSocket._sendReadEvent (dart:io/secure_socket.dart:1116:19)
#9      Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
#10     _Timer._runTimers (dart:isolate-patch/timer_impl.dart:398:19)
#11     _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:429:5)
#12     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)

➜  ws git:(main) ✗ dart lib/test2.dart
Received: Message 1
Received: .\sm.\sm.
Received: G嚕G嚕G
Connection closed by server!

Test in Postman

https://github.com/dart-lang/io/assets/43540889/236c4804-d3be-43ee-9894-567a5fdd40d4

vfiruz97 commented 1 month ago

It seems like the issue lies within the _messageFrameEnd method in the _WebSocketProtocolTransformer class, specifically when decoding bytes. Sometimes, the byte list contains numbers larger than 128, leading to errors and potentially causing the connection to close or unexpected characters to appear.

_eventSink!.add(utf8.decode(bytes));

That seems to be the culprit.

vfiruz97 commented 1 month ago

Dublicated https://github.com/dart-lang/sdk/issues/55810