Skip to content

Commit 2c4363c

Browse files
committed
std.http.Server: rework the API entirely
Mainly, this removes the poorly named `wait`, `send`, `finish` functions, which all operated on the same "Response" object, which was actually being used as the request. Now, it looks like this: 1. std.net.Server.accept() gives you a std.net.Server.Connection 2. std.http.Server.init() with the connection 3. Server.receiveHead() gives you a Request 4. Request.reader() gives you a body reader 5. Request.respond() is a one-shot, or Request.respondStreaming() creates a Response 6. Response.writer() gives you a body writer 7. Response.end() finishes the response; Response.endChunked() allows passing response trailers. In other words, the type system now guides the API user down the correct path. receiveHead allows extra bytes to be read into the read buffer, and then will reuse those bytes for the body or the next request upon connection reuse. respond(), the one-shot function, will send the entire response in one syscall. Streaming response bodies no longer wastefully wraps every call to write with a chunk header and trailer; instead it only sends the HTTP chunk wrapper when flushing. This means the user can still control when it happens but it also does not add unnecessary chunks. Empirically, in my example project that uses this API, the usage code is significantly less noisy, it has less error handling while handling errors more correctly, it's more obvious what is happening, and it is syscall-optimal. Additionally: * Uncouple std.http.HeadParser from protocol.zig * Delete std.Server.Connection; use std.net.Server.Connection instead. - The API user supplies the read buffer when initializing the http.Server, and it is used for the HTTP head as well as a buffer for reading the body into. * Replace and document the State enum. No longer is there both "start" and "first".
1 parent 0d2a611 commit 2c4363c

File tree

6 files changed

+1131
-1006
lines changed

6 files changed

+1131
-1006
lines changed

lib/std/http.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const std = @import("std.zig");
33
pub const Client = @import("http/Client.zig");
44
pub const Server = @import("http/Server.zig");
55
pub const protocol = @import("http/protocol.zig");
6+
pub const HeadParser = @import("http/HeadParser.zig");
67

78
pub const Version = enum {
89
@"HTTP/1.0",
@@ -311,5 +312,6 @@ test {
311312
_ = Method;
312313
_ = Server;
313314
_ = Status;
315+
_ = HeadParser;
314316
_ = @import("http/test.zig");
315317
}

lib/std/http/HeadParser.zig

Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
state: State = .start,
2+
3+
pub const State = enum {
4+
start,
5+
seen_n,
6+
seen_r,
7+
seen_rn,
8+
seen_rnr,
9+
finished,
10+
};
11+
12+
/// Returns the number of bytes consumed by headers. This is always less
13+
/// than or equal to `bytes.len`.
14+
///
15+
/// If the amount returned is less than `bytes.len`, the parser is in a
16+
/// content state and the first byte of content is located at
17+
/// `bytes[result]`.
18+
pub fn feed(p: *HeadParser, bytes: []const u8) usize {
19+
const vector_len: comptime_int = @max(std.simd.suggestVectorLength(u8) orelse 1, 8);
20+
const len: u32 = @intCast(bytes.len);
21+
var index: u32 = 0;
22+
23+
while (true) {
24+
switch (p.state) {
25+
.finished => return index,
26+
.start => switch (len - index) {
27+
0 => return index,
28+
1 => {
29+
switch (bytes[index]) {
30+
'\r' => p.state = .seen_r,
31+
'\n' => p.state = .seen_n,
32+
else => {},
33+
}
34+
35+
return index + 1;
36+
},
37+
2 => {
38+
const b16 = int16(bytes[index..][0..2]);
39+
const b8 = intShift(u8, b16);
40+
41+
switch (b8) {
42+
'\r' => p.state = .seen_r,
43+
'\n' => p.state = .seen_n,
44+
else => {},
45+
}
46+
47+
switch (b16) {
48+
int16("\r\n") => p.state = .seen_rn,
49+
int16("\n\n") => p.state = .finished,
50+
else => {},
51+
}
52+
53+
return index + 2;
54+
},
55+
3 => {
56+
const b24 = int24(bytes[index..][0..3]);
57+
const b16 = intShift(u16, b24);
58+
const b8 = intShift(u8, b24);
59+
60+
switch (b8) {
61+
'\r' => p.state = .seen_r,
62+
'\n' => p.state = .seen_n,
63+
else => {},
64+
}
65+
66+
switch (b16) {
67+
int16("\r\n") => p.state = .seen_rn,
68+
int16("\n\n") => p.state = .finished,
69+
else => {},
70+
}
71+
72+
switch (b24) {
73+
int24("\r\n\r") => p.state = .seen_rnr,
74+
else => {},
75+
}
76+
77+
return index + 3;
78+
},
79+
4...vector_len - 1 => {
80+
const b32 = int32(bytes[index..][0..4]);
81+
const b24 = intShift(u24, b32);
82+
const b16 = intShift(u16, b32);
83+
const b8 = intShift(u8, b32);
84+
85+
switch (b8) {
86+
'\r' => p.state = .seen_r,
87+
'\n' => p.state = .seen_n,
88+
else => {},
89+
}
90+
91+
switch (b16) {
92+
int16("\r\n") => p.state = .seen_rn,
93+
int16("\n\n") => p.state = .finished,
94+
else => {},
95+
}
96+
97+
switch (b24) {
98+
int24("\r\n\r") => p.state = .seen_rnr,
99+
else => {},
100+
}
101+
102+
switch (b32) {
103+
int32("\r\n\r\n") => p.state = .finished,
104+
else => {},
105+
}
106+
107+
index += 4;
108+
continue;
109+
},
110+
else => {
111+
const chunk = bytes[index..][0..vector_len];
112+
const matches = if (use_vectors) matches: {
113+
const Vector = @Vector(vector_len, u8);
114+
// const BoolVector = @Vector(vector_len, bool);
115+
const BitVector = @Vector(vector_len, u1);
116+
const SizeVector = @Vector(vector_len, u8);
117+
118+
const v: Vector = chunk.*;
119+
const matches_r: BitVector = @bitCast(v == @as(Vector, @splat('\r')));
120+
const matches_n: BitVector = @bitCast(v == @as(Vector, @splat('\n')));
121+
const matches_or: SizeVector = matches_r | matches_n;
122+
123+
break :matches @reduce(.Add, matches_or);
124+
} else matches: {
125+
var matches: u8 = 0;
126+
for (chunk) |byte| switch (byte) {
127+
'\r', '\n' => matches += 1,
128+
else => {},
129+
};
130+
break :matches matches;
131+
};
132+
switch (matches) {
133+
0 => {},
134+
1 => switch (chunk[vector_len - 1]) {
135+
'\r' => p.state = .seen_r,
136+
'\n' => p.state = .seen_n,
137+
else => {},
138+
},
139+
2 => {
140+
const b16 = int16(chunk[vector_len - 2 ..][0..2]);
141+
const b8 = intShift(u8, b16);
142+
143+
switch (b8) {
144+
'\r' => p.state = .seen_r,
145+
'\n' => p.state = .seen_n,
146+
else => {},
147+
}
148+
149+
switch (b16) {
150+
int16("\r\n") => p.state = .seen_rn,
151+
int16("\n\n") => p.state = .finished,
152+
else => {},
153+
}
154+
},
155+
3 => {
156+
const b24 = int24(chunk[vector_len - 3 ..][0..3]);
157+
const b16 = intShift(u16, b24);
158+
const b8 = intShift(u8, b24);
159+
160+
switch (b8) {
161+
'\r' => p.state = .seen_r,
162+
'\n' => p.state = .seen_n,
163+
else => {},
164+
}
165+
166+
switch (b16) {
167+
int16("\r\n") => p.state = .seen_rn,
168+
int16("\n\n") => p.state = .finished,
169+
else => {},
170+
}
171+
172+
switch (b24) {
173+
int24("\r\n\r") => p.state = .seen_rnr,
174+
else => {},
175+
}
176+
},
177+
4...vector_len => {
178+
inline for (0..vector_len - 3) |i_usize| {
179+
const i = @as(u32, @truncate(i_usize));
180+
181+
const b32 = int32(chunk[i..][0..4]);
182+
const b16 = intShift(u16, b32);
183+
184+
if (b32 == int32("\r\n\r\n")) {
185+
p.state = .finished;
186+
return index + i + 4;
187+
} else if (b16 == int16("\n\n")) {
188+
p.state = .finished;
189+
return index + i + 2;
190+
}
191+
}
192+
193+
const b24 = int24(chunk[vector_len - 3 ..][0..3]);
194+
const b16 = intShift(u16, b24);
195+
const b8 = intShift(u8, b24);
196+
197+
switch (b8) {
198+
'\r' => p.state = .seen_r,
199+
'\n' => p.state = .seen_n,
200+
else => {},
201+
}
202+
203+
switch (b16) {
204+
int16("\r\n") => p.state = .seen_rn,
205+
int16("\n\n") => p.state = .finished,
206+
else => {},
207+
}
208+
209+
switch (b24) {
210+
int24("\r\n\r") => p.state = .seen_rnr,
211+
else => {},
212+
}
213+
},
214+
else => unreachable,
215+
}
216+
217+
index += vector_len;
218+
continue;
219+
},
220+
},
221+
.seen_n => switch (len - index) {
222+
0 => return index,
223+
else => {
224+
switch (bytes[index]) {
225+
'\n' => p.state = .finished,
226+
else => p.state = .start,
227+
}
228+
229+
index += 1;
230+
continue;
231+
},
232+
},
233+
.seen_r => switch (len - index) {
234+
0 => return index,
235+
1 => {
236+
switch (bytes[index]) {
237+
'\n' => p.state = .seen_rn,
238+
'\r' => p.state = .seen_r,
239+
else => p.state = .start,
240+
}
241+
242+
return index + 1;
243+
},
244+
2 => {
245+
const b16 = int16(bytes[index..][0..2]);
246+
const b8 = intShift(u8, b16);
247+
248+
switch (b8) {
249+
'\r' => p.state = .seen_r,
250+
'\n' => p.state = .seen_rn,
251+
else => p.state = .start,
252+
}
253+
254+
switch (b16) {
255+
int16("\r\n") => p.state = .seen_rn,
256+
int16("\n\r") => p.state = .seen_rnr,
257+
int16("\n\n") => p.state = .finished,
258+
else => {},
259+
}
260+
261+
return index + 2;
262+
},
263+
else => {
264+
const b24 = int24(bytes[index..][0..3]);
265+
const b16 = intShift(u16, b24);
266+
const b8 = intShift(u8, b24);
267+
268+
switch (b8) {
269+
'\r' => p.state = .seen_r,
270+
'\n' => p.state = .seen_n,
271+
else => p.state = .start,
272+
}
273+
274+
switch (b16) {
275+
int16("\r\n") => p.state = .seen_rn,
276+
int16("\n\n") => p.state = .finished,
277+
else => {},
278+
}
279+
280+
switch (b24) {
281+
int24("\n\r\n") => p.state = .finished,
282+
else => {},
283+
}
284+
285+
index += 3;
286+
continue;
287+
},
288+
},
289+
.seen_rn => switch (len - index) {
290+
0 => return index,
291+
1 => {
292+
switch (bytes[index]) {
293+
'\r' => p.state = .seen_rnr,
294+
'\n' => p.state = .seen_n,
295+
else => p.state = .start,
296+
}
297+
298+
return index + 1;
299+
},
300+
else => {
301+
const b16 = int16(bytes[index..][0..2]);
302+
const b8 = intShift(u8, b16);
303+
304+
switch (b8) {
305+
'\r' => p.state = .seen_rnr,
306+
'\n' => p.state = .seen_n,
307+
else => p.state = .start,
308+
}
309+
310+
switch (b16) {
311+
int16("\r\n") => p.state = .finished,
312+
int16("\n\n") => p.state = .finished,
313+
else => {},
314+
}
315+
316+
index += 2;
317+
continue;
318+
},
319+
},
320+
.seen_rnr => switch (len - index) {
321+
0 => return index,
322+
else => {
323+
switch (bytes[index]) {
324+
'\n' => p.state = .finished,
325+
else => p.state = .start,
326+
}
327+
328+
index += 1;
329+
continue;
330+
},
331+
},
332+
}
333+
334+
return index;
335+
}
336+
}
337+
338+
inline fn int16(array: *const [2]u8) u16 {
339+
return @bitCast(array.*);
340+
}
341+
342+
inline fn int24(array: *const [3]u8) u24 {
343+
return @bitCast(array.*);
344+
}
345+
346+
inline fn int32(array: *const [4]u8) u32 {
347+
return @bitCast(array.*);
348+
}
349+
350+
inline fn intShift(comptime T: type, x: anytype) T {
351+
switch (@import("builtin").cpu.arch.endian()) {
352+
.little => return @truncate(x >> (@bitSizeOf(@TypeOf(x)) - @bitSizeOf(T))),
353+
.big => return @truncate(x),
354+
}
355+
}
356+
357+
const HeadParser = @This();
358+
const std = @import("std");
359+
const use_vectors = builtin.zig_backend != .stage2_x86_64;
360+
const builtin = @import("builtin");
361+
362+
test feed {
363+
const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\nHello";
364+
365+
for (0..36) |i| {
366+
var p: HeadParser = .{};
367+
try std.testing.expectEqual(i, p.feed(data[0..i]));
368+
try std.testing.expectEqual(35 - i, p.feed(data[i..]));
369+
}
370+
}

0 commit comments

Comments
 (0)