99%的程序都没有考虑的网络异常

qcloudcommunity · · 660 次点击 · 开始浏览    置顶
这是一个创建于 的主题,其中的信息可能已经有所发展或是发生改变。

> 本文由云+社区发表 绝大多数程序只考虑了接口正常工作的场景,而用户在使用我们的产品时遇到的各类异常,全都丢在看似 ok 的 try catch 中。如果没有做好异常的兼容和兜底处理,会极大的影响用户体验,严重的还会带来安全和资损风险。 接口异常,通常可以分为以下三类: - **CGI 逻辑出错。**如调用方入参缺失类业务逻辑报错; - **服务不稳定。**如服务器不稳定导致 nginx 各类 500、502,cgi 路径调整导致的 404 - **用户网络环境差。**如,网络不稳定、网速慢、运营商劫持等 那么,我们在写代码时,**如何快速的模拟这些接口异常,做好程序的兼容处理呢?** 今天向大家介绍网络调试神器 whistle 的网络异常调试方法,如果你还没用过 whistle,请参考《8102 年的程序员不需要 Hosts 和 Fiddler》。 假设我们有以下前端页面 index.html,放置在自己的本地路径: ```javascript <p id="success" style="color:green;"></p> <p id="fail" style="color:red;"></p> <script> fetch(`/mock?r=${Math.random()}`) .then(response => { return response.json() }) .then(v => { document.getElementById('success').innerHTML = v.data; }).catch(err => { document.getElementById('fail').innerHTML = err.message; }) </script> ``` 接下来,打开 whistle Rules 配置面板 http://127.0.0.1:8899/#rules ,配置模拟的 demo page 和 mock CGI: ```javascript */mock file://({"code":0,"data":"success"}) # 配置 mock cgi 为模拟的 json 数据 example.com file:///Users/kaiye/Projects/Markdown/20181213/ # 配置任意域名到本地 demo 目录,这里注意替换成自己的路径 ``` 打开 http://example.com ,正常逻辑下页面展示出了绿色的 **success** ,现在我们开始加入一些网络异常。 ## 1、业务逻辑异常处理 例如 CGI 没有返回 `data` 字段,而是返回了一个错误码 `code` 和对应的 `message`,针对这种业务逻辑异常我们只需在第二个 `then` 中做好 code 值的判断即可(注意,这里的 code、message、data 只是示例,实际业务 CGI 中的 JSON 结构体的字段名很可能不同): ```javascript fetch(`/mock?r=${Math.random()}`) .then(response => response.json()) .then((v) => { // 业务逻辑异常处理 if (v.code !== 0) { return Promise.reject(new Error(`ERROR_LOGIC_CODE:${v.code}`)); } document.getElementById('success').innerHTML = v.data; }) .catch((err) => { document.getElementById('fail').innerHTML = err.message; }); ``` 相应的 whistle 配置如下: ```javascript */mock file://({"code":12345,"message":"some_logic_error"}) # 模拟业务逻辑异常 ``` ## 2、服务器异常处理 如果服务器直接抛出了 502 错误码,我们希望代码能给用户提示的同时,再做一个异常上报。 ```javascript fetch(`/mock?r=${Math.random()}`) .then((response) => { // 服务器异常处理 if (response.ok) { return response.json(); } return Promise.reject(new Error(`ERROR_STATUS_CODE:${response.status}`)); }) .then((v) => { // 业务逻辑异常处理 if (v.code !== 0) { return Promise.reject(new Error(`ERROR_LOGIC_CODE:${v.code}`)); } document.getElementById('success').innerHTML = v.data; }) .catch((err) => { const [type, value] = err.message.split(':'); // 异常类型上报 console.log(type, value); document.getElementById('fail').innerHTML = err.message; }); ``` 通过 whistle 的模拟配置如下: ```javascript */mock statusCode://502 # 模拟 HTTP 状态码异常 ``` ## 3、接口被劫持注入 如果 CGI 被运营商劫持注入,可能导致接口返回一个不合法的 JSON 结构,最前面的 `response.json()` 会抛异常,我们可以提前 catch 住: ```javascript fetch(`/mock?r=${Math.random()}`).then((response) => { // 服务器异常处理 if (response.ok) { return ( response .json() // 接口数据解码异常处理 .catch(err => Promise.reject(new Error('ERROR_DECODE_JSON'))) ); } return Promise.reject(new Error(`ERROR_STATUS_CODE:${response.status}`)); }); ``` whistle 模拟配置如下: ```javascript */mock file://(<div>hijacking</div>{"code":0,"data":"success"}) # 模拟接口被劫持注入 1 ``` 借助 [htmlAppend](http://wproxy.org/whistle/rules/htmlAppend.html) 和 [values](http://wproxy.org/whistle/webui/values.html) 配置,可以模拟更复杂的注入示例: ~~~javascript */mock file://({"code":0,"data":"success"}) htmlAppend://{hijacking.html} # 模拟接口被劫持注入 2 ```hijacking.html <script> alert('hijacking') </script> ``` ~~~ ## 4、用户网络不稳定 **如果我们要模拟请求发出 10 秒后断网或网络不通的情况**,可以通过 whistle 这样配置: ```javascript */mock reqDelay://10000 enable://abort # 模拟 10 秒超时后网络不通 ``` 让用户苦苦等待 10 秒,再报错的体验太糟糕。我们可以封装一个能配置超时时间的请求发送函数,同时把上面提到的错误异常都一起配置进来。 ```javascript <p id="success" style="color:green;"></p> <p id="fail" style="color:red;"></p> <script> function myFetch(url, configOptions) { const options = Object.assign( { timeout: 3000 }, configOptions ) const { timeout } = options return new Promise((resolve, reject) => { // 超时异常处理 const timer = setTimeout(() => { reject(new Error(`ERROR_TIMEOUT:${timeout}`)) }, timeout) fetch(url, options) .then(data => { clearTimeout(timer) resolve(data) }) .catch(err => { clearTimeout(timer) reject(err) }) }) .then(response => { // 服务器异常处理 if (response.ok) { return ( response .json() // 接口数据解码异常处理 .catch(err => Promise.reject(new Error('ERROR_DECODE_JSON'))) ) } else { return Promise.reject( new Error(`ERROR_STATUS_CODE:${response.status}`) ) } }) .then(v => { // 业务逻辑异常处理 if (v.code !== 0) { return Promise.reject(new Error(`ERROR_LOGIC_CODE:${v.code}`)) } else { return v.data } }) .catch(err => { const [type, value] = err.message.split(':') // 异常类型上报 console.log(type, value) return Promise.reject(err) }) } myFetch(`/mock?r=${Math.random()}`) .then(data => { document.getElementById('success').innerHTML = data }) .catch(err => { document.getElementById('fail').innerHTML = err.message }) </script> ``` 这样,自定义的 `myFetch` 只需关注业务具体逻辑,针对不同的 catch error 做对应的处理。 除以上提到的协议命令字外,**whistle 还支持 resSpeed 用于模拟低网速传输(单位:kb/s),tpl 协议则可以根据请求传入参数来动态模拟不同的数据。**在 Frames 面板,还可以对 WebSocket/Socket 请求进行暂停、延迟等网络异常的模拟。 ## 小程序 fetch API 实现 最后,留一道思考题。 近来微信小程序开发非常火,小程序原生提供的 wx.request API 能用于发送 HTTPS 请求,请在它的基础之上进行封装,支持 promise 调用和 `timeout` 超时时间定义(小程序默认的请求超时定义在 app.json 中,不够灵活),并针对以上提到的 HTTP 状态码异常、接口劫持注入、慢网络、无网络状态等各种网络异常进行兼容处理。 欢迎留言分享你的代码实现 **此文已由作者授权腾讯云+社区发布** ****

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

660 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传