灵巧无负担的共享云端代理

导读

云端代理是在日常抓包工具如Fiddler、Charles等应用受限情况下,自主思考、设计及研发的一套云端共享代理工具,该工具日常应用过程中切实提高了团队配合效率。

背景

软件开发及软件测试工作中,通常检验接口及返回数据的方法就是抓包,借助抓包工具可以一目了然将信息展现在眼前。查阅和分析请求与返回数据、设置断点修改模拟数据,修改DNS切换环境等等,帮助开发及测试人员快速完成接口调试,接口测试,页面效果验证工作。抓包工具Fiddler、Charles在团队配合中必不可少,但在工作过程中,仍有如下强烈的困扰:1、多人合作研发/测试的项目,环境配置及测试模拟数据无法快速共享;2、单人同时支持不同需求及多机型适配/多场景验证时,环境配置及测试数据无法快速切换/隔离;3、实时请求中依据真实数据进行Mock配置时,断点方式要求实时高,强依赖操作速度,Map Local方式又操作复杂;4、请求流被清屏后,偶现问题没有留存的数据可供还原现场; 5、非技术人员发布前参与功能体验,代理配置操作成本高,无法快速连接到测试环境。回到需求本质,我们只要有一个代理,可以支持多人配置共享、多机不同配置隔离,并同时记录所有请求数据即可解决上述困扰。考虑到代理工具选型及研发成本,能在现有工具上进行二次开发是比较好的选择,所以着眼点也是从最经常使用的两款抓包工具分析:1、Fiddler,拥有一个强大扩展脚本CustomRules.js,可以解决配置问题,但它的局限性就是不可跨平台,仅适用于Windows用户;2、Charles,虽然可跨平台使用,但没有提供显式可扩展脚本,对它改造的成本过重。基于以上两款常用工具所存在的问题,选择借助业内其他轻量开源工具进行二次开发,搭建云端代理,并完善常用功能,将Mock&Host配置(后续若无特殊指明,“配置”即代表“Mock&Host配置”的简称)、数据统一化管理,解决上述问题。

项目架构 

项目从整体设计上分为图示的五部分。服务层的代理服务是最核心功能,它完成请求的捕获,并通过查询Host及Mock配置实现请求区别转发;捕获的请求及配置管理提供可视化操作界面,并有相应数据库独立存储。整体功能模块可划分为代理服务、配置数据隔离与共享、数据持久化、数据分析、监控,针对每一个功能与实现方案后续会分别讲述。1、代理服务器代理服务器包含了代理服务及请求展示。市面上开源代理服务器已有很多成熟版本,基于其进行二次开发可以节省大量成本。现提供基于两种不同工具进行二次开发的方案,两种方案各有千秋,熟悉AnyProxy,Nodejs与React的读者可以参考方案一,熟悉Mitmproxy与Python或其他Web开发语言的读者可以参考方案二。如下将详细阐述两种方案细节,供阅读者各取所需。方案一:选用阿里开源代理工具AnyProxy进行二次开发,AnyProxy轻量且开源,已提供代理基础功能和请求可视化查看界面,二次开发成本较低并能满足预期。重构后的基础架构如下图:AnyProxy初始化启动涉及代理服务,证书管理,界面等模块,用户使用时只需安装好证书并将代理配置指向特定端口;代理服务模块监听到特定端口请求后,请求会顺次通过requestHandler和recordHandler模块进行处理、组装,组装完毕以WebSocket方式实时传递至前端界面显示;前端界面提供根据URL进行请求的过滤展示。基于此,为满足多人使用、配置隔离共享等功能,在界面增加了Mock与Host环境的常规配置入口及快速新增查看编辑入口;在服务层通过requestHandler集成了Mock与Host环境配置功能,并同时改造了recordHandler完善数据记录,最终实现云端代理完整功能。由于AnyProxy最初定义是一个本地代理,代理云端化后不再像本地代理一样单人使用场景多,而是会捕获所有团队成员的请求。选定一个唯一标识用以区分不同请求用户,是云端化的先决条件,这也是实现不同用户配置隔离与共享的基础,在此选定了请求的客户端IP地址作为唯一标识。此代理捕获的请求主要包含HTTP以及HTTPS两种,AnyProxy监听请求后,在requestHandler内提供方法通过解析Socket可获取客户端IP。但此方法针对HTTP的请求可获取真实客户端IP,HTTPS的请求得到的IP均为127.0.0.1,以至于所有的HTTPS请求都无法区分请求用户。为解决此问题,从HTTP与HTTPS请求发起和代理原理的差异点进行分析:Proxy初始化仅启动HTTP代理服务器,监听特定端口请求。HTTP代理原理详见图示上半部分。当客户端发起HTTP请求后,由于协议的明文传输性,HTTP代理接收并解析请求可直接获取客户端真实IP。HTTPS代理原理详见图示下半部分。当客户端发起HTTPS请求时,浏览器会先主动发出一个HTTP CONNECT请求建立连接,此请求仍由初始启动的HTTP代理服务监听。监听到此类请求后,HTTP代理服务会占用当前容器空闲端口启动HTTPS代理服务,并通过隧道技术在真实客户端与HTTPS代理间完成加密数据转发。HTTPS代理的requestHandler内解析Socket获取的客户端实际是同容器上的HTTP代理服务,即解释了为什么HTTPS代理同方法取到的都是127.0.0.1的IP地址。解决方案可采用:在隧道建立好后,从HTTP代理-HTTPS代理连接Socket中获取双端端口作为Key,从客户端-HTTP代理连接Socket中获取客户端IP作为Value存入请求处理上下文中。CONNECT请求成功建立连接后,实际包含加密数据的GET/POST等请求被HTTP代理再捕获后通过隧道传递至HTTPS代理,HTTPS代理的requestHanlder内解析加密请求时,使用与HTTP代理连接Socket的双方端口组装Key,从上下文中便可获取真实客户端IP,从而支持HTTPS请求下实现不同用户的配置隔离。解决了以上基本问题后,此代理已实现云端代理配置隔离的基础:区分不同用户,用户以设备IP做界定(即同一人若拥有多台设备,会被判定为多个用户)。在实际工作中,大量的工作场景为:一人多台手机进行适配或团队合作项目,每个设备间配置隔离当然不是预期的提效效果,配置共享功能应运而生--提供查询全用户配置的页面,点击单用户配置后可依据该用户当前配置生成一个二维码,其他用户通过扫码方式可快速将配置拷贝给自己,无需重复录入(针对此处功能,配置隔离与共享模块的配置共享小节有更详细的讲述)。此代理实现方案前台界面默认会展示所有成员的请求,好处是数据透明,开发与测试可方便查看对方操作步骤,请求数据等,互相协助快速分析问题。但全量数据展示不方便使用者聚焦自身的请求数据或特定场景的抓包,针对此问题所做优化为:在AnyProxy自身具备根据请求URL进行请求流实时过滤功能的基础上,扩展了根据客户端IP及请求URL进行多重组合的实时过滤功能(此部分功能依据Redux框架思想进行开发,不再过多阐述),同时扩展将此过滤条件保存进缓存,后续再次使用,缓存过滤直接生效,无需重复配置。方案二:此方案在设计上各模块的耦合度较低,代理服务及Web服务两模块可根据需要灵活选型。重构后的架构如下图所示。Web服务主要负责与用户交互,功能包括抓包请求流的展示、Mock&Host相关设置等,其中请求流实时展示功能通过Ajax请求定时从后端读取新的记录实现。Web服务与代理服务本身并没有直接交互,通过数据库连接,因此在选型上也比较自由,可以根据需要自由选择,前后端分离方案或是传统MVC模式都可以满足需要。代理服务器主要负责根据配置对数据流的各种修改工作。用户/服务器信任根证书并设置代理,此时所有HTTP/HTTPS请求将经过代理服务器,代理服务器会作为中间人,将所有HTTP报文内容保存在数据库中。对于代理服务器的选择,目前有很多优秀的开源项目可以使用,笔者基于扩展性、二次开发成本、性能等因素的考虑,选择了Mitmporxy。此方案在改造成云端代理后,支持以人或者IP的方式进行Mock及Host设置。一般情况下如果客户端为静态IP,如服务器等,可使用IP进行区分;如果为动态IP则更推荐以人进行区分。针对IP区分方式,由于代理服务器在任意协议下都可以直接获取到Client_IP,因此无需进行额外的开发。以IP的方式进行区分虽能够满足大部分应用场景,但同时也存在以下问题:a)展示自己的抓包数据需要额外过滤操作。b)多台设备进行相同设置时需要重复操作。c) IP变更后需要重新设置。d) Mock及环境设置数据查找&维护不方便。针对以上问题,提供了以人为维度的区分方式。对于以用户进行区分主要存在以下困难:首先,要解决的问题是抓包信息需要区分用户,用户A的请求不能在用户B的页面显示,但是从代理的层面来说并不知道这个请求是哪个用户发送过来的,代理能够获取到的只有Client_IP,即手机的IP,所以需要做的就是将IP和用户唯一标识(如OA账号)进行绑定。于是在Web服务中,接入了58OA系统用以标识唯一用户,用户&IP通过扫码即可完成绑定。但这样带来另外一个问题:当用户连上代理后,扫码后发起的请求实际都由代理服务发出,在Web服务中获取到的Client_IP都为代理服务器的地址,为了解决这个问题,可将代理服务器作为中间人,在请求中添加特定Header(Mock-RealIP),后端通过解析此Header便可获取用户真实IP从而将用户及IP进行绑定。用户&IP通过扫码完成绑定后,即便IP发生变化,重新扫码后即可重新将用户&IP进行绑定,对于Mock及Host设置的数据,则可以直接生效,无需重新配置。此方案实现后,实时请求流数据默认仅显示当前OA账户下的关联IP请求,使得用户更聚焦自己的请求。最初在代理模式上计划的方案是使用透明代理的方式,如下图所示,将透明代理部署在智能路由器上,这样用户甚至不需要手动去设置代理地址,仅需要连上对应的Wifi热点即可使用。但是由于智能路由器存储空间及CPU性能问题,最终没有实现,后来也尝试过在树莓派上搭建,在少量用户情况下表现尚可,但在用户达到一定数量后出现CPU使用率过高,响应速度慢的问题。最终还是先实现了基本功能,后期再对易用性进行优化。总结:上述两个方案虽在代理选型及用户区分上有所不同,但殊途同归,都针对本地代理在工作中实际使用场景的不足进行了改进,为云端代理功能实现做好铺垫。方案一偏重代理服务与界面的一体性,利用开源工具省去两者交互层面开发工作。以终端设备IP做用户划分,无明确的用户边界,可查看任意终端设备的访问数据;各终端设备配置独立互不影响,灵活地支持单人多套配置的场景,单人/多人同配置的使用场景可通过配置共享扫码快速实现。方案二无需对代理层进行额外修改,代理服务及Web服务可自由定制。可根据不同场景选择使用IP及账号的方式进行用户区分,更强调账号区分方式,对他人的抓包及数据自动进行隔离,多台设备无需重复设置即可共享配置,手机动态分配IP过期后无需重新设置。阅读者可依据自身使用场景,从两套方案中取长补短,灵活组装。除上面介绍的基础功能外,云端代理服务器由于使用者多,流量大,为解决速度问题,采用分布式部署代理的方式解决。 2、配置数据隔离与共享配置数据主要包括Host配置与Mock配置,其配置管理方式基本一致,且同时适应IP/OA等任意用户体系,使用时仅需用户标识传入该体系的唯一标识即可。2.1)配置隔离:通过Setting页面,对不同用户配置进行单独管理;代理服务捕获请求进行转发前,会根据不同用户配置进行不同的修改,实现请求区别转发,达到配置隔离的效果。配置隔离实现的流程图如下。代理使用用户唯一标识依次获取该用户的Mock及Host配置,若没有任何配置则直接转发请求至真实服务器并返回。若判断有响应Mock,直接使用配置数据构造响应返回至客户端;若判断有请求Mock,首先会修改请求的Url,Header,Body,再进行Host配置的判断;若有Host配置,则修改Domain及Header后再进行请求转发后再将获取到的返回数据返回客户端。Mock和Host配置除上述总功能外,后续将分别介绍两者在此流程中各自特殊的功能与实现方案。a)Host配置Host若判断生效,由于云端代理多人共享,实现请求转发若通过改变dns路由实现会造成用户间互相影响,由此采用另一套解决方案:修改HTTP请求的domain和header实现,具体为:将真实请求域名写进HTTP Header,并将请求链接的域名替换为NginxIP。至此不同用户请求同一域名可分别转发至不同服务器。为方便用户快速确认请求转发的服务器地址,在请求转发服务方后,若非Host指定的服务方,会通过查询真实DNS路由,将服务方真实地址做保存;否则,解析并保存Host配置里的指定服务方地址;最终将保存的服务方地址展示在请求流实时界面,使用者即可快速确认转发情况。b)Mock配置Mock规则配置支持正则和通配符两种方式,统一转换为正则存储,这样提高用户灵活配置的同时,也保证了查询时的匹配效率。Mock配置方式结合用户日常使用场景提供两种:其一保留了日常代理工具常见的Map Remote方式,但进行了优化,在配置编辑页面会显示同接口他人的Mock配置,双击可快速进行配置复用;第二种将断点配置与Mock做结合,在Proxy实时请求流界面上,每条请求都包含请求Mock和响应Mock添加按钮。点击按钮后,分别将请求/响应数据回填至Mock编辑页面,可依据真实数据进行配置的快速修改保存。在Mock配置保存后,请求处理时确认生效,也会将Mock配置、状态保存,并展示在请求流实时界面(此时单条请求的Mock添加按钮会变为编辑按钮),此功能除便于使用者确认配置生效状态外,也提供了基于保存后的数据二次编辑功能。2.2)配置共享:在配置隔离的基础上,提供全用户配置查询页面。输入用户唯一标识,可查询到该用户配置,点击用户配置,会依据该用户唯一标识生成一个配置拷贝二维码,其他任意用户扫描二维码即可将该用户配置全部拷贝至自己配置内,完成配置快速共享。当用户配置代理扫码后,仍会遇到方案二内扫码发起的请求,服务方不能获取用户真实IP问题,此处同样采用在代理中将转发请求的Header内注入真实IP解决方法。3、数据持久化抓包数据通常对应操作路径,对于偶现问题的定位有比较大的参考价值,同时也是日常不断积累的测试数据,对此,我们对请求流数据做了持久化存储,存储形式及用途分为如下两种:a)全量请求数据:当抓包工具清屏后,所有请求数据丢失,偶现问题无法还原现场,回放操作步骤。为解决此问题我们将所有抓包数据不做任何处理全量保存,提供根据数据产生者的IP、URL路径、请求ID进行混合范围查询。考虑到通常都是短期请求清屏后的数据还原,数据存储量较小,在此我们选用轻量,速度快的嵌入式数据库nedb存储,数据总量始终保持在5w-6w条,每当大于6w条时,会将最早发生的1万条数据进行清理。 b)真实有价值数据:全量数据中会混杂部分无效数据,对数据分析会造成一定干扰,因此我们对全量数据进行过滤后,在每天凌晨通过Job将过滤后的数据以版本或时间为维度进行分表存储。并对外提供查询接口,供其他使用方接入扩展功能,同时便于历史问题追溯。4、数据分析此模块相对独立,且对实时性要求亦没有那么高,可以选择了通过Job的方式定时对持久化的数据进行分析。而对报表的需求则应从业务层面进行设计,目前笔者已经在实际工作中使用的报表包括:      a)版本接口覆盖率:此报表主要反映了当前APP版本功能覆盖情况,对IOS、Android及总量分别计算当前版本中测试覆盖的接口数量与近3个版本接口数量的比值,并列出未覆盖接口的具体URL,方便使用者进行回归。覆盖率可作为回归测试是否通过的一个量化标准。      b)异常接口统计:主要针对测试过程中出现的4xx和5xx 的Response Code的请求URL,便于后期根据Log等定位问题。5、监控即便代理服务相对较为稳定,但监控仍是必不可少的一环。当前简洁快速的方案是在代理启动时将启动及执行日志写入依据时间规范制定的Log文档内,并在代理运行过程中通过crontab定时查询进程,如果发现进程不存在则会在自动启动代理服务的同时,保持日志记录规范,并向相关负责人发送通知,以便当问题出现时可依据启动及运行日志排查崩溃原因。 

收益

共享云端代理的实现,为团队工作提效与质量保障都带来较大收益,主要体现在以下方面:1)使用成本降低:由于云端性及可视化Web操作界面,应用场所更自由、可适用业务场景更多。只要处于公司内网环境下,打开手机浏览器也可配置Host、查看请求流详情,调试及协助定位问题变得随时随地;并且仅首次连接代理需安装证书,后续无任何操作即可使用,极大简化非技术人员操作成本。2)环境配置高效:在单人同时支持多个需求或单需求内模拟多场景测试时,可利用多个手机连接后代理分别配置,实现多套环境&场景共存,省去资源切换的消耗;当单人多机兼容测试或多人合作研发&测试时,单机配置后,通过扫码方式也可快速多机共享配置。除此外,相比常见两款代理,所有配置都会跟随当次请求数据显示在实时界面上,可快速确认当前Host,Mock状态,节省环境&配置问题排查时间。3)多维度的数据分析:目前在系统中已有200余万条有效数据,在28个版本中进行了使用,并在版本测试中多次发现功能遗漏回归的情况,避免线上问题。4)多平台对接:代理平台的有价值数据,通过接口输出到其他平台,如对接接口文档平台,利用请求包含的输入输出,作为接口文档Demo的完善;对接接口自动化Case平台,补充Case。 

总结

本文针对目前本地代理存在的问题,通过云端代理的方式提供了一套针对测试时抓包、环境设置、Mock及数据分析的完整方案。在提供更便捷的服务的同时,也提升了开发同学在自测、UED&产品同学在验收过程中工作效率。由于篇幅限制,一些实现细节并未在文中一一详述,如果大家发现文章中的错误或者实现方案上有更好的选择,欢迎在评论区留言交流指正。参考文献:

1.  AnyProxy:https://github.com/alibaba/Anyproxy

2.  Redux:(中文翻译教程:)

3.  Mitmproxy:

作者简介:沈畅:58集团 用户价值增长部同城质量效率部秦偲晟:58集团 安居客质量保障部工程效能团队

活动福利:转发本篇文章到朋友圈集赞,按照点赞数从高到低排序(点赞数需大于等于38)前三名可获得以下福利:

第一名:价值100元的京东E卡; 第二名:价值50元的京东E卡;第三名:价值50元的京东E卡。

参与活动请关注58技术并添加“58技术小助手”号(jishu-58),并于8月30日下午六点前将点赞截图发送给小助手,获奖情况将于8月31日下午六点在58技术平台公布。