Mint|Elixir的一个新的HTTP客户端

145 阅读5分钟

Mint是一个新的低级HTTP客户端,旨在提供一个小而实用的核心,其他人可以在上面构建。Mint是基于连接的:每个连接是一个单一的结构,有一个属于启动连接的进程的相关套接字。由于没有为连接启动额外的进程,你可以选择更适合你的应用程序的进程架构。

为了验证这一点,我们建立了一个通用的API库,支持HTTP/1和HTTP/2的自动版本协商。此外,Mint还配备了一个CA证书存储,以做安全的默认HTTPS连接。

无进程的连接

Mint的HTTP连接是直接在启动连接的进程中管理的,这意味着不使用连接池,也不在打开连接时产生新的进程。这允许库的用户建立他们自己的进程结构,以适应他们的应用。

在Mint中,每个连接都有一个单一的不可变的数据结构,用户需要对其进行管理。Mint使用"活动模式 "套接字。这意味着来自套接字的数据和事件被作为消息发送到启动连接的进程中。用户将消息传递给stream/2 ,该函数返回更新的连接和一个 "响应 "列表。响应是流式返回的,这意味着你不会从stream/2 收到一个完整的 HTTP 响应,相反,响应是以部分响应块的形式返回。一个块可以是状态行、HTTP头信息或响应体的一部分。

让我们看一个用Mint发送请求的例子。

iex(1)> {:ok, conn} = Mint.HTTP.connect(:http, "httpbin.org", 80)
iex(2)> {:ok, conn, request_ref} = Mint.HTTP.request(conn, "GET", "/", [], "")
iex(3)> receive do
...(3)>   message ->
...(3)>     IO.inspect(message, label: :message)
...(3)>     {:ok, conn, responses} = Mint.HTTP.stream(conn, message)
...(3)>     IO.inspect(responses, label: :responses)
...(3)> end
message: {:tcp, #Port<0.8>, "HTTP/1.1 200 OK\r\n" <> ...}
responses: [
  {:status, #Reference<...>, 200},
  {:headers, #Reference<...>, [{"connection", "keep-alive"}, ...},
  {:data, #Reference<...>, "<!DOCTYPE html>" <> ...},
  {:done, #Reference<...>}
]

正如我们所看到的,所有对Mint.HTTP 函数的调用都会返回一个更新的conn ,它持有连接的状态。重要的是要将conn ,否则状态就会被破坏。

在第2行,我们向服务器发送一个请求。返回一个对请求的引用:这个引用在发送并发请求时非常有用,无论是HTTP/1流水线还是HTTP/2复用流。

接下来我们启动一个等待TCP活动模式消息的接收块,并将其传递给stream/2 。该消息被解析并返回对请求的响应。正如你所看到的,响应被分割成多个图元。:status,:headers,:data, 和:done 。这是因为Mint在建立之初就考虑到了流媒体。响应的各部分将随着TCP消息被传递到stream/2 ,连续返回,这样我们就不必在开始处理之前等待完整的响应了。

如果响应体大于一个数据包,stream/2 可能会返回多个:data 图元,如果响应包括尾部头信息,则会返回多个:headers 。当响应完成后,:done 将被返回。

请注意,如果你在HTTP/2连接上发送并发请求,可以使用HTTP/2的流多路复用从请求中交错返回响应。此外,响应可以分散在多个消息中,所以我们可能需要不断地接收消息并将其传递给stream/2

请看文档中更多关于如何使用Mint的例子。

为什么是无进程的?

Mint看起来比你用过的大多数其他HTTP库使用起来更麻烦,在很多方面都是如此。但是通过提供一个没有预定进程架构的低级API,它给库的用户带来了更多的灵活性。

很多时候,你不需要一个通用的连接池,可以避免它带来的额外的复杂性、单点故障和潜在的性能瓶颈。例如,如果你正在构建快速的CLI脚本,你很可能不需要一个池子,用Mint执行一个一次性的请求就足够了。

Mint的另一个好的用例是GenStage。如果你写GenStage流水线,你很可能有一个生产者池,通过HTTP从外部来源获取数据。如果你使用一个高级HTTP库,它有自己的池子,现在你有两个池子,一个是GenStage生产者,另一个来自HTTP库。有了Mint,你可以让每个GenStage生产者管理自己的连接,减少开销并简化代码。

当然,这些都不妨碍你在Mint上面建立一个连接池。关键是,Mint不会把一个架构强加给你。最后,我们希望Mint能成为更复杂的场景和用例的一个有用的构建块。

HTTP/1和HTTP/2

Mint.HTTP 模块有一个单一的接口,用于HTTP/1和HTTP/2连接,并在HTTPS连接上执行版本协商,HTTP连接默认为HTTP/1。你可以指定你想使用的HTTP版本,如果你想使用特定版本的功能,可以直接使用Mint.HTTP1Mint.HTTP2 模块。

安全默认的HTTPS

当通过HTTPS连接时,Mint将默认执行证书验证。我们认为HTTP库默认为开箱即用是至关重要的。

Mint使用对CAStore的可选依赖来提供来自Mozilla的CA证书商店的证书。

当然,你可以调整特定的SSL设置,而不需要自己重新建立安全的默认值。

该库的当前状态

Mint的第一个版本刚刚发布。它是一个实验性的库,尝试用新的方法来构建HTTP库,所以还不要指望一个完全稳定的API。

使用Mint来探索HTTP连接管理的新思路,并在Mint之上构建更高级别的客户端。在未来,连接池和更高级别的API可能会被添加到当前的低级别的API中,直接添加到Mint或通过不同的库来补充。

注意:Mint之所以在Elixir官方博客中宣布,是因为它最初被考虑纳入Elixir本身。然而,在某些时候,Elixir团队决定在Elixir本身包含一个HTTP客户端是没有意义的,至少在Erlang/OTP也有一个客户端的情况下。Mint不是由Elixir团队维护的,尽管它是由Eric和Andrea维护的,他们是团队的一员。