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

websocket is not connecting #329

Open AwaisKhan74 opened 4 months ago

AwaisKhan74 commented 4 months ago

I am getting this error: Bad response 'Sec-WebSocket-Accept' header

Here is my code snippet WebSocketChannel.connect(Uri.parse(wsUrl)); upoun debging websocket_impl.dart I found these values

nonce="cKPAQxX7w6REzh3smWpAYA==" accept="Kfh9QIsMVZcl6xEPYxPHzW8SZ8w="

Expected results It should compare accept and nonce and return true. As it is connecting to socket while connecting from postman or JS.

Actual results The fluter say bad request sec-websocket header. The List for expectedAccept and orignalAccept differ but thy shouldn't.

Code sample: WebSocketChannel.connect(Uri.parse(wsUrl)); values in headers are nonce="cKPAQxX7w6REzh3smWpAYA==" accept="Kfh9QIsMVZcl6xEPYxPHzW8SZ8w=" code from flutter to run is static Future connect( String url, Iterable? protocols, Map<String, dynamic>? headers, {CompressionOptions compression = CompressionOptions.compressionDefault, HttpClient? customClient}) { Uri uri = Uri.parse(url); if (!uri.isScheme("ws") && !uri.isScheme("wss")) { throw WebSocketException("Unsupported URL scheme '${uri.scheme}'"); }

Random random = Random();
// Generate 16 random bytes.
Uint8List nonceData = Uint8List(16);
for (int i = 0; i < 16; i++) {
  nonceData[i] = random.nextInt(256);
}
String nonce = base64Encode(nonceData);

final callerStackTrace = StackTrace.current;

uri = Uri(
    scheme: uri.isScheme("wss") ? "https" : "http",
    userInfo: uri.userInfo,
    host: uri.host,
    port: uri.port,
    path: uri.path,
    query: uri.query,
    fragment: uri.fragment);
return (customClient ?? _httpClient).openUrl("GET", uri).then((request) {
  if (uri.userInfo != null && uri.userInfo.isNotEmpty) {
    // If the URL contains user information use that for basic
    // authorization.
    String auth = base64Encode(utf8.encode(uri.userInfo));
    request.headers.set(HttpHeaders.authorizationHeader, "Basic $auth");
  }
  if (headers != null) {
    headers.forEach((field, value) => request.headers.add(field, value));
  }
  // Setup the initial handshake.
  request.headers
    ..set(HttpHeaders.connectionHeader, "Upgrade")
    ..set(HttpHeaders.upgradeHeader, "websocket")
    ..set("Sec-WebSocket-Key", nonce)
    ..set("Cache-Control", "no-cache")
    ..set("Sec-WebSocket-Version", "13");
  if (protocols != null) {
    request.headers.add("Sec-WebSocket-Protocol", protocols.toList());
  }

  if (compression.enabled) {
    request.headers
        .add("Sec-WebSocket-Extensions", compression._createHeader());
  }

  return request.close();
}).then((response) {
  Future<WebSocket> error(String message) {
    // Flush data.
    response.detachSocket().then((socket) {
      socket.destroy();
    });
    return Future<WebSocket>.error(
        WebSocketException(message), callerStackTrace);
  }

  var connectionHeader = response.headers[HttpHeaders.connectionHeader];
  if (response.statusCode != HttpStatus.switchingProtocols ||
      connectionHeader == null ||
      !connectionHeader.any((value) => value.toLowerCase() == "upgrade") ||
      response.headers.value(HttpHeaders.upgradeHeader)!.toLowerCase() !=
          "websocket") {
    return error("Connection to '$uri' was not upgraded to websocket");
  }
  String? accept = response.headers.value("Sec-WebSocket-Accept");
  if (accept == null) {
    return error(
        "Response did not contain a 'Sec-WebSocket-Accept' header");
  }
  _SHA1 sha1 = _SHA1();
  sha1.add("$nonce$_webSocketGUID".codeUnits);
  List<int> expectedAccept = sha1.close();
  List<int> receivedAccept = base64Decode(accept);
  if (expectedAccept.length != receivedAccept.length) {
    return error(
        "Response header 'Sec-WebSocket-Accept' is the wrong length");
  }
  for (int i = 0; i < expectedAccept.length; i++) {
    if (expectedAccept[i] != receivedAccept[i]) {
      return error("Bad response 'Sec-WebSocket-Accept' header");
    }
  }
  var protocol = response.headers.value('Sec-WebSocket-Protocol');

  _WebSocketPerMessageDeflate? deflate =
      negotiateClientCompression(response, compression);

  return response.detachSocket().then<WebSocket>((socket) =>
      _WebSocketImpl._fromSocket(
          socket, protocol, compression, false, deflate));
});

}

static _WebSocketPerMessageDeflate? negotiateClientCompression( HttpClientResponse response, CompressionOptions compression) { String extensionHeader = response.headers.value('Sec-WebSocket-Extensions') ?? "";

var hv = HeaderValue.parse(extensionHeader, valueSeparator: ',');

if (compression.enabled && hv.value == PER_MESSAGE_DEFLATE) {
  var serverNoContextTakeover =
      hv.parameters.containsKey(_serverNoContextTakeover);
  var clientNoContextTakeover =
      hv.parameters.containsKey(_clientNoContextTakeover);

  int getWindowBits(String type) {
    var o = hv.parameters[type];
    if (o == null) {
      return DEFAULT_WINDOW_BITS;
    }

    return int.tryParse(o) ?? DEFAULT_WINDOW_BITS;
  }

  return _WebSocketPerMessageDeflate(
      clientMaxWindowBits: getWindowBits(_clientMaxWindowBits),
      serverMaxWindowBits: getWindowBits(_serverMaxWindowBits),
      clientNoContextTakeover: clientNoContextTakeover,
      serverNoContextTakeover: serverNoContextTakeover);
}

return null;

}