文杨彬on电商技术一、概述 有赞移动助手(下面简称助手App)网关切换功能(有赞移动App一键切换网关实践),在我们有赞移动整个开发,测试回归,产品回归验收等扮演着重要的角色,近期我们在App集成了本地抓包的功能,更是如虎添翼。在测试抓包,线上问题排查等场景发挥着重要的作用。二、背景 在目前的抓包的场景中,大部分通过手机连接PC,进行IP代理,用三方抓包工具(Charles,Fiddler,Wireshark等)进行抓包,具体有如下痛点:抓包需要将移动设备的WiFi设置中将代理手动设置成PC的IP,和对应的端口号,过程比较繁琐移动设备需要安装Charles提供的Root证书不能随时随地的用移动设备抓包,必须强依赖于PC端 因此我们需要在App增加本地抓包的功能,通过一种技术手段可以实时监听到通过移动助手网关功能连接到ZanProxy服务器的所有网络请求,经过调研,实现这个功能应该具备以下几个条件:利用Socket协议来实现消息推送经过助手App网关功能的网络请求到客户端当我们切换到其他APP进行网络请求时,要让助手App能在后台保活,甚至是常驻在后台,来达到对网络监听的目的需要在有赞助手有对应的页面去展示监听到的网络请求,header,response,request等数据三、有赞移动助手 我们助手App网关功能的原理是Android提供的VpnService,iOS的NetworkExtension将TCP连接的IP数据包通过tun2socks转化成socks5代理,将数据转发到ZanProxy服务器中,实现整个网关功能。3。1tun2socks tun2socks实现一种机制,它可以让你无需改动任何应用程序而完全同名地机那个数据用socks协议封装,转发给一个socks代理,然后由代理程序负责于正式服务器之间转发应用数据。使用代理有两种方式,一种是显示配置代理,数据离开你的主机时它的目标地址就是代理服务器。另一种是做透明代理,即在中途把原始数据重定向到一个应用程序,由该代理程序代理转发。tun2socks在第二种的基础上,完成了socks协议的封装,并且实现该机制时使用了强大的tun虚拟网卡而不必再去配置复杂的iptables规则,如下图所示 3。2socks代理 socks运作原理,就是在TCP数据外包一层socks协议头,到达socks代理服务器后,脱去socks头,然后通过socks服务器与真实服务器之间建立的连接将TCP数据传给真实服务器,socks代理并不理解任何应用层协议,它只是负责转发应用层数据而已,这一点使socks成为了一个通用的代理协议,这一点和HTTP代理服务器是完全不同。四、技术方案 了解了整个流程以后我们回到最初的痛点,需要利用Socket协议来让服务器和App建立长链接,实时监听通过助手App网关的所有httphttps请求4。1Websocket WebSocket是HTML5新增的一种通信协议。WebSocket协议是一种持久化的双向通信协议,它建立在TCP之上,同HTTP一样通过TCP来传输数据,但是它和HTTP最大的不同有两点:WebSocket是一种双向通信协议,在建立连接后,WebSocket服务器和BrowserUA都能主动的向对方发送或接收数据,就像Socket一样,不同的是WebSocket是一种建立在Web基础上的一种简单模拟Socket的协议。WebSocket需要通过握手连接,类似于TCP,它也需要客户端和服务器端进行握手连接,连接成功后才能相互通信。 下面是一个简单的建立握手的时序图: 4。2Socket。IO socket。io是支持浏览器和服务器之间实时、双向基于事件通信的库,它包括Node。js服务端API和浏览器的JavaScript端的API,是一个完全由JavaScript实现、基于Node。js、支持WebSocket协议的用于实时通信、跨平台的开源框架。底层是基于engine。io,在此基础上增加了Namespace、room、自动重连等特性。 socket。io设计的目标是支持任何的浏览器,任何Mobile设备。支持主流的PC浏览器(IE,Safari,Chrome,Firefox,Opera等),Mobile浏览器(iphoneSafariipadSafariAndroidWebKitWebOSWebKit等)。socket。io旨在使实时应用在每个浏览器和移动设备上成为可能,模糊不同的传输机制之间的差异。 但是,WebSocket协议是HTML5新推出的协议,浏览器对它的支持并不完善,由此可以看出,socket。io不可能仅仅是对WebSocket的实现,它还支持其他的通信方式,如上面介绍过的AJAX轮询和LongPolling。根据浏览器的支持程度,自主选择使用哪种方式进行通讯。4。2。1Socket。io支持的通信方式:WebSocketAdobeFlashSocketAJAXlongpollingAJAXmultipartstreamingForeverIFrameJSONPpolling4。2。2可靠性 socket。io即使在下列情况下也能建立联系:代理和负载平衡器个人防火墙和杀毒软件4。2。3自动重连机制 除非有特殊指示,否则断开连接的客户端将尝试重新连接,直到服务器再次可用为止4。2。4断开检测 在engine。io级别实现心跳极值,允许服务器和客户端都知道对方何时不再响应。通过在服务器和客户端设置计时器,在握手连接期间共享超时值(pinginterval和pingTimeout参数),可以实现该功能。4。2。5多路复用 为了在应用程序中创建关注点分离(例如每个模块,或者基于权限),socket。io允许您创建多个名称空间,这些名称空间将作为单独的通信通道,但将共享相同的底层连接。 其他更多的包括,二进制的支持、房间支持等特性,由于想到以后的可扩展性以及客户端的支持程度,以及因此我们选择socket。io作为我们消息推送的技术方案。4。2。6源码分析 在建立连接后,每个客户端会被自动加入到一个默认的命名空间。在每个命名空间中,socket会被默认加入两个名为None和sid的房间。None的房间用于广播,而sid是当前客户端的sessionid,用于单播。除默认的房间外,我们可以根据需要将对应socket加入自定义房间,roomid唯一即可。socket。io基于engine。io,支持websocket和longpolling。如果是longpolling,会定时发送GET,POST请求,当没有数据时,GET请求在拉取队列消息时会hang住(超时时间为pingTimeout),如果hang住期间服务器一直没有数据产生,则需要等到客户端发送下一个POST请求时,此时服务器会往队列中存储POST请求中的消息,这样上一个GET请求才会返回。如果upgrade到了Websocket连接,则探测成功之后会定期pingpong来保活连接。流程如下图所示: 4。3Socket。IO应用 在本次需求中移动端iOS用的开源库socket。ioclientswift是swift语言编写的。Andriod端用的开源库socket。ioandroidchat。下面以iOS为例看下如何使用:MARK:InitializersTypesafewaytocreateanewSocketIOClient。optscanbeomitted。parametersocketURL:Theurlofthesocket。ioserver。parameterconfig:Theconfigforthissocket。publicinit(socketURL:URL,config:SocketIOClientConfiguration〔〕){self。configconfigself。socketURLsocketURLsuper。init()setConfigs(config)} 创建初始化SocketManager实例,指定socketUrl,指定对应的nameSpace(名称空间)来保证单独的通信通道,将UUID(设备唯一id)当作cookie添加到header中,保证能够识别是哪台设备和服务端建立的socket通信,调用connect方法进行socket连接。socket。on(clientEvent:。connect){data,ackinprint(connectsuccess)YZVPNSettingsNetwork。bindDevice(deviceId:device??)。subscribe(onNext:{responseinprint(websocket,绑定成功)},onError:{einprint(websocket,绑定成功)})} 在连接成功后将设备信息发到我们服务器进行设备的绑定。socket。on(rows){(data,ack)inguardletcurdata〔0〕as?Stringelse{return}print(cur)}socket。on(clientEvent:。disconnect){data,ackinprint(socketdisconnect)}socket。on(clientEvent:。error){data,ackinprint(socketerror)}socket。on(clientEvent:。pong){data,ackinprint(socketpongdata。description)}接下来就是各种事件的监听,进行相应的处理。我们这边和服务端约定好定义了名叫rows的事件,那么在该事件中就可以收到httphttps请求的所有信息originRequest、requestData、response信息,那么如何将每个请求的信息对应起来呢?我们这里封装了一个名叫HttpSession的类如下:classHttpSession:HandyJSON{varoriginRequest:String?varrequestData:String?varresponse:String?varid:String?requiredinit(){}} 返回的数据里面有id来作为每个httphttps请求的唯一标示,当接收到数据时,用id进行本地映射,将数据塞入对应的HttpSession里面,当每个HttpSession对象的所有数据都被拿到时,我们再将该条请求信息展示出来。如下图所示: 以上整个本地抓包功能都已经完结,但是还有一个问题,我们去抓包肯定是打开别的APP将助手App退出到后台甚至是程序挂起状态,那么如何保证在助手App退出到后台模式后还能收到服务端推来的socket消息呢?那就需要让程序在后台长时间运行,iOS有以下几种方法:VOIPBackgroundAudio后台播放音乐LocationServices定位服务Newsstanddownloads后台下载Remotenotifications静默推送注册一个后台任务 一般而言,音乐应用在后台是避免kill的,如果在后台应用可用时间即将为0时,播放一段音乐,就会使应用变为假前端状态。可以尝试的解决方案如下:应用申请到后台执行任务后,使用NSTimer开启一个定时任务,主要负责监控应用剩余的后台可执行时间,当可用的时间少于一个值时,播放一段默声音乐,然后调用UIApplication对象的beginBackgroundTaskWithExpirationHandler方法将之前申请的后台执行任务结束掉,最后再重新申请一个后台执行任务,这样就可以实现后台不限时执行任务了 因此我们这边采用后台音乐和beginBackgroundTaskWithExpirationHandler方法保证APP能在后台不被kill。五、总结 以上就是整个本地抓包涉及到的所有技术点,总结一下App如何完成本地抓包的流程:当App通过VpnServiceNetworkExtension配置VPN和定制、扩展核心网络功能通过tun2Socks,将请求TCP数据外包一层socks协议头,到达socks代理服务器后,脱去socks头,然后通过socks服务器与真实服务器之间建立的连接将TCP数据传给真实服务器,然后进行ZanProxy代理访问,完成一次httphttps的完整请求在整个请求过程中,我们用Socket。IO将服务端与助手App建立长连接,将访问的httphttps数据实时推送给App端助手App监听长连接事件,拿到数据进行处理,展示出来 由于篇幅有限,期间还涉及到一些其他的技术点,有兴趣的同学可以去查阅对应的文献资料,也欢迎大家积极参沟通,写出这篇文章,是抛砖引玉,也是为了其他感兴趣的同学提供一些思路。 参考文献: https:socket。iodocslogginganddebugging https:socket。io 作者:有赞技术 来源:微信公众号:有赞coder 出处:https:mp。weixin。qq。comsDY6c2IBtG6XjKHZiNemdQ