7个版本
0.6.7 | 2024年7月16日 |
---|---|
0.6.6 | 2024年4月5日 |
0.6.5 | 2024年1月25日 |
0.6.4 | 2023年11月15日 |
0.6.1 | 2023年5月4日 |
#156 in 网络编程
264 每月下载量
在 3 个crate中使用(通过 sozu-lib)
120KB
2.5K SLoC
Kawa
用于解析、生成和转换HTTP消息的HTTP/1.1和HTTP/2.0的无差别表示,实现零拷贝。
原则
考虑以下存储在Buffer
中的HTTP/1.1响应
HTTP/1.1 200 OK
Transfer-Encoding: chunked // the body of the response is streamed
Connection: Keep-Alive
User-Agent: curl/7.43.0
Trailer: Foo // declares a trailer header named "Foo"
4 // declares one chunk of 4 bytes
Wiki
5 // declares one chunk of 5 bytes
pedia
0 // declares one chunk of 0 byte (the last chunk)
Foo: bar // trailer header "Foo"
HTTP通用表示
它可以在原地解析,提取基本内容(头部名称、值...)并存储为HTTP通用块的向量。Kawa是HTTP的中介,无协议表示
kawa_blocks: [
StatusLine::Response(V11, Slice("200"), Slice("OK")),
Header(Slice("Transfer-Encoding"), Slice("chunked")),
Header(Slice("Connection"), Slice("Keep-Alive")),
Header(Slice("User-Agent"), Slice("curl/7.43.0")),
Header(Slice("Trailer"), Slice("Foo")),
Flags(END_HEADER),
ChunkHeader(Slice("4")),
Chunk(Slice("Wiki")),
Flags(END_CHUNK),
ChunkHeader(Slice("5")),
Chunk(Slice("pedia")),
Flags(END_CHUNK),
Flags(END_BODY),
Header(Slice("Foo"), Slice("bar")),
Flags(END_HEADER | END_STREAM),
]
注意:
ChunkHeader
是唯一的协议特定Block
。它包含HTTP/1.1分块头中的分块大小。HTTP/2转换器可以安全忽略它们。持有上下文相关信息的Flags
块,允许转换器无状态。
重要的是,Chunk
块不一定包含整个分块。它们可能只包含更大的分块的一部分。这意味着这两种表示严格相同
kawa_full_chunk: [
ChunkHeader(Slice("4")),
Chunk(Slice("Wiki")),
Flags(END_CHUNK),
]
kawa_fragmented_chunk: [
ChunkHeader(Slice("4")),
Chunk(Slice("Wi")),
Chunk(Slice("k")),
Chunk(Slice("i")),
Flags(END_CHUNK),
]
注意:这样做是为了在不等待可能非常大的分块完全到达的情况下前进解析头。这种方案允许更有效的流处理,并防止解析器在无法装入其缓冲区的较大分块上软锁定。
使用切片引用缓冲区内容
注意,Blocks
从不复制数据。它们使用Store::Slices
引用请求的部分,这些切片只包含起始索引和长度。可以按以下方式查看Buffer
,用括号标记引用的数据
HTTP/1.1 [200] [OK]
[Transfer-Encoding]: [chunked]
[Connection]: [Keep-Alive]
[User-Agent]: [curl/7.43.0]
[Trailer]: [Foo]
[4]
[Wiki]
[5]
[pedia]
0
[Foo]: [bar]
注意:从括号之外的一切都是无用的,永远不会被使用
Kawa用例
假设我们想
- 删除“User-Agent”头部,
- 添加“Sozu-id”头部,
- 将头信息 "Connection" 改为 "close",
- 将尾部 "Foo" 改为 "bazz",
无论底层协议(HTTP/1 或 HTTP/2)如何,都可以使用通用的 Kawa 表示方法完成这些操作。
kawa_blocks.remove(3); // remove "User-Agent" header
kawa_blocks.insert(3, Header(Static("Sozu-id"), Vec(sozu_id.as_bytes().to_vec())));
kawa_blocks[2].val.modify("close");
kawa_blocks[13].val.modify("bazz");
注意:应仅使用
modify
与将要丢弃的动态值,以提供适当的生命周期。对于静态值(如 "close"),请使用Store::Static
,这只是示例。例如,kawa_blocks[2].val = Static("close")
会更高效。
kawa_blocks: [
StatusLine::Response(V11, Slice("200"), Slice("OK")),
Header(Slice("Transfer-Encoding"), Slice("chunked")),
// "close" is shorter than "Keep-Alive" so it was written in place and kept as a Slice
Header(Slice("Connection"), Slice("close")),
Header(Static("Sozu-id"), Vec("SOZUBALANCEID")),
Header(Slice("Trailer"), Slice("Foo")),
Flags(END_HEADER),
ChunkHeader(Slice("4")),
Chunk(Slice("Wiki")),
Flags(END_CHUNK),
ChunkHeader(Slice("5")),
Chunk(Slice("pedia")),
Flags(END_CHUNK),
Flags(END_BODY),
// "bazz" is longer than "bar" so it was dynamically allocated, this may change in the future
Header(Slice("Foo"), Vec("bazz"))
Flags(END_HEADER | END_STREAM),
]
现在缓冲区看起来是这样的
HTTP/1.1 [200] [OK]
[Transfer-Encoding]: [chunked]
[Connection]: [close]Alive // "close" written in place and Slice adjusted
User-Agent: curl/7.43.0 // no references to this line
[Trailer]: [Foo]
[4]
[Wiki]
[5]
[pedia]
0
[Foo]: bar // no reference to "bar"
现在,响应已成功编辑,我们可以将其转换回特定的协议。为了简化,让我们将其转换回 HTTP/1
kawa_blocks: [] // Blocks are consumed
out: [
// StatusLine::Request
Static("HTTP/1.1"),
Static(" "),
Slice("200"),
Static(" "),
Slice("OK")
Static("\r\n"),
// Header
Slice("Transfer-Encoding"),
Static(": "),
Slice("chunked"),
Static("\r\n"),
// Header
Slice("Connection"),
Static(": "),
Slice("close"),
Static("\r\n"),
// Header
Static("Sozu-id"),
Static(": "),
Vec("SOZUBALANCEID"),
Static("\r\n"),
// Header
Slice("Trailer"),
Static(": "),
Slice("Foo"),
Static("\r\n"),
// Flags(END_HEADER)
Static("\r\n"),
// ChunkHeader
Slice("4")
Static("\r\n")
// Chunk
Slice("Wiki")
// Flags(END_CHUNK)
Static("\r\n")
// ChunkHeader
Slice("5")
Static("\r\n")
// Chunk
Slice("pedia")
// Flags(END_CHUNK)
Static("\r\n")
// Flags(END_BODY),
Static("0\r\n")
// Header
Slice("Foo"),
Static(": "),
Vec("bazz"),
Static("\r\n"),
// Flags(END_HEADER | END_STREAM)
Static("\r\n"),
]
每个元素都以 u8
的切片形式存储数据,无论是静态的、动态的还是来自响应缓冲区。可以从这种表示形式构建一个 IoSlice
向量,并将其高效地发送到套接字。这产生了最终的响应
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Connection: close
Sozu-id: SOZUBALANCEID
Trailer: Foo
4
Wiki
5
pedia
0
Foo: bazz
内存管理
假设套接字只写到了 "Wikipedia" 的 "Wi"(109 字节)。在每次写入后,应使用已写入的字节数调用 Kawa::consume
。这会指示 Kawa 释放其 out
向量中的不必要的 Stores
,并在可能的情况下回收其 Buffer
中的空间。在我们的例子中,从 out
中遍历和丢弃 Stores
后仍然保留
out: [
// <-- previous Stores were completely written so they were removed
Slice("ki"), // Slice was partially written and updated accordingly
Static("\r\n"),
Slice("5"),
Static("\r\n"),
Slice("pedia"),
Static("\r\n"),
Static("0\r\n"),
Slice("Foo"),
Static(": "),
Vec("bazz"),
Static("\r\n"),
Static("\r\n"),
]
请求缓冲区中的大多数数据不再被引用,现在是无用的
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Connection: closeAlive
User-Agent: curl/7.43.0
Trailer: Foo
4
Wi[ki]
[5]
[pedia]
0
[Foo]: bar
这可以通过 Kawa::leftmost_ref
来测量,它返回最左边的 Store::Slice
的起始位置,指示 Buffer
中该点之前的所有内容都是未使用的。在这里,它将返回 115。将使用此值调用 Buffer::consume
。如果 Buffer
认为应该将其数据移动以释放此空间(Buffer::should_shift
),则将调用 Buffer::shift
以将数据通过 memmoving 移回到缓冲区的起始位置。缓冲区将看起来像这样
ki
5
pedia
0
Foo: bar
注意:这是本模块中唯一的复制数据实例,并且是必要的,除非我们更改
Buffer
的数据结构(例如使用真正的环形缓冲区)。尽管如此,这应该微不足道,因为大多数移动只复制 0 或非常少的字节。
因此,输出向量中剩余的 Store::Slices
引用了已移动的数据。
out: [
Slice("ki"), // references data starting at index 115
Static("\r\n"),
Slice("5"), // references data starting at index 119
Static("\r\n"),
Slice("pedia"), // references...
Static("\r\n"),
Static("0\r\n"),
Slice("Foo"),
Static(": "),
Vec("bazz"),
Static("\r\n"),
Static("\r\n"),
]
为了将 Store::Slices
与新缓冲区同步,使用丢弃的字节数调用 Kawa::push_left
以重新定位数据
out: [
Slice("ki"), // references data starting at index 0
Static("\r\n"),
Slice("5"), // references data starting at index 4
Static("\r\n"),
Slice("pedia"), // references...
Static("\r\n"),
Static("0\r\n"),
Slice("Foo"),
Static(": "),
Vec("bazz"),
Static("\r\n"),
Static("\r\n"),
]
[ki]
[5]
[pedia]
0
[Foo]: bar
依赖关系
~1MB
~19K SLoC