Flutter桌面端WebSocket连接失败,手动升级可行,求两者差异原因
我通过以下方式尝试连接WebSocket:
final channel = WebSocketChannel.connect( Uri.parse('wss://xxxxx'), );
我的应用同时构建Web端和Windows桌面端,Web端连接正常,但桌面端出现如下报错:
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: WebSocketChannelException: WebSocketChannelException: WebSocketException: Connection to 'https://xxxxx#' was not upgraded to websocket #0 new IOWebSocketChannel._withoutSocket.<anonymous closure> (package:web_socket_channel/io.dart:98:24) #1 Stream.handleError.<anonymous closure> (dart:async/stream.dart:929:16) #2 _HandleErrorStream._handleError (dart:async/stream_pipe.dart:269:17) #3 _ForwardingStreamSubscription._handleError (dart:async/stream_pipe.dart:157:13) #4 _rootRunBinary (dart:async/zone.dart:1415:47) #5 _CustomZone.runBinary (dart:async/zone.dart:1307:19) #6 _CustomZone.runBinaryGuarded (dart:async/zone.dart:1217:7) #7 _BufferingStreamSubscription._sendError.sendError (dart:async/stream_impl.dart:358:15)
我了解Web端基于dart:html、桌面端基于dart:io实现,但无法理解为何仅桌面端连接失败。查询到不少同类问题指向服务端问题,但这无法解释Web端正常的情况。
最终我通过手动升级方案解决了桌面端连接问题:
Random r = new Random(); String key = base64.encode(List<int>.generate(8, (_) => r.nextInt(255))); HttpClient client = HttpClient(); HttpClientRequest request = await client.getUrl(Uri.parse('https://xxxxx')); request.headers.add('connection', 'Upgrade'); request.headers.add('upgrade', 'websocket'); request.headers.add('Sec-WebSocket-Version', '13'); request.headers.add('Sec-WebSocket-Key', key); HttpClientResponse response = await request.close(); Socket socket = await response.detachSocket(); WebSocket ws = WebSocket.fromUpgradedSocket(socket, serverSide: false); ws.listen((event) { print(event); });
请问为何该手动升级方案可行,而常规的WebSocketChannel.connect方式失败?两者存在哪些关键差异?
核心差异点
请求头处理细节不同
WebSocketChannel.connect依赖dart:io的WebSocket.connect实现,会自动添加握手所需标准头,但可能额外附带服务端不兼容的字段(比如不符合预期的Origin头),或是对Connection等头的格式处理存在细微偏差。而手动方案只添加了服务端明确需要的标准头,没有冗余内容,完全匹配服务端的校验逻辑。URI解析逻辑有区别
报错信息里出现的https://xxxxx#显示,WebSocketChannel.connect可能在处理wss://URI时错误保留了片段部分(#),导致服务端无法正确识别请求路径,拒绝升级连接。手动方案直接使用https://URI,规避了这个解析问题。握手流程的控制权不同
WebSocketChannel.connect封装了完整的握手流程,包含自动重定向、默认代理等额外逻辑,这些逻辑可能与服务端预期不符。手动方案完全掌控每一步:从创建请求、添加头到分离Socket升级,跳过了所有可能干扰的默认行为,完全模拟了浏览器端的握手流程。
手动方案可行的原因
服务端对WebSocket握手的兼容性校验存在差异:Web端的dart:html依赖浏览器原生WebSocket API,其握手请求格式经过广泛兼容测试,能被大多数服务端接受;而dart:io的默认实现细节恰好触发了服务端的校验规则,导致升级失败。手动方案完全复刻了浏览器端的标准握手请求格式,因此通过了服务端的校验,成功建立连接。
内容的提问来源于stack exchange,提问作者Ivo

