好久不写博客了,在元旦到来前水一篇文章,聊聊我在实现代理服务器的过程中遇到的一些坑,同时祝各位读者新年快乐。

背景

长期以来,贴吧开发人员多,业务耦合大,需求变化频繁,因此容易产生 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 住上下行数据并做修改。只有注意在接收到全部数据后再做修改即,整个流程可以用下图简单表示:

当时做完这一套东西以后,我在团队内部做了一次分享, 感兴趣的读者可以去 images.bestswifter.com/Proxy 2.key 下载 PPT。

技术选型

短连接

由于长连接基于 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,然后用手机浏览器打开 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', 2)
if len(parts) > 1 and parts[0][0:2] != b"--":
match = rx.search(parts[0])
if match:
key = match.group(1)
value = parts[1][0:len(parts[1])-2] # Remove last \r\n
r.append((key, value))
复制代码

More

到了这一步,基本上已经成功实现支持 HTTPS 的代理服务器了。后续要处理的可能就是解析 protobuf,完善业务代码等等琐碎的事情,只要小心谨慎,基本上不会有问题。

HTTP 代理服务器技术选型之旅相关推荐

  1. 开发者该如何进行小程序技术选型?

    作者 | 沧海 责编 | Elle 现在小程序开发是越来越火了,除了微信小程序,还有阿里.百度等,都在自己的APP中内置了小程序.而且现在市场上对于小程序的需求也是很多的,跟专门的APP比起来,开发成 ...

  2. 我的“技术架构”之旅

    导语:很久没写过涉及技术的文章了,因为进行职业转型后对技术有种很纠结的心态.热爱--每每看到五颜六色的代码窗口就会心里发酸,想起曾经那是生活中的一份灿烂心情:不自信--这么久离开技术会不会已经落后生疏 ...

  3. 小程序商城后台技术选型

    需求分析 我们这个小程序商城,主要面向的客户是实体店,由我们公司完成一套小程序商城的模板,推广给实体店,为他们进行运行维护升级,商城的业务模型也不大,主要分为如下几个功能,商品模块.优惠券模块.订单模 ...

  4. 微服务平台建设之微服务2.0技术选型思考

    前言 前事不忘后事之师,本篇博客是在拜读和学习了杨波的<微服务架构技术栈选型手册>后结合自己的整理和思考. https://www.infoq.cn/article/micro-servi ...

  5. 关于短视频平台框架搭建与技术选型探讨

    近年来,互联网高速发展,电视等传统媒体加速向媒体融合方向迈进,在三网融合等政策推动以及视频云技术.互动技术.大数据分析等新技术加速应用的背景下,无视频,不网络,短视频成为最重要的信息载体之一.构建高效 ...

  6. 互动直播之WebRTC服务开源技术选型【转】

            最近研究了一下会议服务器相关的知识,看到了这篇文章,介绍了很基础的概念说明和选型比较,这里转载分享一下. 转自:互动直播之WebRTC服务开源技术选型 - 掘金 1 直播基础知识 最原 ...

  7. 直播软件开发互动直播之WebRTC服务开源技术选型

    直播软件开发互动直播之WebRTC服务开源技术选型 1 直播基础知识 最原始的直播系统其实并没有想象的那么复杂,无非就是主播端将音视频数据推送到服务器,观众端则从服务器拉取数据播放. 1.1 基本常识 ...

  8. 前端技术选型的遗憾和经验教训

    我是Max,Spectrum的技术联合创始人.Spectrum 是一个面向大型在线社区的开源聊天应用程序,最近被GitHub收购.我们是一个三人团队,主要拥有前端和设计背景,我们在这个项目上工作了近两 ...

  9. 宅家学习,如何进行Kubernetes Ingress控制器的技术选型?

    导语:在Kubernetes的实践.部署中,为了解决 Pod 迁移.Node Pod 端口.域名动态分配等问题,需要开发人员选择合适的 Ingress 解决方案.面对市场上众多Ingress产品,开发 ...

  10. 小米资深工程师瞿晋萍(男):米聊服务器的技术选型和架构设计

    小米资深工程师瞿晋萍:米聊服务器的技术选型和架构设计 - 资讯频道 - CSDN.NET 小米资深工程师瞿晋萍:米聊服务器的技术选型和架构设计 2012-07-07 11:04 | 238次阅读 | ...

最新文章

  1. 独家 | 综述:情感树库上语义组合的递归深层模型
  2. Python-函数的各种器
  3. 【Java面试题】3 Java中使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?超详细解析...
  4. C++ 用libcurl库进行http 网络通讯编程
  5. [BZOJ4987]Tree
  6. 可拖动的进度条_TIM iOS版重大更新:支持语音进度条拖动和暂停
  7. akka mysql_初试超轻量级actor框架——akka
  8. typecho运行html插件,typecho主题集成HTML压缩功能
  9. 阶段5 3.微服务项目【学成在线】_day02 CMS前端开发_01-vuejs研究-vuejs介绍
  10. termux 安装python3教程_termux怎么安装python
  11. 在python中安装插件pynput实现聊天窗口消息轰炸
  12. python随机抽取样本1500个_python 随机抽取数据
  13. 2022危险化学品经营单位安全管理人员特种作业证考试题库及在线模拟考试
  14. matlab global rbfnet,基于RBF简单的matlab手写识别
  15. 修改火狐浏览器滚动条样式
  16. AcWing 3215 网络延时
  17. 关于工程导论的读书计划表
  18. 唐骏:我的成功可以复制
  19. Python脚本和C#的互相调用
  20. Linux系统引导过程及引导修复详解

热门文章

  1. 链接数据库超级简单的工具类C3P0谁用谁知道
  2. Python中的条件判断和循环
  3. hdu 4314 Save the dwarfs
  4. node.js如何制作命令行工具(一)
  5. easyui ---- jEasyUI-定制提示信息面板组件
  6. BootStrap 模态框禁用空白处点击关闭,手动显示隐藏,垂直居中
  7. C# 设定TextBox 只能输入正数/负数/小数 By KEYPRESS 事件
  8. initwithcoder和 initwithframe 区别?
  9. Android Listener侦听的N种写法
  10. 【转】匈牙利命名法则