前端调试从入门到精通
快来分享你的内容吧~
- 2023-11-20·前端开发上节学了常用的代理功能,这节我们来学其他的 charles 功能: DNS Spoofing 在本地准备一个 index.html,然后 npx http-server . 来跑一个静态服务器: 浏览器访问下: 执行 sudo vim /etc/hosts 修改 hosts 文件,添加一条 www.guangguangtest.com 到 127.0.0.1 的映射: 之后就可以用这个域名来访问了查看全文蜗牛:我是一名后端人员,今天听听说讲才知道 Charles ,我就知道星球里的大佬肯定有类似的教程。果不其然。使用这个工具调试时间缩短了不是,之前是复制参数,现在是只管发送就行。1710分享
用户中心前端 [ nginx docker] 部署
## nginx 方式部署 环境: nginx 版本 1.18.0-1.27.1 os: linux nginx 安装方式很多自行百度,最简单的使用 安装包的方式,下面都是基于安装包的模式进行的。 前端项目构建 `npm run build` 构建成功目录 dist ,里面的内容放入 /usr/share/nginx/html 中或自己定义一个。(安装包模式下 nginx 不是 root 用户注意权限问题,目录所属用户应为 www-data) nginx 配置文件 http 模块如果有 include /etc/nginx/conf.d/*.conf; 下面的文件放入目录即可,如果没有直接将配置放入 htpp 模块 nginx 配置文件 frontend.conf (名字带 .conf 即可) ``` server { listen 80; # gzip config gzip on; gzip_min_length 1k; gzip_comp_level 9; gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; gzip_vary on; gzip_disable "MSIE [1-6]\."; location / { # 用于配合 browserHistory使用 try_files $uri $uri/index.html /index.html; root /usr/share/nginx/html; // 前端代码构建后内容存放的目录 # 如果有资源,建议使用 https + http2,配合按需加载可以获得更好的体验 # rewrite ^/(.*)$ https://preview.pro.ant.design/$1 permanent; } location /api { proxy_pass 后端地址; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; } } ``` 检查 nginx 配置是否正确 `nginx -t` 没有报错,重启 nginx(若有问题根据报错信息修改) `nginx -s reload` 或 `systemctl restart nginx` 直接访问服务器 ip 即可(配置 80端口) ## docker nginx 容器方式 项目结构 ``` ├── config ├── default.conf.template ├── Dockerfile ├── jest.config.ts ├── jsconfig.json ├── node_modules ├── package.json ├── package-lock.json ├── pnpm-lock.yaml ├── public ├── README.md ├── src └── tsconfig.json ``` nginx 模板文件(文件名 default.conf.template )(docker 环境变量 BACKEND_URL,启动容器时配置即可,方便随时修改) ``` server { listen 80; # gzip config gzip on; gzip_min_length 1k; gzip_comp_level 9; gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; gzip_vary on; gzip_disable "MSIE [1-6]\."; location / { # 用于配合 browserHistory使用 try_files $uri $uri/index.html /index.html; root /usr/share/nginx/html; # 如果有资源,建议使用 https + http2,配合按需加载可以获得更好的体验 # rewrite ^/(.*)$ https://preview.pro.ant.design/$1 permanent; } location /api { proxy_pass ${BACKEND_URL}; # // 配置模板变量 proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; } } ``` Dockerfile ``` # date: 2024/9/17 # os: ubuntu 20.0.4 # glibc: Ubuntu GLIBC 2.31-0ubuntu9.16 (与 node 18 有版本要求) # docker hub mirror: aws ecr # docker-ce version: 27.2.1 # 项目ant design pro 6.0.0 需要使用高版本 node ,支持 node 18+ FROM public.ecr.aws/docker/library/node:18.18-alpine AS builder # 创建工作目录 WORKDIR /app # 复制 package.json 和 package-lock.json COPY package*.json . # 安装依赖,建议配置阿里云比较稳定,我这里配置自己的镜像源 # RUN npm config set registry https://registry.npmmirror.com -g RUN npm config set registry http://192.168.1.23:8081/repository/npm_taobo/ -g && npm i --no-cache # 复制项目源码 COPY . . # 构建项目 RUN npm run build # 建议使用 alpine 小 FROM public.ecr.aws/docker/library/nginx:alpine AS production COPY --from=builder /app/dist/ /usr/share/nginx/html/ COPY default.conf.template /etc/nginx/templates/default.conf.template ``` 构建运行命令(建议分步构建) ``` docker build --target builder -t frontend-template-build:0.0.1 . docker build -t frontend-template:0.0.1 . docker run -itd -p 8080:80 -e BACKEND_URL=192.168.1.22:8081 --name frontend-template frontend-template:0.0.1 ``` 部署服务器不建议使用国内的,备案很麻烦。 前端已使用 nginx 进行部署地址: http://165.154.55.198 部署方式已经脚本化代码见仓库: https://github.com/wolf-li/web-frontend-templage ### 问题 1. 前端构建后文件放到 nginx 进行部署后(docker 部署没有问题),展示效果与开发时不一样 (已解决) <img src="https://pic.code-nav.cn/post_picture/1662265025525547010/q1q1ekyza0eSOWiC.webp" alt="屏幕截图 2024-09-18 161102.png" width="100%" /> <img src="https://pic.code-nav.cn/post_picture/1662265025525547010/uF8zjWDXSyeVIWtf.webp" alt="屏幕截图 2024-09-18 161206.png" width="100%" /> 查看浏览器 F12 开发者控制台 发现 没有按照 样式进行加载,nginx 问题 查看nginx 配置配置文件s是否有下面的内容没有添加即可 ``` include mime.types; default_type application/octet-stream; ```
【前端调试专栏(45)】实现 Chrome DevTools 的 Coverage 功能
<html> <head></head> <body> <div class="content ql-editor"> <p>上节我们把 chrome devtools frontend 跑起来,然后连上自己做的 CDP backend,实现了 network 面板、element 面板的对接,明白了 Chrome DevTools 的运行原理。</p> <p>那我们能基于已有的 backend,自己实现 frontend 么?</p> <p>当然也是可以的。</p> <p>我们通过命令行的方式把 chrome 跑起来,通过 remote-debugging-port 指定 backend 的端口(这是 mac 下的 chrome 路径,windows 下的话大家自己找一下):</p> <div class="ql-code-block-container"> <div class="ql-code-block"> /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 </div> </div> <p>然后我们自己通过 WebSocket 客户端连上就可以了。</p> <p>当然自己实现 CDP 的交互还是挺麻烦的,chrome 给提供了一个工具包 chrome-remote-interface,内部实现了和 CDP backend 的 WebSocket 通信,我们只需要调用它的 api 即可:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> const CDP = require('chrome-remote-interface'); async function test() { let client; try { client = await CDP({ host: '127.0.0.1', port: 9222 }); const { Page, DOM, Debugger } = client; //... } catch(err) { console.error(err); } } test(); </div> </div> <p>我们测试一下 DOM 部分的协议:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> const CDP = require('chrome-remote-interface'); const fs = require('fs'); async function test() { let client; try { client = await CDP({ host: '127.0.0.1', port: 9222 }); const { Page, DOM, Debugger } = client; await Page.enable(); await Page.navigate({url: 'https://baidu.com'}); await DOM.enable(); const { root } = await DOM.getDocument({ depth: -1 }); } catch(err) { console.error(err); } } test(); </div> </div> <p>打个断点,看下 backend 返回的消息:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/zbnv5yf7.jpeg" alt=""></p> <p>这就是真实的包含 DOM 信息的 CDP 数据。</p> <p>接下来我们来实现一个真实的 Chrome DevTools 的功能:</p> <p>Chrome DevTools 有一个覆盖率检测的功能,可以检测 JS、CSS 代码里有哪些执行了,哪些没执行。并且还会在 sources 里标记出来。</p> <p>如下图,绿色的部分是执行过的,而红色的部分是没执行的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/uga7oa2g.jpeg" alt=""></p> <p>在 sources 面板里可以直接看到哪些代码没执行,比如下面的红色部分就是没有执行的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/4yxbpy4r.jpeg" alt=""></p> <p>这个功能还是很有用的,可以帮助我们分析哪些代码是用不到的,可以进行延后加载或者删掉等优化。</p> <p>在 More Tools 里开启:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/nkb06y7y.jpeg" alt=""></p> <p>使用还是很简单的,但它是怎么实现的呢?</p> <p>首先,我们要知道页面下载了哪些 JS 和 CSS。</p> <p>这个是通过监听事件拿到的, CSS.styleSheetAdded 和 Debugger.scriptParsed 这俩事件。</p> <p>我们监听下这俩事件:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> const CDP = require('chrome-remote-interface'); async function test() { let client; try { client = await CDP({ host: '127.0.0.1', port: 9222 }); const { Page, DOM, Debugger, Runtime, CSS } = client; await Page.enable(); await Debugger.enable(); await DOM.enable(); await CSS.enable(); CSS.on('styleSheetAdded', async (event) => { debugger; }) Debugger.on('scriptParsed', async (event) => { debugger; }) await Page.navigate({url: 'http://127.0.0.1:8084'}); } catch(err) { console.error(err); } } test(); </div> </div> <p>因为用到 DOM、CSS、Debugger、Page 域的协议,所以需要先 enable 一下,只有 enable的功能才会启用。</p> <p>这个很正常,没 enable 就不启用,这样能节省性能。</p> <p>执行这段代码,看下拿到的事件对象:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/5evex87d.jpeg" alt=""></p> <p>事件对象里是这段 js 的 url 和行列号,再就是 scriptId。</p> <p>然后再看下 CSS.styleSheetAdded 的事件对象:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/rk39ckha.jpeg" alt=""></p> <p>也差不多,只不过这里是 styleSheetId。</p> <p>那怎么拿到 CSS 和 JS 的内容呢?</p> <p>这就需要用到别的 api 了。</p> <p>css 的内容是用 CSS.getStyleSheetText 来拿,传入 styeleSheetId:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> const styleSheetId = event.header.styleSheetId; const content = await CSS.getStyleSheetText({ styleSheetId }); </div> </div> <p>JS 的内容是用 Debugger.getScriptSource 来拿,传入 scriptId:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> const scriptId = event.scriptId; const content = await Debugger.getScriptSource({ scriptId }); </div> </div> <p>我们把它们按照 id 放到 Map 里:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> const cssMap = new Map(); const jsMap = new Map(); CSS.on('styleSheetAdded', async (event) => { const styleSheetId = event.header.styleSheetId; const content = await CSS.getStyleSheetText({ styleSheetId }); cssMap.set(styleSheetId, { meta: event.header, content: content.text }); }) Debugger.on('scriptParsed', async (event) => { const scriptId = event.scriptId; const content = await Debugger.getScriptSource({ scriptId }); jsMap.set(scriptId, { meta: event, content: content.scriptSource }); }) </div> </div> <p>这样就能把页面上所有的 js 和 css 收集起来:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/mbqqfn8m.jpeg" alt=""></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/mpl77r7e.jpeg" alt=""></p> <p>对了,测试页面的内容是这样的:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="index.css"> <style> a { color: red; } </style> </head> <body> <script> function add(a, b) { return a + b; } function minus(a, b) { return a -b; } function multiply(a, b) { return a * b; } </script> <script> add(1, 3); multiply(3, 4); </script> </body> </html> </div> </div> <p>有一个外部 css:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> .aaa { color: red; } div { color: blue; } body { background: pink; } </div> </div> <p>收集到了 JS 和 CSS 的数据只是第一步,要计算出覆盖率数据,还要知道哪些 JS 和 CSS 执行了。</p> <p>这个也有 api:</p> <p>CSS 开启执行数据的收集是用 CSS.startRuleUsageTracking:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> await CSS.enable(); await CSS.startRuleUsageTracking(); </div> </div> <p>然后一段时间后 stop:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> // 延迟一段时间再获取数据,等页面渲染完 await new Promise(resolve => setTimeout(resolve, 3000)); const cssCoverage = await CSS.stopRuleUsageTracking(); </div> </div> <p>这样就能获取 CSS 的执行数据:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/yyubk2rw.jpeg" alt=""></p> <p>返回的结果显示 scriptId 为 89607.4 的 css 的 50 到 80 个字符的代码执行了。</p> <p>我们在 cssMap 里看下这个 id 对应的代码:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/etv1lke4.jpeg" alt=""></p> <p>然后取出 50 到 80 个字符的代码:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/t6tj5em2.jpeg" alt=""></p> <p>也就是说所有 css 里只有这一段代码是生效的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/w8ivgfo3.jpeg" alt=""></p> <p>你用 Chrome DevTools 的 Coverage 分析结果也是这样的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/lu0fua4q.jpeg" alt=""></p> <p>有了所有 CSS 代码的数据,有了执行了哪些 CSS 的代码的数据,覆盖率的计算不就很简单了么?</p> <p>我们再来看下 JS 的:</p> <p>JS 使用 Profiver 的 prociseCoverage 的 api 获取覆盖率数据:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> await Profiler.enable(); await Profiler.startPreciseCoverage(); // 延迟一会再获取数据,等 js 执行完 await new Promise(resolve => setTimeout(resolve, 3000)); const jsCoverage = await Profiler.takePreciseCoverage(); </div> </div> <p>可以看到返回了两个 script 的执行数据:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/hoywybnj.jpeg" alt=""></p> <p>因为我们页面上就两个 script 嘛:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/3xbmsvjj.jpeg" alt=""></p> <p>第一个 script 有 4 个 functions:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/wau5f133.jpeg" alt=""></p> <p>有同学说,不对呀,不是 add、minus、multiply 3 个吗?</p> <p>那个没有名字的代表 script 的匿名代码块。</p> <p>每个 function 都记录了字符的范围,还有执行的次数:</p> <p>比如 add 函数执行了 1 次:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/hg8qq1pn.jpeg" alt=""></p> <p>minus 函数执行了 0 次:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/6tpgdt2o.jpeg" alt=""></p> <p>第二个 script 的匿名代码块执行了 1 次:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/rq515254.jpeg" alt=""></p> <p>这不就和 Chrome DevTools 的 Coverage 结果对上了么:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/jrhvm9vu.jpeg" alt=""></p> <p>不管是覆盖率数据也好,还是在 sources 里可视化展示哪些代码没执行也好,都很容易实现。</p> <p>全部代码如下,也可以从<a href="https://github.com/QuarkGluonPlasma/fe-debug-exercize" target="_blank">小册仓库</a>里找到。</p> <div class="ql-code-block-container"> <div class="ql-code-block"> const CDP = require('chrome-remote-interface'); async function test() { let client; try { client = await CDP({ host: '127.0.0.1', port: 9222 }); const { Page, DOM, Debugger, Runtime, CSS, Profiler } = client; await Page.enable(); await Debugger.enable(); await DOM.enable(); await CSS.enable(); await Profiler.enable(); const cssMap = new Map(); const jsMap = new Map(); CSS.on('styleSheetAdded', async (event) => { const styleSheetId = event.header.styleSheetId; const content = await CSS.getStyleSheetText({ styleSheetId }); cssMap.set(styleSheetId, { meta: event.header, content: content.text }); }) Debugger.on('scriptParsed', async (event) => { const scriptId = event.scriptId; const content = await Debugger.getScriptSource({ scriptId }); jsMap.set(scriptId, { meta: event, content: content.scriptSource }); }) await CSS.startRuleUsageTracking(); await Profiler.startPreciseCoverage(); await Page.navigate({url: 'http://127.0.0.1:8084'}); await new Promise(resolve => setTimeout(resolve, 3000)); const cssCoverage = await CSS.stopRuleUsageTracking(); const jsCoverage = await Profiler.takePreciseCoverage(); debugger; } catch(err) { console.error(err); } } test(); </div> </div> <p>其实 lighthouse 的 cli 就是通过这种方式实现的数据收集和分析:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/3f9lovxl.jpeg" alt=""></p> <p>如果某一天,你也要做一个网页分析工具,是不是也可以通过 CDP 的方式来获取一些网页运行数据做分析呢?</p> <p>所有 Chrome DevTools 的数据,你通过 CDP 都是能拿到的,能做的事情有很多。</p> <h2>总结</h2> <p>可以自己实现 CDP backend,当然也可以实现 frontend,但自己对接 WebSocket 和 CDP 协议还是挺麻烦的,可以直接用 chrome-remote-interface 这个包。</p> <p>这节我们实现了下 Coverage 功能:</p> <p>Chrome DevTools 有 Coverage 面板,可以分析 JS 和 CSS 代码执行的覆盖率,分析出哪些代码没执行,然后做后续优化。</p> <p>这是 Chrome 通过 CDP 暴露给 Chrome DevTools 的,而 CDP 的数据我们也能自己实现 ws 客户端来拿到,那自然也可以自己实现覆盖率的计算。</p> <p>我们通过 chrome-remote-interface 的不同域的 api 来进行了 CSS 和 JS 的代码的收集,代码执行数据的收集,有了这些数据就能轻松算出覆盖率。</p> <p>lighthouse 的 cli 就是通过这种方式来收集 Chrome 运行时数据,做分析和展示的。如果我们想做一个调试工具,或者网页分析工具,也可以用类似的思路。</p> <p>Chrome DevTools 能做的所有事情,我们都能自己实现,因为 CDP 数据是一摸一样的。</p> #前端# </div> </body> </html>
【前端调试专栏(44)】手动对接 CDP 来理解 Chrome DevTools 实现原理
<html> <head></head> <body> <div class="content ql-editor"> <p>前面学习了 Chrome DevTools 的各种工具的使用,从这节开始深入下它的实现原理。</p> <p>调试工具都包含 frontend、backend、调试协议、信道这四个部分:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/8jiutsty.jpeg" alt=""></p> <p>而在 Chrome DevTools 里这个调试协议就是 Chrome DevTools Protocol,简称 CDP。</p> <p>打开 <a href="https://chromedevtools.github.io/devtools-protocol/" target="_blank">CDP 的文档</a>,可以看到 CDP 协议分为了不同的 Domain:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/n4jbbs2f.jpeg" alt=""></p> <p>比如 DOM、CSS、Debugger 等,这个很容易理解,各种工具的数据通信总不能混到一起吧,所以分成了不同的域来管理。</p> <p>每个 Domain 下都包含了 Methods 和 Events:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>Method 就是 frontend 向 backend 请求数据,backend 给它返回相应的数据</li> <li data-list="bullet"><span class="ql-ui"></span>Event 就是 backend 推送给 frontend 的一些数据。</li> </ol> <p>你可以在 Chrome DevTools 的设置里开启 Protocol Monitor 面板:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/uh0dfvjm.jpeg" alt=""></p> <p>在 More Tools 里打开:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/b2ewibgl.jpeg" alt=""></p> <p>然后你就可以看到当前页面所有的 CDP 数据交互:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/cxg9fxdc.jpeg" alt=""></p> <p>双向箭头的就是 Method,单向箭头的就是 backend 推给 frontend 的 Event。</p> <p>你可以在下面的 send a raw CDP method 的输入框里输入协议数据,Chrome DevTools 会把它发给 backend:</p> <p>比如发送 DOM.getDocument 的 method:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> {"method": "DOM.getDocument","params": {}} </div> </div> <p>就会返回整个 DOM 的信息:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/mw03mrzg.jpeg" alt=""></p> <p>比如发送 CSS.getComputedStyleForNode 的 method,带上某个 nodeId:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> {"method": "CSS.getComputedStyleForNode","params": {"nodeId": 920}} </div> </div> <p>就可以拿到这个 node 的所有计算后的样式:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/p9td6fzt.jpeg" alt=""></p> <p>Chrome DevTools 里展示的所有内容都是从 backend 那里拿到的,他只是一个展示和交互的 UI 而已。</p> <p>这个 UI 是可以换的,比如我们可以用 VSCode Debugger 对接 CDP 调试协议来调试网页。</p> <p>Chrome DevTools frontend 也是一个独立的项目,我们可以从 npm 仓库下载 chrome-devtools-frontend 的代码,我这里用的是 1.0.672485 版本的:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> npm install chrome-devtools-frontend@1.0.672485 </div> </div> <p>下载下来的代码有个 front_end 目录,这个就是 Chrome DevTools 的前端代码:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/owjtii5r.jpeg" alt=""></p> <p>它下面有几个 html:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/ke0fdfvb.jpeg" alt=""></p> <p>我们在 node_modules/chrome-devtools-frontend 下执行 "npx http-server ." 起个静态服务看一下:</p> <p>devtools_app.html 就是网页的那个调试页面:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/qualr26f.jpeg" alt=""></p> <p>node_app.html 就是 node 的那个调试页面:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/yylra8ry.jpeg" alt=""></p> <p>这就是 Chrome DevTools 的 frontend 部分。</p> <p>那怎么用这个独立的 frontend 呢?</p> <p>给它配个 WebSocket 的 backend 就行.</p> <p>用 node 创建个 WebSocket 服务端,打印下收到的消息:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> const ws = require('ws'); const wss = new ws.Server({ port: 8080 }); wss.on('connection', function connection(ws) { ws.on('message', function message(data) { console.log('received: %s', data); }); }); </div> </div> <p>在 devtools_app.html 后面加上 ws=localhost:8080 的参数:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/o6zz5xr2.jpeg" alt=""></p> <p>启动 ws 服务,你就会发现控制台打印了一系列收到的消息:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/mxgbb0gp.jpeg" alt=""></p> <p>这就是 CDP 协议的数据。</p> <p>那我们对接一下这个协议,返回相应格式的数据,能在 Chrome DevTools 里做显示么?</p> <p>我们试一下。</p> <p>我们找个网络相关的协议:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/x9cte408.jpeg" alt=""></p> <p>现在 Protocol Monitor 里看看 NetWork 部分都是怎么通过 CDP 交互的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/t5hvg5kd.jpeg" alt=""></p> <p>然后你会发现每次发请求前,backend 都会给 frontend 传一个 Network.requestWillBeSent 的消息,带上这次请求的信息。</p> <p>那我们能不能也发一个这样的消息呢?</p> <p>我模拟构造了一个类似的 CDP 消息:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/xuc1f8t9.jpeg" alt=""></p> <p>然后在 frontend 的页面看一下:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/7lxtallh.jpeg" alt=""></p> <p>你会发现 Network 面板显示了我们发过来的消息!</p> <p>这就是 Chrome DevTools 的原理。</p> <p>(代码在<a href="https://github.com/QuarkGluonPlasma/fe-debug-exercize/tree/main/react-source-debug" target="_blank">小册仓库</a>)</p> <p>测试了下 Network 部分的协议之后,我们再来试下 DOM 的。</p> <p>我用 Protocol Monitor 观察了下 DOM 部分的 CDP 交互:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/acw1lcaz.jpeg" alt=""></p> <p>首先通过 DOM.getDocument 获取 root 的信息,这一级返回的 node 只到 body。</p> <p>然后后面再发 DOM.requestChildNodes 的消息,服务端会回一个 DOM.setChildNodes 的消息来返回子节点的信息。</p> <p>我们也这样实现一下:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/cunooio4.jpeg" alt=""></p> <p>收到 DOM.getDocument 的消息的时候,我们返回 root 的信息,只到 body 那一级。</p> <p>然后发送 DOM.setChildNotes 来返回子节点的信息。</p> <p>还要处理下 DOM.requestChildNodes 的消息,返回空就行。</p> <p>完整代码如下:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> ws.on('message', function message(data) { console.log('received: %s', data); const message = JSON.parse(data); if (message.method === 'DOM.getDocument') { ws.send(JSON.stringify({ id: message.id, result: { root: { nodeId: 1, backendNodeId: 1, nodeType: 9, nodeName: "#document", localName: "", nodeValue: "", childNodeCount: 2, children: [ { nodeId: 2, parentId: 1, backendNodeId: 2, nodeType: 10, nodeName: "html", localName: "", nodeValue: "", publicId: "", systemId: "" }, { nodeId: 3, parentId: 1, backendNodeId: 3, nodeType: 1, nodeName: "HTML", localName: "html", nodeValue: "", childNodeCount: 2, children: [ { nodeId: 4, parentId: 3, backendNodeId: 4, nodeType: 1, nodeName: "HEAD", localName: "head", nodeValue: "", childNodeCount: 5, attributes: [] }, { nodeId: 5, parentId: 3, backendNodeId: 5, nodeType: 1, nodeName: "BODY", localName: "body", nodeValue: "", childNodeCount: 1, attributes: [] } ], attributes: [ "lang", "en" ], frameId: "3A70524AB6D85341B3B613D81FDC2DDE" } ], documentURL: "http://127.0.0.1:8085/", baseURL: "http://127.0.0.1:8085/", xmlVersion: "", compatibilityMode: "NoQuirksMode" } } })); ws.send(JSON.stringify({ method: "DOM.setChildNodes", params: { nodes: [ { attributes: [ "class", "guang" ], backendNodeId: 6, childNodeCount: 0, children: [ { backendNodeId: 6, localName: "", nodeId: 7, nodeName: "#text", nodeType: 3, nodeValue: "光光光", parentId: 6, } ], localName: "p", nodeId: 6, nodeName: "P", nodeType: 1, nodeValue: "", parentId: 5 } ], parentId: 5 } })); } else if (message.method === 'DOM.requestChildNodes') { ws.send(JSON.stringify({ id: message.id, result: {} })); } }); </div> </div> <p>返回的内容如上,我们返回了一个 P 标签,有 class 属性,还有一个文本节点。</p> <p>重启下 backend 服务,在 frontend 里重连一下,你就会发现 frontend 显示了我们返回的 DOM 信息:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/t6hnoiyg.jpeg" alt=""></p> <p>经过这两个案例,我们就搞明白了 Chrome DevTools frontend 是怎么和 backend 交互的。</p> <p>看到自己模拟 DOM 信息这部分,不知道你是否会想到跨端引擎呢。</p> <p>跨端引擎就是通过前端的技术来描述界面(比如也是通过 DOM),实际上用安卓和 IOS 的原生组件来做渲染。</p> <p>它的调试工具也是需要显示 DOM 树的信息的,但是因为并不是网页,所以不能直接用 Chrome DevTools。</p> <p>那如何用 Chrome DevTools 来调试跨端引擎呢?</p> <p>看完上面两个案例,相信你就会有答案了。只要对接了 CDP,自己实现一个 backend,把 DOM 树的信息,通过 CDP 的格式传给 frontend 就可以了。</p> <p>自定义的调试工具基本都是前端部分集成下 Chrome DevTools frontend,后端部分实现下对接 CDP 的 ws 服务来实现的。</p> <p>比如有很多<a href="https://github.com/ChromeDevTools/awesome-chrome-devtools" target="_blank">用 Chrome DevTools 的backend 调试其他代码的工具</a>:</p> <p>比如监听系统级别的(包括浏览器之外)的 http 请求,用 chrome devtools 的Network面板展示的工具 <a href="https://github.com/kdzwinel/betwixt" target="_blank">betwixt</a>:</p> <p>比如用 Chrome DevTools 调试 ios 的网络和数据的工具<a href="https://github.com/square/PonyDebugger" target="_blank">PonyDebugger</a>:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/4jw26p95.jpeg" alt=""></p> <p>frontend 只是一个对接了 CDP 的独立的客户端 UI,自己实现 CDP 的 backend 就可以用它来调试各种东西。</p> <p>对前端来说,常见的就是跨端引擎、小程序引擎的调试工具:</p> <p>小程序引擎的调试工具更简单,因为它实际上渲染是用的网页,有 CDP 的 backend,可以直接和 frontend 对接,不用自己实现 CDP 交互。</p> <p>比如 vivo 的快应用开发工具,它有编辑器、调试器、模拟器这几部分:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/8v3p3fl5.jpeg" alt=""></p> <p>模拟器渲染的内容能够在调试器里调试,就是调试器集成的 frontend 对接了模拟器的 CDP backend。</p> <p>当然,这里就不是通过 WebSocket 的信道了,而是通过其它方式:</p> <p>Chrome DevTools 支持多种信道:</p> <p>比如 WebSocket 时的通信实现是这样的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/i2o537w8.jpeg" alt=""></p> <p>而 electron 环境下是这样的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/dkq9veko.jpeg" alt=""></p> <p>嵌入到一个环境的时候是这样的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/weouvly3.jpeg" alt=""></p> <p>而且,像上面那种在一个窗口里渲染,在另一个窗口里调试的这种需求,electron 直接提供了 api 来支持。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/g3arjbib.jpeg" alt=""></p> <p>使用 setDevToolsWebContents 的 api,就可以让 devtools 的 frontend 显示在任意的窗口里。</p> <p>所以,小程序的调试工具实现起来还是很简单的,不但 CDP 交互不用自己实现,而且一个窗口渲染,一个窗口显示 Chrome DevTools frontend 这种功能 electron 都已经提供了。</p> <h2>总结</h2> <p>这节我们探究了下 Chrome DevTools 的实现原理,它只是一个对接了 CDP 的 UI,完全可以用来调试别的 target,只要实现对应的 CDP backend 即可。</p> <p>CDP 协议可以在 Protocol Monitor 里看到,分成了多个 Domain,每个 Domain 下有很多 Method 和 Event。</p> <p>有很多用 Chrome DevTools 调试别的目标的工具,而前端领域的跨端引擎、小程序引擎也是通过这种方式实现的。</p> <p>跨端引擎要自己实现 CDP 协议的对接,而小程序引擎简单一些,本来就有了 CDP backend,对接到 frontend 即可。</p> #前端# </div> </body> </html>
【前端调试专栏(43)】线上报错如何直接调试本地源码?
<html> <head></head> <body> <div class="content ql-editor"> <p>当线上有报错的时候,大家是怎么定位问题的呢?</p> <p>断点调试么?</p> <p>但是这时候代码是被压缩过的,变量名都是 a、b、c、d 这种,根本看不出啥来。</p> <p>如果调试线上报错能像本地开发的时候一样就好了。</p> <p>其实这是可以做到的,这节就分享下如何优雅的调试线上报错:</p> <p>首先,我们准备一段 JS 代码:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/w4pzyeyf.jpeg" alt=""></p> <p>这是我随便找的一段 JS 代码,里面抛了一个错误。</p> <p>然后用 webpack 进行编译:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/sm1qmt43.jpeg" alt=""></p> <p>在 index.html 里引入构建产物:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/s1201sp4.jpeg" alt=""></p> <p>然后跑个静态服务器 npx http-server .</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/zb2yokyg.jpeg" alt=""></p> <p>浏览器访问,就会发现代码确实报错了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/083cqb74.jpeg" alt=""></p> <p>那问题来了,怎么定位错误原因呢?</p> <p>首先,我们可以使用异常断点,在抛异常的地方断住:</p> <p>创建一个 vscode 调试配置:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/y1og4l7t.jpeg" alt=""></p> <p>勾选 uncaught exceptions,在未被捕获的异常处断住:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/lqj693jz.jpeg" alt=""></p> <p>然后启动调试:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/eov68nwr.jpeg" alt=""></p> <p>你会发现代码在抛异常的地方断住了,这就是异常断点的功能。当你不知道哪里抛的异常的时候,可以用这个。</p> <p>但现在代码是被压缩过的,看不出啥来:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/nddyeb9r.jpeg" alt=""></p> <p>怎么能直接定位到抛异常的源码呢?</p> <p>这时候就要用到 sourcemap 了,它就是用于把编译后的源码映射回源码的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/5r1lvmsj.jpeg" alt=""></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/dx97dafu.jpeg" alt=""></p> <p>首先要生成 sourcemap,这个配置下 webpack 的 devtool 为 hidden-source-map 即可:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/xd3rej59.jpeg" alt=""></p> <p>hidden-source-map 的意思是生成 sourcemap 但是不关联。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/mooig68h.jpeg" alt=""></p> <p>如果你配成 source-map,代码是关联了 sourcemap 的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/fl3ka58l.jpeg" alt=""></p> <p>关联 sourcemap 需要在文件末尾加上 //# sourceMappingURL=xxx.js.map 的代码。</p> <p>但现在这个文件是线上的,不能直接改本地文件。我们可以使用 charles 的断点功能来修改它:</p> <p>charles 默认不代理 127.0.0.1 的请求,我们要配下 hosts:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/nr5wweul.jpeg" alt=""></p> <p>比如我配了一个 www.guangtest.com 的域名到 127.0.0.1。</p> <p>试一下:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/61os8ydd.jpeg" alt=""></p> <p>hosts 配置生效了(如果 hosts 不生效,一般是你用了某个翻墙软件改了系统代理,关掉即可):</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/cjf4m0l1.jpeg" alt=""></p> <p>然后我们要让 charles 拦截这个 url 的请求,需要安装一个插件 SwitchyOmega</p> <p>不过在那之前要指定一个数据目录,也就是浏览器把插件、历史、cookie 等数据保存在哪里:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/kzlmr9dh.jpeg" alt=""></p> <p>不指定的话每次调试都会创建一个临时数据目录来跑调试,上次安装的插件就没有了。</p> <p>chrome 应用商店搜索 switchy omega:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/i4cbfbd9.jpeg" alt=""></p> <p>配置下代理服务器,这里我 charles 是在 127.0.0.1:8888 的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/2fhv8prc.jpeg" alt=""></p> <p>之后配下 auto switch,让 www.guangtest.com 的请求都走我们刚刚配的代理:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/22tsmf0p.jpeg" alt=""></p> <p>之后点击应用选项。</p> <p>代理方式设置成 auto switch,也就是根据配置的规则自动切换代理:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/hqbg0miv.jpeg" alt=""></p> <p>这个网页的代理配成 charles 之后,在 charles 就可以抓到对应的请求了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/1qdcgs0p.jpeg" alt=""></p> <p>接下来就是断点修改响应的内容了:</p> <p>点击 Proxy > Breakpoint Settings</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/7mnjvx5c.jpeg" alt=""></p> <p>添加一个对 guangtest.com 的 dist/index.js 响应的断点:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/lguiq32i.jpeg" alt=""></p> <p>强制刷下页面,charles 就会断住:</p> <p>我们可以修改响应的内容,然后点击 execute 来执行修改:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/cgii16rv.jpeg" alt=""></p> <p>我加上了这样一行 sourcemap 的关联:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/a2hee4de.jpeg" alt=""></p> <p>在 chrome devtools 里可以看到拿到的响应是被修改过的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/bpn540yu.jpeg" alt=""></p> <p>异常断点现在直接在源码处断住了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/hiq0oauz.jpeg" alt=""></p> <p>接下来就可以直接调试源码了,可以通过作用域、调用栈等信息来定位报错原因:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/ytjlwfcx.jpeg" alt=""></p> <p>这样我们就完成了直接本地调试线上报错代码对应的源码!</p> <p>案例代码在<a href="https://github.com/QuarkGluonPlasma/fe-debug-exercize" target="_blank">小册仓库</a></p> <p>有的同学可能会问,前面我们不是学过一个小技巧,sources 面板可以右键点击 add soruce map,就可以手动关联 sourcemap么:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/lcagn1n6.jpeg" alt=""></p> <p>没错,那样比较简单,不用 charles 来断点修改文件内容,但那个是一次性的,刷新就没了。</p> <p>而 charles 断点修改的内容会缓存,强制刷新才会重新请求。</p> <p>这两种方式都可以。</p> <h2>总结</h2> <p>通过 sourcemap,我们可以调试线上报错的时候直接对应到本地源码来断点调试。</p> <p>要让线上代码关联 sourcemap 可以通过 charles 断点修改对应的响应,加上一行 sourceMappingURL=xxx 的注释。</p> <p>然后在 VSCode Debugger 里加个异常断点,这样就可以在异常处断住。</p> <p>这样就可以快速定位线上错误的原因了,体验就和本地开发时一样!</p> <p>当然,更快捷的方式是 VSCode Debugger 异常断点断住的时候右键 chrome devtools 的 sources 面板,手动 add source map。不过这种方式是一次性的。</p> #前端# </div> </body> </html>
【前端调试专栏(42)】Charles 全功能速通(下)
<html> <head></head> <body> <div class="content ql-editor"> <p>上节学了常用的代理功能,这节我们来学其他的 charles 功能:</p> <h2>DNS Spoofing</h2> <p>在本地准备一个 index.html,然后 npx http-server . 来跑一个静态服务器:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/wmmy5c01.jpeg" alt=""></p> <p>浏览器访问下:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/3r7z8zja.jpeg" alt=""></p> <p>执行 sudo vim /etc/hosts 修改 hosts 文件,添加一条 www.guangguangtest.com 到 127.0.0.1 的映射:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/sazn36is.jpeg" alt=""></p> <p>之后就可以用这个域名来访问了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/ahq47b2i.jpeg" alt=""></p> <p>(如果你把科学上网的工具设置为系统代理,hosts 可能就不生效了,关掉即可)</p> <p>除了浏览器,在 Terminal 也是可以通过 curl 访问的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/uolurlg7.jpeg" alt=""></p> <p>ping 也能 ping 通:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/542in2tg.jpeg" alt=""></p> <p>charles 也有这个功能,叫做 DNS Spoofing(DNS 欺骗),在 Tools > DNS Spoofing 开启:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/o7ccs48y.jpeg" alt=""></p> <p>它的功能和 hosts 类似:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/8rq7m5kq.jpeg" alt=""></p> <p>比如我配置了 www.guangguangtest2.com 的域名到 127.0.0.1 的映射,之后就可以浏览器访问这个域名了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/rtvvzib2.jpeg" alt=""></p> <p>但在 Terminal 还不行:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/y1nmjyxx.jpeg" alt=""></p> <p>为什么呢?</p> <p>因为 DNS Spoofing 生效的前提是走了 charles 代理呀!</p> <p>在浏览器里可以通过 SwitchyOmega 的插件切换代理,那命令行里怎么切换呢?</p> <p>可以通过设置这个环境变量:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> export https_proxy=http://127.0.0.1:8888 http_proxy=http://127.0.0.1:8888 </div> </div> <p>设置之后执行 echo $https_proxy 就可以看到它的值:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/qzsgg9op.jpeg" alt=""></p> <p>这个端口就是 charles 代理服务的端口,可以在 Proxy > Proxy Settings 里设置:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/0sxj4e8p.jpeg" alt=""></p> <p>之后在命令行里的 http、https 请求,也会走 charles 代理:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/rsyusdrd.jpeg" alt=""></p> <p>curl 可以走代理,但是 ping 不会:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/w9rb4wsu.jpeg" alt=""></p> <p>因为 charles 只有 http、https 请求会走 charles 代理,这是和配置 hosts 的区别。</p> <p>那如果不想用代理了,怎么取消呢?</p> <p>通过 unset 去掉这个环境变量即可:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/jtfhj8i5.jpeg" alt=""></p> <p>这就是 DNS Spoofing 功能,相比改 hosts 更便捷一些。</p> <h2>Client Process</h2> <p>charles 可以展示发送 http 请求的 url。</p> <p>在 Tools > Client Process Settings 开启:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/5176un6n.jpeg" alt=""></p> <p>开启之后可以在 notes 看到发送请求的客户端进程:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/rbmeku75.jpeg" alt=""></p> <p>这个 google chrome helper 就是网页的一个渲染进程:</p> <p>在进程管理器里也可以看到:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/b8yy5jrn.jpeg" alt=""></p> <p>在终端设置为 charles 代理之后,之后的 http 请求也会被 charles 捕获到:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/h1t2gart.jpeg" alt=""></p> <p>这时候也可以看到对应的客户端进程:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/m5effpmx.jpeg" alt=""></p> <p>我们主要是调试网页用,所以这个功能倒是用的不多。</p> <h2>Compose</h2> <p>能不能用 charles 手动发送请求呢?就像 postman 那样。</p> <p>可以的,这就是 compose 功能。</p> <p>点击 Tools > Compose New,创建一个 www.guangguangtest.com:8081 的请求:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/fh3gxdbn.jpeg" alt=""></p> <p>然后编辑请求的内容,可以修改 header、body 等:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/zcbd2hgm.jpeg" alt=""></p> <p>点击 execute 来发送请求,就可以看到刚才这个请求的响应了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/kpmwo7j2.jpeg" alt=""></p> <p>我们再来构造个 post 请求,修改 header、query、body 等:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/7bdcfi7l.jpeg" alt=""></p> <p>点击 execute 就可以看到这个 post 请求了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/r7ag46fk.jpeg" alt=""></p> <p>除了创建新的请求外,也可以直接修改已有的请求,也就是 compose:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/jt6wnguf.jpeg" alt=""></p> <p>这里变成钢笔就代表在 compose 中:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/n1kgqmqj.jpeg" alt=""></p> <p>修改完点击 execute 就可以发送请求,也可以点击 cancle 来取消 compose。</p> <h2>Repeat</h2> <p>可以 Tools > Repeat 或者右键请求然后点 Repeat 来重发请求:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/dfnw2fid.jpeg" alt=""></p> <p>如果想重发 n 次呢?这可以用高级 repeat 功能。</p> <p>在 Tools > Advanced Repeat 里开启:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/u3nzyj2r.jpeg" alt=""></p> <p>比如重复 10 次、两个并发,重复间隔为 1000ms,在新的 session 里展示结果。</p> <p>效果是这样的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/0eoaryp9.jpeg" alt=""></p> <p>可以用来测试接口。</p> <h2>Throttle</h2> <p>这个是模拟慢速的请求用的,点击这个乌龟的图标开启:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/k1qjxxls.jpeg" alt=""></p> <p>或者在 Proxy > Start Throttling 开启。</p> <p>当然,一般我们只需要对某些域名开启 throttle,可以在 Proxy > Throttle Settings 里设置:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/mu2g2h27.jpeg" alt=""></p> <p>下面可以做更详细的速率设置。</p> <h2>Web Interface</h2> <p>charles 也支持通过 web 网页来控制,在 Proxy > Web Interface Settings 里开启:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/iz8rii9i.jpeg" alt=""></p> <p>可以支持匿名访问,也可以设置用户名和密码访问。</p> <p>这这里可以做一些功能的开启和关闭:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/o0g5ei18.jpeg" alt=""></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/qnton1eo.jpeg" alt=""></p> <p>有同学说,不是已经有客户端了么?</p> <p>本地确实没啥必要用这个,但是远程控制的时候,就可以通过 web interface 了。</p> <h2>Focused Hosts</h2> <p>charles 默认展示全部的 url:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/f4l5x2vl.jpeg" alt=""></p> <p>我们想找 juejin.cn 的请求的话可以过滤:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/x2muq7pz.jpeg" alt=""></p> <p>也可以通过 View > Focused Hosts 来设置关注的域名:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/98wph8ho.jpeg" alt=""></p> <p>这样 charles 就会把其他的域名的请求折叠起来:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/wykcbdsm.jpeg" alt=""></p> <p>也可以在请求上右键开启:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/o7a0t1g3.jpeg" alt=""></p> <h2>Highlight Rules</h2> <p>还可以给不同的 url 指定不同的颜色。</p> <p>在 View > Highlight Rules 开启:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/td7ur1sa.jpeg" alt=""></p> <p>效果如下:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/m5djgtq2.jpeg" alt=""></p> <p>更有用的是可以根据条件来高亮,比如我设置所有 Set-Cookie 的 header 不为空的响应都设置黄色。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/xfucxu0j.jpeg" alt=""></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/mqztr59e.jpeg" alt=""></p> <p>这样所有响应有 Set-Cookie 的 header 的请求都高亮了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/g8se9v19.jpeg" alt=""></p> <p>和 Chrome DevTools 里的 has-response-header 过滤器类似:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/3du7qo6x.jpeg" alt=""></p> <p>但是这个可以过滤的更多。</p> <h2>find</h2> <p>find 是搜索功能,可以在 session、选择的 url 或者某个 url 的请求里搜索。</p> <p>在 Edit > Find 里开启:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/hux7ncrc.jpeg" alt=""></p> <p>比如我选中 juejin.cn,然后在里面搜索 guang:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/11gj2u5y.jpeg" alt=""></p> <p>右键有个 find in 功能,这个就是在某个 path 内搜索:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/3bucpi33.jpeg" alt=""></p> <p>还可以指定搜索 url/header/body。</p> <h2>External Proxy</h2> <p>访问国外的网站需要科学上网,会起一个代理服务器,charles 抓包也要跑一个代理服务器。</p> <p>但是我们网页只能指定代理服务器,那如何同时又能科学上网,又能 charles 抓包呢?</p> <p>这就是 External Proxy 的功能了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/vb3osb7u.jpeg" alt=""></p> <p>可以在 Proxy > External Proxy Settings 设置转发的代理服务器,这样 charles 抓包之后会再转发给别的代理服务器。</p> <p>还可以在 bypass 里设置白名单,这些 url 不走别的代理服务器。</p> <h2>总结</h2> <p>这节我们过了一遍 charles 的其他的功能:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>DNS Spoofing:DNS 欺骗,类似 hosts,但是只影响 http 和 https</li> <li data-list="bullet"><span class="ql-ui"></span>Client Process:展示发请求的进程名</li> <li data-list="bullet"><span class="ql-ui"></span>Compose:compose new 创建一个新的请求,compose 修改已有的请求</li> <li data-list="bullet"><span class="ql-ui"></span>Repeat:重发请求,可以设置重发次数、并发数、重发间隔</li> <li data-list="bullet"><span class="ql-ui"></span>Throttle:模拟慢速请求,可以做具体的速率设置</li> <li data-list="bullet"><span class="ql-ui"></span>Focus:只展示关注的 hosts 的请求,其余的折叠</li> <li data-list="bullet"><span class="ql-ui"></span>Highlight Rules:根据规则来高亮请求,使用不同的颜色</li> <li data-list="bullet"><span class="ql-ui"></span>Web Interface:通过 web 界面来控制一些功能的开启关闭,这适用于远程控制</li> <li data-list="bullet"><span class="ql-ui"></span>Find:在 session、path 等范围内根据关键词搜索</li> <li data-list="bullet"><span class="ql-ui"></span>External Proxy:charles 可以把请求转发给别的代理服务器,比如科学上网的</li> </ol> <p>在调试网络请求的时候,这些都是挺有用的功能。</p> #前端# </div> </body> </html>
【前端调试专栏(41)】Charles 全功能速通(上)
<html> <head></head> <body> <div class="content ql-editor"> <p>Charles 做为主流的代理工具,有很多强大好用的功能,这两节我们就快速过一遍:</p> <h2>charles 代理的两种配置方式</h2> <p>网页默认是走系统代理。</p> <p>可以把 Charles 也设置为系统代理,点击 Proxy > macOS Proxy 即可:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/hwvlbitu.jpeg" alt=""></p> <p>这样就能抓到网页的数据包了。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/8rikc91h.jpeg" alt=""></p> <p>或者不把 charles 设置为系统代理,而是 SwitchyOmega 单独指定网页的代理服务为 charles:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/trvfd8lj.jpeg" alt=""></p> <p>这里的代理服务的端口可以在 Proxy > proxy Settings 里看到:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/w6vh46s9.jpeg" alt=""></p> <p>当然,更好的方式是 auto switch,也就是根据条件自动切换。</p> <p>也就是我们在选项里配置的情景模式:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/hvrakat4.jpeg" alt=""></p> <p>这两种还是很容易理解的:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>默认都是走系统代理,所以把 charles 配置为系统代理服务器就可以抓包了</li> <li data-list="bullet"><span class="ql-ui"></span>网页也可以通过 SwitchyOmega 单独指定代理服务器,这时候请求会直接走 charles</li> </ol> <h2>no caching</h2> <p>可以让 http 请求禁用缓存,在 Tools > No Caching 里开启:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/zlifwqed.jpeg" alt=""></p> <p>当开启之后,你会发现 header 会带上 cache-control: no-cache:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/fxtwtfmc.jpeg" alt=""></p> <p>no-cache 的意思是会用本地的缓存,但每次都协商。</p> <p>而不开启的时候,header 里没有这个:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/4v363vh9.jpeg" alt=""></p> <p>这就是禁用缓存的实现原理。</p> <p>其实浏览器的强制刷新也是这么实现的,你可以强刷一下,抓包看看 header。</p> <p>除了全部请求禁用缓存外,还可以单独指定某些 url 禁用缓存:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/q71wmmfo.jpeg" alt=""></p> <h2>block cookies</h2> <p>这个也很容易理解,就是禁用 cookie 和 set-cookie 的 header:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/zzfmugoc.jpeg" alt=""></p> <p>在 Chrome DevTools 的 Network 里开启这两个 header 的展示:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/i4qpq0yh.jpeg" alt=""></p> <p>在开启 block cookies 之前,是有请求携带或设置 cookie 的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/zxl4sb2k.jpeg" alt=""></p> <p>开启 block cookies 之后,你会发现所有的 set-cookie 都没有了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/hzewyp5w.jpeg" alt=""></p> <p>但请求还是会带上 cookie,这是因为还没到 charles 那层。</p> <p>同样的请求,到 charles 看下,就会发现没有 cookie 了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/v2vc1xfg.jpeg" alt=""></p> <p>关闭 block cookies 之后又有了: <img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/o0c3hi0o.jpeg" alt=""></p> <p>同样,它也可以单独 block 某些 url:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/mabvxoaq.jpeg" alt=""></p> <h2>map remote</h2> <p>charles 可以把请求转发给另一个 url,用 Tools > map remote 的功能:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/ec3nq4qf.jpeg" alt=""></p> <p>比如把 https://juejin.cn 转发到 https://www.baidu.com</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/gz1ikpl5.jpeg" alt=""></p> <p>除了在 Tools > map remote 里开启之外,也可以右键 url 来开启:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/xwamwvaw.jpeg" alt=""></p> <p>好处是请求信息不用自己填一遍了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/tut4sd37.jpeg" alt=""></p> <p>之后也可以在 Tools > map remote 里管理:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/n4uifvaa.jpeg" alt=""></p> <h2>map local</h2> <p>可以把请求转发给另一个 url,也同样可以读取本地的文件内容作为响应,也就是 map local。</p> <p>在 Tools > map local 里,或者右键请求都可以设置:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/0bv5gfdh.jpeg" alt=""></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/cpe93zn6.jpeg" alt=""></p> <p>比如把这个 json 文件的请求换成本地的一个 svg 文件:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/sd1nbgwy.jpeg" alt=""></p> <p>map 之前是这样:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/dn7r91j3.jpeg" alt=""></p> <p>map 之后就是这样了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/a77rv5mh.jpeg" alt=""></p> <p><strong>map local 功能在移动端开发的时候还是挺常用的,比如把请求的 js、css 等换成本地刚打包出来的,就可以在线上环境调试本地代码。</strong></p> <h2>Mirror</h2> <p>mirror 可以把响应内容保存在本地:</p> <p>在 Tools > Mirror 里开启 mirror 功能:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/ellbdpuk.jpeg" alt=""></p> <p>指定保存的位置,之后再访问 url,就会把响应内容保存下来:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/a2219tcp.jpeg" alt=""></p> <p>当然,我们没必要全部保存,也可以开启只 mirror 选择的 url,然后指定 url:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/gc8iv4na.jpeg" alt=""></p> <p>之后在保存的目录下就可以看到该请求的响应数据的文件:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/amkhzzxc.jpeg" alt=""></p> <p>但是这个内容是不能修改的,每次刷新都会覆盖,这也是 mirror 镜像的含义。</p> <p>但是可以配合 map local 使用,比如把这个响应保存下来,然后再复制到别的地方修改之后再 map local。</p> <h2>rewrite</h2> <p>除了转发请求外,自然也支持对请求做修改,也就是 rewrite 功能。</p> <p>在 Tools > rewrite 里开启:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/8iwpdtdl.jpeg" alt=""></p> <p>支持修改的包括 header、host、path、url、query,response status、body,也就是所有的内容都可以改:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/tsyrmecz.jpeg" alt=""></p> <p>比如我添加一个 query param:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/fbmsbvyt.jpeg" alt=""></p> <p>效果如下:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/sqv6ilao.jpeg" alt=""></p> <p>这里操作的时候,如果希望 charles 一直在最上面,可以在 charles > preferences 里开启 always on top:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/cqmpb2hj.jpeg" alt=""></p> <p>它就一直在上面了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/vl8hbrjh.jpeg" alt=""></p> <h2>block list</h2> <p>bloack list 可以禁止一些 url 的请求:</p> <p>在 Tools > block list 里:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/nzazptkt.jpeg" alt=""></p> <p>处理方式可以是 断开连接或者返回 403。</p> <p>当然,也可以右键请求来把它加入到 block list:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/zsxllj6e.jpeg" alt=""></p> <p>比如当设置 block 的 url 的时候:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/euvztyem.jpeg" alt=""></p> <p>效果是这样的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/4v1asry3.jpeg" alt=""></p> <p>当你需要模拟请求失败的时候,可以用这个功能。</p> <h2>allow list</h2> <p>allow list 和 block list 正好反过来,这个是只允许列表中的 url 的请求:</p> <p>比如当设置了 allow list 的时候:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/5xvs196f.jpeg" alt=""></p> <p>所有不在列表中的请求都会失败,可以通过 status-code:0 的过滤器过滤出来:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/cd93b01q.jpeg" alt=""></p> <h2>ssl proxying</h2> <p>https 的代理是很常用的,这个我们上节讲过,需要安装证书到系统。</p> <p>默认抓取到的 https 的数据是加密过的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/rnyoeurz.jpeg" alt=""></p> <p>因为 https 会用证书里的公钥加密请求,用私钥加密响应数据:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/6qanqsza.jpeg" alt=""></p> <p>想要抓取明文数据,需要把证书换成 charles 的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/wq58cakx.jpeg" alt=""></p> <p>首先,在 Proxy > SSL Proxying Settings 里开启这个功能:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/w0nz6kc1.jpeg" alt=""></p> <p>然后点击 help > SSL Proxying > Install Charles Root Certificate,把 charles 证书安装到系统的钥匙串中并信任:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/xwybgjdc.jpeg" alt=""></p> <p>这样证书就换成了 charles 的并被标记为信任:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/f5agbjz3.jpeg" alt=""></p> <p>也就能抓到明文数据了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/7f1eeloa.jpeg" alt=""></p> <h2>breakpoints</h2> <p>断点同样是很常用的功能,这个上节也讲过。</p> <p>在 Proxy > Enable breakpoints 开启这个功能:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/6nocd90t.jpeg" alt=""></p> <p>然后在 Proxy > Breakpoint Settings 里配置:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/tv3g3em3.jpeg" alt=""></p> <p>可以单独开启请求/响应的断点:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/h4uqz3bm.jpeg" alt=""></p> <p>或者右键请求来设置:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/vxh7z7ex.jpeg" alt=""></p> <p>再发送请求的时候或响应的时候就会断住:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/s4i1k8js.jpeg" alt=""></p> <p>可以修改各种内容:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/aglgxll9.jpeg" alt=""></p> <p>然后 execute 即可。</p> <h2>总结</h2> <p>这节我们过了 charles 一些常用的功能:</p> <p>首先设置代理可以用 SwitchyOmega 单独指定网页的代理服务器为 charles,也可以直接把 charles 设置为系统代理。</p> <p>然后我们学习了常用的 charles 代理功能:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>no caching: 禁用缓存</li> <li data-list="bullet"><span class="ql-ui"></span>block cookies:禁用 cookie</li> <li data-list="bullet"><span class="ql-ui"></span>map remote:转发请求到另一个 url</li> <li data-list="bullet"><span class="ql-ui"></span>map local:用本地的文件作为该请求的响应内容</li> <li data-list="bullet"><span class="ql-ui"></span>mirror:把响应保存在本地文件</li> <li data-list="bullet"><span class="ql-ui"></span>rewrite: 重写请求或响应的各种数据</li> <li data-list="bullet"><span class="ql-ui"></span>block list:禁止一些 url 的请求</li> <li data-list="bullet"><span class="ql-ui"></span>allow list:只允许一些 url 的请求</li> <li data-list="bullet"><span class="ql-ui"></span>SSL Proxying:代理 https 请求,用 charles 自己的证书,需要安装 charles 证书到系统并信任</li> <li data-list="bullet"><span class="ql-ui"></span>breakpoints:断点修改请求/响应</li> </ol> <p>这些功能中有重合的部分,比如可以通过 rewrite 实现 no caching 的功能,也就是修改 cache-control 的 header,比如 rewrite 的功能和 breakpoint 的功能很类似,但不同的场景下直接用对应的功能会更方便。</p> <p>这些是比较常用的 charles 代理功能了,下节我们继续学习其余的功能。</p> #前端# </div> </body> </html>
【前端调试专栏(40)】如何用 Charles 断点调试 https 请求?
<html> <head></head> <body> <div class="content ql-editor"> <p>现在的网站基本都是 https 的,而 charles 是常用的 http 抓包工具,所以用 charles 调试 https 请求是常见的需求。</p> <p>这节就来讲下如何用 charles 调试 https 请求,如何断点调试。</p> <p>首先安装 charles,点击 start recording:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/30t20m6z.jpeg" alt=""></p> <p>浏览器访问一些页面,这时候左侧就会展示出抓到的 http/https 请求:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/cxnk4c46.jpeg" alt=""></p> <p>但是这时候抓到的是加密过后的内容,这是 https 的机制导致的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/2m98mk9k.jpeg" alt=""></p> <p>服务端会下发被 CA 认证过的证书,里面包含了公钥,而服务器自己保留私钥,通过这种机制完成对称密钥的传输和身份的认证,之后加密传输数据。</p> <p>中间人拿到的数据自然都是被加密过的,也就是上图的那些乱码:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/6yhnvinq.jpeg" alt=""></p> <p>那抓包工具怎么能拿到明文的数据呢?</p> <p>自己用服务端的证书和服务端对接不就行了?</p> <p>也就是这样:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/0q7l4zko.jpeg" alt=""></p> <p>Charles 自己用服务端的证书来和服务端通信,然后给浏览器一个自己的证书,这样就能解密传输的内容,拿到明文数据了。</p> <p>点击 Proxy 的 SSL Proxy Setting:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/a8g46mx5.jpeg" alt=""></p> <p>添加一条对 juejin 的 https 代理:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/ggy3l4c7.jpeg" alt=""></p> <p>这是 juejin 之前的证书:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/h0ixt3dz.jpeg" alt=""></p> <p>代理之后就换成了 Charles 的证书,但是会提示不安全:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/xkhgtpze.jpeg" alt=""></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/8wi6zzuk.jpeg" alt=""></p> <p>这是因为系统有一个存放所有根证书的地方,要那里存在并且被信任的证书才是安全的。</p> <p>点击 help > SSL Proxying > Install Charles Root Certificate,安装到系统的钥匙串中:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/6bc9edkv.jpeg" alt=""></p> <p>改为始终信任:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/0qvw4dcr.jpeg" alt=""></p> <p>这时候浏览器里就会标记安全了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/y1h9q9yr.jpeg" alt=""></p> <p>并且在 charles 里就会看到明文的 https 请求和响应内容:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/781124wb.jpeg" alt=""></p> <p>这个过程的原理就是这张图:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/wl7iaf7p.jpeg" alt=""></p> <p>现在能够抓 https 包了,但是还不够,现在只能看,很多情况下我们是希望能修改一下请求和响应内容的,这时候就要用断点功能了:</p> <p>右键请求,勾选 breakpoints:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/gn9zi57i.jpeg" alt=""></p> <p>然后开启断点:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/hzhfd4id.jpeg" alt=""></p> <p>刷新页面你会发现它断住了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/h4fttgsy.jpeg" alt=""></p> <p>下面三个按钮分别是取消、终止、执行修改后的请求的意思。</p> <p>上面可以改 url,添加 header,还可以改请求内容和 cookie:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/afpk16ff.jpeg" alt=""></p> <p>点击 execute 之后就会发送请求。</p> <p>之后响应的时候还会断住,这时候就可以用同样的方式修改响应了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/doin8epf.jpeg" alt=""></p> <p>比如我把 title 修改了一下,点击 execute 之后,看到的网页就是修改过后的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/q5q2q5el.jpeg" alt=""></p> <p>这样我们就可以断点调试 https 请求了。</p> <p>为什么可以实现断点功能呢?</p> <p>这个很容易想明白,怎么请求、怎么响应都是 Charles 控制的,那想实现一个断点和编辑的功能,岂不是很容易么?</p> <p>有的同学可能会问,移动端怎么调试呢?</p> <p>其实是一样的,只不过移动端也要把 Charles 证书安装到自己的系统中,需要点击安装 charles 证书到移动设备:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/q10j9bxd.jpeg" alt=""></p> <p>他会提示你在手机设置代理服务器,然后下载 Charles 证书:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/gpr0o2pp.jpeg" alt=""></p> <p>原理和我们在 PC 端下载 Charles 证书是一样的,后续流程也一样。</p> <p>除此以外,chrome 还有一个浏览器插件可以更细粒度的控制代理,叫做 SwitchyOmega:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/rpq24ifo.jpeg" alt=""></p> <p>你可以配置若干个代理服务器,比如 charles 的代理服务器:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/rzc9h0q3.jpeg" alt=""></p> <p>这个可以在 Charles 的 Proxy > Proxy Setting 里配置:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/hxo0czdz.jpeg" alt=""></p> <p>然后就可以配置什么 url 用什么代理,或者不用代理直接连接:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/l1jqtwbh.jpeg" alt=""></p> <p>当你有多个代理服务器,或者想控制有的页面走代理有的不走的时候,就可以用这个插件来控制了。</p> <p>要让你配置的规则生效,这里要选择 auto switch,也就是根据规则自动切换:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/u6n81gvw.jpeg" alt=""></p> <p>这几个选项的意思分别是:</p> <ol> <li data-list="bullet"><span class="ql-ui"></span>直接连接:不走代理</li> <li data-list="bullet"><span class="ql-ui"></span>系统代理:用系统级别的代理配置</li> <li data-list="bullet"><span class="ql-ui"></span>proxy:这个是我们配置的那个代理服务器,可以指定网页走这个代理</li> <li data-list="bullet"><span class="ql-ui"></span>auto switch:根据规则自动切换代理</li> </ol> <p>也可以为这个 url 单独设置代理方式:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/nqzpbm3n.jpeg" alt=""></p> <h2>总结</h2> <p>用 Charles 调试 https 请求是常见的需求,它需要安装 Charles 的证书到本地系统,然后信任,之后就可以抓到明文数据了。</p> <p>原理就是 Charles 会使用服务器的证书来和服务器通信,然后发一个自己的证书给浏览器。</p> <p>Charles 还有断点调试功能,可以修改请求和响应的数据。</p> <p>移动端 https 调试也是同样的原理,只不过需要配置下代理和证书。</p> <p>如果想切换代理服务器或者设置有的页面不走代理,可以用 Chrome 插件 SwitchyOmega 来控制。</p> <p>会断点调试 https 请求还是很有意义的,比如改改 header、改改 body,看看会有啥效果,使用场景有很多。</p> #前端# </div> </body> </html>
【前端调试专栏(39)】远程调试移动端网页的原理
<html> <head></head> <body> <div class="content ql-editor"> <p>前两节我们学习了用 chrome 远程调试安卓移动端网页,用 safari 远程调试 ios 移动端网页:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/rz6abnrl.jpeg" alt=""></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/dcuovqus.jpeg" alt=""></p> <p>那他们是怎么实现呢?</p> <p>首先,我们知道调试是 client server 的架构,比如 chrome 会使用 CDP 协议来传输数据。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/liqy0eor.jpeg" alt=""></p> <p>传递的 CDP 数据可以通过 Protocol monitor 看到:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/w1wztnmv.jpeg" alt=""></p> <p>pc 端是这样,移动端也是这样,只不过传递协议数据的方式不大一样。</p> <p>要想起一个有 CDP server 的浏览器,需要单独指定一些参数。</p> <p>pc 端是跑 chrome 的时候带上 remote-debugging-port 参数,类似这样:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 </div> </div> <p>然后就可以连上这个端口进行调试了,不管你是用 chrome devtools 还是 vscode debugger 或者其余的 frontend UI。</p> <p>那移动端呢?</p> <p>自然也是一样的,要开启调试模式才有这个 CDP Server 可以连接。</p> <p>andorid 的 app 里面的 weview 需要类似这样的方式开启调试:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> // Android 4.4 以上 WebView 才真正使用 Blink 内核,所以需要在此版本及以上系统。 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { WebView.setWebContentsDebuggingEnabled(true); } </div> </div> <p>所以你想调试 app 里网页的话,让移动端开发给你一个调试包即可。</p> <p>有了 CDP Server 的标志就是你访问 9229 端口的 /json 是可以看到所有页面的 websocket 地址的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/se2sn4ra.jpeg" alt=""></p> <p>每个页面就可以连接对应的 ws 服务端来进行 CDP 协议数据的交换,实现调试。</p> <p>那问题来了,pc 端怎么访问移动端的页面呢?</p> <p>有两种方式,第一种是通过 USB,连上 USB 之后就可以使用 android 的 adb 工具来转发端口:</p> <div class="ql-code-block-container"> <div class="ql-code-block"> adb forward tcp:9229 </div> </div> <p>之后就可以进行通信,自然也就可以调试了。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/a6y614ka.jpeg" alt=""></p> <p>ios 下也有类似的工具。</p> <p>当然,也可以通过 wifi 的方式,只不过这种就需要单独起一个 ws 服务做转发了。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/ldpl6ixn.jpeg" alt=""></p> <p>这个 ws 服务做的事情比较简单,就是原封不动转发了下 CDP 数据。</p> <p>这样就能实现 wifi 调试,扫码调试等功能。</p> <p>这就是远程调试移动端网页的原理。</p> <p>最后,android 这个实时看到移动端网页的界面是怎么实现的呢?</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/2julh88j.jpeg" alt=""></p> <p>其实就是一帧帧截图实现的。</p> <p>CDP 协议里有这样一个传输 base64 的截图数据的事件:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/pgosg8o3.jpeg" alt=""></p> <h2>总结</h2> <p>我们可以通过 chrome、safari 调试移动端的网页,原理就是开启调试模式之后,可以通过 CDP server 和 client 进行通信,从而实现调试。</p> <p>pc 端开启调试只要指定 remote-debugging-port 的启动参数即可,而移动端则需要指定 webview 的参数。</p> <p>可以通过 USB 调试,是因为 adb 做了端口转发,也可以通过 wifi 调试,这种就需要自己实现一个 ws 服务做中转了。</p> <p>理解了远程调试的原理,当你再用各种移动端网页调试工具、小程序调试工具、跨端调试工具等,心里就有底了。</p> #前端# </div> </body> </html>
【前端调试专栏(38)】如何远程调试 ios 移动端网页
<html> <head></head> <body> <div class="content ql-editor"> <p>上节学会了用 Chrome 远程调试 Android 网页,这节来调试下 iOS 网页。</p> <p>ios 网页调试只能用 safari 浏览器。</p> <p>打开偏好设置 > 高级,勾选显示“开发”菜单:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/v76k99ij.jpeg" alt=""></p> <p>之后就会出现“开发”菜单,下面会展示所有连接的设备:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/usu8115m.jpeg" alt=""></p> <p>它会提示你在 iPhone 上启用网页检查器。</p> <p>这个是在 设置 > safari 浏览器 > 高级里开启的:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/38dmca8a.jpeg" alt=""></p> <p>网页检查器下面也有这么一行提示:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/00752coy.jpeg" alt=""></p> <p>之后在 Safari 浏览器里打开一个页面,比如 baidu.com:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/a2g9s8mj.jpeg" alt=""></p> <p>在电脑的 Safari 浏览器的开发菜单里就会显示出这个网页:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/btv89j8t.jpeg" alt=""></p> <p>不只是 Safari 浏览器,一些 APP 里内嵌的网页开启了调试的话也可以连接。</p> <p>之后就可以在电脑上远程调试 iphone 上的网页了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/7pbey65v.jpeg" alt=""></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/sibub31l.jpeg" alt=""></p> <p>可以调试网络请求:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/8dxar6vs.jpeg" alt=""></p> <p>可以断点调试:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/zdrqhd7e.jpeg" alt=""></p> <p>比如可以在所有未捕获的异常处断住,这是异常断点。</p> <p>可以在请求某个 URL 的时候断住,这是 URL 断点:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/ncxuody5.jpeg" alt=""></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/aqjbwpmu.jpeg" alt=""></p> <p>可以添加事件断点:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/lc6zac9d.jpeg" alt=""></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/ugwqa61p.jpeg" alt=""></p> <p>还可以在 requestAnimationFrame、setInterval 的回调处断住。</p> <p>而且在 Chrome DevTools 里的 DOM 断点,这里也同样支持:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/17uuc3r5.jpeg" alt=""></p> <p>也就是元素子树修改、属性修改、节点移除的时候断住。</p> <p>而且也可以分析网页的图层:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/ar3c8c7n.jpeg" alt=""></p> <p>基本所有的网页调试功能都支持。</p> <h2>总结</h2> <p>调试 iphone 上的网页要用 Safari 浏览器,支持各种断点、支持元素审查、网络请求的调试等等各种功能。</p> <p>当你需要调试 ios 的网页的时候,不管是 safari 打开的网页,还是 APP 里内嵌的网页,都可以用这种方式来调试。</p> #前端# </div> </body> </html>
【前端调试专栏(37)】如何远程调试 android 移动端网页
<html> <head></head> <body> <div class="content ql-editor"> <p>作为前端开发,我们每天都会用 Chrome DevTools 调试 Chrome 的网页,但其实它还可以远程调试安卓手机的网页。</p> <p>那 Chrome DevTools 如何远程调试安卓网页呢?它的实现原理是什么?</p> <p>今天我们就来了解一下:</p> <h2>远程调试安卓网页</h2> <p>用数据线把安卓手机和电脑连接起来,在手机设置里打开 USB 调试:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/6rfz2h5c.jpeg" alt=""></p> <p>然后在 chrome 打开 chrome://inspect 页面,勾选 Discover USB devices(默认是勾选的):</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/ecakzggs.jpeg" alt=""></p> <p>这时候下面就会出现一个提示:请在设备上接受 debugging 会话</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/hrz26m9v.jpeg" alt=""></p> <p>在手机上点击确定,就会建立调试会话:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/3cpo3e4u.jpeg" alt=""></p> <p>下面就会列出所有可以调试的网页:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/vd4diu4u.jpeg" alt=""></p> <p>浏览器里的网页,或者 APP 调试包的 webview 的网页都会列出来。</p> <p>点击 inspect 就可以调试了:</p> <p>可以审查元素:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/ko3109pd.jpeg" alt=""></p> <p>可以打断点:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/x94qvxop.jpeg" alt=""></p> <p>也可以用 Performance 分析性能:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/i75xexzt.jpeg" alt=""></p> <p>各种调试 PC 网页的功能基本都支持。 这样就可以愉快的调试安卓的移动端网页了。</p> <p>不过这个过程你可能会遇到这样的问题,打开的窗口是空白的或者是 404:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/eq2jw4qz.jpeg" alt=""></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/fxld0dxz.jpeg" alt=""></p> <p>这是因为调试的目标可能是任意 chrome 版本,那么 Chrome DevTools 自然也要用相应的版本才行,所以就需要动态下载。</p> <p>而动态下载的 devtools 网页是在 google 域名下的,需要科学上网才行。</p> <p>科学上网之后,就可以正常的下载 Chrome DevTools 来做调试,也就不会白屏或 404 了。</p> <p>但也不是每次都要科学上网,一个调试目标只需要下载一次 Chrome DevTools 的代码,之后就可以一直用了。</p> <p>我们了解了 Chrome DevTools 怎么调试安卓的网页,那它的原理是什么呢?</p> <h2>Chrome DevTools 的原理</h2> <p>Chrome DevTools 被设计成了和 Chrome 分离的架构,两者之间通过 WebSocket 通信,设计了专门的通信协议: Chrome DevTools Protocol。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/afe680ow.jpeg" alt=""></p> <p>这样只要实现对接 CDP 协议的 ws 服务端,就可以用 Chrome DevTools 来调试,所以 Chrome DevTools 可以用来调试浏览器的网页、调试 Node.js,调试 Electron 等。</p> <p>那自然也就可以远程调试安卓手机的网页了,只要开启了 USB 调试,那手机和电脑就可以做网络通信,从而实现基于 CDP 的调试。</p> <p>这个 CDP 的调试协议是 json 格式的,如果想看它传输了什么也是可以的:</p> <p>在 Chrome DevTools 的设置里开启 Protocol Monitor:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/olfqt8bf.jpeg" alt=""></p> <p>然后在 more tools 里打开 Protocol Monitor 面板:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/abou7sbc.jpeg" alt=""></p> <p>然后你就可以在 Protocol Monitor 面板里看到所有的 CDP 协议的数据交互了:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/8e4m984o.jpeg" alt=""></p> <p>这就是调试的实现原理。</p> <p>那如何知道哪些界面是网页,哪些是原生绘制的呢?</p> <p>Android 下可以在设置里打开显示布局边界:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/s2b32ljr.jpeg" alt=""></p> <p>如果是原生的组件,就会显示边框:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/b5178unc.jpeg" alt=""></p> <p>而没有边框的,就是网页。比如钉钉的这个界面:</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1797461877294809089/zoi78ah8.jpeg" alt=""></p> <h2>总结</h2> <p>Chrome DevTools 和 Chrome 是分离的架构,两者通过 WebSocket 通信,通信协议是 Chrome DevTools Protocol,可以在 Protocol Monitor 里看到 CDP 的数据交互。</p> <p>因为这样的实现原理,Chrome DevTools 可以调试很多目标,其中就包括 USB 设备。</p> <p>打开 USB 调试之后,在 chrome://inspect 页面就可以看到可调试的网页了,点击对应的网页就可以调试。</p> <p>要注意的是调试的目标浏览器要和用的 Chrome DevTools 版本一一对应才行,所以第一次调试会先下载 Chrome DevTools,这需要访问 google 的域名,如果没有科学上网,会有白屏和 404 的问题。</p> <p>理解了调试的原理,Chrome DevTools 调试安卓网页的流程,就可以愉快的远程调试安卓手机的网页了。</p> #前端# </div> </body> </html>
