authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-29 18:57:53-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-02 16:57:16-07:00
log2d090f61be89d0738b9d2644236717e0fada7e4d
treeee98a0d45eade4b1303b63d7302a9e82ea7efbd8
parent05fee3b22b593c6b0829499b53f26f5750df3645

add std.http.Headers

This is a streaming HTTP header parser. All it currently does is detect the end of headers. This will be a non-allocating parser where one can bring supply their own buffer if they want to handle custom headers. This commit also improves std.http.Client to not return the HTTP headers with the read functions.

2 files changed, 91 insertions(+), 5 deletions(-)

lib/std/http.zig+50
......@@ -242,10 +242,60 @@ pub const Status = enum(u10) {
242242 }
243243};
244244
245pub const Headers = struct {
246 state: State = .start,
247 invalid_index: u32 = undefined,
248
249 pub const State = enum { invalid, start, line, nl_r, nl_n, nl2_r, finished };
250
251 /// Returns how many bytes are processed into headers. Always less than or
252 /// equal to bytes.len. If the amount returned is less than bytes.len, it
253 /// means the headers ended and the first byte after the double \r\n\r\n is
254 /// located at `bytes[result]`.
255 pub fn feed(h: *Headers, bytes: []const u8) usize {
256 for (bytes) |b, i| {
257 switch (h.state) {
258 .start => switch (b) {
259 '\r' => h.state = .nl_r,
260 '\n' => return invalid(h, i),
261 else => {},
262 },
263 .nl_r => switch (b) {
264 '\n' => h.state = .nl_n,
265 else => return invalid(h, i),
266 },
267 .nl_n => switch (b) {
268 '\r' => h.state = .nl2_r,
269 else => h.state = .line,
270 },
271 .nl2_r => switch (b) {
272 '\n' => h.state = .finished,
273 else => return invalid(h, i),
274 },
275 .line => switch (b) {
276 '\r' => h.state = .nl_r,
277 '\n' => return invalid(h, i),
278 else => {},
279 },
280 .invalid => return i,
281 .finished => return i,
282 }
283 }
284 return bytes.len;
285 }
286
287 fn invalid(h: *Headers, i: usize) usize {
288 h.invalid_index = @intCast(u32, i);
289 h.state = .invalid;
290 return i;
291 }
292};
293
245294const std = @import("std.zig");
246295
247296test {
248297 _ = Client;
249298 _ = Method;
250299 _ = Status;
300 _ = Headers;
251301}
lib/std/http/Client.zig+41-5
......@@ -16,6 +16,7 @@ pub const Request = struct {
1616 headers: std.ArrayListUnmanaged(u8) = .{},
1717 tls_client: std.crypto.tls.Client,
1818 protocol: Protocol,
19 response_headers: http.Headers = .{},
1920
2021 pub const Protocol = enum { http, https };
2122
......@@ -51,18 +52,53 @@ pub const Request = struct {
5152 }
5253 }
5354
55 pub fn readAll(req: *Request, buffer: []u8) !usize {
56 return readAtLeast(req, buffer, buffer.len);
57 }
58
5459 pub fn read(req: *Request, buffer: []u8) !usize {
60 return readAtLeast(req, buffer, 1);
61 }
62
63 pub fn readAtLeast(req: *Request, buffer: []u8, len: usize) !usize {
64 assert(len <= buffer.len);
65 var index: usize = 0;
66 while (index < len) {
67 const headers_finished = req.response_headers.state == .finished;
68 const amt = try readAdvanced(req, buffer[index..]);
69 if (amt == 0 and headers_finished) break;
70 index += amt;
71 }
72 return index;
73 }
74
75 /// This one can return 0 without meaning EOF.
76 /// TODO change to readvAdvanced
77 pub fn readAdvanced(req: *Request, buffer: []u8) !usize {
78 if (req.response_headers.state == .finished) return readRaw(req, buffer);
79
80 const amt = try readRaw(req, buffer);
81 const data = buffer[0..amt];
82 const i = req.response_headers.feed(data);
83 if (req.response_headers.state == .invalid) return error.InvalidHttpHeaders;
84 if (i < data.len) {
85 const rest = data[i..];
86 std.mem.copy(u8, buffer, rest);
87 return rest.len;
88 }
89 return 0;
90 }
91
92 /// Only abstracts over http/https.
93 fn readRaw(req: *Request, buffer: []u8) !usize {
5594 switch (req.protocol) {
5695 .http => return req.stream.read(buffer),
5796 .https => return req.tls_client.read(req.stream, buffer),
5897 }
5998 }
6099
61 pub fn readAll(req: *Request, buffer: []u8) !usize {
62 return readAtLeast(req, buffer, buffer.len);
63 }
64
65 pub fn readAtLeast(req: *Request, buffer: []u8, len: usize) !usize {
100 /// Only abstracts over http/https.
101 fn readAtLeastRaw(req: *Request, buffer: []u8, len: usize) !usize {
66102 switch (req.protocol) {
67103 .http => return req.stream.readAtLeast(buffer, len),
68104 .https => return req.tls_client.readAtLeast(req.stream, buffer, len),