如何使用 Cloudflare Worker 实现延迟测试工具(建议直接查看完整代码,前面部分介绍为旧版)
在这篇教程中,我们将学习如何使用 Cloudflare Worker 创建一个简单的延迟测试工具。这个工具可以测量多个 URL 的响应延迟,并根据结果重定向用户,同时在页面上显示每个 URL 的延迟信息,使用不同的颜色来指示延迟的高低。
更新日志
- 2024-11-03:完成初稿,发布。
- 2024-11-15:添加页脚,优化代码。
- 2024-11-25:引用tcping.com测速api,修复了一些错误。
准备工作
- 注册 Cloudflare 账户:如果你还没有 Cloudflare 账户,访问 Cloudflare 官网 注册一个账户。
- 创建一个新的 Worker:在 Cloudflare 的控制面板中,选择 “Workers” 并创建一个新的 Worker。
实现步骤
1. 事件监听
首先,我们需要设置一个事件监听器,来处理所有传入的请求。当请求到达时,我们将调用 handleRequest
函数。
1 2 3
| addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)); });
|
2. 构建 HTML 内容
在 handleRequest
函数中,我们将生成一个包含加载动画和结果显示区域的 HTML 页面。以下是 HTML 模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| async function handleRequest(request) { const htmlContent = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Latency Test</title> <style> body { background: linear-gradient(135deg, #e2e2e2, #ffffff); animation: backgroundAnimation 10s infinite alternate; height: 100vh; display: flex; justify-content: center; align-items: center; flex-direction: column; margin: 0; } .loader { border: 16px solid #f3f3f3; border-radius: 50%; border-top: 16px solid #3498db; width: 120px; height: 120px; animation: spin 2s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } #result { display: none; opacity: 0; transition: opacity 0.5s; text-align: center; font-size: 20px; color: #3498db; } @keyframes backgroundAnimation { 0% { background-color: #ffffff; } 100% { background-color: #f3f3f3; } } </style> </head> <body> <div class="loader" id="loader"></div> <div id="result"></div> <script> // JavaScript 代码 </script> </body> </html>`; return new Response(htmlContent, { headers: { 'Content-Type': 'text/html' }, }); }
|
加载动画示例:
3. 测量延迟
创建一个名为 measureLatency
的函数,该函数将发送一个 HEAD
请求来测量 URL 的延迟。
1 2 3 4 5 6 7 8 9 10
| async function measureLatency(url) { const start = Date.now(); try { await fetch(url, { method: 'HEAD' }); return Date.now() - start; } catch (error) { console.error('Error measuring latency:', error); return Infinity; } }
|
4. 根据延迟重定向
在 testLatency
函数中,我们将调用 measureLatency
测量两个 URL 的延迟,并根据结果决定重定向到哪个 URL。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| async function testLatency() { const cfPagesUrl = 'https://666.com'; const vercelUrl = 'https://888.com';
const cfPagesLatency = await measureLatency(cfPagesUrl); const vercelLatency = await measureLatency(vercelUrl);
const resultElement = document.getElementById('result'); const loaderElement = document.getElementById('loader');
loaderElement.style.display = 'none'; resultElement.style.display = 'block';
let targetUrl; if (cfPagesLatency < vercelLatency) { resultElement.innerText = 'Redirecting to CF Pages...'; targetUrl = cfPagesUrl; } else { resultElement.innerText = 'Redirecting to Vercel...'; targetUrl = vercelUrl; } }
|
5. 显示延迟信息
动态更新结果区域,显示各个 URL 的延迟,并使用不同的颜色来指示延迟等级:
1 2 3 4 5 6 7 8 9 10
| function getColorForLatency(latency) { if (latency < 100) return 'green'; else if (latency < 300) return 'orange'; else return 'red'; }
const cfPagesMessage = \`CF Pages 延迟: <span style="color:\${getColorForLatency(cfPagesLatency)};">\${cfPagesLatency} ms</span>\`; const vercelMessage = \`Vercel 延迟: <span style="color:\${getColorForLatency(vercelLatency)};">\${vercelLatency} ms</span>\`; resultElement.innerHTML += '<br>' + cfPagesMessage + '<br>' + vercelMessage;
|
结果动画示例:
6. 完整代码 (2024-11-25优化了一下代码)
将所有部分结合在一起,以下是完整的 Cloudflare Worker 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
| addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)); });
async function handleRequest(request) { const htmlContent = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Latency Test</title> <style> body { font-family: Arial, sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: space-between; min-height: 100vh; background: linear-gradient(135deg, #e2e2e2, #ffffff); } .loader { border: 16px solid #f3f3f3; border-radius: 50%; border-top: 16px solid #3498db; width: 100px; height: 100px; animation: spin 1s linear infinite; margin-top: 20%; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } #result { width: 80%; margin-top: 20px; text-align: center; font-size: 18px; display: flex; flex-direction: column; gap: 10px; } .result-item { font-size: 16px; } footer { width: 100%; background: #ebebeb; color: white; font-size: 14px; text-align: center; padding: 10px; } iframe { width: 100%; border: 0; height: 188px; } </style> </head> <body> <div class="loader" id="loader"></div> <div id="result"></div> <footer> <iframe src="https://ip.skk.moe/simple"></iframe> </footer> <script> const urls = [ { name: 'CF Pages', url: 'https://cf.666.com/' }, { name: 'Vercel', url: 'https://vc.888.com/' }, { name: 'GitHub', url: 'https://gh.666.com/' }, { name: 'Netlify', url: 'https://nl.888.com/' }, { name: '备用', url: 'https://blog.666.com/' }, ];
const tcpingApiBaseUrl = 'https://api.jaxing.cc/v2/Tcping';
async function measureLatency(url, retries = 2, timeout = 2000) { for (let attempt = 0; attempt < retries; attempt++) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout);
const start = Date.now(); try { const response = await fetch(url, { method: 'HEAD', signal: controller.signal }); clearTimeout(timeoutId); if (response.ok) return Date.now() - start; } catch (error) { console.warn(\`HTTP attempt \${attempt + 1} failed for \${url}: \`, error); } } return null; // HTTP 测试失败 }
async function measureTcpLatency(url) { try { const parsedUrl = new URL(url); const host = parsedUrl.hostname; const port = parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80); const apiUrl = \`https://api.jaxing.cc/v2/Tcping?host=\${encodeURIComponent(host)}&port=\${port}\`; const response = await fetch(apiUrl); if (!response.ok) throw new Error('TCPing API failed'); const data = await response.json(); if (data.code === 'ok' && data.data && data.data['平均延迟']) { let avgLatency = data.data['平均延迟']; // 移除单位并解析为数字 if (avgLatency.endsWith('ms')) { avgLatency = parseFloat(avgLatency.replace('ms', '')); // 毫秒直接解析为数字 } else if (avgLatency.endsWith('s')) { avgLatency = parseFloat(avgLatency.replace('s', '')) * 1000; // 秒转换为毫秒 } else { throw new Error('Unsupported latency format'); // 不支持的格式 } // 保留小数点后三位 avgLatency = Math.floor(avgLatency * 1000) / 1000; return avgLatency; } return null; // 如果无法提取平均延迟,则返回 null } catch (error) { console.error(\`TCPing failed for \${url}: \`, error); return null; } }
async function testLatency() { const loaderElement = document.getElementById('loader'); const resultElement = document.getElementById('result');
const results = await Promise.all( urls.map(async ({ name, url }) => { let latency = await measureLatency(url); if (latency === null) { console.warn(\`\${name} HTTP 测试失败,尝试 TCPing。\`); latency = await measureTcpLatency(url); }
const resultItem = document.createElement('div'); resultItem.className = 'result-item';
if (latency !== null) { const color = getColorForLatency(latency); resultItem.textContent = \`\${name} 延迟: \`; const span = document.createElement('span'); span.style.color = color; span.textContent = \`\${latency} ms\`; resultItem.appendChild(span); } else { resultItem.textContent = \`\${name} 延迟: \`; const span = document.createElement('span'); span.style.color = 'red'; span.textContent = '失败'; resultItem.appendChild(span); }
resultElement.appendChild(resultItem); return { name, url, latency }; }) );
loaderElement.style.display = 'none'; const successfulResults = results.filter(r => r.latency !== null); if (successfulResults.length === 0) { resultElement.innerHTML += '<div>所有 URL 测试均失败,请检查网络或目标站点配置。</div>'; return; }
successfulResults.sort((a, b) => a.latency - b.latency); const bestOption = successfulResults[0]; resultElement.innerHTML += \`<div>Redirecting to <strong>\${bestOption.name}</strong>...</div>\`;
setTimeout(() => { window.location.href = bestOption.url; }, 2000); }
function getColorForLatency(latency) { if (latency < 200) return 'green'; if (latency < 600) return 'orange'; return 'red'; }
window.onload = testLatency; </script> </body> </html>`;
return new Response(htmlContent, { headers: { 'Content-Type': 'text/html' }, }); }
|
运行和测试
如需要增加或修改 URL,只需要修改 urls
数组。如下面代码中,我们添加了两个 URL:
1 2 3 4 5 6 7 8 9 10
| async function testLatency() { const urls = [ { name: 'CF Pages', url: 'https://cf.666.com/' }, { name: 'Vercel', url: 'https://vc.888.com/' }, { name: 'GitHub', url: 'https://gh.666.com/' }, { name: 'Netlify', url: 'https://nl.888.com/' }, { name: '备用', url: 'https://blog.666.com/' }, { name: '新URL1', url: 'https://newurl1.com/' }, { name: '新URL2', url: 'https://newurl2.com/' } ];
|
完成上述步骤后,保存并部署你的 Worker。打开分配给你的 Worker 的 URL,你应该能看到加载动画,随后页面将显示测量的延迟和重定向信息。
结语
通过本教程,你学会了如何使用 Cloudflare Worker 创建一个延迟测试工具。