You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 13:12:18