代码拉取完成,页面将自动刷新
<!DOCTYPE html>
<html lang="zh">
<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: 0 auto;
padding: 20px;
}
.container {
display: flex;
flex-direction: column;
gap: 20px;
}
.input-group {
display: flex;
flex-direction: column;
gap: 5px;
}
textarea {
height: 100px;
resize: vertical;
}
button {
padding: 10px;
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
.log-container {
border: 1px solid #ccc;
padding: 10px;
height: 300px;
overflow-y: auto;
background-color: #f5f5f5;
}
.log-entry {
margin: 5px 0;
padding: 5px;
border-bottom: 1px solid #eee;
}
.error {
color: red;
}
.success {
color: green;
}
.header-row {
display: flex;
gap: 10px;
margin-bottom: 5px;
align-items: center;
}
.header-name, .header-value {
flex: 1;
}
.remove-header {
background-color: #f44336;
color: white;
border: none;
padding: 5px 10px;
cursor: pointer;
}
.add-header {
margin-top: 5px;
align-self: flex-start;
}
</style>
</head>
<body>
<div class="container">
<h1>SSE 服务端检测工具</h1>
<div class="input-group">
<label for="url">服务端地址:</label>
<input type="text" id="url" placeholder="https://example.com/sse" value="http://localhost:3000/sse">
</div>
<div class="input-group">
<label for="method">请求方法:</label>
<select id="method">
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
<option value="PATCH">PATCH</option>
</select>
</div>
<div class="input-group">
<label>请求头:</label>
<div id="headers-container">
<!-- 头部会动态添加 -->
</div>
<button class="add-header" id="add-header">添加请求头</button>
</div>
<div class="input-group">
<label for="body">请求体(可选,仅适用于 POST/PUT/PATCH 等方法):</label>
<textarea id="body"></textarea>
</div>
<button id="connect">连接</button>
<button id="disconnect" disabled>断开连接</button>
<div class="log-container" id="log"></div>
</div>
<script>
let eventSource = null;
const logContainer = document.getElementById('log');
const connectBtn = document.getElementById('connect');
const disconnectBtn = document.getElementById('disconnect');
const headersContainer = document.getElementById('headers-container');
const addHeaderBtn = document.getElementById('add-header');
// 常用请求头列表
const commonHeaders = [
'Accept',
'Accept-Encoding',
'Accept-Language',
'Authorization',
'Cache-Control',
'Connection',
'Content-Disposition',
'Content-Encoding',
'Content-Length',
'Content-Type',
'Cookie',
'Host',
'Origin',
'Pragma',
'Referer',
'User-Agent',
'X-Requested-With'
];
// 常用 Content-Type 值
const contentTypes = [
'text/event-stream',
'application/json',
'application/x-www-form-urlencoded',
'multipart/form-data',
'text/plain',
'text/html',
'application/xml'
];
// 添加一行请求头
function addHeaderRow(name = '', value = '') {
const headerRow = document.createElement('div');
headerRow.className = 'header-row';
const headerNameSelect = document.createElement('select');
headerNameSelect.className = 'header-name';
// 添加空选项
const emptyOption = document.createElement('option');
emptyOption.value = '';
emptyOption.textContent = '-- 选择请求头 --';
headerNameSelect.appendChild(emptyOption);
// 添加常用请求头选项
commonHeaders.forEach(header => {
const option = document.createElement('option');
option.value = header;
option.textContent = header;
if (header === name) {
option.selected = true;
}
headerNameSelect.appendChild(option);
});
// 为 Content-Type 添加特殊处理
headerNameSelect.addEventListener('change', function() {
const valueInput = this.parentNode.querySelector('.header-value');
if (this.value === 'Content-Type') {
// 替换输入框为下拉列表
if (valueInput.tagName !== 'SELECT') {
const valueSelect = document.createElement('select');
valueSelect.className = 'header-value';
contentTypes.forEach(type => {
const option = document.createElement('option');
option.value = type;
option.textContent = type;
valueSelect.appendChild(option);
});
valueInput.parentNode.replaceChild(valueSelect, valueInput);
}
} else if (valueInput.tagName === 'SELECT') {
// 替换下拉列表为输入框
const valueInputNew = document.createElement('input');
valueInputNew.type = 'text';
valueInputNew.className = 'header-value';
valueInputNew.value = valueInput.value;
valueInput.parentNode.replaceChild(valueInputNew, valueInput);
}
});
let valueElement;
if (name === 'Content-Type') {
valueElement = document.createElement('select');
valueElement.className = 'header-value';
contentTypes.forEach(type => {
const option = document.createElement('option');
option.value = type;
option.textContent = type;
if (type === value) {
option.selected = true;
}
valueElement.appendChild(option);
});
} else {
valueElement = document.createElement('input');
valueElement.type = 'text';
valueElement.className = 'header-value';
valueElement.value = value;
valueElement.placeholder = '请求头的值';
}
const removeBtn = document.createElement('button');
removeBtn.className = 'remove-header';
removeBtn.textContent = '删除';
removeBtn.addEventListener('click', function() {
headerRow.remove();
});
headerRow.appendChild(headerNameSelect);
headerRow.appendChild(valueElement);
headerRow.appendChild(removeBtn);
headersContainer.appendChild(headerRow);
}
// 获取所有请求头
function getHeaders() {
const headers = {};
document.querySelectorAll('.header-row').forEach(row => {
const name = row.querySelector('.header-name').value;
const value = row.querySelector('.header-value').value;
if (name && value) {
headers[name] = value;
}
});
return headers;
}
// 初始化添加几个常用请求头
addHeaderRow('Accept', '*/*');
addHeaderRow('Content-Type', 'text/event-stream');
addHeaderRow('Cache-Control', 'no-cache');
addHeaderRow('Connection', 'keep-alive');
// 添加新请求头的事件
addHeaderBtn.addEventListener('click', () => {
addHeaderRow();
});
function log(message, type = '') {
const entry = document.createElement('div');
entry.className = `log-entry ${type}`;
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
logContainer.appendChild(entry);
logContainer.scrollTop = logContainer.scrollHeight;
}
function connect() {
try {
const url = document.getElementById('url').value;
const method = document.getElementById('method').value;
const headers = getHeaders();
const body = document.getElementById('body').value;
const controller = new AbortController();
const signal = controller.signal;
// 创建请求选项
const fetchOptions = {
method: method,
headers: headers,
signal: signal
};
// 只有非 GET/HEAD 方法才能包含请求体
if (method !== 'GET' && method !== 'HEAD' && body) {
fetchOptions.body = body;
}
log(`发起${method}请求: ${url}`, 'success');
fetch(url, fetchOptions).then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
log(`收到响应: HTTP ${response.status} ${response.statusText}`, 'success');
// 检查内容类型
const contentType = response.headers.get('Content-Type');
log(`响应内容类型: ${contentType || '未指定'}`, 'success');
// 如果是 SSE 类型,建立流式连接
if (contentType && contentType.includes('text/event-stream')) {
log('检测到 SSE 连接,建立流式处理...', 'success');
const reader = response.body.getReader();
const decoder = new TextDecoder();
function readStream() {
reader.read().then(({done, value}) => {
if (done) {
log('SSE 连接已关闭', 'error');
return;
}
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
lines.forEach(line => {
if (line.startsWith('data:')) {
const data = line.slice(5).trim();
log(`收到消息: ${data}`, 'success');
} else if (line.startsWith('event:')) {
const event = line.slice(6).trim();
log(`事件类型: ${event}`, 'success');
} else if (line.startsWith('id:')) {
const id = line.slice(3).trim();
log(`消息ID: ${id}`, 'success');
} else if (line.startsWith('retry:')) {
const retry = line.slice(6).trim();
log(`重连时间: ${retry}ms`, 'success');
} else if (line.trim()) {
log(`原始数据: ${line}`, 'success');
}
});
readStream();
}).catch(error => {
log(`读取错误: ${error.message}`, 'error');
});
}
readStream();
} else {
// 普通响应,显示响应内容
log('接收普通 HTTP 响应,显示响应内容...', 'success');
response.text().then(text => {
try {
// 尝试解析为 JSON
const json = JSON.parse(text);
log(`响应内容(JSON): ${JSON.stringify(json, null, 2)}`, 'success');
} catch {
// 不是 JSON,显示原始文本
log(`响应内容: ${text}`, 'success');
}
});
}
// 保存控制器以便后续断开连接
eventSource = controller;
connectBtn.disabled = true;
disconnectBtn.disabled = false;
}).catch(error => {
log(`连接错误: ${error.message}`, 'error');
});
} catch (error) {
log(`配置错误: ${error.message}`, 'error');
}
}
function disconnect() {
if (eventSource) {
eventSource.abort();
eventSource = null;
connectBtn.disabled = false;
disconnectBtn.disabled = true;
log('已断开连接', 'success');
}
}
connectBtn.addEventListener('click', connect);
disconnectBtn.addEventListener('click', disconnect);
</script>
</body>
</html>
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。