使用JAX-RS Jetty重传SSE遇缓冲问题求解决方案(OSGi插件场景)
听起来你已经走了大半路程了,读取SSE到控制台没问题,但写入时的缓冲确实是SSE转发里常见的坑——毕竟SSE是基于流式输出的,缓冲会把消息攒着不发,客户端就收不到实时更新。下面我给你拆解解决方案,包括缓冲修复和同函数读写的实现:
一、先搞定缓冲问题
导致SSE写入缓冲的核心原因通常有两个:要么是输出流用了带缓冲的包装,要么是没主动触发刷新,或者OSGi的HttpServletResponse默认带缓冲。直接按这几步来修:
禁用响应缓冲并配置SSE头
在获取输出流之前,先把响应的缓冲设为0,同时设置符合SSE规范的响应头:// 禁用缓冲,强制实时输出 response.setBufferSize(0); // 配置SSE必要响应头 response.setContentType("text/event-stream"); response.setCharacterEncoding("UTF-8"); response.setHeader("Cache-Control", "no-cache"); response.setHeader("Connection", "keep-alive");避免带缓冲的Writer,写完立刻flush
别用BufferedWriter包装响应的Writer,直接使用response.getWriter(),并且每写完一条SSE消息就立刻调用flush,这是解决缓冲的关键:PrintWriter writer = response.getWriter(); // 写入标准格式的SSE消息 writer.write("data: 你的消息内容\n\n"); // 强制把缓冲里的内容推给客户端 writer.flush();确保SSE消息格式完全合规
哪怕你flush了,如果消息格式不对,客户端也可能不识别。每条消息必须以\n\n结尾,带id或事件类型的话要按规范编写:// 带id和事件类型的完整SSE消息 writer.write("id: " + messageId + "\n"); writer.write("event: update\n"); writer.write("data: " + content + "\n\n"); writer.flush();
二、在同一函数中完成SSE读写转发
要在同一个函数里实现“读取上游SSE -> 转发到下游客户端”,可以用循环持续读取上游消息,拿到后立刻写入响应流并flush。这里要注意OSGi的线程模型——因为SSE是长连接,这个函数会一直阻塞直到连接断开,所以要确保OSGi的HttpService允许长连接线程存活。
完整代码示例(OSGi Servlet)
import org.osgi.service.http.HttpService; import org.osgi.service.http.servlet.ServletContextHelper; import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; public class SSEForwardServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 配置SSE响应并禁用缓冲 response.setContentType("text/event-stream"); response.setCharacterEncoding("UTF-8"); response.setHeader("Cache-Control", "no-cache"); response.setHeader("Connection", "keep-alive"); response.setBufferSize(0); PrintWriter writer = response.getWriter(); HttpClient httpClient = HttpClient.newHttpClient(); HttpRequest upstreamRequest = HttpRequest.newBuilder() .uri(URI.create("你的上游SSE源地址")) .GET() .build(); try { // 2. 流式读取上游SSE响应 HttpResponse<Void> upstreamResponse = httpClient.send( upstreamRequest, BodyHandlers.ofConsumer((responseInfo, bodyConsumer) -> { bodyConsumer.accept(byteBuffer -> { try { // 3. 将读取到的SSE消息直接转发到下游 String sseMsg = new String(byteBuffer.array(), responseInfo.charset()); writer.write(sseMsg); writer.flush(); // 控制台打印转发日志(和你之前的逻辑保持一致) System.out.println("转发SSE消息: " + sseMsg.trim()); } catch (IOException e) { // 客户端断开连接时终止转发 e.printStackTrace(); } }); }) ); } catch (InterruptedException e) { Thread.currentThread().interrupt(); e.printStackTrace(); } finally { // 4. 资源清理 writer.close(); } } // 在OSGi Activator中注册Servlet的示例代码 public static void registerToHttpService(HttpService httpService) throws Exception { Servlet sseServlet = new SSEForwardServlet(); httpService.registerServlet("/sse-proxy", sseServlet, null, new ServletContextHelper() {}); } }
替代方案:用EventSource监听上游消息
如果你用的是Java 11以下版本,可以用第三方EventSource库(比如LaunchDarkly的EventSource)来监听上游SSE,在消息回调里完成转发:
import com.launchdarkly.eventsource.EventSource; import com.launchdarkly.eventsource.EventHandler; import java.net.URI; // 省略Servlet配置代码... EventSource eventSource = new EventSource.Builder( new EventHandler() { @Override public void onMessage(String event, String message) throws Exception { // 收到上游消息后立刻转发 writer.write("data: " + message + "\n\n"); writer.flush(); System.out.println("转发消息: " + message); } // 其他回调方法(onOpen、onError)按需实现 }, URI.create("你的上游SSE源地址") ).build(); eventSource.start(); // 等待连接关闭,避免函数提前退出 eventSource.awaitClosed();
三、调试技巧
如果还是有缓冲问题,可以加这些调试步骤:
- 在每次flush后调用
writer.checkError(),如果返回true说明输出流已异常(比如客户端断开) - 用浏览器开发者工具查看Network面板,确认SSE请求的响应头包含
Transfer-Encoding: chunked,这代表流式输出正常 - 先写入一条测试消息:
writer.write("data: test\n\n"); writer.flush();,看客户端是否立刻收到
这样应该就能解决你的缓冲问题,同时在同一个函数里完成SSE的读写转发了。
内容的提问来源于stack exchange,提问作者ssavva05

