549 lines
15 KiB
HTML
549 lines
15 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||
<title>WebSocket测试工具</title>
|
||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
|
||
<style>
|
||
:root {
|
||
--primary-color: #3b82f6;
|
||
--secondary-color: #10b981;
|
||
--danger-color: #ef4444;
|
||
--dark-color: #1e293b;
|
||
--light-color: #f8fafc;
|
||
--gray-color: #94a3b8;
|
||
--border-color: #e2e8f0;
|
||
}
|
||
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
}
|
||
|
||
body {
|
||
background-color: #f1f5f9;
|
||
color: var(--dark-color);
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.container {
|
||
width: 100vw;
|
||
height: 100vh;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
padding-bottom: 20px;
|
||
border-bottom: 1px solid var(--border-color);
|
||
}
|
||
|
||
header h1 {
|
||
color: var(--primary-color);
|
||
margin-bottom: 10px;
|
||
font-size: 2.2rem;
|
||
}
|
||
|
||
header p {
|
||
color: var(--gray-color);
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.main-content {
|
||
display: flex;
|
||
gap: 10px;
|
||
|
||
}
|
||
|
||
|
||
.control-panel {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
height: 600px;
|
||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||
width: 350px;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
label {
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
font-weight: 600;
|
||
color: var(--dark-color);
|
||
}
|
||
|
||
input, textarea {
|
||
width: 100%;
|
||
padding: 10px 12px;
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 4px;
|
||
font-size: 1rem;
|
||
transition: border-color 0.2s;
|
||
}
|
||
|
||
input:focus, textarea:focus {
|
||
outline: none;
|
||
border-color: var(--primary-color);
|
||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
||
}
|
||
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 10px 16px;
|
||
background-color: var(--primary-color);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 1rem;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: background-color 0.2s, transform 0.1s;
|
||
gap: 8px;
|
||
}
|
||
|
||
.btn:hover {
|
||
background-color: #2563eb;
|
||
}
|
||
|
||
.btn:active {
|
||
transform: translateY(1px);
|
||
}
|
||
|
||
.btn-success {
|
||
background-color: var(--secondary-color);
|
||
}
|
||
|
||
.btn-success:hover {
|
||
background-color: #059669;
|
||
}
|
||
|
||
.btn-danger {
|
||
background-color: var(--danger-color);
|
||
}
|
||
|
||
.btn-danger:hover {
|
||
background-color: #dc2626;
|
||
}
|
||
|
||
.btn-group {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.btn-group .btn {
|
||
flex: 1;
|
||
}
|
||
|
||
.message-area {
|
||
background: white;
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 600px;
|
||
flex: 1;
|
||
}
|
||
|
||
.message-history {
|
||
flex: 1;
|
||
padding: 20px;
|
||
overflow-y: auto;
|
||
height: 500px;
|
||
border-bottom: 1px solid var(--border-color);
|
||
width: 100%;
|
||
}
|
||
|
||
.message-controls {
|
||
padding: 10px 20px;
|
||
border-bottom: 1px solid var(--border-color);
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.message-input-area {
|
||
padding: 20px;
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.message-input-area textarea {
|
||
flex: 1;
|
||
resize: none;
|
||
min-height: 80px;
|
||
}
|
||
|
||
.message {
|
||
margin-bottom: 16px;
|
||
max-width: 80%;
|
||
animation: fadeIn 0.3s ease;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(10px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.message-sent {
|
||
margin-left: auto;
|
||
}
|
||
|
||
.message-received {
|
||
margin-right: auto;
|
||
}
|
||
|
||
.message-content {
|
||
padding: 10px 14px;
|
||
border-radius: 12px;
|
||
position: relative;
|
||
word-wrap: break-word;
|
||
word-break: break-all;
|
||
white-space: normal;
|
||
width: 100%;
|
||
}
|
||
|
||
.message-sent .message-content {
|
||
background-color: var(--primary-color);
|
||
color: white;
|
||
border-bottom-right-radius: 4px;
|
||
}
|
||
|
||
.message-received .message-content {
|
||
background-color: #e2e8f0;
|
||
color: var(--dark-color);
|
||
border-bottom-left-radius: 4px;
|
||
}
|
||
|
||
.message-time {
|
||
font-size: 0.8rem;
|
||
color: var(--gray-color);
|
||
margin-top: 4px;
|
||
text-align: right;
|
||
}
|
||
|
||
.status {
|
||
display: inline-block;
|
||
padding: 4px 8px;
|
||
border-radius: 12px;
|
||
font-size: 0.8rem;
|
||
font-weight: 500;
|
||
margin-left: 10px;
|
||
}
|
||
|
||
.status-connected {
|
||
background-color: rgba(16, 185, 129, 0.1);
|
||
color: var(--secondary-color);
|
||
}
|
||
|
||
.status-disconnected {
|
||
background-color: rgba(239, 68, 68, 0.1);
|
||
color: var(--danger-color);
|
||
}
|
||
|
||
.connection-info {
|
||
font-size: 0.9rem;
|
||
color: var(--gray-color);
|
||
margin-top: 5px;
|
||
font-family: monospace;
|
||
word-wrap: break-word;
|
||
word-break: break-all;
|
||
white-space: normal;
|
||
width: 100%;
|
||
}
|
||
|
||
.empty-state {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100%;
|
||
color: var(--gray-color);
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
}
|
||
|
||
.empty-state i {
|
||
font-size: 4rem;
|
||
margin-bottom: 20px;
|
||
opacity: 0.3;
|
||
}
|
||
|
||
.empty-state p {
|
||
max-width: 400px;
|
||
line-height: 1.8;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<header>
|
||
<h1><i class="fas fa-plug"></i> WebSocket 测试工具</h1>
|
||
<p>简单高效地测试WebSocket连接和通信</p>
|
||
</header>
|
||
|
||
<div class="main-content">
|
||
<div class="control-panel">
|
||
<div class="form-group">
|
||
<label for="ws-url">WebSocket 地址</label>
|
||
<input id="ws-url" placeholder="ws://example.com/ws 或 wss://example.com/ws" type="text"
|
||
value="ws://127.0.0.1:10086/fdx">
|
||
<div class="connection-info" id="connection-status">未连接</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="ws-authorization">TOKEN</label>
|
||
<input id="ws-authorization" placeholder="TOKEN"
|
||
type="text"
|
||
value="MSwxLGNlMWU2MmJhNWExNzQ1YzE4NDAxNGNlN2Q5MTkxODU5LDE3NTkyMjAzNjMxODEsMCxQQVNTV09SRA==">
|
||
</div>
|
||
<div class="btn-group">
|
||
<button class="btn btn-success" id="connect-btn">
|
||
<i class="fas fa-play"></i> 连接
|
||
</button>
|
||
<button class="btn btn-danger" disabled id="disconnect-btn">
|
||
<i class="fas fa-stop"></i> 断开
|
||
</button>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>连接信息</label>
|
||
<div class="connection-info" id="connection-details">未连接到服务器</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="message-area">
|
||
<!-- 新增消息控制栏,包含清除按钮 -->
|
||
<div class="message-controls">
|
||
<button class="btn" id="clear-btn">
|
||
<i class="fas fa-trash"></i> 清除消息
|
||
</button>
|
||
</div>
|
||
|
||
<div class="message-history" id="message-history">
|
||
<div class="empty-state">
|
||
<i class="fas fa-comments"></i>
|
||
<p>连接到WebSocket服务器后,消息将显示在这里</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="message-input-area">
|
||
<textarea id="message-input" placeholder="输入要发送的消息..."></textarea>
|
||
<button class="btn" disabled id="send-btn">
|
||
<i class="fas fa-paper-plane"></i> 发送
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// 获取DOM元素
|
||
const wsUrlInput = document.getElementById('ws-url');
|
||
const wsAuthorizationInput = document.getElementById('ws-authorization');
|
||
const connectBtn = document.getElementById('connect-btn');
|
||
const disconnectBtn = document.getElementById('disconnect-btn');
|
||
const sendBtn = document.getElementById('send-btn');
|
||
const clearBtn = document.getElementById('clear-btn'); // 获取清除按钮
|
||
const messageInput = document.getElementById('message-input');
|
||
const messageHistory = document.getElementById('message-history');
|
||
const connectionStatus = document.getElementById('connection-status');
|
||
const connectionDetails = document.getElementById('connection-details');
|
||
|
||
// WebSocket实例
|
||
let websocket = null;
|
||
|
||
// 连接WebSocket
|
||
connectBtn.addEventListener('click', () => {
|
||
let url = wsUrlInput.value.trim();
|
||
if (!url) {
|
||
alert('请输入WebSocket地址');
|
||
return;
|
||
}
|
||
|
||
// 验证URL格式
|
||
if (!url.startsWith('ws://') && !url.startsWith('wss://')) {
|
||
alert('WebSocket地址必须以ws://或wss://开头');
|
||
return;
|
||
}
|
||
|
||
// 获取协议(如果有)
|
||
const authorization = wsAuthorizationInput.value.trim();
|
||
try {
|
||
// 创建WebSocket连接
|
||
url = `${url}?authorization=${authorization}`;
|
||
websocket = new WebSocket(url);
|
||
|
||
// 连接成功
|
||
websocket.onopen = () => {
|
||
updateConnectionStatus(true);
|
||
addSystemMessage('已成功连接到服务器');
|
||
|
||
// 显示连接详情
|
||
connectionDetails.textContent = `地址: ${url}`;
|
||
};
|
||
|
||
// 接收消息
|
||
websocket.onmessage = (event) => {
|
||
addMessage(event.data, false);
|
||
};
|
||
|
||
// 连接关闭
|
||
websocket.onclose = (event) => {
|
||
updateConnectionStatus(false);
|
||
addSystemMessage(`连接已关闭 (代码: ${event.code}, 原因: ${event.reason || '无'})`);
|
||
connectionDetails.textContent = '未连接到服务器';
|
||
};
|
||
|
||
// 连接错误
|
||
websocket.onerror = (error) => {
|
||
addSystemMessage(`发生错误: ${error.message || '未知错误'}`, true);
|
||
};
|
||
} catch (error) {
|
||
alert(`连接失败: ${error.message}`);
|
||
updateConnectionStatus(false);
|
||
}
|
||
});
|
||
|
||
// 断开连接
|
||
disconnectBtn.addEventListener('click', () => {
|
||
if (websocket && websocket.readyState === WebSocket.OPEN) {
|
||
websocket.close(1000, '客户端主动断开连接');
|
||
websocket = null;
|
||
}
|
||
});
|
||
|
||
// 发送消息
|
||
sendBtn.addEventListener('click', sendMessage);
|
||
|
||
// 清除消息
|
||
clearBtn.addEventListener('click', () => {
|
||
// 恢复空状态显示
|
||
messageHistory.innerHTML = `
|
||
<div class="empty-state">
|
||
<i class="fas fa-comments"></i>
|
||
<p>连接到WebSocket服务器后,消息将显示在这里</p>
|
||
</div>
|
||
`;
|
||
// addSystemMessage('消息历史已清除');
|
||
});
|
||
|
||
// 按Enter发送消息,Shift+Enter换行
|
||
messageInput.addEventListener('keydown', (event) => {
|
||
if (event.key === 'Enter' && !event.shiftKey) {
|
||
event.preventDefault();
|
||
sendMessage();
|
||
}
|
||
});
|
||
|
||
// 发送消息函数
|
||
function sendMessage() {
|
||
const message = messageInput.value.trim();
|
||
if (!message) return;
|
||
|
||
if (websocket && websocket.readyState === WebSocket.OPEN) {
|
||
websocket.send(message);
|
||
addMessage(message, true);
|
||
// messageInput.value = '';
|
||
} else {
|
||
addSystemMessage('无法发送消息,未连接到服务器', true);
|
||
}
|
||
}
|
||
|
||
// 添加消息到历史记录
|
||
function addMessage(content, isSent) {
|
||
// 清除空状态提示
|
||
if (messageHistory.querySelector('.empty-state')) {
|
||
messageHistory.innerHTML = '';
|
||
}
|
||
|
||
const messageDiv = document.createElement('div');
|
||
messageDiv.className = `message ${isSent ? 'message-sent' : 'message-received'}`;
|
||
|
||
const now = new Date();
|
||
const timeString = now.toLocaleTimeString();
|
||
|
||
messageDiv.innerHTML = `
|
||
<div class="message-content">${escapeHtml(content)}</div>
|
||
<div class="message-time">${timeString}</div>
|
||
`;
|
||
|
||
messageHistory.appendChild(messageDiv);
|
||
messageHistory.scrollTop = messageHistory.scrollHeight;
|
||
}
|
||
|
||
// 添加系统消息
|
||
function addSystemMessage(content, isError = false) {
|
||
// 清除空状态提示
|
||
if (messageHistory.querySelector('.empty-state')) {
|
||
messageHistory.innerHTML = '';
|
||
}
|
||
|
||
const messageDiv = document.createElement('div');
|
||
messageDiv.className = 'message';
|
||
|
||
const now = new Date();
|
||
const timeString = now.toLocaleTimeString();
|
||
|
||
messageDiv.innerHTML = `
|
||
<div class="message-content" style="background-color: ${isError ? '#fee2e2' : '#eff6ff'}; color: ${isError ? '#dc2626' : '#1e40af'}">
|
||
<i class="${isError ? 'fas fa-exclamation-circle' : 'fas fa-info-circle'}"></i> ${escapeHtml(content)}
|
||
</div>
|
||
<div class="message-time">${timeString}</div>
|
||
`;
|
||
|
||
messageHistory.appendChild(messageDiv);
|
||
messageHistory.scrollTop = messageHistory.scrollHeight;
|
||
}
|
||
|
||
// 更新连接状态
|
||
function updateConnectionStatus(isConnected) {
|
||
if (isConnected) {
|
||
connectBtn.disabled = true;
|
||
disconnectBtn.disabled = false;
|
||
sendBtn.disabled = false;
|
||
wsUrlInput.disabled = true;
|
||
wsAuthorizationInput.disabled = true;
|
||
|
||
connectionStatus.innerHTML = '已连接 <span class="status status-connected">在线</span>';
|
||
} else {
|
||
connectBtn.disabled = false;
|
||
disconnectBtn.disabled = true;
|
||
sendBtn.disabled = true;
|
||
wsUrlInput.disabled = false;
|
||
wsAuthorizationInput.disabled = false;
|
||
|
||
connectionStatus.innerHTML = '未连接 <span class="status status-disconnected">离线</span>';
|
||
}
|
||
}
|
||
|
||
// HTML转义函数,防止XSS
|
||
function escapeHtml(unsafe) {
|
||
return unsafe
|
||
.replace(/&/g, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """)
|
||
.replace(/'/g, "'");
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|