</> speed-test.php

359 行11568 字节.php
📥 下载源码 🔙 返回工具
1<?php
2class=class="string">"keyword">header(class="string">'Content-Type: text/html; charset=utf-class="number">8');
3
4class=class="string">"keyword">class=class="string">"comment">// Speed test data endpoint
5class=class="string">"keyword">if (class=class="string">"keyword">isset($_GET[class="string">'size'])) {
6 $sizeMB = intval($_GET[class="string">'size']);
7 class=class="string">"keyword">if ($sizeMB < class="number">1) $sizeMB = class="number">1;
8 class=class="string">"keyword">if ($sizeMB > class="number">50) $sizeMB = class="number">50;
9 $bytes = $sizeMB * class="number">1024 * class="number">1024;
10
11 class=class="string">"keyword">class=class="string">"comment">// Disable output buffering
12 @ob_end_clean();
13 class=class="string">"keyword">header(class="string">'Content-Type: application/octet-stream');
14 class=class="string">"keyword">header(class="string">'Content-Length: ' . $bytes);
15 class=class="string">"keyword">header(class="string">'Cache-Control: no-cache, no-store, must-revalidate');
16 class=class="string">"keyword">header(class="string">'Pragma: no-cache');
17 class=class="string">"keyword">header(class="string">'Expires: class="number">0');
18
19 $chunkSize = class="number">65536; class=class="string">"keyword">class=class="string">"comment">// 64KB chunks
20 $remaining = $bytes;
21 class=class="string">"keyword">while ($remaining > class="number">0) {
22 $write = min($chunkSize, $remaining);
23 class=class="string">"keyword">echo str_repeat(class="string">'A', $write);
24 $remaining -= $write;
25 flush();
26 class=class="string">"keyword">if (connection_aborted()) class=class="string">"keyword">break;
27 }
28 class=class="string">"keyword">exit;
29}
30
31class=class="string">"keyword">class=class="string">"comment">// Ping check
32class=class="string">"keyword">if (class=class="string">"keyword">isset($_GET[class="string">'ping'])) {
33 class=class="string">"keyword">header(class="string">'Content-Type: text/plain');
34 class=class="string">"keyword">echo class="string">'pong';
35 class=class="string">"keyword">exit;
36}
37?>
38<!DOCTYPE html>
39<class="tag">html lang="zh-CN">
40<class="tag">head>
41<class="tag">meta charset="UTF-class="number">8">
42<class="tag">meta name="viewport" content="width=device-width, initial-scale=class="number">1.0">
43<class="tag">meta property="og:title" content="网络测速 - η空间">
44<class="tag">meta name="description" content="测试到服务器的延迟、下载和上传速度,浏览器直接测速">
45<class="tag">meta name="keywords" content="网络测速,在线测速,网速测试,延迟测试,宽带测速">
46<class="tag">meta property="og:description" content="测试到你服务器的延迟、下载和上传速度">
47<class="tag">meta property="og:type" content="website">
48<class="tag">title>网络测速 - η空间</class="tag">title>
49<class="tag">link rel="stylesheet" href="/tools/share.css">
50 <class="tag">link rel="stylesheet" href="/assets/pet-cat.css">
51<class="tag">style>
52* { margin: class="number">0; padding: class="number">0; box-sizing: border-box; }
53body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: class=class="string">"keyword">class=class="string">"comment">#0f172a; color: #e2e8f0; line-height: class="number">1.6; min-height: 100vh; }
54class=class="string">"keyword">header {
55 background: linear-gradient(135deg, class=class="string">"keyword">class=class="string">"comment">#1e293b class="number">0%, #0f172a class="number">100%);
56 padding: 40px 20px; text-align: center;
57 border-bottom: 1px solid class=class="string">"keyword">class=class="string">"comment">#1e293b;
58}
59class=class="string">"keyword">header h1 { font-size: 28px; background: linear-gradient(135deg, class=class="string">"keyword">class=class="string">"comment">#38bdf8, #818cf8); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
60class=class="string">"keyword">header p { color: class=class="string">"keyword">class=class="string">"comment">#94a3b8; font-size: 14px; margin-top: 4px; }
61.container { max-width: 560px; margin: class="number">0 auto; padding: 24px 16px; }
62.back-link { display: inline-block; margin-bottom: 16px; color: class=class="string">"keyword">class=class="string">"comment">#38bdf8; text-decoration: none; font-size: 14px; }
63.card {
64 background: class=class="string">"keyword">class=class="string">"comment">#1e293b; border-radius: 16px; padding: 32px 24px;
65 box-shadow: class="number">0 4px 24px rgba(class="number">0,class="number">0,class="number">0,class="number">0.3); border: 1px solid class=class="string">"keyword">class=class="string">"comment">#class="number">334155;
66}
67.status-bar {
68 display: flex; justify-content: space-between; align-items: center;
69 margin-bottom: 24px; padding-bottom: 16px;
70 border-bottom: 1px solid class=class="string">"keyword">class=class="string">"comment">#class="number">334155;
71}
72.status-bar .label { font-size: 14px; color: class=class="string">"keyword">class=class="string">"comment">#94a3b8; }
73.status-bar .server { font-size: 13px; color: class=class="string">"keyword">class=class="string">"comment">#64748b; }
74.metrics { display: grid; gap: 16px; margin-bottom: 24px; }
75.metric {
76 background: class=class="string">"keyword">class=class="string">"comment">#0f172a; border-radius: 12px; padding: 20px;
77 text-align: center; border: 1px solid class=class="string">"keyword">class=class="string">"comment">#class="number">334155;
78 position: relative; overflow: hidden;
79}
80.metric .icon { font-size: 24px; margin-bottom: 8px; }
81.metric .value {
82 font-size: 32px; font-weight: class="number">700;
83 transition: color class="number">0.3s;
84}
85.metric .unit { font-size: 14px; color: class=class="string">"keyword">class=class="string">"comment">#64748b; margin-left: 4px; }
86.metric .label { font-size: 13px; color: class=class="string">"keyword">class=class="string">"comment">#64748b; margin-top: 4px; }
87.metric .bar {
88 position: absolute; bottom: class="number">0; left: class="number">0; height: 3px;
89 background: linear-gradient(90deg, class=class="string">"keyword">class=class="string">"comment">#38bdf8, #818cf8);
90 transition: width class="number">0.2s; width: class="number">0%;
91}
92.metric.testing .value { color: class=class="string">"keyword">class=class="string">"comment">#fbbf24; }
93.metric.done .value { color: class=class="string">"keyword">class=class="string">"comment">#22c55e; }
94.metric.fail .value { color: class=class="string">"keyword">class=class="string">"comment">#ef4444; }
95
96.btn {
97 display: block; width: class="number">100%; padding: 14px; border-radius: 12px;
98 border: none; font-size: 16px; font-weight: class="number">600; cursor: pointer;
99 background: linear-gradient(135deg, class=class="string">"keyword">class=class="string">"comment">#38bdf8, #818cf8);
100 color: class=class="string">"keyword">class=class="string">"comment">#fff; transition: opacity class="number">0.2s;
101}
102.btn:hover { opacity: class="number">0.9; }
103.btn:disabled { opacity: class="number">0.4; cursor: not-allowed; }
104.btn-secondary {
105 background: class=class="string">"keyword">class=class="string">"comment">#class="number">334155; margin-top: 8px;
106}
107.result-history { margin-top: 20px; }
108.result-history h3 { font-size: 14px; color: class=class="string">"keyword">class=class="string">"comment">#94a3b8; margin-bottom: 8px; }
109.history-item {
110 display: flex; justify-content: space-between;
111 padding: 8px 12px; background: class=class="string">"keyword">class=class="string">"comment">#0f172a; border-radius: 8px;
112 margin-bottom: 6px; font-size: 13px; color: class=class="string">"keyword">class=class="string">"comment">#94a3b8;
113}
114.history-item .val { color: class=class="string">"keyword">class=class="string">"comment">#e2e8f0; font-weight: class="number">600; }
115footer { text-align: center; padding: 24px; color: class=class="string">"keyword">class=class="string">"comment">#64748b; font-size: 13px; }
116@keyframes pulse {
117 class="number">0%, class="number">100% { opacity: class="number">1; }
118 class="number">50% { opacity: class="number">0.5; }
119}
120.metric.testing .value { animation: pulse 1s infinite; }
121</class="tag">style>
122</class="tag">head>
123<class="tag">body>
124
125<class=class="string">"keyword">header>
126 <class="tag">h1>🌐 网络测速</class="tag">h1>
127 <class="tag">p>测试到你服务器的延迟和带宽</class="tag">p>
128</class=class="string">"keyword">header>
129
130<class="tag">div class=class="string">"keyword">class="container">
131 <class="tag">a href="index.html" class=class="string">"keyword">class="back-link">← 返回工具列表</class="tag">a> · <class="tag">a href="/download/speed-test.zip" style="color:class=class="string">"keyword">class=class="string">"comment">#667eea;font-size:13px;text-decoration:none;">📦 源码下载</class="tag">a>
132
133 <class="tag">div class=class="string">"keyword">class="card">
134 <class="tag">div class=class="string">"keyword">class="status-bar">
135 <class="tag">span class=class="string">"keyword">class="label" id="statusLabel">⚪ 准备就绪</class="tag">span>
136 <class="tag">span class=class="string">"keyword">class="server" id="serverInfo">📍 检测中...</class="tag">span>
137 </class="tag">div>
138
139 <class="tag">div class=class="string">"keyword">class="metrics">
140 <class="tag">div class=class="string">"keyword">class="metric" id="pingMetric">
141 <class="tag">div class=class="string">"keyword">class="icon">📡</class="tag">div>
142 <class="tag">div><class="tag">span class=class="string">"keyword">class="value" id="pingValue">--</class="tag">span><class="tag">span class=class="string">"keyword">class="unit">ms</class="tag">span></class="tag">div>
143 <class="tag">div class=class="string">"keyword">class="label">延迟 (Ping)</class="tag">div>
144 <class="tag">div class=class="string">"keyword">class="bar" id="pingBar"></class="tag">div>
145 </class="tag">div>
146 <class="tag">div class=class="string">"keyword">class="metric" id="downloadMetric">
147 <class="tag">div class=class="string">"keyword">class="icon">⬇️</class="tag">div>
148 <class="tag">div><class="tag">span class=class="string">"keyword">class="value" id="downloadValue">--</class="tag">span><class="tag">span class=class="string">"keyword">class="unit">Mbps</class="tag">span></class="tag">div>
149 <class="tag">div class=class="string">"keyword">class="label">下载速度</class="tag">div>
150 <class="tag">div class=class="string">"keyword">class="bar" id="downloadBar"></class="tag">div>
151 </class="tag">div>
152 <class="tag">div class=class="string">"keyword">class="metric" id="uploadMetric">
153 <class="tag">div class=class="string">"keyword">class="icon">⬆️</class="tag">div>
154 <class="tag">div><class="tag">span class=class="string">"keyword">class="value" id="uploadValue">--</class="tag">span><class="tag">span class=class="string">"keyword">class="unit">Mbps</class="tag">span></class="tag">div>
155 <class="tag">div class=class="string">"keyword">class="label">上传速度</class="tag">div>
156 <class="tag">div class=class="string">"keyword">class="bar" id="uploadBar"></class="tag">div>
157 </class="tag">div>
158 </class="tag">div>
159
160 <class="tag">button class=class="string">"keyword">class="btn" id="startBtn" onclick="startTest()">🚀 开始测速</class="tag">button>
161
162 <class="tag">div class=class="string">"keyword">class="result-history" id="history">
163 <class="tag">h3>📋 历史记录</class="tag">h3>
164 <class="tag">div id="historyList"></class="tag">div>
165 </class="tag">div>
166 </class="tag">div>
167</class="tag">div>
168
169<class="tag">footer>
170 <class="tag">p>测速数据只用于计算,不会上传或记录到服务器</class="tag">p>
171</class="tag">footer>
172
173<class="tag">script>
174class=class="string">"keyword">class=class="string">"comment">// Get server IP
175class="func">fetch(class="string">'/tools/speed-test.php?ping')
176 .class="func">then(r => r.text())
177 .class="func">then(() => {
178 document.class="func">getElementById(class="string">'serverInfo').textContent = class="string">'📍 ' + window.location.hostname;
179 });
180
181class=class="string">"keyword">let history = [];
182
183class=class="string">"keyword">function formatSpeed(bps) {
184 class=class="string">"keyword">if (bps < class="number">1000) class=class="string">"keyword">return (bps).toFixed(class="number">1);
185 class=class="string">"keyword">if (bps < class="number">1000000) class=class="string">"keyword">return (bps / class="number">1000).toFixed(class="number">1);
186 class=class="string">"keyword">return (bps / class="number">1000000).toFixed(class="number">1);
187}
188
189class=class="string">"keyword">function formatSpeedUnit(bps) {
190 class=class="string">"keyword">if (bps < class="number">1000) class=class="string">"keyword">return class="string">'bps';
191 class=class="string">"keyword">if (bps < class="number">1000000) class=class="string">"keyword">return class="string">'Kbps';
192 class=class="string">"keyword">return class="string">'Mbps';
193}
194
195async class=class="string">"keyword">function measurePing() {
196 class=class="string">"keyword">const pingEl = document.class="func">getElementById(class="string">'pingValue');
197 class=class="string">"keyword">const pingBar = document.class="func">getElementById(class="string">'pingBar');
198 class=class="string">"keyword">const pingMetric = document.class="func">getElementById(class="string">'pingMetric');
199 pingMetric.className = class="string">'metric testing';
200
201 class=class="string">"keyword">const samples = [];
202 class=class="string">"keyword">for (class=class="string">"keyword">let i = class="number">0; i < class="number">4; i++) {
203 class=class="string">"keyword">const start = performance.now();
204 try {
205 await class="func">fetch(class="string">'/tools/speed-test.php?ping&t=' + Date.now(), { cache: class="string">'no-store' });
206 class=class="string">"keyword">const ms = performance.now() - start;
207 samples.push(ms);
208 } class="func">catch(e) {}
209 }
210
211 class=class="string">"keyword">if (samples.length === class="number">0) {
212 pingMetric.className = class="string">'metric fail';
213 pingEl.textContent = class="string">'失败';
214 class=class="string">"keyword">return null;
215 }
216
217 class=class="string">"keyword">class=class="string">"comment">// Use median
218 samples.sort((a,b) => a-b);
219 class=class="string">"keyword">const avg = samples[Math.floor(samples.length/class="number">2)];
220
221 pingEl.textContent = avg.toFixed(class="number">0);
222 pingBar.style.width = Math.min(class="number">100, class="number">200 / avg * class="number">50) + class="string">'%';
223 pingMetric.className = class="string">'metric done';
224
225 class=class="string">"keyword">return avg;
226}
227
228async class=class="string">"keyword">function measureDownload() {
229 class=class="string">"keyword">const dlEl = document.class="func">getElementById(class="string">'downloadValue');
230 class=class="string">"keyword">const dlBar = document.class="func">getElementById(class="string">'downloadBar');
231 class=class="string">"keyword">const dlMetric = document.class="func">getElementById(class="string">'downloadMetric');
232 dlMetric.className = class="string">'metric testing';
233
234 class=class="string">"keyword">class=class="string">"comment">// Test with different sizes to get accurate reading
235 class=class="string">"keyword">const sizes = [class="number">1, class="number">2, class="number">5]; class=class="string">"keyword">class=class="string">"comment">// MB
236 class=class="string">"keyword">let bestSpeed = class="number">0;
237
238 class=class="string">"keyword">for (class=class="string">"keyword">const sizeMB of sizes) {
239 class=class="string">"keyword">const start = performance.now();
240 try {
241 class=class="string">"keyword">const resp = await class="func">fetch(`/tools/speed-test.php?size=${sizeMB}&t=${Date.now()}`, { cache: class="string">'no-store' });
242 class=class="string">"keyword">const reader = resp.body.getReader();
243 class=class="string">"keyword">let received = class="number">0;
244 class=class="string">"keyword">while (true) {
245 class=class="string">"keyword">const { done, value } = await reader.read();
246 class=class="string">"keyword">if (done) class=class="string">"keyword">break;
247 received += value.length;
248 }
249 class=class="string">"keyword">const elapsed = (performance.now() - start) / class="number">1000;
250 class=class="string">"keyword">const bits = received * class="number">8;
251 class=class="string">"keyword">const speed = bits / elapsed;
252
253 class=class="string">"keyword">if (speed > bestSpeed) bestSpeed = speed;
254 } class="func">catch(e) {}
255 }
256
257 class=class="string">"keyword">if (bestSpeed === class="number">0) {
258 dlMetric.className = class="string">'metric fail';
259 dlEl.textContent = class="string">'失败';
260 class=class="string">"keyword">return null;
261 }
262
263 class=class="string">"keyword">const display = formatSpeed(bestSpeed);
264 class=class="string">"keyword">const unit = formatSpeedUnit(bestSpeed);
265 dlEl.textContent = display;
266 dlBar.style.width = Math.min(class="number">100, bestSpeed / class="number">50000000 * class="number">100) + class="string">'%';
267 dlMetric.className = class="string">'metric done';
268
269 class=class="string">"keyword">return bestSpeed;
270}
271
272async class=class="string">"keyword">function measureUpload() {
273 class=class="string">"keyword">const ulEl = document.class="func">getElementById(class="string">'uploadValue');
274 class=class="string">"keyword">const ulBar = document.class="func">getElementById(class="string">'uploadBar');
275 class=class="string">"keyword">const ulMetric = document.class="func">getElementById(class="string">'uploadMetric');
276 ulMetric.className = class="string">'metric testing';
277
278 class=class="string">"keyword">class=class="string">"comment">// Generate 2MB of upload data
279 class=class="string">"keyword">const uploadSize = class="number">2 * class="number">1024 * class="number">1024;
280 class=class="string">"keyword">const data = class=class="string">"keyword">new Uint8Array(uploadSize);
281 class=class="string">"keyword">for (class=class="string">"keyword">let i = class="number">0; i < uploadSize; i++) {
282 data[i] = Math.floor(Math.random() * class="number">256);
283 }
284
285 try {
286 class=class="string">"keyword">const start = performance.now();
287 class=class="string">"keyword">const resp = await class="func">fetch(class="string">'/tools/speed-test.php?upload&t=' + Date.now(), {
288 method: class="string">'POST',
289 body: data,
290 cache: class="string">'no-store'
291 });
292 class=class="string">"keyword">const elapsed = (performance.now() - start) / class="number">1000;
293 await resp.text();
294
295 class=class="string">"keyword">const bits = uploadSize * class="number">8;
296 class=class="string">"keyword">const speed = bits / elapsed;
297
298 class=class="string">"keyword">const display = formatSpeed(speed);
299 class=class="string">"keyword">const unit = formatSpeedUnit(speed);
300 ulEl.textContent = display;
301 ulBar.style.width = Math.min(class="number">100, speed / class="number">30000000 * class="number">100) + class="string">'%';
302 ulMetric.className = class="string">'metric done';
303
304 class=class="string">"keyword">return speed;
305 } class="func">catch(e) {
306 ulMetric.className = class="string">'metric fail';
307 ulEl.textContent = class="string">'失败';
308 class=class="string">"keyword">return null;
309 }
310}
311
312async class=class="string">"keyword">function startTest() {
313 class=class="string">"keyword">const btn = document.class="func">getElementById(class="string">'startBtn');
314 btn.disabled = true;
315 btn.textContent = class="string">'⏳ 测速中...';
316
317 class=class="string">"keyword">const statusLabel = document.class="func">getElementById(class="string">'statusLabel');
318
319 statusLabel.textContent = class="string">'📡 测试延迟...';
320 class=class="string">"keyword">const ping = await measurePing();
321
322 statusLabel.textContent = class="string">'⬇️ 测试下载...';
323 class=class="string">"keyword">const dl = await measureDownload();
324
325 statusLabel.textContent = class="string">'⬆️ 测试上传...';
326 class=class="string">"keyword">const ul = await measureUpload();
327
328 statusLabel.textContent = class="string">'✅ 测速完成';
329 btn.textContent = class="string">'🔄 重新测速';
330 btn.disabled = false;
331
332 class=class="string">"keyword">class=class="string">"comment">// Save history
333 class=class="string">"keyword">const record = {
334 time: class=class="string">"keyword">new class="func">Date().toLocaleTimeString(),
335 ping: ping ? ping.toFixed(class="number">0) + class="string">'ms' : class="string">'失败',
336 dl: dl ? formatSpeed(dl) + class="string">' ' + formatSpeedUnit(dl) : class="string">'失败',
337 ul: ul ? formatSpeed(ul) + class="string">' ' + formatSpeedUnit(ul) : class="string">'失败'
338 };
339 history.unshift(record);
340 class=class="string">"keyword">if (history.length > class="number">10) history.pop();
341 renderHistory();
342}
343
344class=class="string">"keyword">function renderHistory() {
345 class=class="string">"keyword">const list = document.class="func">getElementById(class="string">'historyList');
346 list.innerHTML = history.map(r => `
347 <class="tag">div class=class="string">"keyword">class="history-item">
348 <class="tag">span>${r.time}</class="tag">span>
349 <class="tag">span>📡 ${r.ping}</class="tag">span>
350 <class="tag">span>⬇️ ${r.dl}</class="tag">span>
351 <class="tag">span>⬆️ ${r.ul}</class="tag">span>
352 </class="tag">div>
353 `).join(class="string">'');
354}
355</class="tag">script>
356<class="tag">script src="/tools/share.js"></class="tag">script>
357<class="tag">script src="/assets/pet-cat.js"></class="tag">script>
358</class="tag">body>
359</class="tag">html>