缘起

这个博客是用 Hexo 搭建的静态网站。一篇文章的诞生,是从原始 markdown 文件开始,写完后再编译成静态html,最后上传到托管服务,下图展示了这个过程。

Hexo 分别针对这三个环节提供了 hexo new 新建文章、hexo g 生成页面、hexo d 部署三个常用命令。按理说这样已经很方便了,但在实际最近倒腾博客时,又发现一个情况。在有 VS 环境和 npm 环境,编辑完上传很方便。而在其他情况下,要改个字都很难。希望的诉求是更新的方便,随时写随时发。

如何更加自动化呢?我找到了坊间传的一种用 github.io 的方式,它可以做到直接把文件上传托管。然而,它托管的文件实际上是已经编译好的文件,相当于完成了上面流程的第三环节,但是编译的环节还得在本地完成。

于是在想,能否通过上传原始的 md 文件,在云端完成后续的操作呢?

在这个流程中,我设想的是,先把原始文件上传到 Gitee(国内版的 GitHub)托管,再到另一台服务器上把代码拉取下来,执行 hexo 的编译和部署。如果这两步都可以独立完成,那么这个新流程就有如下的优点:

  • 多地访问能保持内容是同步的,只需要都拉取 master 分支即可
  • 在本地可以用任意软件打开编辑,比如 Typora
  • 甚至可以使用 Gitee 的 WebIDE 功能,实现网页上的修改和 preview

唯一可能比较麻烦的是,当我写完一篇文章后,怎么让服务器上的html也随之而更新呢?

答案呼之欲出:WebHook!

这一切是如何运作的呢?请继续向下看。先解释一下基本概念。

什么是 WebHook?

Webhook是一个API概念,并且变得越来越流行,我们能用事件描述的事物越来越多,webhook的作用范围也就越大,webhook作为一个轻量的事件处理应用,正变得越来越有用。

举个例子,你在线下开了一家店,以前付钱的时候很容易搞,一手交钱一手交货嘛。现在呢,大家都用线上支付。但是问题来了,这个线上支付的程序并不是我开发的。以微信支付为例,用户(C)其实把钱转给了第三方(A),那作为商户(B),我要怎么知道这个信息呢?

一种做法,第三方支付(A)提供一个 API 接口,当商户(B)调用的时候,获取支付的结果。

但 WebHook 则是把这种模式给翻转了,由商户(B)提供一个接口,第三方支付(A)去调用这个接口,把支付的结果作为请求传过去。

以前的方法则需要反复请求,效率很低下。而 WebHook 的好处是能够做到实时性,在线上的世界,用户可能随时随地来。商户(B)只需要在 WebHook 中加一个触发器,去执行它自己的业务逻辑即可,比如自动发货、自动发邮件等。

如上图所示,第三方服务提供者一般会面向开发者暴露一些 API 接口,通过查询这些接口可用获取特定的数据。在某些情况下,这个服务也会要求开发者提供它自己的 WebHook 地址,用于推送某些消息。

WebHook 的权限问题

控制翻转的设计带来了更大的自由度,但也带来了一系列安全性问题。以前在商户(B)调用接口(A)的时候,能保证 A 是正确的。但是现在当商户(B)提供了一个 WebHook 以后,可能就有另一方(E)去调用(B)的接口,来伪装成(A)的调用。

如果你的业务流程是,用户在线上完成支付以后,通过 WebHook 给它发一个 SteamKey。那这种绕过,就可能带来金钱上的损失。所以在各种 WebHook 的设计中,我们经常看到一系列的名词,诸如签名鉴权,其目的就是为了保证 A 是 A 自己。

WebHook 的鉴权机制大体可以分为以下几种:

  • 密码机制(彼此知道的特殊字符串)
  • 秘钥签名(基于 hash + secret)

WebHook 的基本构成

一个 WebHook 都至少由以下三个部分组成:

  1. URL
  2. 密码/秘钥
  3. 事件/消息

URL 是指:WebHook 被触发后,发送 HTTP / HTTPS 的目标通知地址。通常是以 POST 请求的形式去发送。

WebHook 密码/签名密钥:用于 WebHook 鉴权的方式,可通过 WebHook 密码 进行鉴权,或通过 签名密钥 生成请求签名进行鉴权,防止 URL 被恶意请求。签名文档可查。

WebHook 事件/消息:在发生何种事件时需要通知,请求中包含对应的事件名和对应的消息,具体看文档说明。

如何自己做一个 WebHook?

听到这里,是不是很想自己动手做一个 WebHook 呢?这里有一篇很不错的入门文章 可以参考。

具体来说,使用 node 环境是最合适、最容易上手看到效果的。为此,我们需要在服务器/本地安装对应的包:

如何安装?查看官方指引,适用于 Windows、Linux、Mac 等环境。

安装完以后,我们先在本地写一个 hello.js 的脚本文件,在里面写

1
2
3
4
5
6
7
var http = require("http");

http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}).listen(8888);

将这段代码保存为 hello.js 文件,在终端中执行命令 node hello.js,然后打开浏览器,输入 http://localhost:8888 就能看到效果。