You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nginx Stream通过代理协议转发TLS连接至Fastify后,Fastify无法响应请求的解决方法

Nginx Stream通过代理协议转发TLS连接至Fastify后,Fastify无法响应请求的解决方法

我太懂你这种卡在半路上的感觉了——Nginx转发的连接能拿到客户端IP,TLS握手也显示完成,但Fastify就是不肯返回"hello world",这种问题真的很磨人。咱们先揪出核心问题,再一步步解决:

问题根源分析

你当前的代码有两个关键矛盾点:

  1. Fastify服务类型不匹配:Fastify启动的是3000端口的HTTP服务,但经过TLS解密后得到的是客户端的HTTPS请求(客户端和Nginx之间是HTTPS连接,Nginx原封不动转发TLS流量),直接把TLS Socket抛给HTTP服务,Fastify根本解析不了加密后的请求结构。
  2. 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}`);
// });

关键修改说明

  1. 替换PROXY头解析方式:用proxy-protocol库替代正则,支持流式解析,解决了数据拆分在不同chunk的解析失败问题。
  2. 让Fastify直接接管处理后的Socket:不再单独启动Fastify的3000端口监听(或仅保留作本地测试),直接用Fastify的HTTP Server实例处理经过TLS解密的Socket,Fastify可以正确解析明文HTTP请求,自然就能返回响应了。
  3. 增强错误处理与资源管理:增加了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 09:33:07