Skip to content

Commit 98de5c7

Browse files
committed
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.
1 parent dd2d15a commit 98de5c7

File tree

2 files changed

+91
-5
lines changed

2 files changed

+91
-5
lines changed

lib/std/http.zig

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,10 +242,60 @@ pub const Status = enum(u10) {
242242
}
243243
};
244244

245+
pub 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+
245294
const std = @import("std.zig");
246295

247296
test {
248297
_ = Client;
249298
_ = Method;
250299
_ = Status;
300+
_ = Headers;
251301
}

lib/std/http/Client.zig

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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),

0 commit comments

Comments
 (0)