一、为什么需要内网穿透的自动化隧道?
在我们本地开发应用的过程中,由于涉及不同业务模块之间的 webhook 对接及联调/预览,经常需要用到内网穿透工具将本地应用映射到公网,给其他系统调用或不同位置的同事预览/测试,那这时候往往需要我们登录到内网穿透的平台去添加域名/添加隧道。
开发里常见的几个痛点
- 上下文切换太频繁。 写代码用的是 IDE,建隧道得跳浏览器。来回切窗口。
- 隧道生命周期跟代码不同步。 本地服务 Ctrl+C 就停了,控制台里的映射还在。下次启动端口变了(8080 换成 3000),公网地址打回来 502,还得再进控制台改一遍。
- 容易留安全隐患。 联调完忘了删映射、忘了关隧道,开发用的入口长期暴露在公网上,留下安全隐患。
那有没有一种内网穿透能做到集成在代码里面,能随应用启动而启动,随应用停止而停止,既省创建隧道的操作,同时解决安全隐患?
ZeroNews 内网穿透能很好的解决这个问题。后面会说明 ZeroNews 是什么、本地 Client API 又能帮你干什么。
二、ZeroNews 和本地 Client API
ZeroNews(零讯)是内网穿透工具。机器上跑一个 Agent,在控制台配好映射,外网就能通过 HTTPS 域名访问你本地的服务,比如 127.0.0.1:8080,不需要用户具备公网 IP ,同时域名证书由平台处理。
ZeroNews 不仅支持控制台管理隧道,同时还支持通过 REST API 管理隧道,实现隧道完全自动化。
平时用控制台管理域名、带宽、设备状态,没问题。但开发联调是高频、短周期的操作——更适合用 API 在代码里完成,而不是反复切窗口。
本地 Client API
Client 执行 zeronews start(或 zeronews service start)后,除了连云端跑隧道,还会在本地监听: http://127.0.0.1:37271 这里有 Web UI,也有 REST API(路径前缀 /api/v1/*)。脚本、CI、应用启动逻辑都可以直接调。
常用接口:
- GET /healthz — 看 API 进程是否正常
- GET /api/v1/status — Client 有没有连上云端
- POST /api/v1/tunnels — 建隧道;请求里可以不传 domain,ZeroNews 会分配公网地址,响应里有 public_url
- GET /api/v1/endpoints — 查隧道是否已落地(status 是否为 active)
- POST /api/v1/reconnect、POST /api/v1/shutdown — 重连或关闭 Client
- API 只绑 127.0.0.1,本机才能访问。这是刻意的,别改成 0.0.0.0 对外暴露。
用 API 集成后,几个实际变化:
- 启动时调接口建隧道,退出时调 shutdown,不用记着去控制台删映射
- public_url 直接打印在终端,Webhook 地址不用手抄
- 可以先查 Client 是否在线,再决定要不要建隧道,少碰[地址有了但 502]的情况
- self-hosted CI 机器上 Client 常驻,Job 里 curl 一下就能临时开入口
三、动手集成
3.1 确认 Client 正常
zeronews start
# 或:zeronews service start
curl -s http://127.0.0.1:37271/healthz
# {"code":200,"data":{"ok":true}}
curl -s http://127.0.0.1:37271/api/v1/status | jq
'.data.connected'
# true 表示已连上云端
3.2 建隧道(自动分配域名)
请求里只传协议和本地端口,不传 domain:
curl -s -X POST http://127.0.0.1:37271/api/v1/tunnels \
-H 'Content-Type: application/json' \
-d '{
"type": "https",
"local_ip": "127.0.0.1",
"local_port": 8080
}'
返回:
{
"code": 200,
"data": {
"state": "accepted_remote",
"tunnel_id": "tunnel-abc123",
"type": "https",
"local": "127.0.0.1:8080",
"public_url": "https://x7k2mp.ny.takin.cc"
}
}
public_url 就是 Webhook 回调地址。accepted_remote 表示控制面已受理;要确认隧道完全通了,可以再查一次 /api/v1/endpoints,看对应项的 status 是不是 active。
后面 Python / Go / Node 示例返回结构相同,不再重复贴 JSON。
Python 中集成
需安装 requests。应用启动时调一次,取出 public_url 即可:
import requests
resp = requests.post("http://127.0.0.1:37271/api/v1/tunnels", json={
"type": "https",
"local_ip": "127.0.0.1",
"local_port": 8080,
})
body = resp.json()
print(body)
print("Webhook 地址:", body["data"]["public_url"])
FastAPI / Django 可以放在启动钩子里;进程退出时如需清理,再 POST /api/v1/shutdown。
Go 中集成
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
payload, _ := json.Marshal(map[string]any{
"type": "https",
"local_ip": "127.0.0.1",
"local_port": 8080,
})
resp, _ := http.Post(
"http://127.0.0.1:37271/api/v1/tunnels",
"application/json",
bytes.NewReader(payload),
)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
var result struct {
Data struct {
PublicURL string `json:"public_url"`
} `json:"data"`
}
json.Unmarshal(body, &result)
fmt.Println("Webhook 地址:", result.Data.PublicURL)
}
Gin / Echo 在 main() 里、服务 listen 之前调用这段逻辑。
Node.js 中集成
Node 18+ 自带 fetch:
const resp = await fetch('http://127.0.0.1:37271/api/v1/tunnels', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'https',
local_ip: '127.0.0.1',
local_port: 8080,
}),
})
const body = await resp.json()
console.log(body)
console.log('Webhook 地址:', body.data.public_url)
Express / NestJS 在 listen 之前 await 即可。
补充两点:
要用固定域名,请求体里加上 "domain": "xxx.example.zeronews.cc";
Client 建议 zeronews service start 常驻,别在每次 HTTP 请求里调 shutdown——那会关掉整个 Client,只适合开发退出时用。
在 macOS 上试过(Client v4.x,本地 Node 8080):控制台手工建隧道要登录填表,大概2-3分钟;代码里调 API,几秒就能拿到 public_url,而且不用提前去控制台建域名。
ZeroNews 本地 API 的价值,是把建隧道这件事从[打开控制台点鼠标]变成[应用启动时顺手调个接口]。Webhook 联调、临时预览、CI 里跑集成测试,都适用。