Flutter WebSocket连接响应状态码返回NULL问题求解
您好:
我正在开发基于WebSocket实现的登录功能, 当传入正确参数时可成功建立登录连接,但即便连接已建立,也无法返回类似201格式的状态码响应消息。当我故意输入错误参数时,会返回如下错误:WebSocketChannelException: WebSocketChannelException: WebSocketException: Connection to 'the url i entered#' was not upgraded to websocket,实现代码如下:
IOWebSocketChannel channel = new IOWebSocketChannel.connect( "THE URL I ENTERED", headers: {"USER_ID":widget.user,"PASSWORD":widget.password}, ); channel.stream.listen( (dynamic message) { debugPrint('message $message'); channel.sink.close(status.goingAway); print(status.goingAway); }, onDone: () { debugPrint('ws channel closed'); }, onError: (error) { print('socket closed: reason=[${channel.closeReason}], code:[${channel.closeCode}]'); debugPrint('ws error $error'); }, );
终端输出信息如下:
I/flutter ( 5598): socket closed: reason=[null], code:[null] I/flutter ( 5598): ws error WebSocketChannelException: WebSocketChannelException: WebSocketException: Connection to 'THE URL I ENTERED' was not upgraded to websocket I/flutter ( 5598): ws channel closed
我的诉求是:无论输入参数正确或错误,都希望能正常获取服务端返回的响应状态码。 我之前使用Python等其他语言实现相同逻辑时均可正常获取响应反馈,但在Flutter实现中相关返回值始终为null,我已花费4天时间检索各类资料查找该问题的解决方案,始终未找到有效方法,恳请大家提供帮助。
此致
IOWebSocketChannel 默认封装的connect方法不会暴露WebSocket握手阶段的HTTP响应细节:
- 当服务端返回非101状态码(即握手失败、未升级为WebSocket连接)时,内部会直接抛出异常,不会把HTTP状态码、响应体透传给外层,此时连接从未成功建立,
closeCode、closeReason自然始终为null。 - WebSocket协议本身没有HTTP风格的状态码机制,你提到的201类状态码属于服务端自定义的业务响应,只会在连接建立后通过消息帧下发,不会出现在连接关闭相关的字段里。
Python等语言的WebSocket客户端库默认会把握手阶段的HTTP响应信息挂载到异常对象上,Dart的默认封装没有做这层处理,这是拿不到状态码的核心原因。
1. 握手失败场景获取HTTP状态码
不要直接调用封装好的IOWebSocketChannel.connect,手动拆分握手流程,先发起HTTP升级请求,拿到响应状态码后再判断是否继续建立WebSocket连接,示例代码:
import 'dart:io'; import 'dart:convert'; import 'dart:math'; import 'package:web_socket_channel/io.dart'; Future<Map<String, dynamic>> connectWebSocket(String url, Map<String, String> authHeaders) async { final httpClient = HttpClient(); final request = await httpClient.openUrl('GET', Uri.parse(url)); // 传入自定义认证头 authHeaders.forEach((key, value) => request.headers.add(key, value)); // 填充WebSocket升级必须的协议头 request.headers.add('Connection', 'Upgrade'); request.headers.add('Upgrade', 'websocket'); request.headers.add('Sec-WebSocket-Version', '13'); final key = base64.encode(List<int>.generate(16, (_) => Random().nextInt(256))); request.headers.add('Sec-WebSocket-Key', key); final response = await request.close(); final statusCode = response.statusCode; // 握手失败,直接返回状态码和响应内容 if (statusCode != HttpStatus.switchingProtocols) { final responseBody = await response.transform(utf8.decoder).join(); httpClient.close(); return { 'success': false, 'statusCode': statusCode, 'body': responseBody }; } // 握手成功,将底层socket包装为WebSocketChannel final socket = await response.detachSocket(); final channel = IOWebSocketChannel(socket); httpClient.close(); return { 'success': true, 'statusCode': statusCode, 'channel': channel }; }
调用该方法时,无论握手成功还是失败,都能直接从返回结果里拿到服务端的HTTP状态码,不会再出现null的情况。
2. 连接成功场景获取业务状态码
连接建立后,服务端返回的201类业务状态码会通过消息帧下发,直接在stream.listen的消息回调里解析即可,不需要从连接关闭相关字段读取:
final connectResult = await connectWebSocket( "YOUR_WS_URL", {"USER_ID": widget.user, "PASSWORD": widget.password} ); if (!connectResult['success']) { // 处理握手失败逻辑,直接读取statusCode做业务判断 print('握手失败,状态码:${connectResult['statusCode']}'); return; } final channel = connectResult['channel'] as IOWebSocketChannel; channel.stream.listen( (dynamic message) { debugPrint('收到服务端消息:$message'); // 在此处解析消息内容,提取服务端返回的业务状态码(如201) }, onDone: () => debugPrint('ws channel closed'), onError: (error) => debugPrint('ws error $error'), );
内容的提问来源于stack exchange,提问作者cem Karakose

