内网穿透技术详解:让你的本地服务被其他人访问

你在本地跑了一个 API 服务,想让 Dify、微信小程序、或者远端服务器调用它,却发现 localhost 只有你自己能访问。怎么办?答案就是——内网穿透。本文将从原理到实操,带你彻底搞懂这项开发者必备技能。


📋 本文目录一览

本文将系统性地讲解内网穿透技术,涵盖以下内容:

  1. 基本概念与原理:什么是内网穿透,NAT 和隧道是如何工作的
  2. 应用场景:为什么需要内网穿透,它解决了哪些实际问题
  3. 工具对比:localtunnel、ngrok、frp 等主流方案的优缺点分析
  4. 实操演示:以天气查询 API 为例,手把手演示 localtunnel 穿透全流程
  5. Dify 集成:如何将穿透后的公网地址配置为 Dify 自定义插件
  6. 安全与注意事项:使用内网穿透时必须注意的安全事项和最佳实践
  7. 完整代码附录:天气查询 API 的完整代码,可直接运行

💡 阅读提示:如果你是初学者,建议按顺序阅读全文;如果你只想快速上手,可以直接跳到第四章实操演示


一、什么是内网穿透?

1.1 从一个真实问题说起

假设你在本地电脑上用 FastAPI 写了一个天气查询接口,运行在 http://localhost:8081。你自己测试没问题,但当你想让 Dify 云端平台、手机 App、或者同事的电脑来调用这个接口时,问题出现了:

localhost 只对本机有效,外部设备根本无法访问。

原因很简单:你的电脑处于一个**局域网(内网)**中,通过路由器的 NAT(网络地址转换)共享一个公网 IP 上网。外部网络无法主动发起连接到达你的电脑——就像一个小区有统一门牌号,但快递员不知道你住在几栋几单元。

1.2 内网穿透的定义

内网穿透(NAT Traversal / Reverse Tunnel) 是一种技术手段,它的核心目标是:

让处于内网中的本地服务,通过一台拥有公网 IP 的中转服务器,暴露给外部网络访问。

简单来说,就是给你的本地服务开一条”隧道”,让外部流量能穿过 NAT 和防火墙,直达你的电脑。

1.3 工作原理图解

1
2
3
4
5
6
7
┌──────────────┐         ┌──────────────────┐         ┌──────────────────┐
│ 外部用户 │ │ 公网中转服务器 │ │ 你的本地电脑 │
│ (Dify云端) │────────▶│ (loca.lt:443) │────────▶│ (localhost:8081) │
│ │◀────────│ │◀────────│ │
└──────────────┘ └──────────────────┘ └──────────────────┘
① 请求公网URL ② 转发到隧道 ③ 隧道转发到本地
④ 返回响应 ⑤ 响应回传 ⑥ 本地服务返回结果

核心流程分为六步:

  1. 外部用户(如 Dify)向公网地址 https://xxx.loca.lt/weather 发起请求
  2. 公网中转服务器收到请求,通过已建立的隧道连接找到对应的本地服务
  3. 中转服务器将请求转发到你本地的 localhost:8081
  4. 你的本地 FastAPI 服务处理请求并返回结果
  5. 结果沿着隧道回传到中转服务器
  6. 中转服务器将结果返回给外部用户

关键前提:隧道是由你的本地电脑主动发起的(出站连接),所以不会被 NAT 和防火墙拦截。这就是内网穿透能生效的根本原因。


二、为什么需要内网穿透?

内网穿透解决的问题远不止”让别人访问我的 API”这么简单。以下是几个典型场景:

2.1 AI 平台插件开发(本文重点)

在使用 Dify、Coze 等 AI 平台时,如果你想接入自定义工具插件,平台要求你提供一个公网可访问的 HTTPS 地址。但你的 API 服务通常跑在本地:

1
2
3
本地服务:http://localhost:8081/weather
↓ 内网穿透
公网地址:https://random-name.loca.lt/weather ← Dify 可以调用

2.2 Webhook 回调接收

微信支付、GitHub、钉钉等平台的 Webhook 回调都需要公网地址。开发阶段用内网穿透,可以实时在本地接收和调试回调数据。

2.3 远程开发与调试

  • SSH 远程访问:通过内网穿透,从外部 SSH 连接到公司内网的开发机
  • 远程桌面:穿透后使用 RDP/VNC 访问内网电脑
  • IoT 设备管理:远程访问家庭网络中的树莓派、NAS 等设备

2.4 演示与客户验收

项目还没部署到正式服务器,但需要给客户演示?内网穿透可以在 5 分钟内给客户一个临时可访问的地址。

2.5 跨网络联调

前后端分离开发时,前端在 A 网络,后端在 B 网络,内网穿透可以快速打通联调通道。


三、常见内网穿透工具对比

市面上有非常多的内网穿透方案,下面从易用性、成本、稳定性和适用场景四个维度进行对比:

3.1 工具速览对比表

工具 类型 安装难度 费用 HTTPS 自定义域名 适用场景
localtunnel SaaS ⭐ 极简 免费 ✅ 自动 快速原型、AI 插件开发
ngrok SaaS ⭐⭐ 简单 免费/付费 ✅ 自动 付费版支持 开发调试、Webhook
frp 自建 ⭐⭐⭐ 中等 免费(需服务器) 需自配 生产环境、长期运行
Cloudflare Tunnel SaaS ⭐⭐ 简单 免费 ✅ 自动 绑定自有域名的场景
花生壳 SaaS ⭐⭐ 简单 免费/付费 付费版支持 付费版支持 国内远程桌面、NAS
serveo SaaS ⭐ 极简 免费 ✅ 自动 临时 SSH 隧道

3.2 各工具详细分析

localtunnel —— 最简单的”一行命令”方案

1
2
npm install -g localtunnel
lt --port 8081

优点:

  • 安装极简,一条命令搞定
  • 完全免费,无需注册账号
  • 自动分配 HTTPS 域名
  • 基于 Node.js,跨平台

缺点:

  • 每次重启 URL 会变化(不支持固定域名)
  • 首次访问有一个确认页面(Click to Continue)
  • 稳定性一般,长时间运行可能断开
  • 服务器在海外,国内访问速度不稳定

最适合:快速原型验证、AI 插件开发调试(如本文的天气查询 API 场景)

ngrok —— 开发者群体中最知名的方案

1
2
# 安装后
ngrok http 8081

优点:

  • 提供可视化的 Web 管理界面
  • 免费版功能已经足够开发使用
  • 支持请求日志查看和重放
  • 稳定性优于 localtunnel

缺点:

  • 需要注册账号获取 authtoken
  • 免费版域名随机,且限制连接数
  • 固定域名和高级功能需要付费($8/月起)

最适合:Webhook 调试、API 联调、需要查看请求日志的场景

frp —— 最灵活、最可控的自建方案

1
2
3
4
5
6
7
8
9
10
11
12
13
# frps.toml(服务端配置)
bindPort = 7000

# frpc.toml(客户端配置)
serverAddr = "your-server.com"
serverPort = 7000

[[proxies]]
name = "web"
type = "tcp"
localIP = "127.0.0.1"
localPort = 8081
remotePort = 6000

优点:

  • 完全开源免费,无功能限制
  • 自定义域名、固定端口
  • 支持 TCP/UDP/HTTP/HTTPS 等多种协议
  • 适合长期稳定运行

缺点:

  • 需要一台有公网 IP 的服务器
  • 配置相对复杂,有一定学习成本
  • 需要自己维护服务端

最适合:生产环境、需要长期稳定运行的内网穿透场景

3.3 如何选择?

1
2
3
4
5
6
你的需求是什么?

├── 只是临时测试/快速演示 → localtunnel(最快上手)
├── 需要日志调试/Webhook → ngrok(功能丰富)
├── 长期稳定运行/生产环境 → frp(自建可控)
└── 绑定自有域名 → Cloudflare Tunnel / frp

四、实操演示:用 localtunnel 穿透天气查询 API

下面我们以一个真实项目为例,完整演示如何通过内网穿透将一个本地天气查询 API 接入 Dify 平台。

4.1 项目背景

我们有一个用 FastAPI 编写的天气查询服务(main.py),它:

  • 监听 0.0.0.0:8081
  • 提供 POST /weather 接口,接收城市名称,返回天气信息
  • 使用 Bearer Token 进行简单认证

完整代码可参考项目中的 main.py

4.2 第一步:启动本地服务

安装依赖(如尚未安装):

1
2
3
4
5
6
# 创建并激活虚拟环境
python -m venv weather-env
weather-env\Scripts\activate # Windows

# 安装依赖
pip install fastapi uvicorn requests

启动服务:

1
python main.py

看到以下输出说明服务已就绪:

1
INFO:     Uvicorn running on http://0.0.0.0:8081 (Press CTRL+C to quit)

本地验证: 在启动服务前,先确保接口能正常工作。打开一个新终端,用 curl 测试:

1
curl -X POST http://localhost:8081/weather -H "Authorization: Bearer itcast" -H "Content-Type: application/json" -d "{\"location\": \"北京\"}"

预期返回:

1
"北京今天是晴,温度33℃/22℃"

本地服务没问题,接下来就是关键一步——把它穿透到公网。

4.3 第二步:安装 localtunnel

localtunnel 是一个基于 Node.js 的工具,需要先确保安装了 Node.js 环境。

1
2
# 全局安装 localtunnel
npm install -g localtunnel

安装完成后,验证:

1
lt --version

💡 没有 Node.js? 前往 nodejs.org 下载 LTS 版本安装即可。

4.4 第三步:启动穿透服务

新开一个终端窗口(保持 FastAPI 服务的终端不要关闭),执行:

1
lt --port 8081

输出结果:

1
your url is: https://warm-frog-42.loca.lt

这个 URL 就是你的公网地址! 复制并保存好它。

⚠️ 重要提醒:

  • 这个终端窗口必须保持开启,关闭即意味着隧道断开
  • 每次重启 lt 命令,URL 会发生变化
  • 建议将 URL 复制到一个文本文件中备用

4.5 第四步:验证公网可访问性

用刚才获取的公网 URL 进行测试:

1
curl -X POST https://warm-frog-42.loca.lt/weather -H "Authorization: Bearer itcast" -H "Content-Type: application/json" -H "Bypass-Tunnel-Reminder: true" -d "{\"location\": \"广州\"}"

💡 注意:localtunnel 默认会在首次访问时返回一个确认页面。在请求头中添加 Bypass-Tunnel-Reminder: true 可以跳过这个确认页。在浏览器中访问时,需要点击 “Click to Continue” 按钮。

预期返回:

1
"广州今天是多云,温度30℃/24℃"

✅ 恭喜!你的本地服务现在可以被全网访问了。

4.6 第五步:在 Dify 中配置为插件

现在有了公网地址,就可以将它配置到 Dify 平台作为自定义工具插件了。

1. 准备 OpenAPI Schema

Dify 要求提供 OpenAPI 3.1.0 格式的接口描述文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
{
"openapi": "3.1.0",
"info": {
"title": "天气查询API",
"description": "查询中国城市当前天气信息",
"version": "v1.0.0"
},
"servers": [
{
"url": "https://warm-frog-42.loca.lt"
}
],
"paths": {
"/weather": {
"post": {
"summary": "查询城市天气",
"security": [
{
"BearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称",
"example": "北京"
}
},
"required": ["location"]
}
}
}
},
"responses": {
"200": {
"description": "成功获取天气信息"
}
}
}
}
},
"components": {
"schemas": {},
"securitySchemes": {
"BearerAuth": {
"type": "http",
"scheme": "bearer"
}
}
}
}

⚠️ 关键操作:将 servers.url 替换为你自己的 localtunnel URL。

2. 在 Dify 中创建插件

  • 登录 Dify 控制台
  • 导航到 「插件」「创建插件」
  • 选择 「OpenAPI」 类型
  • 将上面的 JSON Schema 粘贴到编辑器中
  • 在认证配置中选择 「Bearer Token」,填入 itcast
  • 点击 「保存」 并发布

3. 在应用中使用

创建一个 Agent 类型的应用,启用天气查询插件,然后对话测试:

1
2
3
4
用户:北京今天天气怎么样?

Agent:让我帮您查询北京的天气...
北京今天是晴,温度33℃/22℃

🎉 大功告成! 从本地代码到 Dify 云端调用,全程通过内网穿透打通。


五、注意事项与安全考虑

内网穿透虽然方便,但也意味着将你的本地服务暴露到了公网。以下事项务必注意:

5.1 安全层面

风险 说明 应对措施
未授权访问 公网地址被扫描发现 必须添加身份认证(如 Bearer Token)
数据泄露 传输数据被中间人截获 使用 HTTPS(localtunnel 已自动提供)
服务滥用 被恶意高频调用 添加频率限制(Rate Limiting)
本地网络安全 穿透暴露整个内网 仅穿透指定端口,不要穿透整个网络

必须做到的安全底线:

1
2
3
4
5
6
7
8
# 1. 你的 API 必须有身份认证
VALID_TOKEN = "itcast" # 生产环境请使用更复杂的 Token

@app.post("/weather")
def get_current_weather(request: Request, body: WeatherRequest):
auth_header = request.headers.get("Authorization")
if auth_header != f"Bearer {VALID_TOKEN}":
raise HTTPException(status_code=403, detail="Invalid Authorization header")
1
2
3
# 2. 不要在穿透的服务中处理敏感数据
# 3. 使用完毕后及时关闭穿透隧道
# 4. 定期检查穿透服务的访问日志

5.2 稳定性层面

  • localtunnel 的 URL 不固定:每次重启都会变化,需要手动更新 Dify 中的配置
  • 隧道可能断开:长时间运行(通常 24 小时后)可能失效,需要重启
  • 网络波动:localtunnel 服务器在海外,国内访问可能不稳定

应对策略:

1
2
3
4
5
6
7
# 编写一个自动重启脚本 restart_tunnel.bat(Windows)
@echo off
:loop
lt --port 8081
echo Tunnel disconnected, restarting in 5 seconds...
timeout /t 5
goto loop

5.3 性能层面

  • localtunnel 是免费公共服务,带宽有限
  • 不适合高并发场景,仅用于开发测试
  • 如果需要生产级性能,请迁移到 frp 自建方案或云服务器部署

六、实际应用场景与最佳实践

6.1 开发阶段:快速验证

1
2
3
场景:你在开发一个 Dify 自定义插件
流程:编写 API → 本地启动 → localtunnel 穿透 → Dify 配置测试 → 验证功能
耗时:30 分钟内完成全流程

这是内网穿透最高频的使用场景——在投入部署之前,先验证功能是否跑通

6.2 测试阶段:Webhook 调试

1
2
3
场景:对接微信支付/支付宝回调
流程:本地服务 → ngrok 穿透 → 配置为回调地址 → 在本地实时查看回调数据
优势:无需反复部署到服务器,大幅缩短调试周期

6.3 演示阶段:给客户看效果

1
2
3
场景:给客户演示还未上线的系统
流程:本地启动完整系统 → frp 穿透到自有域名 → 客户通过域名访问
优势:无需购买正式服务器即可完成演示

6.4 从开发到生产的演进路径

一个推荐的最佳实践路径:

1
2
3
4
5
第一阶段(开发验证):localtunnel / ngrok
↓ 功能验证通过
第二阶段(测试联调):ngrok 付费版 / Cloudflare Tunnel
↓ 测试稳定
第三阶段(正式上线):frp 自建 / 直接部署到云服务器(如 Render、Railway、阿里云)

6.5 团队协作建议

  • 统一使用 frp:团队自建一个 frp 服务,所有成员共用,固定域名和端口
  • 环境隔离:开发、测试、生产使用不同的穿透通道
  • 配置版本化:将 frp 配置文件纳入 Git 管理
  • 监控告警:对穿透服务添加可用性监控,断开时自动告警

七、总结

内网穿透是现代开发者工具箱中不可或缺的一环。无论是 AI 插件开发、Webhook 调试还是远程访问,它都能帮你快速打通内外网之间的壁垒。

核心要点回顾:

  1. 原理:通过本地主动发起出站连接建立隧道,绕过 NAT 限制
  2. 选型:临时用 localtunnel,调试用 ngrok,生产用 frp
  3. 安全:必须加认证,用完即关,不暴露敏感数据
  4. 实操:本地 API → 穿透获取公网 URL → 配置到 Dify 等平台 → 完成端到端联调

一句话总结:内网穿透的本质是——用一条从内向外打通的隧道,换一个从外到内的访问通道。掌握了这个核心思想,无论用什么工具,你都能得心应手。


附录:天气查询 API 完整代码(main.py)

以下是本文示例中使用的 FastAPI 天气查询服务完整代码,可直接保存为 main.py 运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
from fastapi import FastAPI, Request, HTTPException
from pydantic import BaseModel
import requests

app = FastAPI()

# 身份验证令牌(个人使用,简单安全)
VALID_TOKEN = "itcast"

# 城市编码数据(直接硬编码,无需外部文件)
CITY_CODES = {
"北京": "101010100",
"上海": "101020100",
"广州": "101280101",
"深圳": "101280601",
"杭州": "101210101",
"成都": "101270101",
"武汉": "101200101",
"西安": "101110101",
"南京": "101190101",
"重庆": "101040100",
"天津": "101030100",
"苏州": "101190401",
"郑州": "101180101",
"长沙": "101250101",
"青岛": "101120201",
"大连": "101070201",
"宁波": "101210401",
"厦门": "101230201",
"福州": "101230101",
"济南": "101120101",
"合肥": "101220101",
"南昌": "101240101",
"昆明": "101290101",
"南宁": "101300101",
"贵阳": "101260101",
"哈尔滨": "101050101",
"长春": "101060101",
"沈阳": "101070101",
"石家庄": "101090101",
"太原": "101100101",
"呼和浩特": "101080101",
"乌鲁木齐": "101130101",
"拉萨": "101140101",
"兰州": "101110501",
"西宁": "101150101",
"银川": "101170101",
"海口": "101310101",
"三亚": "101310201"
}

class WeatherRequest(BaseModel):
location: str

@app.post("/weather")
def get_current_weather(request: Request, body: WeatherRequest):
"""
天气查询接口
- 需要Authorization头认证
- 返回自然语言格式的天气信息
"""
# 1. 验证身份
auth_header = request.headers.get("Authorization")
if auth_header != f"Bearer {VALID_TOKEN}":
raise HTTPException(status_code=403, detail="Invalid Authorization header")

location = body.location

# 2. 查找城市编码
city_code = CITY_CODES.get(location)
if not city_code:
return {
"status": "error",
"message": f"请提供{location}对应的编码方可查询,目前支持的城市:{','.join(CITY_CODES.keys())}"
}

# 3. 调用天气API
url = f"http://t.weather.itboy.net/api/weather/city/{city_code}"
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
except Exception as e:
return {"status": "error", "message": f"天气服务请求失败: {str(e)}"}

# 4. 解析天气数据
try:
forecast = data["data"]["forecast"][0]
weather_type = forecast["type"]
high = forecast["high"].replace("高温 ", "")
low = forecast["low"].replace("低温 ", "")
temperature = f"{high}/{low}"

# 5. 返回自然语言格式
return f"{location}今天是{weather_type},温度{temperature}"
except (KeyError, IndexError) as e:
return {"status": "error", "message": f"天气数据解析失败: {str(e)}"}

# 启动入口
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8081)

运行方式:

1
2
3
4
5
# 安装依赖
pip install fastapi uvicorn requests

# 启动服务
python main.py

服务启动后监听 http://0.0.0.0:8081,此时即可启动 localtunnel 进行内网穿透:

1
lt --port 8081