HTTP 代理服务器技术选型之旅
HTTP 代理服务器技术选型之旅
背景
长期以来,贴吧开发人员多,业务耦合大,需求变化频繁,因此容易产生 bug。而我所负责的广告相关业务,和 UI 密切相关,一旦因为某种原因(甚至是被别人改了代码)产生了 bug,必然大幅度影响广告收入。
解决问题的一种方法在于频繁的测试,既然避免不了代码层面的耦合,那总是可以通过定时的检查来避免问题。所以我们维护了一组核心 case,密切关注最核心的功能。选择核心 case 实际上是在覆盖面和测试成本之间的权衡,然而多个 case 有不同的测试步骤,测试效率始终难以提高。
因此,我们的目标是建立一个代理服务器,能够在运行时把任何包(包括线上包)的数据改成我希望的样子。换句话说,这个代理服务器也可以理解为一个私服,它能够获得客户端的请求数据并作出修改,也可以获得服务端的响应数据并做修改。
代理服务器工作模型
在早期版本中,我们选择了简单的 HTTP 协议。这种选择对技术的要求最低,我们自己实现了一个代理服务器,开启 socket,监听端口,然后将客户端的请求发送给服务器,再把服务器的返回数据传回客户端。这种模式也被称为:“中间人模式”(MITM: Man In The Middle)。
虽然道理很简单,但实现起来还是有些地方要注意。首先,当 socket 接受数据后,应该新开一个进程/线程 进行处理。既然涉及到新的进程/线程,就一定要注意它的释放时机,否则会导致内存无限制增加。
其次,对于 socket 来说,它并没有等待函数,也就是说我无从得知何时有数据可读,因此这个艰巨的任务就交给了 select。我们把需要监听的 socket 对象作为参数传入其中,函数会一直阻塞,直到有可读、可写的对象,或者达到超时时间。
Keep-Alive 字段可以复用 TCP 连接,是一种常见的 HTTP 协议的优化方式,在 HTTP 1.1 中已经是默认选项。填写这个字段后,Server 返回的数据可能是分批次的,这样能够改善用户体验,但也会增加代理服务器的实现难度。所以代理服务器在作为客户端,向真正服务器请求数据时,应该删除这个字段。
由于整套流程都是自己实现,因此可以比较容易的 HOOK 住上下行数据并做修改。只有注意在接收到全部数据后再做修改即,整个流程可以用下图简单表示:
技术选型
短连接
由于长连接基于 TCP,不用每次新建连接,也省略了不必要的 HTTP 报文头部,效率明显优于 HTTP。所以各大公司基本上选择了长连接作为实际生产环境下的连接方式。然而由于不熟悉 WebSocket 协议,并且我们依然支持短连接,所以代理服务器最终选择了 HTTP 协议。
要想实现这一点, 就得在应用启动时,模拟后台向客户端发送一段控制信息,强制客户端选择 HTTP 请求。这样一来,即使是线上包也可以走代理服务器。
HTTPS
由于苹果强制要求使用 HTTPS,虽然已经延期,但也是明年的趋势。考虑到后续的使用,我们决定对之前实现的代理服务器进行升级。由于 HTTPS 涉及到请求协议的解析,以及加密解密和证书管理,上述自研方案很难 hold 住。经过一番调研,最后选择了一个比较知名的开源库 mitmproxy。
Mitmproxy
选择这个库最主要的理由是它直接支持 HTTPS,不过没有中文文档,国内的使用相对来说比较少,所以在接入的时候可能会略花一点时间。
这是一个 python 库, 首先要安装 virtualenv,如果本地没装的话输入:
sudo pip install virtualenv
安装好了以后,进入 mitmproxy/venv3.5/bin 文件夹输入:
source ./active
这样就可以启用 virtualenv 环境了。
Hook 脚本
这个库可以理解为命令行中可交互版本的 Charles,不过我并不打算用它的这个功能。因为我的需求主要是利用脚本来 Hook 请求, 所以我选择了 mitmdump 这个工具。使用它的时候可以指定脚本:
mitmdump -s "xxx.py"
脚本也很简单,我们可以重写 requeest 或者 receive 函数:
def request(flow):
flow.response.content = "<p>hello world</p>"
运行脚本以后,把手机的代理设为本机 ip 地址,端口号改为 8080,然后用手机浏览器打开 http://mitm.it/,如果一切配置顺利,你会看到证书的安装界面。
安装好证书后,用手机访问任何一个网站(包括 HTTPS),你应该都会看到一个小小的 hello world,至此所有的配置就完成了。
bug 修改
这个开源库有一个很严重的 bug,在解析 multipart 类型的数据时可能会发生。它使用了 splitline 方法来分割换行符,然而如果数据中有 \n 的话,就会因此丢失。很不幸的是,很多 protobuf 编码后的数据都有 \n,一旦丢失就会导致解析失败。
如果你不幸遇到了和我一样的坑,可以把相关代码改成我的版本:
for i in content.split(b"--" + boundary):
parts = i.split(b'\r\n\r\n', )
if len(parts) > and parts[][:] != b"--":
match = rx.search(parts[])
if match:
key = match.group()
value = parts[][:len(parts[])-] # Remove last \r\n
r.append((key, value))
More
到了这一步,基本上已经成功实现支持 HTTPS 的代理服务器了。后续要处理的可能就是解析 protobuf,完善业务代码等等琐碎的事情,只要小心谨慎,基本上不会有问题。
HTTP 代理服务器技术选型之旅的更多相关文章
- 【SSM之旅】Spring+SpringMVC+MyBatis+Bootstrap整合基础篇(一)项目简介及技术选型相关介绍
试水 一直想去搭建个自己的个人博客,苦于自己的技术有限,然后也个人也比较懒散.想动而不能动,想动而懒得动,就这么一直拖到了现在.总觉得应该把这几年来的所学总结一番,这样才能有所成长. 不知在何时,那就 ...
- #数据技术选型#即席查询Shib+Presto,集群任务调度HUE+Oozie
郑昀 创建于2014/10/30 最后更新于2014/10/31 一)选型:Shib+Presto 应用场景:即席查询(Ad-hoc Query) 1.1.即席查询的目标 使用者是产品/运营/销售 ...
- 老王讲自制RPC框架.(一.前言与技术选型)
(#)背景 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进. 单一应用架构 当网站流量很小时,只 ...
- Atitit 开发2d游戏的技术选型attilax总结
Atitit 开发2d游戏的技术选型attilax总结 1.1. 跨平台跨平台:一定要使用跨平台的gui技术,目前最好的就是h5(canvas,webgl,dom) +js了..1 1.2. 游戏前后 ...
- 《2016ThoughtWorks技术雷达峰会----js爆炸下的技术选型》
JS爆炸下的技术选型 刘尚奇 ThoughtWorks, 高级咨询师 JS每6个星期出现一个新框架,那么如何进行JS的选型.以下从四个方面来分析. 1.工具 NPM for all the t ...
- 手机web站点和手机app 技术选型的困惑于思考
今年一直在关注移动端技术的发展,自己也用博客园的rss接口玩了半年,关于技术选型的困惑和大家说说 一 趋势 随着手机硬件不断的升级,外加4g牌照的发放,不出2年时间移动端web站点和手机app一定会进 ...
- atitit.技术选型方法总结为什么java就是比.net有前途
atitit.技术选型方法总结为什么java就是比.net有前途 #----按照不同的需要有不铜的法... 一般有开发效率,稳定性上的需要.. 作者 老哇的爪子 Attilax 艾龙, EMAIL: ...
- 消息中间件的技术选型心得-RabbitMQ、ActiveMQ和ZeroMQ
消息中间件的技术选型心得-RabbitMQ.ActiveMQ和ZeroMQ 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs RabbitMQ.Active ...
- AutoLayout技术选型和应用
前言:这篇文章是笔者在项目中对布局技术进行技术选型和应用的相关介绍,供大家参考. && [self.buttonscount] > 0) { UIButton *button = ...
随机推荐
- 国内最火的五款HTML5前端开发框架
2013-04-11 本文主要为大家推荐五款国内最火的HTML5前端开发框架,它们分别是腾讯团队开发的JX.淘宝团队开发的KISSY.百度团队开发的QWrap和Tangram,以及上海康尚实验室推出的 ...
- [转]图解分布式一致性协议Paxos
Paxos协议/算法是分布式系统中比较重要的协议,它有多重要呢? <分布式系统的事务处理>: Google Chubby的作者MikeBurrows说过这个世界上只有一种一致性算法,那就是 ...
- 清除li内a标签的float=left实现a标签在li内居中显示(ul内li不居中显示)
写在前面: 修改cnblogs主页面菜单显示问题. 问题描述:在给主菜单添加hover样式后发现菜单内容并未居中.见图1. 网上搜索到资料其中一篇讲的可以说简明扼要了,也是伸手党的福利(点我查看原文) ...
- libgdx游戏引擎教程
第一讲:libgdx游戏引擎教程(一)性能优良的游戏引擎—libgdx http://www.apkbus.com/android-57355-1-1.html 第二讲: libgdx游戏引擎教程(二 ...
- session会话保持原理
1. 什么是会话保持? 会话保持是负载均衡最常见的问题之一,也是一个相对比较复杂的问题.会话保持有时候又叫做粘滞会话(Sticky Sessions).会话保持是指在负载均衡器上的一种机制,可以识别客 ...
- error nr.1045 access denied for user 'root'@'localhost' (using passwd:no)
在windows上卸载了mysql,再次重新安装的时候运行失败,并报以下错误: 解决办法: 1.服务里面停止Mysql服务. 2.卸载Mysql,删除MySQL的安装目录. 3.此外还要删除以下目录的 ...
- C/C++返回内部静态成员的陷阱
在我们用C/C++开发的过程中,总是有一个问题会给我们带来苦恼.这个问题就是函数内和函数外代码需要通过一块内存来交互(比如,函数返回字符串),这个问题困扰和很多开发人员.如果你的内存是在函数内栈上分配 ...
- 还没被玩坏的robobrowser(4)——从页面上抓取感兴趣的内容
背景 本节的知识实际上是属于Beautiful Soup的内容. robobrowser支持Beautiful Soup,一般来说通过下面3个方法获取页面上感兴趣的内容 find find_all s ...
- 缓存机制和Hibernate的缓存机制介绍
缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能.缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事 ...
- 温故而知新 前端日志上传新姿势 navigator.sendBeacon(信标)
原文地址:https://mp.weixin.qq.com/s/-moAfEZicrFmun4qfWLdOQ 简单示例js: var data = JSON.stringify({ name: 'Be ...