欢迎来到风尚网
风尚网首页 > 首页 > 阅读 > 猫哥网络编程系列:HTTP PEM 万能调试法

猫哥网络编程系列:HTTP PEM 万能调试法

作者:猫哥学前班 2016-02-19 12:12 来源:简书 编辑:美朵

文章摘要
注:本文内容较长且细节较多,建议先收藏再阅读,原文将在Github上维护与更新。在HTTP接口开发与调试过程中,我们经常遇到以下类似的问题:为什么本地环境接口可以调用成功,但放到手机上就跑不起来?这个接口很复杂,内部调用了好几个其他接口,如何定位问题究竟出在哪一步?后端开发还没有把接口提供好,前端开发任务无法推进……「猫哥网络编程系列」最核心的任务便是向各位分享一个我从多年的前后端项目中总结而来的「万能」HTTP调试法,掌握并从网络编程原理上理解它,能让我们顺利定位并解决所有HTTP接口问题。由于该方法主

注:本文内容较长且细节较多,建议先收藏再阅读,原文将在 Github 上维护与更新。

在 HTTP 接口开发与调试过程中,我们经常遇到以下类似的问题:

  • 为什么本地环境接口可以调用成功,但放到手机上就跑不起来?
  • 这个接口很复杂,内部调用了好几个其他接口,如何定位问题究竟出在哪一步?
  • 后端开发还没有把接口提供好,前端开发任务无法推进……

「猫哥网络编程系列」最核心的任务便是向各位分享一个我从多年的前后端项目中总结而来的「万能」HTTP 调试法,掌握并从网络编程原理上理解它,能让我们顺利定位并解决所有 HTTP 接口问题。由于该方法主要涉及到的知识点包括 HTTP 代理(Proxy)、编辑(Edit)与数据模拟(Mock),因此我称之为「HTTP PEM 调试法」。

接下来,我们就针对前面提出的几个问题,详细讲解下 PEM 调试法的思路。

如何调试线上 App 中的 H5 页面?

「HTTP PEM 调试法」之 Proxy

在上一期《猫哥网络编程系列:详解 BAT 面试题》中,我们有介绍到 Windows 下的 Fiddler 和 Mac 下的 Charles 这两款 HTTP 抓包工具,其实它们就是两个 HTTP 代理服务器(HTTP Proxy Server)。

由于 HTTP 是一种符合 REST 架构风格(Representational State Transfer)的协议,具有无状态(Stateless)与统一接口(Uniform Interface)的架构约束,因此其代理机制的实现十分的简单。

打个比方,我们可以把 Proxy Server 理解成一个快递中转站,当一个包裹经过中转站时,包裹的信息(发件人、收件人与包裹里的货物)通常不会做任何的改动,直接发往下一个中转站或顾客手中。但中转站完全有能力修改快递单信息、拆箱检查货物,甚至是私吞或调换货物。

当我们需要快速定位「线上产品的接口问题」时,如果没有源码、数据、依赖服务和足够的时间去搭建一个测试环境,则通常会使用 HTTP 代理服务器来进行快速抓包调试。

Fiddler 默认只允许本地 IP(127.0.0.1)使用代理服务,通过设置「Tools -> Connections -> Allow remote computers to connect」可以开启其他 IP(通常是同一局域网内的其他设备)使用代理服务。

Fiddler 开启 Remote Proxy

Charles 默认开放代理服务,但陌生设备首次连接时需要授权确认,通过以下配置可以设置成无需授权。

授权所有设备使用 Charles 代理服务

以上两款软件默认的代理端口均是 8888 ,软件开启之后,我们可以在对应的平台终端下通过 ipconfig(Windows) 或 ifconfig(Mac)命令查询本机的局域网 IP,还可以使用 telnet 命令检查代理通道是否可用。(注:Win7 下如何开启 telnet 命令请参考百度经验。

以下是 Windows 下 CMD 终端的使用截图,Mac 系统下请类比参考。

CMD 下 ipconfig 与 telnet

接下来,我们将手机的 Wi-Fi 代理设置为上述的 IP 与 端口号,以下是 iOS 的设置截图( Android 系统通常是长按已连接的 Wi-Fi ,在弹出的高级设置菜单中配置代理服务器)。

iOS 下设置 HTTP 代理

至此,手机上任意应用发起的 HTTP 请求都将会被代理服务器(本例中的 Fiddler/Charles 软件)监听到。

「HTTP PEM 调试法」之 Edit

通过代理服务器监听到 HTTP 请求之后,我们可以通过浏览报文的详细信息,定位出可能的接口问题。

Fiddler 与 Charles 都具有同样强大的 HTTP 编辑(Edit)、重发(Replay/Repeat)、断点(Breakpoints)功能。Charles 的基础与高级用法请参考《Charles 从入门到精通》,Fiddler 教程可以参考 OSChina 专题《HTTP调试代理 Fiddler》,以下介绍 Fiddler 的部分常见用法。

Fiddler Edit 与 AutoResponder

抓到手机 HTTP 请求之后,通过编辑(Unlock For Editing)和重发(Replay)操作可以不断地调试接口的响应是否符合预期。

Fiddler 手动修改调试请求

通过设置自动响应规则(AutoResponder Rules)可以将响应头设置成常见状态码的返回,或将响应体映射成本地文件,通过外部编辑器修改文件内容进行调试。

其中,若设置响应为 *bpu*bpafter 可以在请求前与响应前的事件触发时进行断点调试,十分方便。

Fiddler 将请求映射本地文件

需要注意的是,在 Fiddler 中使用 Replay 功能重发请求时,请求由 Fiddler 代理重新发起而非手机,因此手机 App 中的 H5 不会有任何变化。只有重新刷新 App 的 H5 页面,配合 HTTP 断点调试(Breakpoints )的方式才可以让修改后的 HTTP 响应体在 App中生效。这里介绍另外一种配合 Weinre 的调试用法。

Weinre 基本用法

Weinre 属于知名 Hybrid 框架 Cordova 中的一款 Web App 远程调试工具。通过在页面中注入一段 JS 脚本,可以在 PC 和手机端的 H5 页面之间建立一个 Socket 双向数据传输通道。原理上可以理解为,当我们在 PC 端的后台进行 debug 时,相关的操作被序列化成一组 JSON 字符串,数据经由通道传输给手机端中的 H5 页面,页面在接收到这些数据之后反序列化成相应的 JS 脚本操作,在其 window 上下文中执行,并将执行的结果回传给通道,PC 端的 Chrome 通过监听通道获取到相应的数据在 debug 后台中展现出来。

以下介绍 Weinre 的基本用法:

  1. 通过 npm 全局安装 weinre: npm install -g weinre
  2. 在本地 8081 端口上启动 weinre 服务:weinre --boundHost 0.0.0.0 --httpPort 8081

    通常在 Node.js 的服务中绑定 IP 为 0.0.0.0 而非 127.0.0.1(本地 IP),意味着可以让任意来源的 IP 访问该服务

  3. 通过上文介绍的 ipconfig(Mac 为 ifconfig)命令获取本机 IP 后,在本机 Chrome 浏览器中访问 Weinre 管理后台:http://10.2.69.47:8081 (本例中我的 IP 为 10.2.69.47,请注意将其替换成自己的局域网 IP)
  4. 在管理后台我们能看到相关使用说明,要求将以下脚本插入需要调试的 H5 页面中:<script src="http://10.2.69.47:8081/target/target-script-min.js#anonymous"></script>
  5. 将以上脚本插入进 H5 页面后,我们在 PC 端 Chrome 中,通过 http://10.2.69.47:8081/client/#anonymous 后台点击进入相应的客户端调试界面

问题是,我们「如何将 Weinre Script 自动注入到手机的 H5 页面中」?

HTTP Script 注入

想必用过中国电信宽带的同学都有过这样的体验:在刚开始浏览网页时,会自动跳出一些「宽带升级优惠」、「宽带缴费提醒」之类的页面。这种耍流氓的方式便是宽带运营商在 HTTP 代理层面的 Script 注入行为。前面已经提到 HTTP 协议是一种 REST 风格的架构,并且他的头部与主体报文为字符串文本流(对比二机制、十六进制数据流),在不使用 HTTPS 的情况下,很容易被中间路由或代理网关进行消息篡改。

通过 Fiddler Script 特性,我们可以自动对经过 Fiddler 的 HTTP 流量进行二次修改,注入任意内容(Mac 用户若已了解相关知识点,请直接跳至下方的 Charles 截图)。

打开 Fiddler 菜单「Rules -> Customize Rules… 」,如果是首次开启会要求先下载安装 Fiddler ScriptEditor。打开 Fiddler ScriptEditor 之后,找到以下代码块(或使用菜单「Go -> to OnBeforeResponse」):

static function OnBeforeResponse(oSession: Session) {
    if (m_Hide304s && oSession.responseCode == 304) {
        oSession["ui-hide"] = "true";
    }
}

Fiddler Script 使用的编程语言是 JScript.NET(JavaScript 和 C# 的混合语法,类似 TypeScript),OnBeforeResponse 是 HTTP Response 响应前的事件函数,我们只需要在这里判断「如果开启了 Weinre Debug 功能,那么就在所有的 HTML 响应体中注入 Weinre Script」,以下是我修改的示例代码,覆盖以上代码块即可。

public static RulesOption("Enable Weinre Script")
var m_EnableWeinreScript: boolean = true;

public static var g_weinreScriptString: String = ’<script src="http://127.0.0.1:8080/target/target-script-min.js#anonymous"></script>’;

public static ToolsAction("Config Weinre Script")
function ConfigWeinreScript(){ 
    g_weinreScriptString = FiddlerObject.prompt("Text beblow will inject into HTML pages when ’Enable Weinre Script’ rule is Enabled.", g_weinreScriptString , "Please Input the Weinre Script");
}

static function OnBeforeResponse(oSession: Session) {
    if (m_Hide304s && oSession.responseCode == 304) {
        oSession["ui-hide"] = "true";
    }
    if (m_EnableWeinreScript && oSession.oResponse.headers.ExistsAndContains("Content-Type","text/html")){
        oSession.utilDecodeResponse();

        if(oSession.utilFindInResponse("</html>", false)>-1){
            oSession["ui-backcolor"] = "#5E30B5";
            oSession["ui-color"] = "white";
            oSession.utilReplaceRegexInResponse("</html>", g_weinreScriptString + ’</html>’);
        }
    }
}

修改保存后重启 Fiddler(或使用菜单「Tools -> Reset Script」)以生效规则,接下来运行「Tools」菜单中新出现的「Config Weinre Script」,将 127.0.0.1:8080 替换成自己本机的局域网 IP 与 weinre 服务端口号,同时开启菜单「Rules -> Enable Weinre Script」。至此,所有 HTML 页面将会被自动注入 Weinre Script,之后我们就可以在 weinre 后台中开始调试相关页面。

以下是参考截图:

Fiddler 中的 HTTP Script 注入

可以看到 HTTP 响应体中已经被动态注入 Weinre Script。

在 Mac Charles 下的 Script 注入配置更加容易,只需利用其 「Rewrite」功能进行简单的配置即可,参看下图:

Charles Rewrite 配置 HTTP Script 注入

通过 Fiddler/Charles 代理工具将 JS 脚本注入成功后,我们便可以通过前文提到的 weinre 后台开始 debug 相应的页面,以下是在 iPhone 模拟器中调试新浪微博界面的截图:

Weinre 后台 debug Webview H5 页面

使用该方法可以调试 Android 和 iOS 中「任意 App 的 H5 页面」,但由于主要使用了 weinre 服务,其原理决定了该方法无法像真正的 Chrome DevTools 一样支持 JS 断点调试、Profiles 性能分析等功能,具有一定的局限性。在实际 Web App 开发过程中,推荐使用以下工具进行调试 :

  • 微信官方调试工具 调试基于微信的 Web App
  • Chrome Remote Debugging 调试 Android Web App
  • Safari Remote Debugging 调试 iOS Web App

由此可见,「HTTP PEM 调试法」是一个通用的 HTTP 接口调试方案,可以用来快速定位线上接口问题,对于开发人员来说掌握其背后的 HTTP 协议及其代理机制的原理更加重要,接下来我们聊聊常见的 HTTP 接口开发协作方法与 Mock 思路。

我的开发任务没法推进,因为某某的接口还没提供给我。

「HTTP PEM 调试法」之 Mock

希望新手程序员在看完这一章节之后,不要再向你的项目组和上级反馈这样的说法,因为 HTTP Mock(接口数据模拟)是一项网络编程的基础技能,从实际项目经验来看,大部分基于 HTTP 接口的任务都可以并行开发。

最简 HTTP API

不同岗位(例如前端开发与后台开发)或不同业务(例如订单系统与账户系统)的开发人员开始并行开发任务之前,首先要做的应该是对耦合和相互依赖的任务进行边界划分与规则约定。具体到某个 HTTP API 接口的约定上,至少应该明确以下信息:

  1. 是否按照 RESTful API 的约定来设计接口
  2. 接口的路径、提交方法、参数、编码类型(Enctype/Content-Type)
  3. 接口返回的错误码(code)、消息说明(message)、业务数据(data)

针对以上三条信息,我设想的「最简」 HTTP API 包含以下几条原则,供各位参考:

1、不使用 RESTful API 来设计接口

RESTful API 实际上是利用 HTTP 协议的语义(提交类型、返回码、Hypermedia Link)来将所有接口操作抽象化为一系列资源对象。这要求 API 的设计者与调用者都具备深厚的 HTTP 协议功底、语义化与抽象化能力。

  • RESTful 作为一个 Buzzword(流行词),其含义已经被曲解。HTTP 协议和 REST 架构的设计者 Roy Fielding 很反感这一点,还专门开了博客以正视听。大多数人只将 HTTP 当做一种传输协议来使用(既成事实),并不能真正理解 REST 架构风格;
  • RESTful API 将所有请求抽象化为资源名词(Resources)的做法争议很大。这种做法总会让我回想起上个世纪用 FrontPage 做网页的经历,「设置一个超链接,从某个资源跳到另外一个资源」。

    在经过 Web 2.0 浪潮,进入移动互联网时代后,这种 API 设计容易给人带来困惑。例如「登录、注册」这样的「动词」如何抽象成「名词」(还好有 Github API 可以参考 )。而刻意的使用 「HTTP CRUD」(POST/GET/PUT/DELETE Method)操作「资源化」之后的接口,并未带来更多实质上的收益;

  • HTTP 状态码的分层思路在 RESTful API 模式下被破坏了。HTTP 1.0 中定义的常见状态码已经足够网络中间组件(代理、网关、路由)使用,HTTP 1.1 中加入的很多状态码缺乏实际场景(例如 306 状态码的废弃),它们增加了中间组件以及浏览器对规范理解与实现的要求。

    尽可能的将状态码交给相应的接口逻辑层而非 HTTP 协议层,能够将问题简化;

  • 对比以英文为母语的国外开发者而言,国内开发者对语义化的认知难度更高,例如 RESTful 建议资源命名用复数形式,那收货地址单词 address 的复数形式是什么?address or addresses ?address-list or address-lists?(没过英语八级的同学已经哭晕在厕所 T_T)
  • 每个人对 RESTful API 的理解都不同,在 HTTP 协议层面做扩展与实现,不如交给接口设计者与调用者自己来约定数据结构(或者参考 JSON-RPC 规范)。把 HTTP 只当做传输协议来使用的好处是,当后端服务间的接口需要直接基于 TCP 传输层来做性能优化时,可以十分方便的切换成 Socket 的实现(之前在腾讯做微博相关项目时,微博开放平台对外只提供 HTTP 的 Open API,但对内可以提供更高频率与频次调用的原生 Socket 协议)。

2、只使用 GET/POST Method

由于 HTTP 1.0 尤其是 HTML 的规范与应用已经深入人心。大部分开发者能够很自然的这样理解:「GET」 表示「读」操作,「POST」 表示「写」操作。

这样既可以保证中间组件与浏览器很好的利用 GET 的缓存机制,又能降低接口设计的复杂度。HTTP 之父 Roy Fielding 也说过「It is okay to use POST」:

Some people think that REST suggests not to use POST for updates. Search my dissertation and you won’t find any mention of CRUD or POST. (很多人认为 RESTful 建议不要使用 POST 用于提交更新,去翻一翻我的论文,压根就没提到过 POST 和其他「增查改删」方面的内容。)

但使用 POST 方法时尤其要注意:「使用统一的 Content-Type」。这是一个容易被新手忽略的细节,也是接口设计中经常出错的点。

小主,按键盘右方向键 → 翻页可以跳过片头呢

  

上一篇:2016 年我们应该学点什么

  

下一篇:猫哥网络编程系列:详解 BAT 面试题

  

本文标题:猫哥网络编程系列:HTTP PEM 万能调试法

原文链接:http://i.she.vc/27859.html

和本文相似的内容:

点击排行