随着互联网生态圈的发展,现今的 Web 项目中开始越来越多的使用第三方服务,通常这些第三方服务都是由 Client 通过 Server 的 API 主动发起请求,但是 Server 回调 Client 这种方式也是很多服务中不可避免的一种方式。这样的场景下,对于开发者就有个比较麻烦的问题:
如何在开发的过程中让处于内网的开发机收到回调?
传统解决方案中,如果没有固定 ip 首先需要动态域名,然后需要维护一份外网到内网的端口映射表,最后如果 Client 中有取 Host 信息的操作还需要响应的 Hack(这点后面会提到)。当然,如果你连公网 ip 都没有,那么就可以直接放弃这个方案了。
一种更为有效的解决方案是:使用一台拥有公网 IP 的主机,通过隧道来实现转发。其实在很早之前,为了让处于校园网内网的服务器在外网可以访问,我经常通过 SSH Tunnel 来解决这个问题。
# 将远程主机的 10086 转发到本地的 3000
ssh -C -f -N -g -R 10086:127.0.0.1:3000 user@Tunnel_Server
这种方式虽然使用简单,但是稳定性并不理想,一段时间内没有请求 Tunnel 就会自动断开。而且使用者必须有 Tunnel_Server 的 ssh 登录权限,每开一个服务就需要占用 Tunnel_Server 一个端口。
正当我苦于写 SSH Tunnel 的各种连接脚本和守护脚本的时候,第一次接触到了 Ngrok。(2013年)那个时候 ngrok 还是一个很冷门的小工具,它所依赖的 Go 也一样,加上文档有限,各种尝试之后并没有把这套服务搭起来。
再后来,国内出现了金数据团队维护的 tunnel.mobi,默默的为国内的开发者提供了很长一段时间的便利。国内 ngrok 的快速普及,个人觉得很大程度上都得益与 tunnel.mobi 的影响。然而这样一个优秀的服务,在维持一年之后(2014.10-2015.10),选择了关闭。
此后,国内各种 ngrok 服务提供者如雨后春笋般出现,我司对于 ngrok 的依赖也比较大,于是我也在我们自己的服务器上搭了一套 ngrok,不觉然都快过去一年了。在这段时间的使用里,也发现了一些不方便或不够友好的地方,加上之前的搭建的那台服务器如今已不堪重负,于是趁周末的时候重新搭建了一份,并做了一点点配置上的优化。
我使用的环境是 Aliyun ECS + Ubuntu 14.04,双网卡(内网网卡+外网网卡)
首先装必要的工具:
sudo apt-get install build-essential golang mercurial git
获取 ngrok 源码:
git clone https://github.com/inconshreveable/ngrok.git ngrok
cd ngrok
编译&安装:
sudo make release-server
sudo cp bin/ngrokd /usr/local/bin/ngrokd
如果你并不需要最新版本的 ngrokd, 同时对源码安装也没什么兴趣,那么其实可以偷懒
sudo apt-get install ngrok-server
选定你要使用的域名,比如:yii.im,添加两条解析到你的服务器
ngrok 通讯依赖 TLS 证书来加密,所以启动的时候需要指定你的域名和对应的证书
既然依赖证书的话,那你应该先有一份证书。在搭建 ngrok 服务的时候,对于证书的处理有多种方式可选:
本文中使用第一种方式,域名证书通过 沃通CA免费SSL证书 取得。
关于第二种方式,可以参考:https://imququ.com/post/self-hosted-ngrokd.html。目前网上流行的 ngrok 服务或教程,基本上都是基于这种方式的。
第三种方式,除了需要用户添加根证书以外,其他配置与本文一样。
由于 ngrok 工作是通过分配 subdomain 的方式,所以我们实际使用到的域名都是 yii.im 的子域名,如 pub.yii.im 如果要对这个子域名启用 https 服务,那么至少需要三点支持:
根据这三点要求,我们重新解读上面三种证书的处理方式:
第一种:由于免费证书是单域名证书,所以你需要给可能会用到二级域名也签上证书才行,当然,如果够钱,买个包含所有二级域名的证书也是可以的
第二种:自签名证书很容易做到第二点,然而并无卵用,除了自编译的 ngrok-client外谁也不认这个证书
第三种:可以支持 https,但是要所有用户(包括访问用户)都添加根证书这种要求,略微有点…
综上所述,我们选择放弃了 https ,因为日常使用并没有强制要求 https 的情况,能跑就够了,要什么自行车。
ㄟ( ▔, ▔ )ㄏ手动滑稽
前面生成了 ngrokd 就是 ngrok server ,指定证书、域名和端口就可以启动它了:
# 获取帮助信息
ngrokd -h
# Usage of ngrokd:
# -domain="ngrok.com": Domain where the tunnels are hosted
# -httpAddr=":80": Public address for HTTP connections, empty string to disable
# -httpsAddr=":443": Public address listening for HTTPS connections, emptry string to disable
# -log="stdout": Write log messages to this file. 'stdout' and 'none' have special meanings
# -tlsCrt="": Path to a TLS certificate file
# -tlsKey="": Path to a TLS key file
# -tunnelAddr=":4443": Public address listening for ngrok client
# 试着启动
ngrokd -tlsKey=server.key -tlsCrt=server.crt -domain=yii.im -httpAddr=:8081 -httpsAddr=
到这一步,ngrok 服务已经跑起来了,可以通过屏幕上显示的日志查看更多信息。httpAddr、httpsAddr 分别是 ngrok 用来转发 http、https 服务的端口,可以随意指定。由于我不需要 https,所以留空了。 ngrokd 还会开一个 4443 端口用来跟客户端通讯(可通过 -tunnelAddr=”:xxx” 指定),如果你配置了 iptables 规则,需要放行这几个端口上的 TCP 协议。
现在,通过 http://sub.yii.im:8081 就可以访问到 ngrok 提供的转发服务。在客户端连进来之前,你应该会看到:
Tunnel sub.yii.im:8081 not found
这说明万事俱备,只差客户端来连了。
url 上带上端口通常来说并不会有什么影响,而且通过 nginx 隐藏起来也很简单:
# ngrokd.conf
server {
server_name *.yii.im;
listen 80;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host:8081;
proxy_redirect off;
proxy_pass http://127.0.0.1:8081;
}
}
但是!这里就有一个很烦躁的地方了,ngrokd 里面有一层自己的 Host 处理,于是 proxy_set_header Host
必须带上 ngrokd 所监听的端口,否则就算请求被转发到对应端口上, ngrokd 也不会正确的处理。
带上端口号又会导致了另一个操蛋的问题:你请求的时候是 sub.yii.im,你在 web 应用中获取到的 Host 是 sub.yii.im:8081,如果你的程序里面有基于 Request Host 的重定向,就会被重定向到 sub.yii.im:8081 下面去。
要完美的解决这个端口的问题,就需要让 ngrokd 直接监听 80 端口。
通常来说 VPS 都是双网卡的(一内一外),让 ngrokd 监听外网的 80 实在有些浪费,这个端口还是留给 nginx 比较合理。所以比较理想的方式是:nginx 监听外网 80,ngrokd 监听内网 80,让 nginx 将对应的请求转发到内网 80 上来。
如:
启动 ngrokd:
sudo ngrokd -tlsKey=server.key -tlsCrt=server.crt -domain=yii.im -httpAddr=10.160.xx.xx:80 -httpsAddr=
配置 nginx:
# ngrokd.conf
server {
listen 112.124.xx.xx:80;
server_name *.yii.im;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
proxy_pass http://10.160.xx.xx:80;
}
}
# the_others_need_80.conf
server {
listen 112.124.xx.xx:80;
#...
}
如果你是单网卡,那么还可以通过 docker 来解决: http://www.hteen.cn/docker/docker-ngrok.html
也可以手动添加ip
[root@nginx conf]# ifconfig eth0:0 192.168.3.60/24 up
[root@nginx conf]# ifconfig -a
eth0 Link encap:Ethernet HWaddr 00:0C:29:12:99:D4
inet addr:192.168.3.49 Bcast:192.168.3.255 Mask:255.255.255.0
inet6 addr: fe80::20c:29ff:fe12:99d4/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:792652 errors:0 dropped:0 overruns:0 frame:0
TX packets:460481 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:972250327 (927.2 MiB) TX bytes:92493014 (88.2 MiB)
eth0:0 Link encap:Ethernet HWaddr 00:0C:29:12:99:D4
inet addr:192.168.3.60 Bcast:192.168.3.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
由于 ngrokd 的启动命令老长老长,偶尔发现它死了需要重启,拼(找)命令都拼半天,于是我顺手写了一个维护脚本
注意: Ubuntu 适用,Centos 需要一点修改
wget https://gist.githubusercontent.com/IvanChou/1be8b15b1b41bf0ce2e9d939866bbfec/raw/1a2445599fe7fd706505a6e103a9dc60b4d3a0ed/ngrokd -O ngrokd
# 修改 脚本中的配置
vi ngrokd
chomd +x ngrokd
sudo mv ngrokd /etc/init.d/ngrokd
ngrok 是 TCP 穿透,也就是说只要是基于 TCP 协议的通讯,它都能协助我们进行穿透,当然也包括 SSH 和 mstsc。
ngrok 在进行 TCP 连接的时候,是通过额外开启一个端口的方式,如果 Client 没有指定端口,ngrokd 将会随机开启一个大号端口。如指定 ngrokd 使用 10086 端口,连接建立后可通过 yii.im:10086 访问到 Client 的指定端口。
建议可以在 iptables 中放行少量 大口径端口 备用。
由于使用的是 CA 证书,所以不需要自行编译客户端,可以网上自行下载各种 ngrok v1.7 的客户端,理论上都是可用的(有的似乎对客户端做了修改,或许有其它未知原因而无法使用,请自行略过)
2016年10月: Mac 升级到 10.12 后,稍微旧一点的 ngrok-client 都有心跳 bug,导致经常断开且界面状态无变化,需要重新下载或编译一下最新的客户端。
资源地址 ㄟ( ▔, ▔ )ㄏ => http://pan.baidu.com/s/1b548fO
MAC & Linux 下,可以将 ngrok 放到 /usr/local/bin/
下备用
server_addr: "yii.im:4443"
trust_host_root_certs: true
这段配置是用来指定 Server 和 认证方式 的:
更多关于 ngrok configuration file 的设定可以参考:https://ngrok.com/docs#config
由于官网现在只有 2.0 以上版本的支持,这里只能参照配置的写法,启动方式请勿参考。
ngrok -config=path/to/ngrok.yml -proto=http -subdomain pub 3000
可以看到
ngrok (Ctrl+C to quit)
Tunnel Status online
Version 1.7/1.7
Forwarding http://pub.yii.im -> 127.0.0.1:3000
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms
说明连接成功,现在访问 http://pub.yii.im 就可以访问到本机 3000 端口上的服务了
在上面的运行时界面中,有一个 Web Interface 地址,这是 ngrok 提供的监控界面。通过这个界面可以看到远端转发过来的 http 详情,包括完整的 request/response 信息,相当于附带了一个抓包工具。
指定 server 端口需要在 ngrok.yml 中配置才能实现
不指定端口
ngrok -config=path/to/ngrok.yml -proto=tcp 22
连接状态:
ngrok (Ctrl+C to quit)
Tunnel Status online
Version 1.7/1.7
Forwarding tcp://yii.im:17476 -> 127.0.0.1:22
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms
盗用 Jerry Qu 的一句话
实际上,由于 ngrok 可以转发 TCP,所以还有很多玩法,原理都一样,这里就不多写了。
文中使用的 yii.im 只是为了说明使用的域名,这个域名下并没有搭 ngork 服务
(/= _ =)/~┴┴ ╮(╯▽╰)╭