前言
最近在做一个物联网相关的需求,大致要求是用户使用指纹识别开门后,除了将设备属性上报到华为云IoT平台外,还需要在华为云配置数据转发,将开门日志转发到后端服务并存到数据库,用于向用户端展示开门日志。
这其实是一个挺朴实无华的需求的,只需要在华为云上把数据转发配置为自己服务器的地址就行了。问题就在于:我们只有一个已经无法容纳其它较大量级服务的腾讯云服务器,也就是说,我们无法将服务部署上云。
问题
好了,现在的问题就在于:项目在本地进行开发,如何让华为云将数据转发到本地的Web服务?
这里我想到了三个解决方法:
- 硬头皮直接将项目部署到云服务器。(下下下策)
- 通过内网穿透技术,让公网IP映射到本地服务。
- 在服务器部署一个轻量的Python脚本(大的跑不动,小脚本还是能跑的),用于转发我们的Http请求,到时候我只需要在本机和服务器之间开一个端口映射即可。
第一种方案并不可行,项目采用SpringBoot进行开发,引入了一些中间件,部署很繁琐,就算使用docker-compose,每次都要重新打jar包和重新构建镜像也挺烦人的,本地改完代码即可运行它不香吗?
第二个策略------内网穿透,看着很香,但我没试过(于是踩了很多坑)
第三个策略:下下策,实现方案太不优雅了,相当于又引入一个服务。
内网穿透踩坑
研究来研究去,只剩下内网穿透这条路可以走了,那还多说什么,来试试吧,试试就逝世,在这个过程中我踩了挺多坑的,下面我将把这些坑总结一下,方便有和我一样需求的小伙伴少走些弯路。
PS:本文旨在让一些和我一样没用过内网穿透的小伙伴少走些弯路,大佬们看个乐子就好了。当然如果有更好的解决方案也可以教教小弟。
踩坑1 :采用ngrok进行内网穿透,并采用其配置的免费域名或者随机生成的域名。
- 为何采用此方案:免费,不用自己买服务器和域名。
- 问题 :
- 第一次访问需要携带特殊的请求头,否则是一个ngrok自己的页面。
- 后面不知道为何只有本机能访问。
- 国外服务器,速度极慢,华为云上连不通。
- 后续:后面想通过在服务器和本地都跑一个ngrok,但看到ngrok的仓库上一次commit还是在八年前,emmm,想想还是算了。
踩坑2:采用CloudFlare的tunnel做内网穿透
- 教程:参考各类博客,大致只需要将自己的域名的DNS改成Cloudflare那边的,在平台开通tunnel后在本地跑一个cloudflare的守护进程即可。
- 问题 :
- 华为云上一直报出URL格式错误,原因暂时未知,卡了很久。
- 带宽太小,加上服务器在国外,华为云那边的连通性测试都无法通过,10次连通性测试大概只有1次能跑通
踩坑3:让用户直接请求我服务器的公网IP,服务器用Nginx反向代理到CloudFlare那边的域名
- 为啥会这么做:正如我上面所说,因为刚开始我发现只用内网穿透的域名,在华为云那边配置的时候一直会出现callback url error,就是URL的格式错误,我很不能理解,以为加一层代理应该就没问题了。
- 问题 :
- 事实证明这样做也无法解决URL格式错误的问题。
- 其本质上也没有解决带宽太小的问题。
- 反向代理后,Host如果没经过特殊修改,还会报出
Direct IP access not allowed
的错误。脑瓜子更疼了。
最终方案 :在云服务器和本地都跑一个frp,通过frp的HTTP代理就可以实现自定义域名访问内网的Web服务。 不过还是踩了个小坑:在配置frp的服务端的时候,要将http监听的端口放到bindPort之上,否则会报
new proxy [iot-server] type [http] error: type [http] not supported when vhost http port is not set
,原因暂时未知。
下文我将分享,使用frp实现的自定义域名访问内网服务的过程。
frp实践
什么是frp
frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议,且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
frp 仓库的更新很活跃,截至写作本文,最近一次commit是在16hour前,这也是为什么我采用frp而不是ngrok。仓库地址:frp/README_zh.md at dev · fatedier/frp (github.com)
实战操作
下面给出用frp实现内网穿透的步骤。实话说官方文档写得很详细了,大家也可以看看官方文档通过自定义域名访问内网的 Web 服务 | frp (gofrp.org)(不过似乎在HTTP代理配置那里有个小坑)
下载
首先,打开frp官网,找到上面GitHub的Release地址,根据系统下载不同的版本。例如我本地下的是Windows amd64,服务器下的是Linux amd64,下载后将Linux版的传到服务器并解压。
frp 主要由两个组件组成:客户端(frpc) 和 服务端(frps)。通常情况下,服务端部署在具有公网 IP 地址的机器上,而客户端部署在需要穿透的内网服务所在的机器上。release下载的压缩包上同时含有frpc和frps,我们只需要使用其中一个即可(对于本地我们只需要frpc,对于服务器我们使用frps)
配置
根据具体需求,具体请看官网通过 SSH 访问内网机器 | frp (gofrp.org),下面我给出暴露本地服务的配置。
- 客户端的配置文件是:
frpc.toml
,toml的格式要求很严格,按照官网的要求写。下面是我的配置:
toml
serverAddr = "服务器公网IP"
serverPort = 7000
[[proxies]]
name = "iot-server"
type = "http"
localPort = 9090
customDomains = ["自定义域名,也可以直接用公网IP"]
- 服务端的配置文件是:
frps.toml
,我的配置如下
toml
vhostHTTPPort = 9090
bindPort = 7000
注意,这里有个坑,如果不把vhostHTTPPort写到bindPort前面 ,会报出下面的错误:new proxy [iot-server] type [http] error: type [http] not supported when vhost http port is not set
。
通过上述的配置,我就能通过访问公网ip:9090
来访问我本地在9090
端口跑的SpringBoot应用啦。
启动
- 客户端:
./frpc -c ./frpc.toml
- 服务端:
./frps -c ./frps.toml
最佳实践:使用systemd管理
在 Linux 系统下,使用 systemd
可以方便地控制 frps 服务端的启动、停止、配置后台运行以及开机自启动。
- 安装systemd:
bash
yum install systemd
- 创建frps.service文件:
bash
sudo vim /etc/systemd/system/frps.service
- 写入内容:
ini
[Unit]
# 服务名称,可自定义
Description = frp server
After = network.target syslog.target
Wants = network.target
[Service]
Type = simple
# 启动frps的命令,需修改为您的frps的安装路径
ExecStart = /path/to/frps -c /path/to/frps.toml
[Install]
WantedBy = multi-user.target
- 使用systemd命令管理frps服务
bash
# 启动frp
sudo systemctl start frps
# 停止frp
sudo systemctl stop frps
# 重启frp
sudo systemctl restart frps
# 查看frp状态
sudo systemctl status frps
- 设置开机自启动:
bash
sudo systemctl enable frps
这样,我们就成功实现了通过自定义域名/公网IP来访问本地服务的需求。由于内网服务缺乏公网 IP 地址,因此无法直接被非局域网内的用户访问。用户通过访问服务端的 frps,frp 负责根据请求的端口或其他信息将请求路由到相应的内网机器,从而实现通信。
总结
对于今后有从华为云IoT平台将数据转发到第三方HTTP应用服务器需求的UU,考虑到开发和测试的方便,目前的最佳实践是:(前提是您的项目不需要部署上线,只作为内部测试/比赛演示)
- 在各大云产商那里购买一个低配的服务器,从而拿到一个公网IP
- 在本地和服务器上都跑一个frp
- 利用frp配置 HTTP 类型的代理,让你可以通过自定义域名访问内网的 Web 服务