大家好,最近有知乎网友问我Reqable技术选型的问题,恰好Reqable也刚刚发布了非常重要的1.3版本更新,所以此次写一篇关于Reqable项目技术栈的全方面总结。
本篇文章的目的,是向大家分享我关于Reqable项目的一些技术思考、细节和填坑概要等。希望能够对大家有所帮助、避坑、少走一些弯路、更快交付。有必要说明的是,Reqable项目完全由我个人独立完成,受限于本人经验和能力,肯定有非常多的不足。如果你对本篇总结中提到的技术选型、处理方式等有不同的见解,欢迎提交评论,一起讨论,共同学习。
如果有不了解Reqable的,可以看上面这张图,官网首页:https://reqable.com
下面,话不多说,正文开始。
1. 客户端
Reqable客户端是基于Flutter和C++开发的,由于整体不涉及非常复杂的层次架构,所以我就不放Arch图了。其实简而言之,除了网络流量分析框架和API请求框架,这两个模块是使用C++语言开发外,其他几乎都是使用Dart语言开发。下面,我们来依次进行介绍。
1.1 网络流量分析模块
这个模块内部的代号是Netbare
,如果你查看Reqable解压的运行库,应该能看到这个。Netbare
从项目初始就确定了使用C++语言进行开发,最核心的考量标准就是性能。Netbare
的职责包含下面几个:
- 建立代理服务器
- 分析入流量,解析代理协议
- SSL证书重签和握手(
MITM
) - 中间人客户端
- 中间人服务器
- HTTP协议解析和封包
- 插件和拦截器处理
从上面的职责可以看到,Netbare
既承担了服务器的角色,又承担了客户端的角色。这对性能其实是有一定要求的,也就是说多线程是非常有必要的。又由于Reqable是跨平台的项目,C和C++成了首要选择语言,Java、Javascript、Dart、C#、Python等基本可以不考虑,Dart又是单线程机制,Rust也是一个可选项,但是这个语言我完全不熟悉。在我的技术栈里面,C++以绝对的优势胜出,恰好最近几年内我写了大量C++的代码,手还热乎着。
确定C++作为Netbare
开发语言后,网络框架库就非常自然地选择了asio
。但是我并不想引入Boost
这个庞大的库,所以选择了独立版本的asio,然后SSL选择了openssl,最后是格式化io库fmt。除此之外,虽然HTTP2协议规范是由Netbare
自己实现的,但还是引入了nghttp2作为辅助。这几个开源项目都是作为git submodule引入的,非常稳定,我没有修改过任何源码,在此向这些伟大的开源项目致敬。
Netbare
采用和我曾经开源的NetBare-Android项目类似的架构设计,支持拦截器机制,但是简化和优化了非常多。C++同样是面向对象的语言,我保留了HTTP3(QUIC)的扩展接口,方便我后面可以很快地接入。Netbare
提供了一套非常简单的API,简单接入便可以单独编译出可执行文件直接运行,具备完整的抓包功能。事实上,大多数网络协议相关的调试分析我都是直接在Netbare
项目中进行的。
Netbare
使用C++ 11标准,Cmake
作为编译组织脚本,目前支持在Windows、Mac和Linux平台下ARM和x86编译。目前还没有支持Android和iOS两个平台的编译,但后面很快会支持。
光有Netbare
库还无法直接在Reqable客户端中使用,还需要考虑接入的问题。尽管Netbare
提供了一套C的API接口,但由于客户端主体使用Flutter
框架(Dart语言)开发,这里就涉及到跨语言调用的问题。为了给客户端业务逻辑提供更好的支持,我又额外提供了一套Dart
的接口:
换句话说,就是另外创建了一个Dart
模块项目,接入了Netbare
的C++库,并提供了纯Dart
语言的接口供客户端调用,在客户端项目中只需要关注Dart
的API即可。从上面的图可以看出,无论是C++接口还是Dart接口的调用方式都是基本一致的。
1.2 Rest API请求模块
由于Reqable确定要支持HTTP3(QUIC)协议,根据我之前的项目经验,当时可靠的方案有两个:curl
vs Cronet
,最终我选择了Cronet
,下面分别来说下两个方案的优劣。
curl
是一个非常老牌的网络库了,其已经内置到大部分系统中,例如MacOS、Linux等。非常多常用的软件也都是基于curl发送网络请求,例如Git
、Unity
等等。另外,curl
很早就开始支持HTTP3的草案版本了,但是至今仍然处于实验功能阶段,详见这里。我曾经在2021年,编译并测试过curl的HTTP3协议版本,测试结果让我迅速放弃了它。
Cronet
是chromium
项目的一个子模块,源码详见这里。Cronet本身并不是HTTP众多版本协议的实现者(真正的实现在这个net目录),只是对net
目录代码封装,而net
才是整个chromium
的网络核心。当然,Cronet的好处是作为一个模块,可以编译出单独的模块制品,另外API设计得也更加地简单。据我之前了解,B站、字节系等相关产品都采用了Cronet的方案,但大多数是移动端。也许是因为Google本身的大量移动端产品使用了此方案,另外,Cronet还提供了Java等移动端语言的接口和成品库。
更多的实现细节,我都在这篇中详细地讲解了,有兴趣可以看看:https://juejin.cn/post/7254076875780620325
1.3 Dart & Flutter
Reqable客户端超过80%的代码都是通过Dart
编写的,包含UI、UX以及业务逻辑,另外还有一些Flutter插件,用于与Native平台进行相互调用。
我为什么选用Flutter
呢?
很多小伙伴会觉得Electron
更成熟、生态更完善、开发效率更高,而在2022年初,Flutter
桌面端正式版本都没有发布呢!当然,这些都毫无疑问是正确的、无懈可击的。但是,我还是有一些不同角度的看法。
首先,Reqable的目标是支持桌面端+移动端,覆盖全部的终端设备平台。Electron
能够很好地解决桌面端的需求,但是解决不了移动端,这是一个致命的影响决策的因素。QT
也是一样还要收费,而像Tauri
等又小众了一些。
其次,性能考虑。Electron
的性能声名(狼藉)在外,大多数技术员可能都知道这个,不是所有基于Electron
的应用都能够像VS Code
那样去做特定优化,Postman
就是一个反面例子。另一个糟心的就是安装包大小和存储空间,以Mac为例,如果一个应用安装包体积超过了100M,十有八九是基于Electron
,安装后更大,像企x微x能够占用用到上G的空间,而Flutter
安装包只有20M左右、甚至更低。Reqable是面向技术人员的产品,我相信没有多少技术人员喜欢在自己电脑里面安装一堆浏览器内核。猜一猜下图里面(本人开发机器)装有多少个浏览器?
当然,完全从性能考虑,QT
应该是最优项,但是C++的开发效率又太低了,此外在没有设计伙伴支持的情况下UI/UX也很难做出赏心悦目的效果。
Flutter
是一个非常新的框架,至少在桌面端如是,不过基本功能都是相对齐全的,此外大多数不支持的特性都可以通过插件机制来解决。Flutter
的插件机制是一个非常好的设计,支持与Native语言交互从而可以调用系统的API接口,这个几乎可以解决所有的问题。
另外,作为一个技术人员,我相信新的技术是驱动这个社会进步的动力,相比于沉湎在旧技术的舒适区,拥抱一个全新的技术会带来更大的乐趣,挑战与机遇并存。
选择Flutter
就意味着我选择了Dart
,在语言层面,这并没有太大的障碍。在我当时已有的技术栈里面,Java、C/C++、C#、Python、JavaScript、Kotlin和Groovy等我都比较熟悉,也都有过项目实践。所以,在看了几天Dart
的官方文档后,便正式开始Flutter
之旅了。
1.3.1 Flutter状态管理
熟悉Flutter
的同学,应该都能够理解状态管理
这个概念。Flutter
并不像Android、iOS、Unity或者前端等支持分离式布局,无论是布局、数据还是逻辑都是在Dart
源文件中直接编写,这也是目前非常多UI框架的采用的方式。众所周知,布局、数据还是逻辑肯定不应该写在同一个Dart
源文件中。MVC也好,MVVM也罢,在Flutter
中肯定也是需要一种类似的分离布局、数据和逻辑的框架,在Flutter
中这个叫做状态管理
框架,大概是因为Widget
分为有状态和无状态两种。
Reqable的定位是中大型Flutter项目,选择合适的状态管理框架非常地重要。项目稳定后,推翻这个框架的成本是绝对无法承受的。当时可选的大概就是下面这些:
- GetX
- Provider
- BLoC
- Riverpod
我的需求是找一个轻量级的状态管理框架,适合大型项目、利于维护、方便定制扩展。结论是Reqable选择了BLoC
。
首先我排除掉了GetX
,当时简单看了下,第一感觉这是一个非常重的框架,什么都有,像一个杂货店;无法想象在Flutter频繁更新的状态下这个框架的稳定性会怎么样,如果停止维护了又是一番什么样的景象。Provider
又太简单了些,无法应对复杂的场景。Riverpod
很不错,成熟度差一些。BLoC
的功能很完善,代码简单、稳定性和成熟度也很高,是一个非常契合Reqable项目的选择。
基于BLoC
,Reqable主要业务逻辑主要分为了三个部分,实现了MVC的基本理念:
Reqable
├── bloc // 逻辑层
├── ui // UI层
└── model // 数据层
当然BLoC
并不是完美的,Provider + Builder的编写方式,需要写大量的模板代码,开发效率并不高。适当地基于BLoC
再包装一层代码非常有必要,这也是Reqable实际的处理方式。
1.3.2 组件通信机制
除了状态
管理,另一个非常重要的机制就是组件通信。例如当底部栏左下角点击侧边栏图标后,侧边栏状态需要展开&#x