Nginx Stream通过代理协议转发TLS连接至Fastify后,Fastify无法响应请求的解决方法
Nginx Stream通过代理协议转发TLS连接至Fastify后,Fastify无法响应请求的解决方法
我太懂你这种卡在半路上的感觉了——Nginx转发的连接能拿到客户端IP,TLS握手也显示完成,但Fastify就是不肯返回"hello world",这种问题真的很磨人。咱们先揪出核心问题,再一步步解决:
问题根源分析
你当前的代码有两个关键矛盾点:
- Fastify服务类型不匹配:Fastify启动的是3000端口的HTTP服务,但经过TLS解密后得到的是客户端的HTTPS请求(客户端和Nginx之间是HTTPS连接,Nginx原封不动转发TLS流量),直接把TLS Socket抛给HTTP服务,Fastify根本解析不了加密后的请求结构。
- PROXY协议处理不健壮:用正则匹配
chunk的方式,无法处理PROXY头和TLS数据拆分在不同chunk的情况,容易导致数据流断裂。
分步解决代码
我们直接重构代码,用更可靠的方式处理PROXY协议、TLS终止,并让Fastify正确接手请求:
1. 先安装依赖
首先安装专门处理PROXY协议的库,避免正则的坑:
npm install proxy-protocol
2. 修正后的完整代码
import Fastify from 'fastify'; import fs from 'fs'; import net from 'net'; import tls from 'tls'; import path from 'path'; import { fileURLToPath } from 'url'; import { ProxyProtocol } from 'proxy-protocol'; // 用专业库处理PROXY头 const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // TLS证书和密钥(确保是PEM格式,且与访问域名api.xxx.com匹配) const tlsOptions = { key: fs.readFileSync(path.join(__dirname, 'key.txt')), cert: fs.readFileSync(path.join(__dirname, 'cert.txt')), }; const fastify = Fastify({ logger: true, // 开启日志,方便排查请求是否到达Fastify }); // 定义路由(保持不变) fastify.get('/', async (req, res) => { console.log('✅ 收到客户端请求'); return 'hello world'; }); // 直接获取Fastify的HTTP Server实例,无需单独启动端口监听 const fastifyHttpServer = fastify.server; // 启动处理PROXY+TLS的TCP服务器 const tcpServer = net.createServer(async (rawSocket) => { try { // 用ProxyProtocol库解析PROXY头,处理流式数据更可靠 const proxyParser = new ProxyProtocol(rawSocket); const proxyData = await proxyParser.accept(); console.log(`🌐 真实客户端IP:${proxyData.sourceAddress}`); // 将原始Socket升级为TLS Socket,完成终止TLS const tlsSocket = tls.connect({ socket: rawSocket, isServer: true, ...tlsOptions, }); // TLS握手完成后,将Socket交给Fastify处理 tlsSocket.on('secureConnect', () => { console.log('🔐 TLS握手完成'); // 让Fastify的HTTP Server接管解密后的Socket,此时流量是明文HTTP fastifyHttpServer.emit('connection', tlsSocket); // 增加Socket生命周期处理,避免资源泄漏 tlsSocket.on('end', () => tlsSocket.destroy()); }); tlsSocket.on('error', (err) => { console.error('❌ TLS Socket错误:', err.message); tlsSocket.destroy(); }); } catch (err) { console.error('❌ PROXY协议解析/TLS处理失败:', err.message); rawSocket.destroy(); } }); // 启动TCP服务器监听4433端口(与Nginx配置的backend_1端口一致) tcpServer.listen(4433, '127.0.0.1', () => { console.log('⚡ TCP + TLS + PROXY服务器已启动在4433端口'); }); // 【可选】如果需要本地测试Fastify的HTTP服务,可以保留,不影响Nginx转发 // fastify.listen({ port: 3000, host: '127.0.0.1' }, (err, address) => { // if (err) throw err; // console.log(`🚀 Fastify本地测试服务运行在${address}`); // });
关键修改说明
- 替换PROXY头解析方式:用
proxy-protocol库替代正则,支持流式解析,解决了数据拆分在不同chunk的解析失败问题。 - 让Fastify直接接管处理后的Socket:不再单独启动Fastify的3000端口监听(或仅保留作本地测试),直接用Fastify的HTTP Server实例处理经过TLS解密的Socket,Fastify可以正确解析明文HTTP请求,自然就能返回响应了。
- 增强错误处理与资源管理:增加了Socket的
end事件处理,避免资源泄漏,同时完善了各环节的错误日志,方便排查问题。
额外注意事项
- 确保你的
package.json中配置了"type": "module",支持ES Module语法。 - 检查TLS证书:证书必须与客户端访问的域名
api.xxx.com匹配,否则会导致TLS握手失败(即使日志显示握手完成,也可能存在隐性问题)。 - Nginx配置验证:确认
stream块的proxy_protocol on已开启,且backend_1指向127.0.0.1:4433。 - 测试命令:可以用
curl -v https://api.xxx.com/ --resolve api.xxx.com:443:127.0.0.1模拟客户端请求,绕过DNS直接测试本地Nginx转发。
内容来源于stack exchange
相关产品推荐
相关产品推荐

