DEBUG.WIKI
阿里云;从建站到云上业务;云服务器 · 数据库 · 对象存储;查看产品 →
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS

Quarkus REST Client 多收到一条空 SSE 事件,检查被拆开的 CRLF

Quarkus SSE 解析器在 CRLF 跨输入块时可能多派发空事件。核对 3.40.1 代码,发送端可统一 LF,但不能过滤所有空 data。

服务端只发一条 data:foo,Quarkus REST Client 却先回调 foo,随后又回调一条空数据事件。如果发送端使用 CRLF 换行,且空行末尾的 CR 与 LF 分属两块输入,需要检查 SSE 解析器的跨块状态。

这个具体故障在主分支有完整触发序列;正式 3.40.1 的 SseParser 也保留同一处处理:读到 CR 时,只会吞掉当前缓冲区中紧随其后的 LF。CR 恰好位于块末尾时,没有保存“下一块开头还可能有 LF”的状态。

一个事件如何被识别两次

同一组合法字节可以这样分块,反斜杠表示换行控制字符:

第一块:data:foo\r\n\r
第二块:\n

第一块末尾的 CR 已让解析器派发事件;下一块的 LF 又被当成一条空行,于是多派发一次。TCP、HTTP 与客户端读取都可能改变数据分块,服务端一次 write 并不保证客户端一次读到。增大发送缓冲、让两字节尽量一起 flush,不能从协议上保证消除这个边界。

SSE 允许 CRLF、单独 CR 和单独 LF 作为行结束符。因此不能把服务端合法使用 CRLF 判成格式错误,也不能把网络分块当成业务事件边界。

诊断时保存受控事件的原始字节与客户端回调序列,比较连续输入和上述拆分输入。记录事件数量、data、id 与 event 类型,不只看最后一个 payload。不要把真实用户事件完整写进日志。

能控制发送端时,统一由序列化器输出 LF

短期可以只在这个 SSE 输出入口,把行结束符统一为 LF。例如一个事件的线上字节为:

data:foo\n\n

这里表示实际的 LF 字节,不是发送两个字符的 \n。在 SSE 序列化器层统一换行,继续使用 UTF-8、原有 Content-Type 与刷新行为;不要对已经组装好的任意业务字符串做全局替换,更不要破坏多行 data、注释心跳和 id 字段。

单 LF 不存在 CRLF 被拆成两块的问题,但它只避开这一条已定位的路径。若发送端不能改变,应选择经过相同分块用例验证的客户端实现,或等待明确包含状态修正的正式 Quarkus 版本。问题描述中的修复设想和主分支记录,都不能替代发行版本核证。

不能直接在业务回调里丢掉所有 data == "" 的事件。SSE 中 data: 后不带内容、再以空行结束,可以表示一条合法的空数据消息;带 id 或 event 类型的事件也可能承担业务含义。过滤空值会把解析问题变成静默丢数据。

用相同字节的多种切分验收

保留 CRLF 恰好跨块的用例,再覆盖 LF、CRLF 完整到达、多个相邻事件、多行 data 和业务允许的空数据消息。应当比较事件数量与字段内容,不只断言“不抛异常”。如果修改的是发送端,只变更对应流的序列化设置,保留原配置供回退,并检查其他消费者仍能正确读取。

这类多回调问题也不同于压缩或代理缓冲造成的延迟:后者通常让完整事件迟到或成批到达,不会凭空增加一个解析后的事件。先核对输入字节和回调,才能决定该查解析器还是传输链路。