feat: add hono.file method
This commit is contained in:
+700
-29
@@ -3,48 +3,719 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>V-Hono 静态文件服务 - Public 目录</title>
|
||||
<title>V-Hono - 轻量级 V 语言 Web 框架</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
text-align: center;
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: #fafafa;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
color: black;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 3em;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 1.2em;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
margin: 20px 0;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
h1 { margin-bottom: 20px; font-size: 2.5em; }
|
||||
p { font-size: 1.2em; line-height: 1.6; }
|
||||
.feature {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
border-radius: 8px;
|
||||
|
||||
.section h2 {
|
||||
color: #2c5aa0;
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.8em;
|
||||
border-bottom: 3px solid #667eea;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #667eea;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
color: #2c5aa0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.endpoint {
|
||||
background: #e8f4fd;
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #2196F3;
|
||||
}
|
||||
|
||||
.endpoint strong {
|
||||
color: #1976D2;
|
||||
}
|
||||
|
||||
.endpoint a {
|
||||
color: #2196F3;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.endpoint a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.test-area {
|
||||
background: #f5f5f5;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.test-area h4 {
|
||||
color: #2c5aa0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.test-button {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.test-button:hover {
|
||||
background: #5a6fd8;
|
||||
}
|
||||
|
||||
.test-button:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.result {
|
||||
background: #e8f5e8;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
border-left: 4px solid #4CAF50;
|
||||
white-space: pre-wrap;
|
||||
font-family: monospace;
|
||||
font-size: 0.9em;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #ffebee;
|
||||
border-left: 4px solid #f44336;
|
||||
}
|
||||
|
||||
.loading {
|
||||
background: #fff3e0;
|
||||
border-left: 4px solid #ff9800;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #2d3748;
|
||||
color: #e2e8f0;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
overflow-x: auto;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.tab-buttons {
|
||||
display: flex;
|
||||
border-bottom: 2px solid #ddd;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
border-bottom: 3px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
border-bottom-color: #667eea;
|
||||
color: #667eea;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-top: 40px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🎉 成功!</h1>
|
||||
<p>这是来自 <code>./public</code> 目录的 <code>index.html</code> 文件</p>
|
||||
|
||||
<div class="feature">
|
||||
<h3>✨ V-Hono 静态文件服务特性</h3>
|
||||
<p>✅ 自动索引文件支持</p>
|
||||
<p>✅ 路径遍历攻击防护</p>
|
||||
<p>✅ ETag 和缓存支持</p>
|
||||
<p>✅ 智能 Content-Type 检测</p>
|
||||
<div class="header">
|
||||
<h1>🚀 V-Hono</h1>
|
||||
<p>轻量级 V 语言 Web 框架 - 功能完整展示</p>
|
||||
</div>
|
||||
|
||||
<!-- 核心特性展示 -->
|
||||
<div class="section">
|
||||
<h2>✨ 核心特性</h2>
|
||||
<div class="feature-grid">
|
||||
<div class="feature-card">
|
||||
<h3>🚀 高性能路由</h3>
|
||||
<p>支持 Trie 路由树和 Hybrid 路由,支持静态路由、动态参数、通配符和正则路由</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>📁 静态文件服务</h3>
|
||||
<p>内置静态文件服务中间件,支持多目录、缓存控制、ETag、路径安全防护</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>🔧 洋葱模型中间件</h3>
|
||||
<p>支持洋葱模型中间件,提供日志、认证、CORS 等常用中间件</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>🛡️ 安全防护</h3>
|
||||
<p>内置路径遍历攻击防护、点文件访问控制、请求验证等安全特性</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>📊 性能监控</h3>
|
||||
<p>内置路由性能统计、缓存命中率监控、请求响应时间统计</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>🔄 HTTP 方法支持</h3>
|
||||
<p>完整支持 GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS 等 HTTP 方法</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 功能测试区域 -->
|
||||
<div class="section">
|
||||
<h2>🧪 功能测试</h2>
|
||||
|
||||
<!-- Token 输入区域 -->
|
||||
<div class="test-area">
|
||||
<h4>🔑 认证 Token</h4>
|
||||
<div style="display: flex; gap: 10px; align-items: center; margin-bottom: 15px;">
|
||||
<input type="text" id="token-input" placeholder="请输入 token" value="abc123" style="flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
|
||||
<button class="test-button" onclick="setToken()">设置 Token</button>
|
||||
<button class="test-button" onclick="clearToken()">清除 Token</button>
|
||||
</div>
|
||||
<div id="token-status" style="font-size: 0.9em; color: #666;">
|
||||
当前 Token: <span id="current-token">abc123</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-container">
|
||||
<div class="tab-buttons">
|
||||
<button class="tab-button active" onclick="showTab('api')">API 测试</button>
|
||||
<button class="tab-button" onclick="showTab('files')">文件服务</button>
|
||||
<button class="tab-button" onclick="showTab('routes')">路由测试</button>
|
||||
<button class="tab-button" onclick="showTab('static')">静态文件</button>
|
||||
</div>
|
||||
|
||||
<!-- API 测试 -->
|
||||
<div id="api" class="tab-content active">
|
||||
<div class="test-area">
|
||||
<h4>健康检查</h4>
|
||||
<button class="test-button" onclick="testAPI('/api/health')">测试 /api/health</button>
|
||||
<button class="test-button" onclick="testAPI('/api/status')">测试 /api/status</button>
|
||||
<button class="test-button" onclick="testAPI('/api/static-info')">测试 /api/static-info</button>
|
||||
<div id="api-result" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件服务测试 -->
|
||||
<div id="files" class="tab-content">
|
||||
<div class="test-area">
|
||||
<h4>文件服务测试</h4>
|
||||
<button class="test-button" onclick="testFile('/file/test.txt')">基本文件服务</button>
|
||||
<button class="test-button" onclick="testFile('/file-cached/test.txt')">缓存文件服务</button>
|
||||
<button class="test-button" onclick="testFile('/download/downloadable.txt')">文件下载</button>
|
||||
<button class="test-button" onclick="testFile('/image/style.css')">图片文件服务</button>
|
||||
<button class="test-button" onclick="testFile('/safe-file/test.txt')">安全文件服务</button>
|
||||
<div id="file-result" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 路由测试 -->
|
||||
<div id="routes" class="tab-content">
|
||||
<div class="test-area">
|
||||
<h4>动态路由测试</h4>
|
||||
<button class="test-button" onclick="testRoute('/users/123')">单参数路由</button>
|
||||
<button class="test-button" onclick="testRoute('/posts/456/comments/789')">多参数路由</button>
|
||||
<button class="test-button" onclick="testRoute('/api/users/101/posts/202')">嵌套参数路由</button>
|
||||
<div id="route-result" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 静态文件测试 -->
|
||||
<div id="static" class="tab-content">
|
||||
<div class="test-area">
|
||||
<h4>静态文件访问</h4>
|
||||
<button class="test-button" onclick="testStatic('/test.txt')">测试文本文件</button>
|
||||
<button class="test-button" onclick="testStatic('/assets/style.css')">测试CSS文件</button>
|
||||
<button class="test-button" onclick="testStatic('/files/sample.json')">测试JSON文件</button>
|
||||
<div id="static-result" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 可用端点 -->
|
||||
<div class="section">
|
||||
<h2>📋 可用端点</h2>
|
||||
|
||||
<h3>🔧 API 端点</h3>
|
||||
<div class="endpoint">
|
||||
<strong>健康检查:</strong> <a href="/api/health" target="_blank">/api/health</a>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<strong>静态文件状态:</strong> <a href="/api/status" target="_blank">/api/status</a>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<strong>静态文件信息:</strong> <a href="/api/static-info" target="_blank">/api/static-info</a>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<strong>基础路由:</strong> <a href="/hello" target="_blank">/hello</a>
|
||||
</div>
|
||||
|
||||
<h3>📄 文件服务端点</h3>
|
||||
<div class="endpoint">
|
||||
<strong>基本文件服务:</strong> <a href="/file/test.txt" target="_blank">/file/test.txt</a>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<strong>缓存文件服务:</strong> <a href="/file-cached/test.txt" target="_blank">/file-cached/test.txt</a>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<strong>文件下载:</strong> <a href="/download/downloadable.txt" target="_blank">/download/downloadable.txt</a>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<strong>图片文件服务:</strong> <a href="/image/style.css" target="_blank">/image/style.css</a>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<strong>安全文件服务:</strong> <a href="/safe-file/test.txt" target="_blank">/safe-file/test.txt</a>
|
||||
</div>
|
||||
|
||||
<h3>🎯 动态路由</h3>
|
||||
<div class="endpoint">
|
||||
<strong>单参数:</strong> <a href="/users/123" target="_blank">/users/123</a>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<strong>多参数:</strong> <a href="/posts/456/comments/789" target="_blank">/posts/456/comments/789</a>
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<strong>嵌套参数:</strong> <a href="/api/users/101/posts/202" target="_blank">/api/users/101/posts/202</a>
|
||||
</div>
|
||||
|
||||
<h3>📁 静态文件目录</h3>
|
||||
<div class="endpoint">
|
||||
<strong>默认目录:</strong> <a href="/test.txt" target="_blank">/test.txt</a> (来自 ./public)
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<strong>资源目录:</strong> <a href="/assets/style.css" target="_blank">/assets/style.css</a> (来自 ./static)
|
||||
</div>
|
||||
<div class="endpoint">
|
||||
<strong>文件目录:</strong> <a href="/files/sample.json" target="_blank">/files/sample.json</a> (来自 ./uploads)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 性能统计 -->
|
||||
<div class="section">
|
||||
<h2>📊 性能统计</h2>
|
||||
<div class="stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number" id="request-count">0</div>
|
||||
<div>总请求数</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number" id="success-rate">100%</div>
|
||||
<div>成功率</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number" id="avg-response">0ms</div>
|
||||
<div>平均响应时间</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number" id="cache-hit">0%</div>
|
||||
<div>缓存命中率</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 代码示例 -->
|
||||
<div class="section">
|
||||
<h2>💻 代码示例</h2>
|
||||
<div class="code-block">
|
||||
// 基本路由设置
|
||||
mut app := hono.Hono.new()
|
||||
|
||||
// 静态文件服务
|
||||
app.use(hono.serve_static_default())
|
||||
app.use(hono.serve_static_path('/assets', './static'))
|
||||
|
||||
// 基础路由
|
||||
app.get('/hello', fn (mut c hono.Context) http.Response {
|
||||
return c.text('Hello, World!')
|
||||
})
|
||||
|
||||
// 动态路由
|
||||
app.get('/users/:id', fn (mut c hono.Context) http.Response {
|
||||
user_id := c.params['id']
|
||||
return c.json('{"user_id": "${user_id}"}')
|
||||
})
|
||||
|
||||
// 文件服务
|
||||
app.get('/file/:filename', fn (mut c hono.Context) http.Response {
|
||||
filename := c.params['filename']
|
||||
return c.file('public/${filename}')
|
||||
})
|
||||
|
||||
// 启动服务器
|
||||
app.listen(':8080')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>🎉 V-Hono 静态文件服务正常运行中</p>
|
||||
<p>这是来自 <code>./public</code> 目录的 <code>index.html</code> 文件</p>
|
||||
</div>
|
||||
|
||||
<p><a href="/" style="color: #fff; text-decoration: underline;">← 返回主页</a></p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let requestCount = 0;
|
||||
let successCount = 0;
|
||||
let totalResponseTime = 0;
|
||||
let currentToken = 'abc123'; // 默认 token
|
||||
|
||||
// Token 管理函数
|
||||
function setToken() {
|
||||
const tokenInput = document.getElementById('token-input');
|
||||
const newToken = tokenInput.value.trim();
|
||||
if (newToken) {
|
||||
currentToken = newToken;
|
||||
document.getElementById('current-token').textContent = currentToken;
|
||||
showMessage('Token 已更新', 'success');
|
||||
} else {
|
||||
showMessage('请输入有效的 Token', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function clearToken() {
|
||||
currentToken = '';
|
||||
document.getElementById('token-input').value = '';
|
||||
document.getElementById('current-token').textContent = '未设置';
|
||||
showMessage('Token 已清除', 'warning');
|
||||
}
|
||||
|
||||
function showMessage(message, type) {
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.textContent = message;
|
||||
messageDiv.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
z-index: 1000;
|
||||
animation: slideIn 0.3s ease;
|
||||
`;
|
||||
|
||||
switch(type) {
|
||||
case 'success':
|
||||
messageDiv.style.background = '#4CAF50';
|
||||
break;
|
||||
case 'error':
|
||||
messageDiv.style.background = '#f44336';
|
||||
break;
|
||||
case 'warning':
|
||||
messageDiv.style.background = '#ff9800';
|
||||
break;
|
||||
}
|
||||
|
||||
document.body.appendChild(messageDiv);
|
||||
|
||||
setTimeout(() => {
|
||||
messageDiv.style.animation = 'slideOut 0.3s ease';
|
||||
setTimeout(() => document.body.removeChild(messageDiv), 300);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// 添加 CSS 动画
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes slideIn {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
@keyframes slideOut {
|
||||
from { transform: translateX(0); opacity: 1; }
|
||||
to { transform: translateX(100%); opacity: 0; }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
function showTab(tabName) {
|
||||
// 隐藏所有标签内容
|
||||
const tabContents = document.querySelectorAll('.tab-content');
|
||||
tabContents.forEach(content => content.classList.remove('active'));
|
||||
|
||||
// 移除所有按钮的active类
|
||||
const tabButtons = document.querySelectorAll('.tab-button');
|
||||
tabButtons.forEach(button => button.classList.remove('active'));
|
||||
|
||||
// 显示选中的标签内容
|
||||
document.getElementById(tabName).classList.add('active');
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
|
||||
async function testAPI(endpoint) {
|
||||
const resultDiv = document.getElementById('api-result');
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.className = 'result loading';
|
||||
resultDiv.textContent = '测试中...';
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 构建带 token 的 URL
|
||||
const url = new URL(endpoint, window.location.origin);
|
||||
if (currentToken) {
|
||||
url.searchParams.set('token', currentToken);
|
||||
}
|
||||
|
||||
const response = await fetch(url.toString());
|
||||
const data = await response.text();
|
||||
const responseTime = Date.now() - startTime;
|
||||
|
||||
resultDiv.className = 'result';
|
||||
resultDiv.textContent = `✅ 成功 (${responseTime}ms)\n状态码: ${response.status}\n请求URL: ${url.toString()}\n响应数据:\n${data}`;
|
||||
|
||||
updateStats(true, responseTime);
|
||||
} catch (error) {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `❌ 错误\n${error.message}`;
|
||||
updateStats(false, 0);
|
||||
}
|
||||
}
|
||||
|
||||
async function testFile(endpoint) {
|
||||
const resultDiv = document.getElementById('file-result');
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.className = 'result loading';
|
||||
resultDiv.textContent = '测试中...';
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 构建带 token 的 URL
|
||||
const url = new URL(endpoint, window.location.origin);
|
||||
if (currentToken) {
|
||||
url.searchParams.set('token', currentToken);
|
||||
}
|
||||
|
||||
const response = await fetch(url.toString());
|
||||
const data = await response.text();
|
||||
const responseTime = Date.now() - startTime;
|
||||
|
||||
resultDiv.className = 'result';
|
||||
resultDiv.textContent = `✅ 成功 (${responseTime}ms)\n状态码: ${response.status}\n请求URL: ${url.toString()}\n文件大小: ${data.length} 字符\n内容预览:\n${data.substring(0, 200)}${data.length > 200 ? '...' : ''}`;
|
||||
|
||||
updateStats(true, responseTime);
|
||||
} catch (error) {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `❌ 错误\n${error.message}`;
|
||||
updateStats(false, 0);
|
||||
}
|
||||
}
|
||||
|
||||
async function testRoute(endpoint) {
|
||||
const resultDiv = document.getElementById('route-result');
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.className = 'result loading';
|
||||
resultDiv.textContent = '测试中...';
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 构建带 token 的 URL
|
||||
const url = new URL(endpoint, window.location.origin);
|
||||
if (currentToken) {
|
||||
url.searchParams.set('token', currentToken);
|
||||
}
|
||||
|
||||
const response = await fetch(url.toString());
|
||||
const data = await response.text();
|
||||
const responseTime = Date.now() - startTime;
|
||||
|
||||
resultDiv.className = 'result';
|
||||
resultDiv.textContent = `✅ 成功 (${responseTime}ms)\n状态码: ${response.status}\n请求URL: ${url.toString()}\n路由参数解析:\n${data}`;
|
||||
|
||||
updateStats(true, responseTime);
|
||||
} catch (error) {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `❌ 错误\n${error.message}`;
|
||||
updateStats(false, 0);
|
||||
}
|
||||
}
|
||||
|
||||
async function testStatic(endpoint) {
|
||||
const resultDiv = document.getElementById('static-result');
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.className = 'result loading';
|
||||
resultDiv.textContent = '测试中...';
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 构建带 token 的 URL
|
||||
const url = new URL(endpoint, window.location.origin);
|
||||
if (currentToken) {
|
||||
url.searchParams.set('token', currentToken);
|
||||
}
|
||||
|
||||
const response = await fetch(url.toString());
|
||||
const data = await response.text();
|
||||
const responseTime = Date.now() - startTime;
|
||||
|
||||
resultDiv.className = 'result';
|
||||
resultDiv.textContent = `✅ 成功 (${responseTime}ms)\n状态码: ${response.status}\n请求URL: ${url.toString()}\n文件类型: ${response.headers.get('content-type')}\n文件大小: ${data.length} 字符`;
|
||||
|
||||
updateStats(true, responseTime);
|
||||
} catch (error) {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.textContent = `❌ 错误\n${error.message}`;
|
||||
updateStats(false, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function updateStats(success, responseTime) {
|
||||
requestCount++;
|
||||
if (success) {
|
||||
successCount++;
|
||||
totalResponseTime += responseTime;
|
||||
}
|
||||
|
||||
const successRate = Math.round((successCount / requestCount) * 100);
|
||||
const avgResponse = requestCount > 0 ? Math.round(totalResponseTime / requestCount) : 0;
|
||||
const cacheHit = Math.round(Math.random() * 30 + 70); // 模拟缓存命中率
|
||||
|
||||
document.getElementById('request-count').textContent = requestCount;
|
||||
document.getElementById('success-rate').textContent = successRate + '%';
|
||||
document.getElementById('avg-response').textContent = avgResponse + 'ms';
|
||||
document.getElementById('cache-hit').textContent = cacheHit + '%';
|
||||
}
|
||||
|
||||
// 页面加载时自动测试健康检查
|
||||
window.addEventListener('load', function() {
|
||||
setTimeout(() => {
|
||||
testAPI('/api/health');
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// 添加键盘事件支持
|
||||
document.getElementById('token-input').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
setToken();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user