ServiceStack ServerEventsClient无法处理含空格字符串事件求助
我搭建了一个Java SpringBoot服务器,负责接收消息并向C#和JS客户端广播。发送不含空格的消息时,所有客户端都能正常接收;但发送含空格的消息时,C#端的ServiceStack ServerEventsClient会触发OnException异常,非ServiceStack的JS客户端则完全正常——不管空格在什么位置、有多少个,只要有单个空格就会导致C#客户端失败。
异常栈追踪
System.ArgumentException: Invalid Selector '{"characterId":"6ed46423-6d70-4aae-a37f-4f5e799d326b","message":"\"hello' at ServiceStack.ServerEventsClient.ToTypedMessage(ServerEventMessage e) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Client/ServerEventsClient.cs:line 804 at ServiceStack.ServerEventsClient.ProcessEventMessage(ServerEventMessage e) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Client/ServerEventsClient.cs:line 859 at ServiceStack.ServerEventsClient.<>c__DisplayClass115_0.<ProcessResponse>b__0(Task`1 t) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Client/ServerEventsClient.cs:line 725
注:示例消息的第一个单词“hello”后带有空格。消息是一个包含Guid类型characterId和string类型message的DTO。
相关代码
Java SpringBoot服务器代码
@PostMapping(path = "/message") public boolean sendMessage(@RequestBody DTOs.MessageDTO request) { for(SseEmitter sseEmitter: emitters){ try { sseEmitter.send(request); } catch (IOException e) { throw new RuntimeException(e); } } return true; }
C#客户端代码(使用ServiceStack)
private ServerEventsClient _create_sse_client() { baseUri = $"..."; return new ServerEventsClient(baseUri) { OnMessage = this._events.Enqueue, OnException = errors.Add, OnCommand = this._commands.Enqueue, AllRequestFilters = req => { req.Method = HttpMethod.Get; req.AddHeader("Accept", "text/event-stream"); }, }; }
我是C#新手,想知道有没有明显遗漏的配置或处理步骤。
问题核心是SpringBoot的SseEmitter默认序列化格式与ServiceStack ServerEventsClient的解析逻辑不兼容。ServiceStack的客户端基于自身的Server Events协议实现,对SSE消息的格式有特定要求,而SpringBoot直接发送DTO对象生成的内容,在包含空格、引号时会触发解析截断错误。
方案1:修改SpringBoot服务器的消息发送格式
手动构造符合ServiceStack要求的标准SSE消息结构,确保完整的JSON被包裹在data字段中:
@PostMapping(path = "/message") public boolean sendMessage(@RequestBody DTOs.MessageDTO request) { ObjectMapper objectMapper = new ObjectMapper(); // 序列化DTO为完整转义的JSON字符串 String jsonPayload = objectMapper.writeValueAsString(request); // 构造标准SSE消息格式:data: [JSON内容]\n\n String sseMessage = "data: " + jsonPayload + "\n\n"; for(SseEmitter sseEmitter: emitters){ try { // 发送原始字符串格式的SSE消息 sseEmitter.send(sseMessage); } catch (IOException e) { throw new RuntimeException(e); } } return true; }
这样能保证ServiceStack客户端接收到完整、格式正确的消息,不会出现解析截断。
方案2:C#客户端自定义消息解析
如果不想修改服务器代码,可以绕过ServiceStack默认的解析逻辑,手动处理原始消息:
private ServerEventsClient _create_sse_client() { baseUri = $"..."; var client = new ServerEventsClient(baseUri) { OnException = errors.Add, OnCommand = this._commands.Enqueue, AllRequestFilters = req => { req.Method = HttpMethod.Get; req.AddHeader("Accept", "text/event-stream"); }, }; // 替换默认OnMessage,用OnRawMessage手动解析JSON client.OnRawMessage = (msg) => { try { var messageDto = JsonSerializer.Deserialize<MessageDTO>(msg.Data); this._events.Enqueue(messageDto); } catch (Exception ex) { errors.Add(ex); } }; return client; }
通过OnRawMessage获取原始JSON字符串,自己处理反序列化,避免ServiceStack默认解析的问题。
额外验证
在服务器端打印序列化后的JSON字符串,确认格式完整且转义正确,比如:{"characterId":"6ed46423-6d70-4aae-a37f-4f5e799d326b","message":"hello world"}
内容的提问来源于stack exchange,提问作者Curious User 123

