Files
kevisual-center/public/sse-test.html

215 lines
5.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSE 测试</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
.status {
padding: 10px;
margin: 10px 0;
border-radius: 4px;
}
.status.connected { background: #d4edda; color: #155724; }
.status.connecting { background: #fff3cd; color: #856404; }
.status.disconnected { background: #f8d7da; color: #721c24; }
.events {
border: 1px solid #ddd;
padding: 10px;
height: 400px;
overflow-y: auto;
background: #f9f9f9;
}
.event-item {
padding: 8px;
margin: 5px 0;
background: #fff;
border: 1px solid #eee;
border-radius: 4px;
}
.event-item .time {
color: #888;
font-size: 12px;
}
.event-item .data {
white-space: pre-wrap;
word-break: break-all;
}
</style>
</head>
<body>
<h1>SSE / HTTP Stream 测试</h1>
<div>
<label>URL: </label>
<input type="text" id="url" value="http://localhost:4005/root/v3/" style="width: 400px;">
<label>类型: </label>
<select id="streamType">
<option value="sse">SSE (EventSource)</option>
<option value="stream">HTTP Stream (Fetch)</option>
</select>
<button onclick="connect()">连接</button>
<button onclick="disconnect()">断开</button>
</div>
<div id="status" class="status disconnected">未连接</div>
<h3>事件日志</h3>
<div id="events" class="events"></div>
<script>
let eventSource = null;
let abortController = null;
async function connect() {
const url = document.getElementById('url').value;
const type = document.getElementById('streamType').value;
if (!url) {
alert('请输入 URL');
return;
}
disconnect();
const eventsDiv = document.getElementById('events');
eventsDiv.innerHTML = '';
updateStatus('connecting', '正在连接...');
if (type === 'sse') {
connectSSE(url);
} else {
connectStream(url);
}
}
function connectSSE(url) {
try {
eventSource = new EventSource(url);
eventSource.onopen = function() {
updateStatus('connected', '已连接 (SSE)');
addEvent('系统', 'SSE 连接已建立');
};
eventSource.onmessage = function(event) {
addEvent('消息', event.data, 'message');
};
eventSource.onerror = function(error) {
// 如果 readyState 是 CLOSED说明是后端主动关闭不重连
if (eventSource.readyState === EventSource.CLOSED) {
updateStatus('disconnected', '连接已关闭');
addEvent('系统', '后端已关闭连接,无须重连');
return;
}
// 如果是其他错误状态,显示错误信息,不自动重连
updateStatus('disconnected', '连接错误');
addEvent('错误', 'SSE 连接发生错误');
};
eventSource.addEventListener('data', function(event) {
addEvent('自定义事件', event.data, 'data');
});
} catch (e) {
updateStatus('disconnected', '连接失败: ' + e.message);
addEvent('错误', e.message);
}
}
async function connectStream(url) {
try {
abortController = new AbortController();
updateStatus('connected', '已连接 (Stream)');
addEvent('系统', 'HTTP Stream 连接已建立');
const response = await fetch(url, {
signal: abortController.signal,
headers: {
'Accept': 'text/plain, text/event-stream'
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) {
addEvent('系统', 'Stream 连接已关闭');
break;
}
// 解码并处理数据
buffer += decoder.decode(value, { stream: true });
// 按行处理
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.trim()) {
addEvent('数据', line, 'stream');
}
}
}
} catch (e) {
if (e.name !== 'AbortError') {
updateStatus('disconnected', '连接错误: ' + e.message);
addEvent('错误', e.message);
}
}
}
function disconnect() {
if (eventSource) {
eventSource.close();
eventSource = null;
}
if (abortController) {
abortController.abort();
abortController = null;
}
updateStatus('disconnected', '已断开');
addEvent('系统', '连接已关闭');
}
function updateStatus(type, message) {
const statusDiv = document.getElementById('status');
statusDiv.className = 'status ' + type;
statusDiv.textContent = message;
}
function addEvent(type, data, eventType = '') {
const eventsDiv = document.getElementById('events');
const item = document.createElement('div');
item.className = 'event-item';
const time = new Date().toLocaleTimeString();
item.innerHTML = `
<div class="time">[${time}] ${eventType ? '[' + eventType + '] ' : ''}${type}</div>
<div class="data">${escapeHtml(data)}</div>
`;
eventsDiv.insertBefore(item, eventsDiv.firstChild);
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
</script>
</body>
</html>