ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Cloudflare Workers VPC 连接实战:基于 TCP Sockets API 打通私有网络

2026/9/12 23:18:24 拓冰建站 浏览量
Cloudflare Workers VPC 连接实战:基于 TCP Sockets API 打通私有网络 Cloudflare Workers VPC 连接实战基于 TCP Sockets API 打通私有网络【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills导读本文以 Cloudflare Workers 的TCP Sockets APIcloudflare:sockets为核心系统讲解如何让 Worker 通过出站 TCP 连接访问 AWS、Azure、GCP、本地数据中心等私有网络中的资源并联合 Cloudflare Tunnel、Hyperdrive、Smart Placement 组成完整的私有网络接入方案。读完本文你将掌握connect()的完整 API 用法、TLS/StartTLS 三种加密模式、Wrangler 配置与多环境部署、Worker Tunnel 的架构搭建以及重试、超时、SSRF 防护等生产级实战模式。说明本文对应仓库 workers-vpc 参考文档 系列含 api.md、configuration.md、patterns.md、gotchas.md。该文档体系隶属于本仓库cloudflare-deployskill见 SKILL.md 中 Networking Connectivity 产品索引。注意本文档讲述的是 TCP Sockets API更新的 Workers VPC Services 产品仅支持 HTTP 的 service binding内置 SSRF 防护目前处于 beta 阶段2025属另一套独立文档。技术选型先回答该用哪个面对Worker 需要访问私有网络资源的需求第一件事不是写代码而是按需选型。下面是文档给出的快速决策表需求使用原因私有网络中的 HTTP/HTTPS APIVPC Servicesbeta独立文档SSRF 安全、声明式绑定PostgreSQL/MySQL 数据库Hyperdrive连接池、缓存、链路优化自定义 TCP 协议SSH、MQTT、私有协议TCP Sockets本文主题完全掌控线上协议简单 HTTP、追求最低延迟TCP Sockets Smart Placement手动优化链路把内网服务暴露到公网入站方向Cloudflare Tunnel非 Worker 专属能力何时该用 TCP Sockets适合使用 TCP Sockets 的场景✅ 需要直接控制线上协议如 Postgres 线上协议、SSH、Redis RESP✅ 非 HTTP 协议MQTT、SMTP、自定义二进制协议✅ 需要 StartTLS 或自定义 TLS 协商✅ 需要通过 TCP 流式传输二进制数据不要使用 TCP Sockets 的场景❌ 只是需要 HTTP/HTTPS请用fetch()或 VPC Services❌ 需要 PostgreSQL/MySQL请用 Hyperdrive 做连接池❌ 需要 WebSocket请用 Workers 原生 WebSocket 能力这个取舍背后是明确的工程理由TCP Sockets 给你的是裸线上协议级别的控制力代价是你要自己处理协议细节、连接生命周期与安全边界。仓库的 gotchas.md 中何时使用替代方案一节进一步印证了这一原则数据库场景推荐 Hyperdrive自带连接池与缓存、HTTP 场景推荐fetch()更简单、内置、需要 SSRF 保护的 HTTP 场景则等待 VPC Servicesbeta提供声明式绑定。快速开始一个最小可运行的示例在 Worker 中发起 TCP 连接核心只有一个函数connect()从cloudflare:sockets模块导入import { connect } from cloudflare:sockets; export default { async fetch(req: Request): PromiseResponse { // 连接私有服务 const socket connect( { hostname: db.internal.company.net, port: 5432 }, { secureTransport: on } ); try { await socket.opened; // 等待连接建立 const writer socket.writable.getWriter(); await writer.write(new TextEncoder().encode(QUERY\r\n)); await writer.close(); const reader socket.readable.getReader(); const { value } await reader.read(); return new Response(value); } finally { await socket.close(); // 始终关闭 socket } } };注意三个关键点await socket.opened确保连接成功后才读写写入与读取分别通过writable/readable两个 Web Streamfinally中await socket.close()保证无论成功失败都释放连接。try/finally关闭模式是本 API 最核心的规范详见下文最佳实践。TCP Sockets API 详解connect()函数签名function connect( address: SocketAddress, options?: SocketOptions ): Socket创建一条到指定地址的出站 TCP 连接。SocketAddress目标地址interface SocketAddress { hostname: string; // DNS 主机名或 IP 地址 port: number; // TCP 端口1-65535排除被屏蔽端口 }字段类型说明示例hostnamestring目标主机名或 IPdb.internal.net、10.0.1.50portnumberTCP 端口号5432、443、22DNS 名称在连接时解析。支持 IPv4、IPv6 以及私有 IP10.x、172.16.x、192.168.x。SocketOptions连接选项interface SocketOptions { secureTransport?: off | on | starttls; allowHalfOpen?: boolean; }字段类型默认值说明secureTransportoff \| on \| starttlsoffTLS 模式allowHalfOpenbooleanfalse是否允许半开连接secureTransport三种模式的适用场景模式行为适用场景off纯 TCP不加密测试、内部可信网络on立即进行 TLS 握手HTTPS、安全数据库、SSHstarttls先明文连接之后用startTls()升级Postgres、SMTP、IMAPallowHalfOpen默认false时关闭读流会自动关闭写流设为true后读写流相互独立适合需要半开连接语义的自定义协议。Socket 接口interface Socket { // 数据流 readable: ReadableStreamUint8Array; writable: WritableStreamUint8Array; // 连接状态 opened: PromiseSocketInfo; closed: Promisevoid; // 方法 close(): Promisevoid; startTls(): Socket; }readable用于读取数据的流通过getReader()消费。const { done, value } await reader.read();每次读取一个 chunk。writable用于写入数据的流通过getWriter()发送。写入后建议await writer.close()通知对端写入结束。opened连接成功时 resolve、失败时 reject 的 Promise可拿到SocketInforemoteAddress、localAddress均可能为undefinedinterface SocketInfo { remoteAddress?: string; // 可能为 undefined localAddress?: string; // 可能为 undefined } try { const info await socket.opened; } catch (error) { // 连接失败 }closedsocket 完全关闭双向时 resolve 的 Promise。close()优雅关闭连接会等待待写入数据完成。务必在finally中调用const socket connect({ hostname: api.internal, port: 443 }); try { // 使用 socket } finally { await socket.close(); }startTls()将连接升级为 TLS仅当创建时指定了secureTransport: starttls才可用。升级后必须使用返回的新 socket而不是原 socketconst socket connect( { hostname: db.internal, port: 5432 }, { secureTransport: starttls } ); // 先发送协议专用的 StartTLS 命令 const writer socket.writable.getWriter(); await writer.write(new TextEncoder().encode(STARTTLS\r\n)); // 升级为 TLS - 使用返回的 socket而不是原来的 const secureSocket socket.startTls(); const secureWriter secureSocket.writable.getWriter();StartTLS 的典型协议是 SMTP、IMAP、Postgres这些协议先以明文建立会话客户端发送STARTTLS命令、等待服务端确认 OK 后再调用startTls()升级为加密通道时机错误会触发 TLS 错误详见后文常见坑。完整示例与快速参考import { connect } from cloudflare:sockets; export default { async fetch(req: Request): PromiseResponse { const socket connect({ hostname: echo.example.com, port: 7 }, { secureTransport: on }); try { await socket.opened; const writer socket.writable.getWriter(); await writer.write(new TextEncoder().encode(Hello, TCP!\n)); await writer.close(); const reader socket.readable.getReader(); const { value } await reader.read(); return new Response(value); } finally { await socket.close(); } } };常用操作速查表任务代码导入import { connect } from cloudflare:sockets;连接connect({ hostname: host, port: 443 })带 TLSconnect(addr, { secureTransport: on })StartTLS握手后socket.startTls()写入await writer.write(data); await writer.close();读取const { value } await reader.read();错误处理try { await socket.opened; } catch { }始终关闭try { } finally { await socket.close(); }Wrangler 配置与多环境部署基础配置TCP Sockets 在 Workers 运行时默认可用无需任何特殊配置。一个最简wrangler.jsonc{ name: private-network-worker, main: src/index.ts, compatibility_date: 2025-01-01 }环境变量连接信息不写死在代码里{ vars: { DB_HOST: 10.0.1.50, DB_PORT: 5432 } }interface Env { DB_HOST: string; DB_PORT: string; } export default { async fetch(req: Request, env: Env): PromiseResponse { const socket connect({ hostname: env.DB_HOST, port: parseInt(env.DB_PORT) }); } };注意vars中的值都是字符串port需要parseInt()转换。多环境staging / production配置{ vars: { DB_HOST: localhost }, env: { staging: { vars: { DB_HOST: staging-db.internal.net } }, production: { vars: { DB_HOST: prod-db.internal.net } } } }部署时用--env指定环境wrangler deploy --env staging或wrangler deploy --env production。这让你可以用同一份代码、不同的目标地址做隔离发布。密钥管理敏感凭据用 Secret数据库口令、TLS 私钥等敏感信息不要放进wrangler.jsonc应使用wrangler secretwrangler secret put DB_PASSWORD # 按提示输入值在 Worker 中通过env.DB_PASSWORD读取用于协议握手或身份认证。这是文档明确强调的安全边界vars会随代码进入版本库secrets只存在于运行时。本地开发使用wrangler dev调试。注意本地模式可能无法访问私有网络开发阶段应使用公网端点或 mock 服务器const config process.env.NODE_ENV dev ? { hostname: localhost, port: 5432 } // Mock : { hostname: db.internal.example.com, port: 5432 }; // Production连接串解析很多配置系统以连接串形式给出数据库地址可解析出 host 与 portfunction parseConnectionString(connStr: string): SocketAddress { const url new URL(connStr); // e.g., postgres://10.0.1.50:5432/mydb return { hostname: url.hostname, port: parseInt(url.port) || 5432 }; }兼容性说明TCP Sockets 在所有现代 Workers 运行时均可用设置当前日期如compatibility_date: 2025-01-01即可无需任何特殊 compatibility flags。部署前可先按 SKILL.md 的要求执行npx wrangler whoami确认已认证CI/CD 场景设置CLOUDFLARE_API_TOKEN环境变量。架构模式Worker Tunnel 打通私有网络TCP Sockets 本身只解决了出站 TCP问题而私有网络通常没有公网入口。文档给出的标准架构是Worker Cloudflare Tunnel组合┌─────────┐ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ Worker │────▶│ TCP Socket │────▶│ Tunnel │────▶│ Private │ │ │ │ (this API) │ │ (cloudflared)│ │ Network │ └─────────┘ └─────────────┘ └──────────────┘ └─────────────┘Worker 向 Tunnel 主机名发起 TCP socket 连接Tunnel 端点将流量路由到私有 IP响应沿 Tunnel 原路返回 Worker数据链路为Worker (TCP Socket) → Tunnel hostname → cloudflared → Private Network。快速搭建步骤在私有网络内的服务器上安装 cloudflared详见 Tunnel 参考文档创建隧道cloudflared tunnel create my-private-network在config.yml中配置路由tunnel: TUNNEL_ID credentials-file: /path/to/TUNNEL_ID.json ingress: - hostname: db.internal.example.com service: tcp://10.0.1.50:5432 - service: http_status:404 # 必需的兜底规则运行隧道cloudflared tunnel run my-private-network从 Worker 连接const socket connect( { hostname: db.internal.example.com, port: 5432 }, // Tunnel 主机名 { secureTransport: on } );从仓库的 Tunnel 配置参考 可以补充几个深化点ingress 规则自上而下匹配第一条命中生效支持精确主机名、通配符主机名*.example.com、路径正则path: \.(jpg|png|css|js)$service: http_status:404兜底规则必须有service 类型支持tcp://localhost:2222、ssh://localhost:22、rdp://localhost:3389、unix:/path/to/socket等本文的数据库场景走tcp://配置后可用cloudflared tunnel ingress validate校验配置、cloudflared tunnel ingress rule https://foo.example.com模拟匹配生产环境更推荐token 化隧道cloudflared tunnel --no-autoupdate run --token TOKEN路由在 Cloudflare DashboardZero Trust Networks Tunnels集中管理无需分发配置文件、改动即时生效。配合 Smart Placement 降低延迟在 Worker 多次访问私有后端时可开启 Smart Placement 让 Worker 自动迁移到离后端更近的位置执行{ placement: { mode: smart } }Workers 会观察与 TCP socket 目标之间的连接延迟自动选择更优的执行位置。详见 Smart Placement 参考。数据库场景优先 Hyperdrive如果目标是 PostgreSQL/MySQL强烈建议优先使用 Hyperdrive而非裸 TCP socket它自带连接池省去每次请求的 TCP/TLS/auth 握手往返、边缘建连与查询缓存非变更查询默认 60s TTL{ hyperdrive: [{ binding: DB, id: HYPERDRIVE_ID }] }创建配置npx wrangler hyperdrive create my-db --connection-stringpostgres://user:passhost:5432/db。完整配置见 Hyperdrive 参考。这也与主文档最佳实践第 3 条数据库用 Hyperdrive一致原始 Postgres 线上协议复杂度高启动、认证、查询消息生产环境不应在裸 socket 上重造轮子。实战模式从协议到生产级错误处理仓库的 patterns.md 提供了大量可直接落地的模式。读取全部数据TCP 是流式传输一次read()拿不到完整报文需要循环累积async function readAll(socket: Socket): PromiseUint8Array { const reader socket.readable.getReader(); const chunks: Uint8Array[] []; while (true) { const { done, value } await reader.read(); if (done) break; chunks.push(value); } const total chunks.reduce((sum, c) sum c.length, 0); const result new Uint8Array(total); let offset 0; for (const chunk of chunks) { result.set(chunk, offset); offset chunk.length; } return result; }流式响应socket 直接接 HTTP Response// 将 socket 数据直接流式写入 HTTP 响应 const socket connect({ hostname: stream.internal, port: 9000 }, { secureTransport: on }); const writer socket.writable.getWriter(); await writer.write(new TextEncoder().encode(STREAM\n)); await writer.close(); return new Response(socket.readable);协议示例Redis RESP 与 MQTTRedis RESP发送*2\r\n$3\r\nGET\r\n$keylen\r\nkey\r\n接收$len\r\ndata\r\n或$-1\r\n表示 nullconst socket connect({ hostname: redis.internal, port: 6379 }); const writer socket.writable.getWriter(); await writer.write(new TextEncoder().encode(*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n));MQTTCONNECT 报文0x10 len 0x00 0x04 MQTT 0x04 flags ...PUBLISH 报文0x30 len topic_len topic messageconst socket connect({ hostname: mqtt.broker, port: 1883 }); const writer socket.writable.getWriter(); // CONNECT: 0x10 len 0x00 0x04 MQTT 0x04 flags ... // PUBLISH: 0x30 len topic_len topic messagePostgreSQL文档明确提示生产环境用 Hyperdrive因为裸 Postgres 协议非常复杂startup、auth、query 消息。错误处理三件套指数退避重试async function connectWithRetry(addr: SocketAddress, opts: SocketOptions, maxRetries 3): PromiseSocket { for (let i 1; i maxRetries; i) { try { const socket connect(addr, opts); await socket.opened; return socket; } catch (error) { if (i maxRetries) throw error; await new Promise(r setTimeout(r, 1000 * Math.pow(2, i - 1))); // 指数退避 } } throw new Error(Unreachable); }超时控制API 没有内置超时必须用Promise.race()自行实现async function connectWithTimeout(addr: SocketAddress, opts: SocketOptions, ms 5000): PromiseSocket { const socket connect(addr, opts); const timeout new Promisenever((_, reject) setTimeout(() reject(new Error(Timeout)), ms)); await Promise.race([socket.opened, timeout]); return socket; }主备降级async function connectWithFallback(primary: string, fallback: string, port: number): PromiseSocket { try { const socket connect({ hostname: primary, port }, { secureTransport: on }); await socket.opened; return socket; } catch { return connect({ hostname: fallback, port }, { secureTransport: on }); } }安全模式SSRF 防护TCP Sockets 给了任意出站连接的能力必须防 SSRF——不能让用户可控的目标直达内部服务。标准做法是严格白名单const ALLOWED_HOSTS [db.internal.company.net, api.internal.company.net, /^10\.0\.1\.\d$/]; function isAllowed(hostname: string): boolean { return ALLOWED_HOSTS.some(p p instanceof RegExp ? p.test(hostname) : p hostname); } export default { async fetch(req: Request): PromiseResponse { const target new URL(req.url).searchParams.get(host); if (!target || !isAllowed(target)) return new Response(Forbidden, { status: 403 }); const socket connect({ hostname: target, port: 443 }); // Use socket... } };白名单同时支持精确字符串与正则两种匹配。连接池每次请求新建 TCP 连接开销很大可以自建小型连接池复用注意池大小受平台并发限制约束class SocketPool { private pool new Mapstring, Socket[](); async acquire(hostname: string, port: number): PromiseSocket { const key ${hostname}:${port}; const sockets this.pool.get(key) || []; if (sockets.length 0) return sockets.pop()!; const socket connect({ hostname, port }, { secureTransport: on }); await socket.opened; return socket; } release(hostname: string, port: number, socket: Socket): void { const key ${hostname}:${port}; const sockets this.pool.get(key) || []; if (sockets.length 3) { sockets.push(socket); this.pool.set(key, sockets); } else socket.close(); } }多协议网关用同一个 Worker 暴露多种协议的连通性探测能力以 Redis PING 为例interface Protocol { name: string; defaultPort: number; test(host: string, port: number): Promisestring; } const PROTOCOLS: Recordstring, Protocol { redis: { name: redis, defaultPort: 6379, async test(host, port) { const socket connect({ hostname: host, port }); try { const writer socket.writable.getWriter(); await writer.write(new TextEncoder().encode(*1\r\n$4\r\nPING\r\n)); writer.releaseLock(); const reader socket.readable.getReader(); const { value } await reader.read(); return new TextDecoder().decode(value || new Uint8Array()); } finally { await socket.close(); } } } }; export default { async fetch(req: Request): PromiseResponse { const url new URL(req.url); const proto url.pathname.slice(1); // /redis const host url.searchParams.get(host); if (!host || !PROTOCOLS[proto]) return new Response(Invalid, { status: 400 }); const result await PROTOCOLS[proto].test(host, parseInt(url.searchParams.get(port) || ) || PROTOCOLS[proto].defaultPort); return new Response(result); } };常见坑与排查指南仓库的 gotchas.md 汇总了平台限制与高频错误这里全部列出。平台限制限制项值每个请求的最大并发 socket 数6硬限制socket 生命周期与请求同生命周期连接超时平台决定无配置项超出 6 个连接会直接抛错解决方式是分批处理每批 ≤6for (let i 0; i hosts.length; i 6) { const batch hosts.slice(i, i 6).map(h connect({ hostname: h, port: 443 })); await Promise.all(batch.map(async s { /* use */ await s.close(); })); }被屏蔽的目标Cloudflare 自身 IP如 1.1.1.1、localhost127.0.0.1、端口 25SMTP、Worker 自己的 URL均因安全原因被禁止。解决办法是使用公网 IP 或 Tunnel 主机名。作用域限制在全局作用域创建的 socket 会失败因为 socket 与请求生命周期绑定必须在 handler 内创建export default { async fetch() { const socket connect(...); } }。常见错误对照错误信息原因解决方案proxy request failed目标被屏蔽Cloudflare IP / localhost / 25 端口、DNS 失败、网络不可达校验目标、使用 Tunnel 主机名、try/catch 兜底TCP Loop detectedWorker 连接到了自身连接外部服务不要连 Worker 自己的主机名Port 25 prohibitedSMTP 端口被屏蔽改用 Email Workers API 处理邮件socket is not open关闭后还在读写始终用 try/finally 保证关闭顺序连接超时API 无内置超时用Promise.race()自行实现见上文超时模式TLS/SSL 陷阱StartTLS 时机必须先发送协议专用 STARTTLS 命令、等待服务端 OK再调用socket.startTls()过早升级会失败。证书校验自签名证书会失败。要么使用正规证书要么走 Cloudflare Tunnel由 Tunnel 处理 TLS 终止。性能与数据处理的坑不建连接池每次请求新建连接开销大。数据库场景直接换 Hyperdrive内置池化。不用 Smart Placement后端延迟高时在wrangler.jsonc开启{ placement: { mode: smart } }。忘记关闭 socket资源泄漏务必try/finally { await socket.close(); }。假设一次读完TCP 分块到达必须循环reader.read()直到done true。编码错误明确指定编码如new TextDecoder(iso-8859-1).decode(data)。调试技巧记录连接信息const info await socket.opened; console.log(info.remoteAddress);先用公网服务验证可先用 tcpbin.com:4242 之类的 echo 服务器测试基本连通性验证 Tunnelcloudflared tunnel info name与cloudflared tunnel route ip list私有网络路由场景最佳实践清单综合 README.md 与全系列文档落地时遵循以下原则始终关闭 socket——用try/finally包裹finally中await socket.close()校验目标地址——用白名单防 SSRF绝不让用户输入直接决定连接目标数据库用 Hyperdrive——连接池与缓存远胜裸 TCP且省去实现 Postgres/MySQL 线上协议的复杂度HTTP 优先fetch()——只有非 HTTP 或需要线上协议控制时才用 TCP配合 Smart Placement——降低到私有网络的端到端延迟分批处理并发——单请求内并发 socket 不超过 6 个密钥走wrangler secret地址等非敏感配置走vars并用env区分 staging/production。相关技术导航HyperdrivePostgreSQL/MySQL 连接池化与缓存Cloudflare Tunnel安全访问私有网络含 configuration.md 的 ingress 规则与 networking.md 网络预检Smart Placement让 Worker 自动就近后端Workers承载本文所有代码的运行时同目录配套阅读顺序api.md接口与类型→ configuration.mdWrangler 配置与 Tunnel 集成→ patterns.md实战模式→ gotchas.md限制与排查VPC Servicesbeta, 2025仅 HTTP 的 service binding、内置 SSRF 保护属独立文档体系后续可关注【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考