文档章节

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

腾讯云开发者社区
 腾讯云开发者社区
发布于 2018/12/26 17:50
字数 1511
阅读 84
收藏 2

本文由云+社区发表

绝大多数程序只考虑了接口正常工作的场景,而用户在使用我们的产品时遇到的各类异常,全都丢在看似 ok 的 try catch 中。如果没有做好异常的兼容和兜底处理,会极大的影响用户体验,严重的还会带来安全和资损风险。

接口异常,通常可以分为以下三类:

  • **CGI 逻辑出错。**如调用方入参缺失类业务逻辑报错;
  • **服务不稳定。**如服务器不稳定导致 nginx 各类 500、502,cgi 路径调整导致的 404
  • **用户网络环境差。**如,网络不稳定、网速慢、运营商劫持等

那么,我们在写代码时,如何快速的模拟这些接口异常,做好程序的兼容处理呢?

今天向大家介绍网络调试神器 whistle 的网络异常调试方法,如果你还没用过 whistle,请参考《8102 年的程序员不需要 Hosts 和 Fiddler》。

假设我们有以下前端页面 index.html,放置在自己的本地路径:

<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:

*/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 结构体的字段名很可能不同):

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 配置如下:

*/mock file://({"code":12345,"message":"some_logic_error"}) # 模拟业务逻辑异常

2、服务器异常处理

如果服务器直接抛出了 502 错误码,我们希望代码能给用户提示的同时,再做一个异常上报。

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 的模拟配置如下:

*/mock statusCode://502 # 模拟 HTTP 状态码异常

3、接口被劫持注入

如果 CGI 被运营商劫持注入,可能导致接口返回一个不合法的 JSON 结构,最前面的 response.json() 会抛异常,我们可以提前 catch 住:

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 模拟配置如下:

*/mock file://(<div>hijacking</div>{"code":0,"data":"success"}) # 模拟接口被劫持注入 1

借助 htmlAppendvalues 配置,可以模拟更复杂的注入示例:

*/mock file://({"code":0,"data":"success"}) htmlAppend://{hijacking.html} # 模拟接口被劫持注入 2
```hijacking.html
<script>
alert('hijacking')
</script>
```

4、用户网络不稳定

如果我们要模拟请求发出 10 秒后断网或网络不通的情况,可以通过 whistle 这样配置:

*/mock reqDelay://10000 enable://abort # 模拟 10 秒超时后网络不通

让用户苦苦等待 10 秒,再报错的体验太糟糕。我们可以封装一个能配置超时时间的请求发送函数,同时把上面提到的错误异常都一起配置进来。

<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 状态码异常、接口劫持注入、慢网络、无网络状态等各种网络异常进行兼容处理。

欢迎留言分享你的代码实现

此文已由作者授权腾讯云+社区发布


© 著作权归作者所有

腾讯云开发者社区
粉丝 180
博文 246
码字总数 661360
作品 0
深圳
私信 提问
99%的程序都没有考虑的网络异常?使用Fundebug.notify()主动上报

近日看到一篇文章99%的程序都没有考虑的网络异常,开篇提到: > 绝大多数程序只考虑了接口正常工作的场景,而用户在使用我们的产品时遇到的各类异常,全都丢在看似 ok 的 try catch 中。如果...

Fundebug
07/08
0
0
kubernetes性能测试实践

本文经作者张文娟授权发布。 欢迎访问网易云社区,了解更多网易技术产品运营经验。 概述 随着容器技术的发展,容器服务已经成为行业主流,然而想要在生产环境中成功部署和操作容器,关键还是...

网易云
2018/11/23
0
0
自动检测iOS网络并可跳转至设置界面设置网络

转载网址:http://blog.sina.com.cn/s/blog_6bb90d70010123h8.html 还没有对这篇文章中提到的 尝试过,谁用过之后 可以达到效果,吱一声哈! 若不想写繁琐的网络状况判断代码,并且应用程序能...

meilidashijie
2013/01/02
0
0
移动APP安全测试中最容易被遗忘的问题

1.软件权限 1)扣费风险:包括发送短信、拨打电话、连接网络等 2)隐私泄露风险:包括访问手机信息、访问联系人信息等 3)对App的输入有效性校验、认证、授权、敏感数据存储、数据加密等方面...

fiawfo
2017/02/27
90
0
如何处理异常? catch Exception OR catch Throwable

在Java中,当你需要统一处理异常的时候,你是会选择catch (Exception),还是直接catch (Throwable)? Java的异常体系 Throwable: Java中所有异常和错误类的父类。只有这个类的实例(或者子类...

狐狸糊涂
2014/12/23
0
0

没有更多内容

加载失败,请刷新页面

加载更多

php 遇到 No input file specified的解决方法

(一)IIS Noinput file specified 方法一:改PHP.ini中的doc_root行,打开ini文件注释掉此行,然后重启IIS 方法二: 请修改php.ini 找到 ; cgi.force_redirect = 1 去掉前面分号,把后面的1...

chenhongjiang
今天
5
0
MySQL 基础

一、常用命令 在命令行中,配置好环境变量后,通过cmd可以直接进入mysql命令行模式,同时列举几种常用命令 # 进入mysql数据库,密码可以先不写,打完-p后再输入,防止被别人看到mysql -u账...

华山猛男
今天
6
0
简单的博客系统(四)Django请求HTML页面视图信息--基于函数的视图

1. 编写用于查询数据的功能函数 应用目录 下的 views.py 文件通常用于保存响应各种请求的函数或类 from django.shortcuts import renderfrom .models import BlogArticles# Create your ...

ZeroBit
今天
5
0
用脚本将本地照片库批量导入到Day One中

因为目前iCloud 空间已经不足,其中95%都是照片,之前入手了DayOne,且空间没有限制,订阅费一年也不少,再加上DayOne作为一款日记App 也比较有名,功能方面最大的就是地理视图与照片视图,尤...

在山的那边
昨天
21
0
jupyter部署安装

python373 -m ipykernel install --name python373 ipython kernelspec list sc create myjupyterservice binpath="D:\apply\Python373\Scripts\jupyter-notebook --config=V:/my_work/jupyt......

mbzhong
昨天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部