响应式编程 之 SSE接口调用
以下是 Web 前端(浏览器) 和 Java 客户端 调用现有 SSE(Server-Sent Events)接口的完整示例详解,涵盖连接、事件监听、错误处理、认证、重连等关键场景。
一、SSE 接口前提假设
假设后端已提供一个标准的 SSE 接口:
- URL:
http://localhost:8080/events - 协议:HTTP GET
- 响应类型:
Content-Type: text/event-stream - 事件格式示例:
event: message data: {"id":1,"content":"Hello"} id: 1001 retry: 3000 event: update data: {"status":"done"}
二、Web 前端调用 SSE(浏览器环境)
✅ 核心 API:EventSource
所有现代浏览器(Chrome, Firefox, Edge, Safari)原生支持,无需额外库。
1. 基础连接与监听
<!DOCTYPE html>
<html>
<head>
<title>SSE Client</title>
</head>
<body>
<div id="log"></div>
<script>
// 1. 创建 EventSource 实例(自动发起 GET 请求)
const eventSource = new EventSource('http://localhost:8080/events');
// 2. 监听默认事件(无 event 字段 或 event="message")
eventSource.onmessage = function(event) {
console.log('收到消息:', event.data);
document.getElementById('log').innerHTML += `<p>消息: ${event.data}</p>`;
};
// 3. 监听自定义事件(如 event="update")
eventSource.addEventListener('update', function(event) {
console.log('状态更新:', event.data);
// 处理业务逻辑
});
// 4. 连接打开
eventSource.onopen = function() {
console.log('SSE 连接已建立');
};
// 5. 错误处理(包括断线)
eventSource.onerror = function(err) {
console.error('SSE 连接出错:', err);
// 注意:浏览器会自动重连(除非调用 close())
};
// 6. 手动关闭连接(如用户退出页面)
window.addEventListener('beforeunload', () => {
eventSource.close();
});
</script>
</body>
</html>
2. 带认证的 SSE 请求
场景:需要传递 Token 或 Cookie
// 方式1:通过 URL 参数(不安全,仅用于演示)
const eventSource = new EventSource('/events?token=abc123');
// 方式2:通过 withCredentials(携带 Cookie)
const eventSource = new EventSource('/events', { withCredentials: true });
// 方式3:无法直接设置 Authorization 头!
// ❌ EventSource 不支持自定义请求头(安全限制)
⚠️ 重要限制:
EventSource不能设置自定义 HTTP 头(如Authorization: Bearer xxx),这是浏览器安全策略决定的。
解决方案:
- 使用 Cookie 认证(配合
withCredentials: true)- 将 token 放在 URL 查询参数中(注意:可能被日志记录,不推荐敏感场景)
- 改用 WebSocket(支持自定义头)
3. 手动控制重连
let retryCount = 0;
const maxRetries = 5;
eventSource.onerror = function() {
retryCount++;
if (retryCount > maxRetries) {
console.error('重试次数超限,停止连接');
eventSource.close();
} else {
console.log(`第 ${retryCount} 次重试...`);
// 浏览器会按服务器返回的 retry 值或默认 3 秒重连
}
};
三、Java 客户端调用 SSE(非浏览器环境)
由于 Java 没有内置 SSE 客户端,需手动解析
text/event-stream响应流。
方案选择:
- Java 11+:使用
HttpClient+ 自定义BodyHandler - 旧版本:使用 OkHttp、Apache HttpClient 等第三方库
示例 1:Java 11+ 原生 HttpClient
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.Flow;
public class SseJavaClient {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/events"))
.header("Accept", "text/event-stream")
// 如需 Cookie 认证
// .header("Cookie", "JSESSIONID=abc123")
.build();
// 自定义 BodyHandler 解析流
HttpResponse.BodyHandler<Void> bodyHandler = responseInfo ->
HttpResponse.BodySubscribers.fromLineSubscriber(
new Flow.Subscriber<String>() {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
subscription.request(1); // 请求第一行
}
@Override
public void onNext(String line) {
System.out.println("收到行: " + line);
// 简单解析 SSE 行(实际需更健壮的解析器)
if (line.startsWith("data:")) {
String data = line.substring(5).trim();
System.out.println("数据内容: " + data);
// 可反序列化 JSON
}
subscription.request(1); // 请求下一行
}
@Override
public void onError(Throwable throwable) {
System.err.println("SSE 流错误: " + throwable.getMessage());
throwable.printStackTrace();
}
@Override
public void onComplete() {
System.out.println("SSE 流结束");
}
},
"\n" // 按行分割
);
// 发起请求(异步)
client.sendAsync(request, bodyHandler)
.thenRun(() -> System.out.println("请求已发送"));
// 保持主线程运行
Thread.sleep(60_000);
}
}
示例 2:使用 OkHttp(更简洁)
// 添加依赖: com.squareup.okhttp3:okhttp:4.12.0
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.BufferedSource;
import java.io.IOException;
public class SseOkHttpClient {
public static void main(String[] args) throws IOException {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://localhost:8080/events")
.addHeader("Accept", "text/event-stream")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
ResponseBody body = response.body();
if (body == null) return;
BufferedSource source = body.source();
StringBuilder eventBuffer = new StringBuilder();
while (!Thread.interrupted()) {
String line = source.readUtf8Line();
if (line == null) break; // 连接关闭
if (line.isEmpty()) {
// 空行表示事件结束
parseAndHandleEvent(eventBuffer.toString());
eventBuffer.setLength(0); // 清空
} else {
eventBuffer.append(line).append("\n");
}
}
}
}
private static void parseAndHandleEvent(String eventText) {
System.out.println("完整事件:\n" + eventText);
// 这里可解析 event/data/id 字段
if (eventText.contains("data:")) {
String data = eventText.split("data:")[1].trim();
System.out.println("数据: " + data);
}
}
}
四、关键注意事项
1. SSE 是单向通信
- 服务器 → 客户端,客户端无法通过 SSE 向服务器发消息。
- 如需双向通信,改用 WebSocket。
2. 连接管理
- 前端:页面卸载时务必调用
eventSource.close()避免内存泄漏。 - Java 客户端:需在异常或完成时关闭流(try-with-resources)。
3. 错误与重连
- 浏览器会自动重连(间隔由服务器
retry:字段或默认 3 秒控制)。 - Java 客户端需自行实现重连逻辑(如指数退避)。
4. 跨域问题(CORS)
- 后端需设置 CORS 头:
@CrossOrigin(origins = "http://localhost:3000") @GetMapping("/events") public SseEmitter events() { ... } - 或全局配置:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/events").allowedOrigins("http://localhost:3000"); } }
5. 代理与超时
- Nginx 等反向代理需配置:
location /events { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ''; proxy_cache off; proxy_buffering off; # 关键!禁用缓冲 proxy_read_timeout 86400s; # 长超时 }
五、总结对比
| 客户端类型 | 实现方式 | 认证支持 | 重连机制 | 适用场景 |
|---|---|---|---|---|
| Web 前端 | new EventSource(url, options) |
Cookie / URL 参数 | 浏览器自动重连 | 浏览器实时通知 |
| Java 客户端 | HttpClient / OkHttp 手动解析流 | 支持任意 Header | 需手动实现 | 服务间调用、测试 |
💡 最佳实践建议:
- 浏览器场景:优先用
EventSource,简单可靠。- 服务端调用:若只是消费 SSE,可用 Java 手动解析;但长期建议后端改用 消息队列(如 Kafka) 或 gRPC 流 替代 SSE(SSE 设计初衷是面向浏览器的)。
本文来自博客园,作者:蓝迷梦,转载请注明原文链接:https://www.cnblogs.com/hewei-blogs/articles/19571133

浙公网安备 33010602011771号