feat(login, zulip): 引入 JWT 验证并重构 API 密钥管理 #33

Closed
ANGJustinl wants to merge 13 commits from ANGJustinl/whale-town-end:master into main
6 changed files with 0 additions and 953 deletions
Showing only changes of commit f335b72f6d - Show all commits

View File

@@ -1,61 +0,0 @@
server {
listen 443 ssl;
server_name whaletownend.xinghangee.icu;
ssl_certificate /home/ubuntu/node_test/keys/whaletownend.xinghangee.icu_bundle.crt;
ssl_certificate_key /home/ubuntu/node_test/keys/whaletownend.xinghangee.icu.key;
client_max_body_size 500M;
ssl_session_timeout 5m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
location /socket.io/ {
proxy_pass http://127.0.0.1:3000/socket.io/;
# 基础反向代理头
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Socket.IO/WebSocket 核心配置
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 关键Socket.IO 需要长超时 + 关闭缓冲
proxy_connect_timeout 75s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
proxy_buffering off;
proxy_cache off; # 关闭缓存,避免 Socket.IO 消息延迟
}
location / {
proxy_pass http://127.0.0.1:3000;
# 必须加的 header
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 避免第一次请求断开
proxy_http_version 1.1;
proxy_set_header Connection "";
# 调大超时,避免初始化时被踢掉
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 建议关闭缓冲,防止页面流式加载时被截断
proxy_buffering off;
}
}

View File

@@ -1,89 +0,0 @@
# 完整的nginx配置 - 支持HTTP重定向和WebSocket
# 在 http 块中添加 WebSocket 升级映射
http {
# WebSocket 升级映射 - 必须在 http 块中
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# HTTP server - 重定向到HTTPS
server {
listen 80;
server_name whaletownend.xinghangee.icu;
# 重定向所有HTTP请求到HTTPS
return 301 https://$host$request_uri;
}
# HTTPS server - 主要配置
server {
listen 443 ssl;
server_name whaletownend.xinghangee.icu;
# SSL配置
ssl_certificate /home/ubuntu/node_test/keys/whaletownend.xinghangee.icu_bundle.crt;
ssl_certificate_key /home/ubuntu/node_test/keys/whaletownend.xinghangee.icu.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
# 安全头
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
client_max_body_size 500M;
# Socket.IO 配置 - 使用升级映射
location /socket.io/ {
proxy_pass http://127.0.0.1:3000/socket.io/;
# 基础反向代理头
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 核心配置 - 使用映射变量
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade; # 使用映射变量
# 超时配置
proxy_connect_timeout 75s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
# 关闭缓冲
proxy_buffering off;
proxy_cache off;
}
# 普通HTTP请求
location / {
proxy_pass http://127.0.0.1:3000;
# 基础代理头
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# HTTP配置
proxy_http_version 1.1;
proxy_set_header Connection "";
# 超时配置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 关闭缓冲
proxy_buffering off;
}
}
}

View File

@@ -1,117 +0,0 @@
const io = require('socket.io-client');
console.log('🔍 测试不同WebSocket协议的差异');
console.log('='.repeat(50));
async function testProtocol(name, url, options) {
console.log(`\n🧪 测试: ${name}`);
console.log(`📡 URL: ${url}`);
return new Promise((resolve) => {
const socket = io(url, options);
let resolved = false;
const timeout = setTimeout(() => {
if (!resolved) {
resolved = true;
socket.disconnect();
console.log(' ❌ 连接超时');
resolve({ success: false, error: 'timeout' });
}
}, 8000);
socket.on('connect', () => {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
console.log(' ✅ 连接成功');
console.log(` 📡 Socket ID: ${socket.id}`);
console.log(` 🚀 传输方式: ${socket.io.engine.transport.name}`);
console.log(` 🔗 实际URL: ${socket.io.uri}`);
socket.disconnect();
resolve({
success: true,
transport: socket.io.engine.transport.name,
actualUrl: socket.io.uri
});
}
});
socket.on('connect_error', (error) => {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
console.log(` ❌ 连接失败: ${error.message}`);
console.log(` 🔍 错误类型: ${error.type || 'unknown'}`);
resolve({ success: false, error: error.message, type: error.type });
}
});
});
}
async function runProtocolTests() {
const tests = [
{
name: 'WS协议 (错误方式)',
url: 'ws://whaletownend.xinghangee.icu/game',
options: { transports: ['websocket'], timeout: 5000 }
},
{
name: 'WSS协议 (直接指定)',
url: 'wss://whaletownend.xinghangee.icu/game',
options: { transports: ['websocket'], timeout: 5000 }
},
{
name: 'HTTPS协议 (推荐方式)',
url: 'https://whaletownend.xinghangee.icu/game',
options: { transports: ['websocket', 'polling'], timeout: 5000 }
},
{
name: 'HTTP协议 (本地测试)',
url: 'http://localhost:3000/game',
options: { transports: ['websocket', 'polling'], timeout: 5000 }
}
];
const results = [];
for (const test of tests) {
const result = await testProtocol(test.name, test.url, test.options);
results.push({ ...test, result });
// 等待1秒再测试下一个
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log('\n' + '='.repeat(50));
console.log('📊 协议测试结果对比');
console.log('='.repeat(50));
results.forEach((test, index) => {
const status = test.result.success ? '✅ 成功' : '❌ 失败';
const transport = test.result.transport ? ` (${test.result.transport})` : '';
const error = test.result.error ? ` - ${test.result.error}` : '';
console.log(`${index + 1}. ${test.name}: ${status}${transport}${error}`);
if (test.result.actualUrl) {
console.log(` 实际连接: ${test.result.actualUrl}`);
}
});
console.log('\n💡 协议选择建议:');
console.log('✅ 推荐: 使用 https:// 让Socket.IO自动处理协议选择');
console.log('⚠️ 避免: 直接使用 ws:// 或 wss://,容易出错');
console.log('🔧 本地: 使用 http:// 进行本地开发测试');
console.log('\n📚 协议说明:');
console.log('• ws:// - WebSocket over HTTP (明文传输)');
console.log('• wss:// - WebSocket over HTTPS (加密传输)');
console.log('• http:// → Socket.IO自动选择 ws:// 或 polling');
console.log('• https:// → Socket.IO自动选择 wss:// 或 polling');
process.exit(0);
}
runProtocolTests().catch(console.error);

View File

@@ -1,205 +0,0 @@
const https = require('https');
const http = require('http');
const io = require('socket.io-client');
console.log('🔍 测试HTTP重定向和WebSocket配置');
console.log('='.repeat(50));
// 1. 测试HTTP重定向
async function testHttpRedirect() {
console.log('\n1⃣ 测试HTTP重定向...');
return new Promise((resolve) => {
const options = {
hostname: 'whaletownend.xinghangee.icu',
port: 80,
path: '/',
method: 'GET',
timeout: 10000
};
const req = http.request(options, (res) => {
console.log(`📊 HTTP状态码: ${res.statusCode}`);
console.log('📋 响应头:');
Object.entries(res.headers).forEach(([key, value]) => {
console.log(` ${key}: ${value}`);
});
if (res.statusCode === 301 || res.statusCode === 302) {
console.log('✅ HTTP重定向配置正确');
console.log(`🔄 重定向到: ${res.headers.location}`);
} else if (res.statusCode === 200) {
console.log('⚠️ HTTP没有重定向直接返回内容');
} else {
console.log(`❌ HTTP重定向异常: ${res.statusCode}`);
}
resolve({ statusCode: res.statusCode, location: res.headers.location });
});
req.on('error', (error) => {
console.log(`❌ HTTP连接失败: ${error.message}`);
resolve({ error: error.message });
});
req.on('timeout', () => {
console.log('❌ HTTP连接超时');
req.destroy();
resolve({ error: 'timeout' });
});
req.end();
});
}
// 2. 测试WebSocket升级映射
async function testWebSocketUpgradeMapping() {
console.log('\n2⃣ 测试WebSocket升级映射...');
// 检查nginx是否有$connection_upgrade映射
return new Promise((resolve) => {
const options = {
hostname: 'whaletownend.xinghangee.icu',
port: 443,
path: '/socket.io/?EIO=4&transport=websocket',
method: 'GET',
headers: {
'Upgrade': 'websocket',
'Connection': 'Upgrade',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
'Sec-WebSocket-Version': '13',
'Origin': 'https://whaletownend.xinghangee.icu'
},
timeout: 8000
};
const req = https.request(options, (res) => {
console.log(`📊 WebSocket握手状态码: ${res.statusCode}`);
console.log('📋 WebSocket响应头:');
Object.entries(res.headers).forEach(([key, value]) => {
console.log(` ${key}: ${value}`);
});
if (res.statusCode === 101) {
console.log('✅ WebSocket升级成功');
} else if (res.statusCode === 400) {
console.log('❌ WebSocket升级失败 - 400错误');
console.log('💡 可能缺少 $connection_upgrade 映射');
} else if (res.statusCode === 502) {
console.log('❌ WebSocket升级失败 - 502错误');
console.log('💡 后端连接问题');
} else {
console.log(`❌ WebSocket升级失败 - ${res.statusCode}错误`);
}
resolve({ statusCode: res.statusCode });
});
req.on('error', (error) => {
console.log(`❌ WebSocket握手失败: ${error.message}`);
resolve({ error: error.message });
});
req.on('timeout', () => {
console.log('❌ WebSocket握手超时');
req.destroy();
resolve({ error: 'timeout' });
});
req.end();
});
}
// 3. 测试WS协议是否能通过重定向工作
async function testWSProtocolWithRedirect() {
console.log('\n3⃣ 测试WS协议重定向...');
return new Promise((resolve) => {
console.log('🧪 尝试连接 ws://whaletownend.xinghangee.icu/game');
const socket = io('ws://whaletownend.xinghangee.icu/game', {
transports: ['websocket'],
timeout: 8000,
forceNew: true
});
let resolved = false;
const timeout = setTimeout(() => {
if (!resolved) {
resolved = true;
socket.disconnect();
console.log(' ❌ WS协议连接超时');
resolve({ success: false, error: 'timeout' });
}
}, 8000);
socket.on('connect', () => {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
console.log(' ✅ WS协议连接成功通过重定向');
console.log(` 📡 Socket ID: ${socket.id}`);
console.log(` 🔗 实际URL: ${socket.io.uri}`);
socket.disconnect();
resolve({ success: true, actualUrl: socket.io.uri });
}
});
socket.on('connect_error', (error) => {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
console.log(` ❌ WS协议连接失败: ${error.message}`);
console.log(` 🔍 错误详情: ${error.description?.message || 'N/A'}`);
resolve({ success: false, error: error.message });
}
});
});
}
async function runRedirectTests() {
const httpResult = await testHttpRedirect();
const websocketResult = await testWebSocketUpgradeMapping();
const wsProtocolResult = await testWSProtocolWithRedirect();
console.log('\n' + '='.repeat(50));
console.log('📊 重定向和WebSocket测试结果');
console.log('='.repeat(50));
console.log(`1. HTTP重定向: ${httpResult.statusCode === 301 || httpResult.statusCode === 302 ? '✅ 配置正确' : '❌ 未配置或异常'}`);
if (httpResult.location) {
console.log(` 重定向目标: ${httpResult.location}`);
}
console.log(`2. WebSocket升级: ${websocketResult.statusCode === 101 ? '✅ 正常' : '❌ 失败'}`);
console.log(`3. WS协议重定向: ${wsProtocolResult.success ? '✅ 工作' : '❌ 不工作'}`);
console.log('\n💡 分析结果:');
if (httpResult.statusCode === 301 || httpResult.statusCode === 302) {
console.log('✅ HTTP重定向配置正确');
} else {
console.log('❌ 缺少HTTP重定向配置');
console.log('🔧 需要添加HTTP server块进行重定向');
}
if (websocketResult.statusCode !== 101) {
console.log('❌ WebSocket升级配置有问题');
console.log('🔧 需要检查nginx配置中的:');
console.log(' 1. map $http_upgrade $connection_upgrade 映射');
console.log(' 2. proxy_set_header Upgrade $http_upgrade');
console.log(' 3. proxy_set_header Connection $connection_upgrade');
}
if (!wsProtocolResult.success) {
console.log('❌ WS协议无法通过重定向工作');
console.log('💡 原因: WebSocket协议升级发生在TCP层无法像HTTP那样重定向');
console.log('📝 解决方案: 客户端应该直接使用WSS协议');
}
process.exit(0);
}
runRedirectTests().catch(console.error);

View File

@@ -1,287 +0,0 @@
const https = require('https');
const http = require('http');
const io = require('socket.io-client');
console.log('🔍 详细测试WebSocket握手重定向机制');
console.log('='.repeat(60));
// 1. 手动模拟WebSocket握手请求 - HTTP阶段
async function testWebSocketHandshakeHTTP() {
console.log('\n1⃣ 测试WebSocket握手的HTTP阶段...');
return new Promise((resolve) => {
console.log('📡 发送WebSocket握手请求到 HTTP (80端口)');
const options = {
hostname: 'whaletownend.xinghangee.icu',
port: 80,
path: '/socket.io/?EIO=4&transport=websocket',
method: 'GET',
headers: {
'Upgrade': 'websocket',
'Connection': 'Upgrade',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
'Sec-WebSocket-Version': '13',
'Origin': 'http://whaletownend.xinghangee.icu',
'User-Agent': 'websocket-handshake-test'
},
timeout: 10000
};
const req = http.request(options, (res) => {
console.log(`📊 HTTP响应状态码: ${res.statusCode}`);
console.log('📋 响应头:');
Object.entries(res.headers).forEach(([key, value]) => {
console.log(` ${key}: ${value}`);
});
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
if (data && data.length < 500) {
console.log(`📄 响应内容: ${data}`);
}
console.log('\n📊 分析结果:');
if (res.statusCode === 301 || res.statusCode === 302) {
console.log('✅ WebSocket握手请求被重定向');
console.log(`🔄 重定向到: ${res.headers.location}`);
console.log('💡 证明: WebSocket握手的HTTP阶段支持重定向');
} else if (res.statusCode === 101) {
console.log('✅ WebSocket握手成功升级');
} else if (res.statusCode === 400) {
console.log('❌ WebSocket握手失败 - 400错误');
} else {
console.log(`⚠️ 意外的响应: ${res.statusCode}`);
}
resolve({
statusCode: res.statusCode,
location: res.headers.location,
isRedirect: res.statusCode === 301 || res.statusCode === 302
});
});
});
req.on('error', (error) => {
console.log(`❌ HTTP请求失败: ${error.message}`);
resolve({ error: error.message });
});
req.on('timeout', () => {
console.log('❌ HTTP请求超时');
req.destroy();
resolve({ error: 'timeout' });
});
req.end();
});
}
// 2. 测试Socket.IO客户端是否能自动处理重定向
async function testSocketIORedirectHandling() {
console.log('\n2⃣ 测试Socket.IO客户端重定向处理...');
const testConfigs = [
{
name: 'WS协议 - 测试重定向',
url: 'ws://whaletownend.xinghangee.icu/game',
options: {
transports: ['websocket'],
timeout: 8000,
forceNew: true
}
},
{
name: 'HTTP协议 - 测试重定向',
url: 'http://whaletownend.xinghangee.icu/game',
options: {
transports: ['websocket', 'polling'],
timeout: 8000,
forceNew: true
}
}
];
const results = [];
for (const config of testConfigs) {
console.log(`\n🧪 ${config.name}`);
console.log(`📡 URL: ${config.url}`);
const result = await new Promise((resolve) => {
const socket = io(config.url, config.options);
let resolved = false;
// 监听连接事件
socket.on('connect', () => {
if (!resolved) {
resolved = true;
console.log(' ✅ 连接成功');
console.log(` 📡 Socket ID: ${socket.id}`);
console.log(` 🚀 传输方式: ${socket.io.engine.transport.name}`);
console.log(` 🔗 最终URL: ${socket.io.uri}`);
// 检查是否发生了协议升级
const originalProtocol = config.url.startsWith('ws://') ? 'ws' : 'http';
const finalProtocol = socket.io.uri.startsWith('wss://') ? 'wss' :
socket.io.uri.startsWith('ws://') ? 'ws' :
socket.io.uri.startsWith('https://') ? 'https' : 'http';
if (originalProtocol !== finalProtocol) {
console.log(` 🔄 协议升级: ${originalProtocol}:// → ${finalProtocol}://`);
}
socket.disconnect();
resolve({
success: true,
transport: socket.io.engine.transport.name,
finalUrl: socket.io.uri,
protocolChanged: originalProtocol !== finalProtocol
});
}
});
socket.on('connect_error', (error) => {
if (!resolved) {
resolved = true;
console.log(` ❌ 连接失败: ${error.message}`);
console.log(` 🔍 错误类型: ${error.type || 'unknown'}`);
// 检查是否是重定向相关的错误
if (error.message.includes('redirect') || error.message.includes('301') || error.message.includes('302')) {
console.log(' 💡 这可能是重定向处理问题');
}
resolve({
success: false,
error: error.message,
type: error.type
});
}
});
// 超时处理
setTimeout(() => {
if (!resolved) {
resolved = true;
socket.disconnect();
console.log(' ❌ 连接超时');
resolve({ success: false, error: 'timeout' });
}
}, config.options.timeout);
});
results.push({ config: config.name, ...result });
// 等待1秒再测试下一个
await new Promise(resolve => setTimeout(resolve, 1000));
}
return results;
}
// 3. 测试不同客户端库的重定向行为
async function testRawWebSocketRedirect() {
console.log('\n3⃣ 测试原生WebSocket重定向行为...');
const WebSocket = require('ws');
return new Promise((resolve) => {
console.log('📡 使用原生WebSocket连接 ws://whaletownend.xinghangee.icu/socket.io/?EIO=4&transport=websocket');
try {
const ws = new WebSocket('ws://whaletownend.xinghangee.icu/socket.io/?EIO=4&transport=websocket');
ws.on('open', () => {
console.log(' ✅ 原生WebSocket连接成功');
console.log(' 💡 说明: 重定向在WebSocket握手阶段被正确处理');
ws.close();
resolve({ success: true });
});
ws.on('error', (error) => {
console.log(` ❌ 原生WebSocket连接失败: ${error.message}`);
if (error.message.includes('Unexpected server response: 301') ||
error.message.includes('Unexpected server response: 302')) {
console.log(' 💡 发现重定向响应但WebSocket库未自动处理');
console.log(' 📝 说明: 需要客户端库支持重定向处理');
}
resolve({ success: false, error: error.message });
});
ws.on('close', (code, reason) => {
console.log(` 🔌 WebSocket关闭: ${code} - ${reason}`);
});
} catch (error) {
console.log(` ❌ WebSocket创建失败: ${error.message}`);
resolve({ success: false, error: error.message });
}
});
}
async function runHandshakeRedirectTests() {
console.log('开始WebSocket握手重定向测试...\n');
const httpResult = await testWebSocketHandshakeHTTP();
const socketIOResults = await testSocketIORedirectHandling();
const rawWSResult = await testRawWebSocketRedirect();
console.log('\n' + '='.repeat(60));
console.log('📊 WebSocket握手重定向测试结果');
console.log('='.repeat(60));
console.log(`1. WebSocket握手HTTP阶段: ${httpResult.isRedirect ? '✅ 支持重定向' : '❌ 无重定向'}`);
if (httpResult.location) {
console.log(` 重定向目标: ${httpResult.location}`);
}
console.log(`2. Socket.IO客户端处理:`);
socketIOResults.forEach((result, index) => {
const status = result.success ? '✅ 成功' : '❌ 失败';
console.log(` ${index + 1}. ${result.config}: ${status}`);
if (result.protocolChanged) {
console.log(` 协议升级: 是`);
}
if (result.error) {
console.log(` 错误: ${result.error}`);
}
});
console.log(`3. 原生WebSocket: ${rawWSResult.success ? '✅ 成功' : '❌ 失败'}`);
if (rawWSResult.error) {
console.log(` 错误: ${rawWSResult.error}`);
}
console.log('\n💡 技术原理验证:');
if (httpResult.isRedirect) {
console.log('✅ 验证: WebSocket握手的HTTP阶段确实支持重定向');
console.log('📝 机制: ws://先发HTTP GET请求(带Upgrade头) → 收到301/302 → 可以重定向');
} else {
console.log('❌ 未检测到WebSocket握手重定向');
}
const successfulSocketIO = socketIOResults.filter(r => r.success);
if (successfulSocketIO.length > 0) {
console.log('✅ Socket.IO客户端能够处理某些重定向场景');
} else {
console.log('❌ Socket.IO客户端无法处理当前的重定向配置');
}
console.log('\n🔧 修正后的准确表述:');
console.log('1. ✅ HTTP请求(包括WebSocket握手请求)支持301/302重定向');
console.log('2. ✅ WebSocket的"升级请求(HTTP层)"可以被重定向');
console.log('3. ✅ ws://先通过80端口发HTTP握手请求再尝试升级为WebSocket');
console.log('4. ⚠️ 客户端库需要支持重定向处理才能正常工作');
process.exit(0);
}
runHandshakeRedirectTests().catch(console.error);

View File

@@ -1,194 +0,0 @@
const io = require('socket.io-client');
const https = require('https');
const http = require('http');
console.log('🔧 实现支持重定向的WebSocket连接');
console.log('='.repeat(50));
/**
* 手动处理WebSocket重定向的Socket.IO连接
*
* 原理:
* 1. 先发送HTTP请求检查是否有重定向
* 2. 如果有重定向使用重定向后的URL
* 3. 如果没有重定向使用原始URL
*/
async function connectWithRedirectSupport(originalUrl, options = {}) {
console.log(`🔍 检查URL重定向: ${originalUrl}`);
// 解析原始URL
const urlObj = new URL(originalUrl.replace('ws://', 'http://').replace('wss://', 'https://'));
// 1. 发送HTTP请求检查重定向
const redirectInfo = await checkRedirect(urlObj);
// 2. 确定最终连接URL
let finalUrl;
if (redirectInfo.isRedirect) {
console.log(`🔄 检测到重定向: ${redirectInfo.location}`);
// 将重定向的URL转换为适合Socket.IO的格式
const redirectedUrl = new URL(redirectInfo.location);
if (redirectedUrl.protocol === 'https:') {
finalUrl = `https://${redirectedUrl.host}${redirectedUrl.pathname.replace('/socket.io/', '')}`;
} else {
finalUrl = `http://${redirectedUrl.host}${redirectedUrl.pathname.replace('/socket.io/', '')}`;
}
console.log(`✅ 使用重定向后的URL: ${finalUrl}`);
} else {
finalUrl = originalUrl.replace('ws://', 'http://').replace('wss://', 'https://');
console.log(`✅ 使用原始URL: ${finalUrl}`);
}
// 3. 使用最终URL建立Socket.IO连接
console.log(`🚀 建立Socket.IO连接...`);
return new Promise((resolve, reject) => {
const socket = io(finalUrl, {
transports: ['websocket', 'polling'],
timeout: 10000,
forceNew: true,
...options
});
socket.on('connect', () => {
console.log('✅ 连接成功!');
console.log(`📡 Socket ID: ${socket.id}`);
console.log(`🚀 传输方式: ${socket.io.engine.transport.name}`);
console.log(`🔗 最终URL: ${socket.io.uri}`);
resolve(socket);
});
socket.on('connect_error', (error) => {
console.log(`❌ 连接失败: ${error.message}`);
reject(error);
});
});
}
/**
* 检查URL是否有重定向
*/
async function checkRedirect(urlObj) {
return new Promise((resolve) => {
const isHttps = urlObj.protocol === 'https:';
const httpModule = isHttps ? https : http;
const port = urlObj.port || (isHttps ? 443 : 80);
const options = {
hostname: urlObj.hostname,
port: port,
path: '/socket.io/?EIO=4&transport=polling', // 使用Socket.IO的polling路径检查
method: 'HEAD', // 使用HEAD请求减少数据传输
timeout: 5000
};
const req = httpModule.request(options, (res) => {
const isRedirect = res.statusCode === 301 || res.statusCode === 302;
resolve({
isRedirect,
statusCode: res.statusCode,
location: res.headers.location
});
});
req.on('error', (error) => {
console.log(`⚠️ 重定向检查失败: ${error.message}`);
resolve({ isRedirect: false, error: error.message });
});
req.on('timeout', () => {
req.destroy();
resolve({ isRedirect: false, error: 'timeout' });
});
req.end();
});
}
/**
* 测试支持重定向的连接
*/
async function testRedirectSupport() {
const testUrls = [
'ws://whaletownend.xinghangee.icu/game',
'http://whaletownend.xinghangee.icu/game',
'https://whaletownend.xinghangee.icu/game'
];
for (const url of testUrls) {
console.log(`\n${'='.repeat(50)}`);
console.log(`🧪 测试URL: ${url}`);
console.log(`${'='.repeat(50)}`);
try {
const socket = await connectWithRedirectSupport(url);
// 测试基本功能
console.log('\n📤 测试登录功能...');
const loginResult = await new Promise((resolve) => {
const loginMessage = {
type: 'login',
token: 'test_token_for_redirect_test'
};
socket.emit('login', loginMessage);
socket.on('login_success', (data) => {
console.log('✅ 登录成功');
resolve({ success: true, data });
});
socket.on('login_error', (error) => {
console.log('⚠️ 登录失败预期因为使用测试token');
resolve({ success: false, error });
});
// 3秒超时
setTimeout(() => {
resolve({ success: false, error: 'timeout' });
}, 3000);
});
socket.disconnect();
console.log(`✅ URL ${url} 连接测试成功`);
} catch (error) {
console.log(`❌ URL ${url} 连接测试失败: ${error.message}`);
}
// 等待1秒再测试下一个
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
// 运行测试
async function runTest() {
try {
await testRedirectSupport();
console.log(`\n${'='.repeat(50)}`);
console.log('📊 重定向支持测试完成');
console.log(`${'='.repeat(50)}`);
console.log('\n💡 结论:');
console.log('✅ WebSocket握手重定向在协议层面完全支持');
console.log('✅ 通过手动处理重定向可以解决客户端库限制');
console.log('✅ ws:// 协议可以通过重定向正常工作');
console.log('\n🔧 实用建议:');
console.log('1. 对于支持重定向的场景,可以使用上述方案');
console.log('2. 对于简单场景,直接使用 https:// 更可靠');
console.log('3. 生产环境建议配置好重定向处理逻辑');
} catch (error) {
console.error('测试过程中发生错误:', error);
}
process.exit(0);
}
runTest();