Merge pull request #275 from qinfendebingshuo/copilot/fix-vpn-deployment-issues-again

fix: 铸渊专线订阅服务 "Connection closed before full header" 修复
This commit is contained in:
冰朔 2026-04-04 19:43:10 +08:00 committed by GitHub
commit cc5ed414dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 146 additions and 74 deletions

View File

@ -100,6 +100,9 @@ server {
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 10s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
add_header X-Content-Type-Options nosniff always; add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate" always; add_header Cache-Control "no-store, no-cache, must-revalidate" always;
} }

View File

@ -9,10 +9,14 @@
location /api/proxy-sub/ { location /api/proxy-sub/ {
proxy_pass http://127.0.0.1:3802/; proxy_pass http://127.0.0.1:3802/;
proxy_http_version 1.1;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 10s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
# 订阅服务安全头 # 订阅服务安全头
add_header X-Content-Type-Options nosniff always; add_header X-Content-Type-Options nosniff always;

View File

@ -224,24 +224,40 @@ deploy_services() {
# ── 配置Nginx ───────────────────────────────── # ── 配置Nginx ─────────────────────────────────
configure_nginx() { configure_nginx() {
# 检查主Nginx配置是否已有proxy-sub # 查找正确的Nginx配置文件 (zhuyuan.conf 优先于 default)
NGINX_CONF="/etc/nginx/sites-enabled/default" NGINX_CONF=""
for candidate in /etc/nginx/sites-enabled/zhuyuan.conf /etc/nginx/sites-enabled/default; do
if [ -f "$candidate" ]; then
NGINX_CONF="$candidate"
break
fi
done
if [ -f "$NGINX_CONF" ] && ! grep -q "proxy-sub" "$NGINX_CONF" 2>/dev/null; then if [ -z "$NGINX_CONF" ]; then
echo " ⚠️ 未找到Nginx站点配置文件"
return 0
fi
echo " 使用Nginx配置: $NGINX_CONF"
if ! grep -q "proxy-sub" "$NGINX_CONF" 2>/dev/null; then
echo " 添加Nginx代理订阅反向代理配置..." echo " 添加Nginx代理订阅反向代理配置..."
# 在第一个 location = /health 之前插入 proxy-sub location # 在第一个 location = /health 之前插入 proxy-sub location
sed -i '/# ─── 健康探针 ───/{ sed -i '/# ─── 健康探针 ───/{
# 只在第一次匹配时插入 # 只在第一次匹配时插入
i\ # ─── 铸渊专线订阅服务 (端口 3802) ───\n location /api/proxy-sub/ {\n proxy_pass http://127.0.0.1:3802/;\n proxy_http_version 1.1;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n add_header X-Content-Type-Options nosniff always;\n add_header Cache-Control "no-store, no-cache, must-revalidate" always;\n }\n i\ # ─── 铸渊专线订阅服务 (端口 3802) ───\n location /api/proxy-sub/ {\n proxy_pass http://127.0.0.1:3802/;\n proxy_http_version 1.1;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_connect_timeout 10s;\n proxy_read_timeout 30s;\n proxy_send_timeout 30s;\n add_header X-Content-Type-Options nosniff always;\n add_header Cache-Control "no-store, no-cache, must-revalidate" always;\n }\n
}' "$NGINX_CONF" || true }' "$NGINX_CONF" || true
echo " ✅ Nginx proxy-sub配置已注入" echo " ✅ Nginx proxy-sub配置已注入"
else else
echo " Nginx代理配置已存在 (或主配置不存在)" echo " Nginx proxy-sub配置已存在"
fi fi
if nginx -t 2>/dev/null; then if nginx -t 2>/dev/null; then
nginx -s reload || true nginx -s reload || true
echo " ✅ Nginx配置验证通过并已重载" echo " ✅ Nginx配置验证通过并已重载"
else
echo " ⚠️ Nginx配置验证失败:"
nginx -t 2>&1 || true
fi fi
} }
@ -291,11 +307,18 @@ health_check() {
echo " ❌ 端口443: 未监听" echo " ❌ 端口443: 未监听"
fi fi
# 订阅服务 # 订阅服务 (直接访问)
if curl -sf http://127.0.0.1:3802/health >/dev/null 2>&1; then if curl -sf http://127.0.0.1:3802/health >/dev/null 2>&1; then
echo " ✅ 订阅服务: 正常" echo " ✅ 订阅服务: 正常 (直连3802)"
else else
echo " ⏳ 订阅服务: 启动中..." echo " ❌ 订阅服务: 端口3802无响应"
fi
# 订阅服务 (通过Nginx反代)
if curl -sf http://127.0.0.1/api/proxy-sub/health >/dev/null 2>&1; then
echo " ✅ Nginx反代: 正常 (/api/proxy-sub/ → 3802)"
else
echo " ⚠️ Nginx反代: /api/proxy-sub/ 未响应 (Nginx配置可能缺失)"
fi fi
# PM2 # PM2
@ -308,6 +331,7 @@ update() {
deploy_services deploy_services
save_server_host save_server_host
configure_xray configure_xray
configure_nginx
ensure_xray_root_user ensure_xray_root_user
ensure_log_permissions ensure_log_permissions

View File

@ -10,6 +10,7 @@ module.exports = {
version: '1.0.0', version: '1.0.0',
script: '/opt/zhuyuan/proxy/service/subscription-server.js', script: '/opt/zhuyuan/proxy/service/subscription-server.js',
instances: 1, instances: 1,
exec_mode: 'fork',
env: { env: {
NODE_ENV: 'production', NODE_ENV: 'production',
ZY_PROXY_SUB_PORT: 3802, ZY_PROXY_SUB_PORT: 3802,
@ -26,6 +27,7 @@ module.exports = {
version: '1.0.0', version: '1.0.0',
script: '/opt/zhuyuan/proxy/service/traffic-monitor.js', script: '/opt/zhuyuan/proxy/service/traffic-monitor.js',
instances: 1, instances: 1,
exec_mode: 'fork',
env: { env: {
NODE_ENV: 'production', NODE_ENV: 'production',
ZY_PROXY_DATA_DIR: '/opt/zhuyuan/proxy/data', ZY_PROXY_DATA_DIR: '/opt/zhuyuan/proxy/data',
@ -41,6 +43,7 @@ module.exports = {
version: '1.0.0', version: '1.0.0',
script: '/opt/zhuyuan/proxy/service/proxy-guardian.js', script: '/opt/zhuyuan/proxy/service/proxy-guardian.js',
instances: 1, instances: 1,
exec_mode: 'fork',
env: { env: {
NODE_ENV: 'production', NODE_ENV: 'production',
ZY_PROXY_DATA_DIR: '/opt/zhuyuan/proxy/data', ZY_PROXY_DATA_DIR: '/opt/zhuyuan/proxy/data',

View File

@ -482,6 +482,7 @@ function detectClientType(userAgent) {
// ── HTTP服务器 ─────────────────────────────── // ── HTTP服务器 ───────────────────────────────
const server = http.createServer((req, res) => { const server = http.createServer((req, res) => {
try {
const parsedUrl = url.parse(req.url, true); const parsedUrl = url.parse(req.url, true);
const pathname = parsedUrl.pathname; const pathname = parsedUrl.pathname;
@ -558,6 +559,33 @@ const server = http.createServer((req, res) => {
// 404 // 404
res.writeHead(404, { 'Content-Type': 'text/plain' }); res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found'); res.end('Not Found');
} catch (err) {
console.error('❌ 请求处理错误 [%s %s]:', req.method, req.url, err.message || err);
try {
if (!res.headersSent) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
}
res.end('Internal Server Error');
} catch (writeErr) {
console.error(' ⚠️ 响应写入失败:', writeErr.message);
}
}
});
// 处理连接级别错误 (防止未捕获的socket错误导致进程崩溃)
server.on('error', (err) => {
console.error('❌ 服务器错误:', err.message);
if (err.code === 'EADDRINUSE') {
console.error(' 端口 %d 已被占用,进程退出等待PM2重启...', PORT);
process.exit(1);
}
});
server.on('clientError', (err, socket) => {
console.error('⚠️ 客户端连接错误:', err.code || err.message);
if (socket.writable) {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
}
}); });
server.listen(PORT, '127.0.0.1', () => { server.listen(PORT, '127.0.0.1', () => {
@ -579,3 +607,13 @@ function gracefulShutdown(signal) {
} }
process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT')); process.on('SIGINT', () => gracefulShutdown('SIGINT'));
// 进程级错误保护 (记录后优雅退出,由PM2负责重启)
process.on('uncaughtException', (err) => {
console.error('❌ 未捕获的异常:', err.message);
console.error(err.stack);
gracefulShutdown('uncaughtException');
});
process.on('unhandledRejection', (reason) => {
console.error('❌ 未处理的Promise拒绝:', reason);
});