自定义协议实现浏览器调用应用

研究这个事情的起因是产品的一个需求,要求对远程的接口抓包进行实时展示。一般做法都是通过 tcpdump 进行抓包,然后使用 Wireshark 进行可视化展示,但是产品要求是要“实时”,也就不能通过生成 pcap 文件下载让用户手动打开 Wireshark 的方式了。

1. 技术选型

接到这个需求后,我们就开始了技术选型。主要就两个方向,一个是前端做数据展示,另一个是通过调用 Wireshark 做数据展示。

前端实现的话,单单靠 JavaScript 是没法达到要求的,首先没有现成的 pcap 解析库,从头开始造轮子太耗时间,而且 JavaScript 的性能瓶颈在那,首先就排除了 JavaScript 实现的可能。

虽然 JavaScript 可能达不到性能要求,但是现代浏览器已经支持 WebAssembly(简称 WASM),可以使用别的语言实现最消耗性能的部分。经过一番搜索,找到了一个开源的工具库:@goodtools/wiregasm,一个将 Wireshark 的包分析器编译成 WASM 的库,NICE!yes

如果是调用 Wireshark 做展示的话,则完全是另一套逻辑,是与操作系统强相关的路线。

使用过 迅雷 的童鞋一定对 thunder: 协议不会陌生,在你安装了迅雷的情况下,直接点击页面的 thunder: 连接,浏览器就会弹出打开迅雷的提示,点击打开后,迅雷软件会自动打开并开始下载点击的链接。包括现在的很多网盘,都采用了相同的方式从浏览器端唤醒客户端。

虽然在此之前我没做过相关需求,但是这个功能实现我一直记得,也是让我知道了从页面唤起应用的可能。

2. 技术实现

前端做可视化的话,就按照库作者提供的示例进行使用就行,中途也没什么大问题,性能也很强,测试打开 14w 个包的 pcap 文件除了解析耗时久一点,解析完成后流畅度非常好。这里有我提供的示例项目

虽然前端实现看起来很棒,但是缺点也很明显:解析文件完全放在内存中,解析的文件越大,内存占用越高;其次是作者只提供了一次性 load 数据的接口,并不支持动态 add 的接口,这就导致无法直接实现产品要的 实时 展示。虽然也想过通过浏览器提供的 indexedDB 来间接实现 实时解析,但这个复杂度过高,在有更好的方案前作为备选方案。

如果是通过调用 Wireshark 客户端做数据可视化,那么就简单很多,我们只需要参照上面提到的唤起迅雷的方案,实现自定义协议,通过自定义协议调用我们写的程序做数据包获取处理,再将处理后的数据 “转发” 给 Wireshark 即可。因为之前做过调研,Wireshark 支持管道输入,所以技术上没有障碍。

2.1 数据获取程序

获取 pcap 数据本质上就是从远程接收二进制数据,能支持传输二进制数据的方式主要是基于 HTTP 的文件下载,另一个就是 websocket 的二进制消息。

文件下载因为文件流不知道什么时候会结束,会产生内存积压从而导致程序崩溃,而 websocket 是基于消息,不会有这个问题,故 tcpdump 的结果传输就基于 websocket 传输。

代码实现上,虽然我们很熟悉 nodejs,但是考虑到目前 nodejs 构建出的可执行程序的体积,我们还是换用了其它编译语言:golang,GUI 框架使用了 lxn/walk

2.2 自定义协议实现

浏览器能处理的自定义协议完全就跟操作系统强相关了,通过查询学习,这里分别列出 Windows MacOS 下的注册方式。

2.2.1 Windows

Windows 下是通过添加注册表的方式进行注册,下面是注册表内容,需根据实际情况做修改。

Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\custom.protocol]
@="URL:custom.protocol"
"URL Protocol"=""
[HKEY_CLASSES_ROOT\custom.protocol\DefaultIcon]
@="C:\\WINDOWS\\system32\\cmd.exe,1"
[HKEY_CLASSES_ROOT\calc\shell]
[HKEY_CLASSES_ROOT\calc\shell\open]
[HKEY_CLASSES_ROOT\calc\shell\open\command]
@="\"C:\\WINDOWS\\system32\\calc.exe\" \"%1\""

将上面内容另存为 reg 文件并执行后,我们就在系统中注册了“custom.protocol:”协议了。

2.2.2 MacOS

MacOS 下是通过修改自定义程序的"Contents/Info.plist"文件来实现,在最外层的 Dict 下的任意位置,添加如下内容即可。

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLName</key>
    <string>CalcURLHandler</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>calc</string>
    </array>
  </dict>
</array>

注意,如果是修改已有的程序,程勋签名会失效,需要重新签名并打开一次程序才能注册自定义协议。

3. 安装包构建

目前只打包了 Windows 下的安装包,使用的打包工具是 Inno Setup,程序自带向导,构建一个简单的安装包还是很容易的。

因为我们程序依赖 Wireshark,所以最好是在安装包中自带 Wireshark,如果用户环境没有安装 Wireshark,那我们可以提示用户安装 Wireshark,这部分 iss 代码如下:

[Files]
Source: "files\{#WiresharkInstaller}"; DestDir: "{tmp}"; Flags: dontcopy

[Tasks]
Name: installWireshark; Description: "Install Wireshark"; Check: ShouldInstallWireshark

[Code]
const
  ERROR_SUCCESS = 0;

function ShouldInstallWireshark: Boolean;
var
  InstallPath: string;
begin
  Result := not RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\Wireshark.exe', '', InstallPath);

  if Result then
    Result := not RegKeyExists(HKEY_CURRENT_USER, 'Software\Wireshark');
end;

procedure CurStepChanged(CurStep: TSetupStep);
var
  ResultCode: Integer;
begin
  if CurStep = ssPostInstall then
  begin
    if WizardIsTaskSelected('installWireshark') then
    begin
      ExtractTemporaryFile('{#WiresharkInstaller}');
      Exec(ExpandConstant('{tmp}\{#WiresharkInstaller}'), '', '', SW_SHOW, ewWaitUntilTerminated, ResultCode);
    end;
  end;
end;

4. 总结

整个功能下来,复杂度感觉没有多少,完全就是看研发人员是否对涉及到的东西是否有一定的了解,知识点很杂,考验的就是一个知识广度。