利用 内网穿透 + chii 打造舒适的WebView开发调试环境

1,090 阅读6分钟

大家做移动端 H5 需求或查找问题时, 对下面这些场景应该不陌生~

  1. 页面依赖了 js bridge 导致网页在电脑浏览器上无法直接进行调试,没有安卓手机的同学需要借用安卓测试机进行开发调试
  2. 手机死活连不上 chrome://inspect (部分低版本安卓系统 webview 不支持)
  3. 某某机型页面展示异常 但是公司没有这样的机型 只能使用云手机进行修复验证,云手机并不能连接到我的 inspect 页面
  4. ios 使用 safari 进行调试时,需要打 debug 包,如果 debug 人数满了,需要借 ios 测试机
  5. 第三方应用内的小程序 Webview、公众号可能不支持 ADB

一个简单舒适的 WebView 开发调试环境,应该满足下面 3 个条件:

  1. 支持远程设备 无须手机和电脑连接
  2. 所见即所得 本地开发的内容能实时显示在真机上
  3. ios 安卓 通用

Q: 假设我们有一款 APP,APP 内嵌入了一个 H5 页面,页面的地址是test.com, 那么有没有一种可能,当我们打开 APP 访问test.com的时候,可以返回我们本地的服务localhost:8000呢?

如果可以,是不是刚好就满足了上面的 3 个条件,支持远程设备、所见即所得、ios/安卓通用

内网穿透就可以实现这样的需求

内网穿透

内网穿透,即 NAT(Network Address Translator)穿透,是指计算机在内网(局域网)内使用私有 IP 地址,在连接外网(互联网)时使用全局 IP 地址的技术。简单点说就是能让外网的设备找到处于内网的设备,从而实现数据通信。

比如: 你在本地启动了一个 localhost:8000 的服务,这个时候外网是无法访问的,而内网穿透可以将本地的 localhost:8000 的服务暴露出去,让外网能够有能力访问到你的本地服务。

目前市面上有很多工具,较为知名的开源工具有NgrokFRPfcngoProxy

使用 FRP 将本地 HTTP/HTTPS 服务提供给外网

FRP

FRP 是一种快速反向代理,可帮助您将 NAT 或防火墙后面的本地服务器暴露给 Internet。目前,它支持TCPUDP,以及HTTPHTTPS协议,可以通过域名将请求转发到内部服务。

安装

首先根据操作系统 从 FRP下载页面分别下载对应的服务端及客户端 FRP 安装文件

服务端

下载

wget https://github.com/fatedier/frp/releases/download/v0.44.0/frp_0.44.0_linux_amd64.tar.gz

解压

tar -xvf frp_0.44.0_linux_amd64.tar.gz

客户端

找到对应的系统进行下载
Mac frp_0.44.0_darwin_amd64.tar.gz
Windows frp_0.44.0_windows_amd64.zip

文件说明

frps.ini: 服务端配置文件
frps: 服务端软件
frpc.ini: 客户端配置文件
frpc: 客户端软件

配置和使用

服务端

frps.ini

[common]
bind_port = 8000 #自定义frp服务端端口
dashboard_port = 8080 #自定义控制台端口
token = token #服务端和客户端的连接密钥 自己设定 和frpc.ini的设置保持一致
dashboard_user = admin #自定义控制台用户名
dashboard_pwd = admin #自定义控制台密码
vhost_http_port = 8088 #http访问端口

启动

./frps -c ./frps.ini

# 后台启动
nohup ./frps -c ./frps.ini &

注意服务器设置中需要打开对应的端口 阿里云服务器开启 8000 ~ 9000 端口

image.png

客户端

将刚刚下载的客户端文件进行解压,在前端项目目录下新建 bin 文件夹,将 frp 文件放入 bin 文件夹内

image.png

frpc.ini

[common]
server_addr = test.com #frps服务器公网 IP/域名
server_port = 8000 #服务器端口 与frps bind_port保持一致
token = token #服务端和客户端的连接密钥 自己设定 和frps.ini的设置保持一致

[web]
type = http #http | https | udp | ssh
local_ip = 127.0.0.1 #本地服务IP
local_port = 8001 #本地服务端口
use_encryption = true #加密
use_compression = true #压缩

启动

./bin/frp/frpc -c ./bin/frpfrpc.ini

如果看到如下页面 说明服务已经启动成功 image.png

这个时候就可以通过访问之前在 frps 中配置的的域名加端口号(test.com:8088)访问到自己本地的 web 服务了

虽然已经实现了外网访问本地服务,但实际开发过程中,不可能只有一个开发人员,所以我们还需要修改一下 frp 配置,使得多人可以共享一台 frps 服务器

修改 frps.ini 添加 subdomain_host

[common]
bind_port = 8000 #frp服务端端口
dashboard_port = 8080 #控制台端口
token = token #服务端和客户端的连接密钥 自己设定 和frpc.ini的设置保持一致
dashboard_user = admin #控制台用户名
dashboard_pwd = admin #控制台密码
vhost_http_port = 8088 #http访问端口
subdomain_host = test.com #多人共享一台 frps 服务器时,frps服务的顶级域名

重启 frps 服务

./frps -c ./frps.ini

# 如果之前是后台启动需要先杀掉之前的进程
ps -ef
kill -9 进程号
nohup ./frps -c ./frps.ini &

前端项目 frp 文件夹下新建 frpc.template.ini 添加 subdomain 自定义子域名,区分不同开发人员

[common]
server_addr = test.com
server_port = 8000
token = token

[${name}] #自定义name
type = http
local_ip = 127.0.0.1
local_port = ${port} #自定义端口
use_encryption = true
use_compression = true
subdomain = ${name} #自定义子域名 区分不同开发/服务

注意域名需支持子域名解析 阿里域名解析配置示列: image.png

前端项目下新建 createFrp.js 这里主要是获取开发人员电脑的用户名,填写 frpc.template.ini 模版,并启动 frp 和前端项目本地服务

const os = require('os'); // 获取系统信息
const execa = require('execa'); // 执行shell
const fs = require('fs'); // 文件系统
const path = require('path'); // 路径

// 获取自定义port
let [, httpPort = 8000] =
  process.argv
    .slice(2)
    .join(' ')
    .match(/-p\s*(\d+)/) || [];

// 获取客户端系统用户名 用来区分不同的开发 这里可以使用其他方式 只要保证唯一就可以
function getFrpcName() {
  return os.userInfo().username;
}

function configFrpc() {
  const username = getFrpcName();
  // 使用frpc.template.ini 创建我们的frpc.ini文件
  fs.writeFileSync(
    path.join(__dirname, 'bin/frp/frpc.ini'),
    fs
      .readFileSync(path.join(__dirname, 'bin/frp/frpc.template.ini'))
      .toString()
      .replace(/\$\{name\}/g, username)
      .replace(/\$\{port\}/g, httpPort),
  );

  // 启动frpc服务
  execa('./bin/frp/frpc', ['-c', './bin/frp/frpc.ini'], {
    cwd: process.cwd(),
    stdio: 'inherit',
  });

  // 启动next服务,这里使用了next作为演示,其他项目需要修改启动命令
  execa(`./node_modules/next/dist/bin/next`, ['dev', '-p', httpPort], {
    cwd: process.cwd(),
    stdio: 'inherit',
  });
}

configFrpc();

package.json
修改启动命令

"scripts": {
 "dev": "node creatFrp -p 8001",
}

启动前端项目

npm run dev
or
yarn dev

假设有小明和二狗两个开发人员
这时小明可以使用 xiaomin.test.com:8088 访问到自己的本地服务
二狗可以使用 ergou.test.com:8088 访问到自己的本地服务

到这里已经实现了外网访问本地服务、多人共享一台 frp,回到刚刚我们的假设,我们希望的是在访问 test.com 的时候可以返回我们本地 localhost 的服务,但是小明现在实际只能通过 xiaomin.test.com:8088 才能访问本地服务,这时候我们就需要使用到Nginx的转发功能,在业务服务器上配置 Nginx 转发,当请求带上了proxy_subdomain这个 cookie 的时候,我们就将请求转发到${proxy_subdomain}.test.com上 比如小明带了一个proxy_subdomain=xiaomin的 cookie 访问test.com,这时候我们就将请求转发到xiaomin.test.com:8088上,这样小明就连上了自己的本地服务

配置 nginx

vim /etc/nginx/nginx.conf

nginx.conf 由于我的 frp 服务就部署在外网服务器上,所以转发使用的域名是 127.0.0.1

server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  test.com;
        location / {
            set $subdomain '';
            if ($http_cookie ~* "proxy_subdomain") {
              # 有proxy_subdomain cookie的时候转发到frp服务上
              proxy_pass http://127.0.0.1:8088; #frp服务器域名
              set $subdomain ${cookie_proxy_subdomain}.;
            }
              proxy_set_header Host ${subdomain}test.com;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header X-Forwarded-Proto https;
              proxy_pass http://127.0.0.1:8888/; #没有cookie的时候 访问正常服务
        }
    }

当 cookie 中写入 proxy_subdomain,访问线上地址时,Nginx 就会根据设置的不同 proxy_subdomain 返回对应的本地服务

最后我们只需要加上调试面板,方便我们查看 DOM 结构、网络请求,console 信息等,这里可以直接使用移动端调试工具vConsole

打通最后一公里

Q: chrome://inspect 是如何实现的?,我们能实现 chrome://inspect 同样的效果吗?

chii

像 weinre这样的远程调试工具,用最新的chrome devtools 前端替换 web 检查器。

如下图所示,利用 内网穿透 配合 chii 就可以实现和 chrome://inspect 相同的能力,左边是 pc 上的 devTools,右边是手机上访问的页面

image.png

安装 chii

npm install chii -g

frpc.template.ini
在 frpc.template.ini 新增一个 chii 的 http 内网穿透配置

[chii]
type = http
local_ip = 127.0.0.1
local_port = 8999
use_encryption = true
use_compression = true
subdomain = chii${name}

creatFrp configFrpc 函数下新增脚本命令

execa(`chii`, ['start', '-p', 8999], {
  cwd: process.cwd(),
  stdio: 'inherit',
});

启动

npm run dev
or
yarn dev

访问 chii 服务 浏览器打开 localhost: 8999

将页面中提示的 js 放入前端项目中

You can use this script to inject the chii target code into your web page.
localhost:8999/target.js
将这里的 target.js 的域名换成刚刚配置的 chii 内网穿透地址 如:chiixuxiaohua.test.com:8088/target.js\ 在前端项目中引入 <script defer src="http://chiixuxiaohua.test.com:8088/target.js" />

访问业务域名就可以看到如下页面 点击 inspect 就可以看到调试面板

image.png

现在就实现了多人共用、所见即所得、支持远程设备、支持 ios/安卓的 webView 开发调试环境。剩下的就只需要判断是开发者的时候,在页面上提供一个悬浮的开发者按钮,点击后可自定义代理地址(proxy_subdomain)

总结

调试设备 webview 确实麻烦,首先需要安卓手机连接电脑,进入 chrome://inspect 页面,然后找到对应的 webview,点击inspect按钮进入调试页,将地址替换成本地服务,并且不支持远程设备,不支持 ios。

通过内网穿透 + vConsole/chii 后,只需要打开 webview,输入代理地址就可以进行开发了,并且无论是 ios、安卓、第三方应用、小程序、大屏、低版本安卓、云端设备...等等,只要是网页就可以通过这种方式进行开发调试。快速定位解决问题。