比较有趣的是,作为一个通常意义上用于「反向代理」的工具,Pingora
中也有可以实现 HTTP Server
的部分。与大部分 HTTP Server
类似,但也有 Pingora
的醍醐味在里面。话不多说,赶紧端上来吧(
ToC
Example: Logging
官方示例里有一个 logging
的部分,代码如下:
可以看到,通过高亮部分的代码,这段 logging
的代码在 127.0.0.1:6192
上启动了一个 Prometheus metric server
。这个 Server
是按什么逻辑去实现的呢?
trait ServeHttp
我们自底向上分解一下。首先来看 ServeHttp
:
这是一个标准又简单的 HTTP Server
,将 Request
映射为 Response
。这个 Trait
的定义也比较简单,不支持 streaming,只能一次性返回全部 Body
内容。
struct HttpServer
而 HttpServer
则是一个捆绑了 ServeHttp
和一系列 HttpModules
中间件的存在:
针对 SV: ServeHttp
,它实现了 HttpServerApp
。这实际也是约束了 app
必须是一个实现了 ServeHttp
的 Server
实现:
HttpModule
HttpModule
可以简单理解成用于修改 ServeHttp
接收和返回的中间件,用于修改请求和返回的内容。Pingora
中有一系列用于构造/使用 HttpModule
的方式。
trait HttpModule
我们先从最基本的 Trait
定义来看:
可以看到,HttpModule
对请求和返回都可以进行修改。对于请求,Trait
中定义了 request_header_filter()
用于修改 header
,以及 request_body_filter()
用来修改 Body
;而对于返回,HttpTask
中的所有部分都可以更改。HttpTask
定义如下:
基本上涵盖了 Response
返回的所有内容。
trait HttpModuleBuilder
定义了模块后,我们还不能直接使用。我们需要为这个模块创建一个 Builder
,符合以下 Trait
的定义:
在 HttpModuleBuilder
中,Pingora
规定了 HttpModule
加载与应用的优先级。
HttpModules
最后,回到我们 HttpServer
中使用的 HttpModules
类型。这其实是一个很像 Builder
的结构,定义如下:
在构造期间,我们需要通过 HttpModules::add_module
添加新的 ModuleBuilder
。在每次添加时,都会以 HttpModuleBuilder::order()
进行排序,确定最终模块的执行顺序([2])。
在每次 HTTP
请求来临时,build_ctx()
都会被调用。在这个过程中,Pingora
会通过 HttpModuleBuilder::init()
创建对应的 HttpModule
([3]),并且将顺序信息以 HashMap
的形式存储来生成一个 index
([4])。顺序信息只会创建一次,之后便通过 OnceCell
的形式保留在 module_index
中,供后续复用([1])。
HttpModuleCtx
HttpModule
的最后一环就是 HttpModuleCtx
了。其实这个部分已经没什么可讲的了,就是很简单的顺序执行 Module
的内容,代码一看就懂了x
回到 Prometheus
看完一圈底层的实现,我们再回到 Prometheus
。现在再来看 PrometheusServer
的实现就非常简单清晰了:
整个过程中最主要的步骤只有:
- 给
PrometheusHttpApp
实现 ServeHttp
- 定义一个
PrometheusServer
,将 PrometheusHttpApp
构造进去,并且添加上需要的 HttpModuleBuilder
真的是⑨都能看懂的程度,这里就不过多赘述了。
最后看看……ResponseCompression
?
PrometheusServer
中用到了 ResponseCompressionBuilder
,而这也是 Pingora
目前在其仓库内实现的唯一一个 HttpModule
。这里我们简单看看它在 pingora-core
中的基本实现,详情留到以后再展开(咕):
嘛,就是这样(x