Flutter SSE客户端处理大响应时报FormatException: Unterminated string如何解决
问题根本原因
- 分片截断导致UTF8解码失败:http请求返回的流式响应是按字节分片传输的,分片边界完全由网络层决定,不保证对应完整的UTF8字符。当响应体积较大时,很容易出现某个分片末尾刚好截断了一个多字节UTF8字符的情况,此时直接调用
utf8.decode(d)就会抛出Unterminated string格式异常。 - SSE事件解析逻辑不符合流规范:现有逻辑每拿到一个分片就直接按换行拆分、硬取下标为1的元素作为事件内容,完全没有考虑单条SSE事件可能会跨多个分片传输的场景。大体积的SSE事件必然会被拆到多个分片中,直接解析单个分片只能拿到不完整的事件内容,不仅会触发解析错误,还会导致业务数据丢失。
修复方案
你需要新增字节缓冲区逻辑,先攒够完整的SSE事件再做解析,核心修改逻辑如下:
- 初始化一个字节缓存区,每次接收到新的分片字节,先全部追加到缓存区中
- 从缓存区中拆分出所有完整的SSE行(行分隔符支持
\n/\r\n/\r),拆分后剩余的不完整字节留在缓存区等待后续分片拼接 - 按SSE协议规范逐行解析事件字段,遇到空行时判定当前事件已经接收完整,再将完整事件数据传入
SSEModel.fromData处理
修复后的核心代码示例
while (true) { try { _client = http.Client(); var request = http.Request("GET", Uri.parse(url)); request.headers["Cache-Control"] = "no-cache"; request.headers["Accept"] = "text/event-stream"; request.headers["Cookie"] = token; var response = await _client.send(request); // 新增字节缓冲区 List<int> buffer = []; await for (final chunk in response.stream) { buffer.addAll(chunk); // 拆分完整行 int lineStart = 0; for (int i = 0; i < buffer.length; i++) { // 匹配换行符 \n是10,\r是13 if (buffer[i] == 10 || buffer[i] == 13) { if (i > lineStart) { // 解码完整的一行字节 final line = utf8.decode(buffer.sublist(lineStart, i)); if (line.isNotEmpty) { // 按业务逻辑处理完整行,可自行调整适配SSEModel的解析规则 yield SSEModel.fromData(line); } } // 跳过\r\n的第二个换行符 if (buffer[i] == 13 && i + 1 < buffer.length && buffer[i+1] == 10) { i++; } lineStart = i + 1; } } // 保留未处理的不完整字节到下一轮 buffer = buffer.sublist(lineStart); } } catch (e) { print('---ERROR---'); print(e); yield SSEModel(data: '', id: '', event: ''); } await Future.delayed(const Duration(seconds: 1)); }
如果需要兼容包含id、event、多段data的标准SSE事件格式,可以在解析行的逻辑里新增事件字段缓存,遇到空行时再组装成完整的SSE事件对象即可。
内容的提问来源于stack exchange,提问作者Pratik Baid
相关产品推荐
相关产品推荐

