authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-20 03:30:51-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-23 02:37:11-07:00
log6395ba852a88f0e0b2a2f0659f1daf9d08e90157
tree9cc1a8b6dce2ccb049667a008c596ab8bcf86e14
parent9129fb28dcc3c94a709cefd8040f41b9125693ee

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".

6 files changed, 1131 insertions(+), 1006 deletions(-)

lib/std/http.zig+2
......@@ -3,6 +3,7 @@ const std = @import("std.zig");
33pub const Client = @import("http/Client.zig");
44pub const Server = @import("http/Server.zig");
55pub const protocol = @import("http/protocol.zig");
6pub const HeadParser = @import("http/HeadParser.zig");
67
78pub 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 created+370
......@@ -0,0 +1,370 @@
1state: State = .start,
2
3pub 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]`.
18pub 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
338inline fn int16(array: *const [2]u8) u16 {
339 return @bitCast(array.*);
340}
341
342inline fn int24(array: *const [3]u8) u24 {
343 return @bitCast(array.*);
344}
345
346inline fn int32(array: *const [4]u8) u32 {
347 return @bitCast(array.*);
348}
349
350inline 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
357const HeadParser = @This();
358const std = @import("std");
359const use_vectors = builtin.zig_backend != .stage2_x86_64;
360const builtin = @import("builtin");
361
362test 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}
lib/std/http/Server.zig+698-507
......@@ -1,613 +1,805 @@
1connection: Connection,
2/// This value is determined by Server when sending headers to the client, and
3/// then used to determine the return value of `reset`.
4connection_keep_alive: bool,
1//! Blocking HTTP server implementation.
2
3connection: net.Server.Connection,
4/// Keeps track of whether the Server is ready to accept a new request on the
5/// same connection, and makes invalid API usage cause assertion failures
6/// rather than HTTP protocol violations.
7state: State,
8/// User-provided buffer that must outlive this Server.
9/// Used to store the client's entire HTTP header.
10read_buffer: []u8,
11/// Amount of available data inside read_buffer.
12read_buffer_len: usize,
13/// Index into `read_buffer` of the first byte of the next HTTP request.
14next_request_start: usize,
515
6/// The HTTP request that this response is responding to.
7///
8/// This field is only valid after calling `wait`.
9request: Request,
10
11state: State = .first,
16pub const State = enum {
17 /// The connection is available to be used for the first time, or reused.
18 ready,
19 /// An error occurred in `receiveHead`.
20 receiving_head,
21 /// A Request object has been obtained and from there a Response can be
22 /// opened.
23 received_head,
24 /// The client is uploading something to this Server.
25 receiving_body,
26 /// The connection is eligible for another HTTP request, however the client
27 /// and server did not negotiate connection: keep-alive.
28 closing,
29};
1230
1331/// Initialize an HTTP server that can respond to multiple requests on the same
1432/// connection.
15/// The returned `Server` is ready for `reset` or `wait` to be called.
16pub fn init(connection: std.net.Server.Connection, options: Server.Request.InitOptions) Server {
33/// The returned `Server` is ready for `readRequest` to be called.
34pub fn init(connection: net.Server.Connection, read_buffer: []u8) Server {
1735 return .{
18 .connection = .{
19 .stream = connection.stream,
20 .read_buf = undefined,
21 .read_start = 0,
22 .read_end = 0,
23 },
24 .connection_keep_alive = false,
25 .request = Server.Request.init(options),
36 .connection = connection,
37 .state = .ready,
38 .read_buffer = read_buffer,
39 .read_buffer_len = 0,
40 .next_request_start = 0,
2641 };
2742}
2843
29pub const State = enum {
30 first,
31 start,
32 waited,
33 responded,
34 finished,
35};
36
37pub const ResetState = enum { reset, closing };
38
39pub const Connection = @import("Server/Connection.zig");
40
41/// The mode of transport for responses.
42pub const ResponseTransfer = union(enum) {
43 content_length: u64,
44 chunked: void,
45 none: void,
46};
47
48/// The decompressor for request messages.
49pub const Compression = union(enum) {
50 pub const DeflateDecompressor = std.compress.zlib.Decompressor(Server.TransferReader);
51 pub const GzipDecompressor = std.compress.gzip.Decompressor(Server.TransferReader);
52 // https://github.com/ziglang/zig/issues/18937
53 //pub const ZstdDecompressor = std.compress.zstd.DecompressStream(Server.TransferReader, .{});
54
55 deflate: DeflateDecompressor,
56 gzip: GzipDecompressor,
57 // https://github.com/ziglang/zig/issues/18937
58 //zstd: ZstdDecompressor,
59 none: void,
44pub const ReceiveHeadError = error{
45 /// Client sent too many bytes of HTTP headers.
46 /// The HTTP specification suggests to respond with a 431 status code
47 /// before closing the connection.
48 HttpHeadersOversize,
49 /// Client sent headers that did not conform to the HTTP protocol.
50 HttpHeadersInvalid,
51 /// A low level I/O error occurred trying to read the headers.
52 HttpHeadersUnreadable,
6053};
6154
62/// A HTTP request originating from a client.
63pub const Request = struct {
64 method: http.Method,
65 target: []const u8,
66 version: http.Version,
67 expect: ?[]const u8,
68 content_type: ?[]const u8,
69 content_length: ?u64,
70 transfer_encoding: http.TransferEncoding,
71 transfer_compression: http.ContentEncoding,
72 keep_alive: bool,
73 parser: proto.HeadersParser,
74 compression: Compression,
75
76 pub const InitOptions = struct {
77 /// Externally-owned memory used to store the client's entire HTTP header.
78 /// `error.HttpHeadersOversize` is returned from read() when a
79 /// client sends too many bytes of HTTP headers.
80 client_header_buffer: []u8,
81 };
55/// The header bytes reference the read buffer that Server was initialized with
56/// and remain alive until the next call to receiveHead.
57pub fn receiveHead(s: *Server) ReceiveHeadError!Request {
58 assert(s.state == .ready);
59 s.state = .received_head;
60 errdefer s.state = .receiving_head;
61
62 // In case of a reused connection, move the next request's bytes to the
63 // beginning of the buffer.
64 if (s.next_request_start > 0) {
65 if (s.read_buffer_len > s.next_request_start) {
66 const leftover = s.read_buffer[s.next_request_start..s.read_buffer_len];
67 const dest = s.read_buffer[0..leftover.len];
68 if (leftover.len <= s.next_request_start) {
69 @memcpy(dest, leftover);
70 } else {
71 mem.copyBackwards(u8, dest, leftover);
72 }
73 s.read_buffer_len = leftover.len;
74 }
75 s.next_request_start = 0;
76 }
8277
83 pub fn init(options: InitOptions) Request {
84 return .{
85 .method = undefined,
86 .target = undefined,
87 .version = undefined,
88 .expect = null,
89 .content_type = null,
90 .content_length = null,
91 .transfer_encoding = .none,
92 .transfer_compression = .identity,
93 .keep_alive = false,
94 .parser = proto.HeadersParser.init(options.client_header_buffer),
95 .compression = .none,
78 var hp: http.HeadParser = .{};
79 while (true) {
80 const buf = s.read_buffer[s.read_buffer_len..];
81 if (buf.len == 0)
82 return error.HttpHeadersOversize;
83 const read_n = s.connection.stream.read(buf) catch
84 return error.HttpHeadersUnreadable;
85 s.read_buffer_len += read_n;
86 const bytes = buf[0..read_n];
87 const end = hp.feed(bytes);
88 if (hp.state == .finished) return .{
89 .server = s,
90 .head_end = end,
91 .head = Request.Head.parse(s.read_buffer[0..end]) catch
92 return error.HttpHeadersInvalid,
93 .reader_state = undefined,
9694 };
9795 }
96}
9897
99 pub const ParseError = error{
100 UnknownHttpMethod,
101 HttpHeadersInvalid,
102 HttpHeaderContinuationsUnsupported,
103 HttpTransferEncodingUnsupported,
104 HttpConnectionHeaderUnsupported,
105 InvalidContentLength,
106 CompressionUnsupported,
98pub const Request = struct {
99 server: *Server,
100 /// Index into Server's read_buffer.
101 head_end: usize,
102 head: Head,
103 reader_state: union {
104 remaining_content_length: u64,
105 },
106
107 pub const Compression = union(enum) {
108 pub const DeflateDecompressor = std.compress.zlib.Decompressor(std.io.AnyReader);
109 pub const GzipDecompressor = std.compress.gzip.Decompressor(std.io.AnyReader);
110 pub const ZstdDecompressor = std.compress.zstd.Decompressor(std.io.AnyReader);
111
112 deflate: DeflateDecompressor,
113 gzip: GzipDecompressor,
114 zstd: ZstdDecompressor,
115 none: void,
107116 };
108117
109 pub fn parse(req: *Request, bytes: []const u8) ParseError!void {
110 var it = mem.splitSequence(u8, bytes, "\r\n");
111
112 const first_line = it.next().?;
113 if (first_line.len < 10)
114 return error.HttpHeadersInvalid;
115
116 const method_end = mem.indexOfScalar(u8, first_line, ' ') orelse
117 return error.HttpHeadersInvalid;
118 if (method_end > 24) return error.HttpHeadersInvalid;
119
120 const method_str = first_line[0..method_end];
121 const method: http.Method = @enumFromInt(http.Method.parse(method_str));
122
123 const version_start = mem.lastIndexOfScalar(u8, first_line, ' ') orelse
124 return error.HttpHeadersInvalid;
125 if (version_start == method_end) return error.HttpHeadersInvalid;
126
127 const version_str = first_line[version_start + 1 ..];
128 if (version_str.len != 8) return error.HttpHeadersInvalid;
129 const version: http.Version = switch (int64(version_str[0..8])) {
130 int64("HTTP/1.0") => .@"HTTP/1.0",
131 int64("HTTP/1.1") => .@"HTTP/1.1",
132 else => return error.HttpHeadersInvalid,
118 pub const Head = struct {
119 method: http.Method,
120 target: []const u8,
121 version: http.Version,
122 expect: ?[]const u8,
123 content_type: ?[]const u8,
124 content_length: ?u64,
125 transfer_encoding: http.TransferEncoding,
126 transfer_compression: http.ContentEncoding,
127 keep_alive: bool,
128 compression: Compression,
129
130 pub const ParseError = error{
131 UnknownHttpMethod,
132 HttpHeadersInvalid,
133 HttpHeaderContinuationsUnsupported,
134 HttpTransferEncodingUnsupported,
135 HttpConnectionHeaderUnsupported,
136 InvalidContentLength,
137 CompressionUnsupported,
138 MissingFinalNewline,
133139 };
134140
135 const target = first_line[method_end + 1 .. version_start];
136
137 req.method = method;
138 req.target = target;
139 req.version = version;
140
141 while (it.next()) |line| {
142 if (line.len == 0) return;
143 switch (line[0]) {
144 ' ', '\t' => return error.HttpHeaderContinuationsUnsupported,
145 else => {},
146 }
147
148 var line_it = mem.splitSequence(u8, line, ": ");
149 const header_name = line_it.next().?;
150 const header_value = line_it.rest();
151 if (header_value.len == 0) return error.HttpHeadersInvalid;
152
153 if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
154 req.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close");
155 } else if (std.ascii.eqlIgnoreCase(header_name, "expect")) {
156 req.expect = header_value;
157 } else if (std.ascii.eqlIgnoreCase(header_name, "content-type")) {
158 req.content_type = header_value;
159 } else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) {
160 if (req.content_length != null) return error.HttpHeadersInvalid;
161 req.content_length = std.fmt.parseInt(u64, header_value, 10) catch
162 return error.InvalidContentLength;
163 } else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) {
164 if (req.transfer_compression != .identity) return error.HttpHeadersInvalid;
165
166 const trimmed = mem.trim(u8, header_value, " ");
167
168 if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
169 req.transfer_compression = ce;
170 } else {
171 return error.HttpTransferEncodingUnsupported;
141 pub fn parse(bytes: []const u8) ParseError!Head {
142 var it = mem.splitSequence(u8, bytes, "\r\n");
143
144 const first_line = it.next().?;
145 if (first_line.len < 10)
146 return error.HttpHeadersInvalid;
147
148 const method_end = mem.indexOfScalar(u8, first_line, ' ') orelse
149 return error.HttpHeadersInvalid;
150 if (method_end > 24) return error.HttpHeadersInvalid;
151
152 const method_str = first_line[0..method_end];
153 const method: http.Method = @enumFromInt(http.Method.parse(method_str));
154
155 const version_start = mem.lastIndexOfScalar(u8, first_line, ' ') orelse
156 return error.HttpHeadersInvalid;
157 if (version_start == method_end) return error.HttpHeadersInvalid;
158
159 const version_str = first_line[version_start + 1 ..];
160 if (version_str.len != 8) return error.HttpHeadersInvalid;
161 const version: http.Version = switch (int64(version_str[0..8])) {
162 int64("HTTP/1.0") => .@"HTTP/1.0",
163 int64("HTTP/1.1") => .@"HTTP/1.1",
164 else => return error.HttpHeadersInvalid,
165 };
166
167 const target = first_line[method_end + 1 .. version_start];
168
169 var head: Head = .{
170 .method = method,
171 .target = target,
172 .version = version,
173 .expect = null,
174 .content_type = null,
175 .content_length = null,
176 .transfer_encoding = .none,
177 .transfer_compression = .identity,
178 .keep_alive = false,
179 .compression = .none,
180 };
181
182 while (it.next()) |line| {
183 if (line.len == 0) return head;
184 switch (line[0]) {
185 ' ', '\t' => return error.HttpHeaderContinuationsUnsupported,
186 else => {},
172187 }
173 } else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) {
174 // Transfer-Encoding: second, first
175 // Transfer-Encoding: deflate, chunked
176 var iter = mem.splitBackwardsScalar(u8, header_value, ',');
177188
178 const first = iter.first();
179 const trimmed_first = mem.trim(u8, first, " ");
180
181 var next: ?[]const u8 = first;
182 if (std.meta.stringToEnum(http.TransferEncoding, trimmed_first)) |transfer| {
183 if (req.transfer_encoding != .none)
184 return error.HttpHeadersInvalid; // we already have a transfer encoding
185 req.transfer_encoding = transfer;
186
187 next = iter.next();
188 }
189
190 if (next) |second| {
191 const trimmed_second = mem.trim(u8, second, " ");
192
193 if (std.meta.stringToEnum(http.ContentEncoding, trimmed_second)) |transfer| {
194 if (req.transfer_compression != .identity)
195 return error.HttpHeadersInvalid; // double compression is not supported
196 req.transfer_compression = transfer;
189 var line_it = mem.splitSequence(u8, line, ": ");
190 const header_name = line_it.next().?;
191 const header_value = line_it.rest();
192 if (header_value.len == 0) return error.HttpHeadersInvalid;
193
194 if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
195 head.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close");
196 } else if (std.ascii.eqlIgnoreCase(header_name, "expect")) {
197 head.expect = header_value;
198 } else if (std.ascii.eqlIgnoreCase(header_name, "content-type")) {
199 head.content_type = header_value;
200 } else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) {
201 if (head.content_length != null) return error.HttpHeadersInvalid;
202 head.content_length = std.fmt.parseInt(u64, header_value, 10) catch
203 return error.InvalidContentLength;
204 } else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) {
205 if (head.transfer_compression != .identity) return error.HttpHeadersInvalid;
206
207 const trimmed = mem.trim(u8, header_value, " ");
208
209 if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
210 head.transfer_compression = ce;
197211 } else {
198212 return error.HttpTransferEncodingUnsupported;
199213 }
200 }
214 } else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) {
215 // Transfer-Encoding: second, first
216 // Transfer-Encoding: deflate, chunked
217 var iter = mem.splitBackwardsScalar(u8, header_value, ',');
201218
202 if (iter.next()) |_| return error.HttpTransferEncodingUnsupported;
203 }
204 }
205 return error.HttpHeadersInvalid; // missing empty line
206 }
219 const first = iter.first();
220 const trimmed_first = mem.trim(u8, first, " ");
207221
208 inline fn int64(array: *const [8]u8) u64 {
209 return @bitCast(array.*);
210 }
211};
212
213/// Reset this response to its initial state. This must be called before
214/// handling a second request on the same connection.
215pub fn reset(res: *Server) ResetState {
216 if (res.state == .first) {
217 res.state = .start;
218 return .reset;
219 }
220
221 if (!res.request.parser.done) {
222 // If the response wasn't fully read, then we need to close the connection.
223 res.connection_keep_alive = false;
224 return .closing;
225 }
222 var next: ?[]const u8 = first;
223 if (std.meta.stringToEnum(http.TransferEncoding, trimmed_first)) |transfer| {
224 if (head.transfer_encoding != .none)
225 return error.HttpHeadersInvalid; // we already have a transfer encoding
226 head.transfer_encoding = transfer;
226227
227 res.state = .start;
228 res.request = Request.init(.{
229 .client_header_buffer = res.request.parser.header_bytes_buffer,
230 });
228 next = iter.next();
229 }
231230
232 return if (res.connection_keep_alive) .reset else .closing;
233}
231 if (next) |second| {
232 const trimmed_second = mem.trim(u8, second, " ");
234233
235pub const SendAllError = std.net.Stream.WriteError;
234 if (std.meta.stringToEnum(http.ContentEncoding, trimmed_second)) |transfer| {
235 if (head.transfer_compression != .identity)
236 return error.HttpHeadersInvalid; // double compression is not supported
237 head.transfer_compression = transfer;
238 } else {
239 return error.HttpTransferEncodingUnsupported;
240 }
241 }
236242
237pub const SendOptions = struct {
238 version: http.Version = .@"HTTP/1.1",
239 status: http.Status = .ok,
240 reason: ?[]const u8 = null,
241 keep_alive: bool = true,
242 extra_headers: []const http.Header = &.{},
243 content: []const u8,
244};
243 if (iter.next()) |_| return error.HttpTransferEncodingUnsupported;
244 }
245 }
246 return error.MissingFinalNewline;
247 }
245248
246/// Send an entire HTTP response to the client, including headers and body.
247/// Automatically handles HEAD requests by omitting the body.
248/// Uses the "content-length" header.
249/// Asserts status is not `continue`.
250/// Asserts there are at most 25 extra_headers.
251pub fn sendAll(s: *Server, options: SendOptions) SendAllError!void {
252 const max_extra_headers = 25;
253 assert(options.status != .@"continue");
254 assert(options.extra_headers.len <= max_extra_headers);
255
256 switch (s.state) {
257 .waited => s.state = .finished,
258 .first => unreachable, // Call reset() first.
259 .start => unreachable, // Call wait() first.
260 .responded => unreachable, // Cannot mix sendAll() with send().
261 .finished => unreachable, // Call reset() first.
262 }
249 inline fn int64(array: *const [8]u8) u64 {
250 return @bitCast(array.*);
251 }
252 };
263253
264 s.connection_keep_alive = options.keep_alive and s.request.keep_alive;
265 const keep_alive_line = if (s.connection_keep_alive)
266 "connection: keep-alive\r\n"
267 else
268 "";
269 const phrase = options.reason orelse options.status.phrase() orelse "";
270
271 var first_buffer: [500]u8 = undefined;
272 const first_bytes = std.fmt.bufPrint(
273 &first_buffer,
274 "{s} {d} {s}\r\n{s}content-length: {d}\r\n",
275 .{
276 @tagName(options.version),
277 @intFromEnum(options.status),
278 phrase,
279 keep_alive_line,
280 options.content.len,
281 },
282 ) catch unreachable;
283
284 var iovecs: [max_extra_headers * 4 + 3]std.posix.iovec_const = undefined;
285 var iovecs_len: usize = 0;
286
287 iovecs[iovecs_len] = .{
288 .iov_base = first_bytes.ptr,
289 .iov_len = first_bytes.len,
254 pub const RespondOptions = struct {
255 version: http.Version = .@"HTTP/1.1",
256 status: http.Status = .ok,
257 reason: ?[]const u8 = null,
258 keep_alive: bool = true,
259 extra_headers: []const http.Header = &.{},
290260 };
291 iovecs_len += 1;
292261
293 for (options.extra_headers) |header| {
294 iovecs[iovecs_len] = .{
295 .iov_base = header.name.ptr,
296 .iov_len = header.name.len,
297 };
298 iovecs_len += 1;
262 /// Send an entire HTTP response to the client, including headers and body.
263 ///
264 /// Automatically handles HEAD requests by omitting the body.
265 /// Uses the "content-length" header unless `content` is empty in which
266 /// case it omits the content-length header.
267 ///
268 /// If the request contains a body and the connection is to be reused,
269 /// discards the request body, leaving the Server in the `ready` state. If
270 /// this discarding fails, the connection is marked as not to be reused and
271 /// no error is surfaced.
272 ///
273 /// Asserts status is not `continue`.
274 /// Asserts there are at most 25 extra_headers.
275 pub fn respond(
276 request: *Request,
277 content: []const u8,
278 options: RespondOptions,
279 ) Response.WriteError!void {
280 const max_extra_headers = 25;
281 assert(options.status != .@"continue");
282 assert(options.extra_headers.len <= max_extra_headers);
283
284 const keep_alive = request.discardBody(options.keep_alive);
285
286 const phrase = options.reason orelse options.status.phrase() orelse "";
287
288 var first_buffer: [500]u8 = undefined;
289 var h = std.ArrayListUnmanaged(u8).initBuffer(&first_buffer);
290 h.writerAssumeCapacity().print("{s} {d} {s}\r\n", .{
291 @tagName(options.version), @intFromEnum(options.status), phrase,
292 }) catch |err| switch (err) {};
293 if (keep_alive)
294 h.appendSliceAssumeCapacity("connection: keep-alive\r\n");
295 if (content.len > 0)
296 h.writerAssumeCapacity().print("content-length: {d}\r\n", .{content.len}) catch |err|
297 switch (err) {};
298
299 var iovecs: [max_extra_headers * 4 + 3]std.posix.iovec_const = undefined;
300 var iovecs_len: usize = 0;
299301
300302 iovecs[iovecs_len] = .{
301 .iov_base = ": ",
302 .iov_len = 2,
303 .iov_base = h.items.ptr,
304 .iov_len = h.items.len,
303305 };
304306 iovecs_len += 1;
305307
306 iovecs[iovecs_len] = .{
307 .iov_base = header.value.ptr,
308 .iov_len = header.value.len,
309 };
310 iovecs_len += 1;
308 for (options.extra_headers) |header| {
309 iovecs[iovecs_len] = .{
310 .iov_base = header.name.ptr,
311 .iov_len = header.name.len,
312 };
313 iovecs_len += 1;
314
315 iovecs[iovecs_len] = .{
316 .iov_base = ": ",
317 .iov_len = 2,
318 };
319 iovecs_len += 1;
320
321 iovecs[iovecs_len] = .{
322 .iov_base = header.value.ptr,
323 .iov_len = header.value.len,
324 };
325 iovecs_len += 1;
326
327 iovecs[iovecs_len] = .{
328 .iov_base = "\r\n",
329 .iov_len = 2,
330 };
331 iovecs_len += 1;
332 }
311333
312334 iovecs[iovecs_len] = .{
313335 .iov_base = "\r\n",
314336 .iov_len = 2,
315337 };
316338 iovecs_len += 1;
317 }
318339
319 iovecs[iovecs_len] = .{
320 .iov_base = "\r\n",
321 .iov_len = 2,
322 };
323 iovecs_len += 1;
340 if (request.head.method != .HEAD and content.len > 0) {
341 iovecs[iovecs_len] = .{
342 .iov_base = content.ptr,
343 .iov_len = content.len,
344 };
345 iovecs_len += 1;
346 }
324347
325 if (s.request.method != .HEAD) {
326 iovecs[iovecs_len] = .{
327 .iov_base = options.content.ptr,
328 .iov_len = options.content.len,
329 };
330 iovecs_len += 1;
348 try request.server.connection.stream.writevAll(iovecs[0..iovecs_len]);
331349 }
332350
333 return s.connection.stream.writevAll(iovecs[0..iovecs_len]);
334}
335
336pub const Response = struct {
337 transfer_encoding: ResponseTransfer,
338};
339
340pub const SendError = Connection.WriteError || error{
341 UnsupportedTransferEncoding,
342 InvalidContentLength,
343};
344
345/// Send the HTTP response headers to the client.
346pub fn send(res: *Server) SendError!void {
347 switch (res.state) {
348 .waited => res.state = .responded,
349 .first, .start, .responded, .finished => unreachable,
350 }
351 pub const RespondStreamingOptions = struct {
352 /// An externally managed slice of memory used to batch bytes before
353 /// sending. `respondStreaming` asserts this is large enough to store
354 /// the full HTTP response head.
355 ///
356 /// Must outlive the returned Response.
357 send_buffer: []u8,
358 /// If provided, the response will use the content-length header;
359 /// otherwise it will use transfer-encoding: chunked.
360 content_length: ?u64 = null,
361 /// Options that are shared with the `respond` method.
362 respond_options: RespondOptions = .{},
363 };
351364
352 var buffered = std.io.bufferedWriter(res.connection.writer());
353 const w = buffered.writer();
354
355 try w.writeAll(@tagName(res.version));
356 try w.writeByte(' ');
357 try w.print("{d}", .{@intFromEnum(res.status)});
358 try w.writeByte(' ');
359 if (res.reason) |reason| {
360 try w.writeAll(reason);
361 } else if (res.status.phrase()) |phrase| {
362 try w.writeAll(phrase);
363 }
364 try w.writeAll("\r\n");
365
366 if (res.status == .@"continue") {
367 res.state = .waited; // we still need to send another request after this
368 } else {
369 res.connection_keep_alive = res.keep_alive and res.request.keep_alive;
370 if (res.connection_keep_alive) {
371 try w.writeAll("connection: keep-alive\r\n");
365 /// The header is buffered but not sent until Response.flush is called.
366 ///
367 /// If the request contains a body and the connection is to be reused,
368 /// discards the request body, leaving the Server in the `ready` state. If
369 /// this discarding fails, the connection is marked as not to be reused and
370 /// no error is surfaced.
371 ///
372 /// HEAD requests are handled transparently by setting a flag on the
373 /// returned Response to omit the body. However it may be worth noticing
374 /// that flag and skipping any expensive work that would otherwise need to
375 /// be done to satisfy the request.
376 ///
377 /// Asserts `send_buffer` is large enough to store the entire response header.
378 /// Asserts status is not `continue`.
379 pub fn respondStreaming(request: *Request, options: RespondStreamingOptions) Response {
380 const o = options.respond_options;
381 assert(o.status != .@"continue");
382
383 const keep_alive = request.discardBody(o.keep_alive);
384 const phrase = o.reason orelse o.status.phrase() orelse "";
385
386 var h = std.ArrayListUnmanaged(u8).initBuffer(options.send_buffer);
387
388 h.writerAssumeCapacity().print("{s} {d} {s}\r\n", .{
389 @tagName(o.version), @intFromEnum(o.status), phrase,
390 }) catch |err| switch (err) {};
391 if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n");
392
393 if (options.content_length) |len| {
394 h.writerAssumeCapacity().print("content-length: {d}\r\n", .{len}) catch |err| switch (err) {};
372395 } else {
373 try w.writeAll("connection: close\r\n");
396 h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n");
374397 }
375398
376 switch (res.transfer_encoding) {
377 .chunked => try w.writeAll("transfer-encoding: chunked\r\n"),
378 .content_length => |content_length| try w.print("content-length: {d}\r\n", .{content_length}),
379 .none => {},
399 for (o.extra_headers) |header| {
400 h.appendSliceAssumeCapacity(header.name);
401 h.appendSliceAssumeCapacity(": ");
402 h.appendSliceAssumeCapacity(header.value);
403 h.appendSliceAssumeCapacity("\r\n");
380404 }
381405
382 for (res.extra_headers) |header| {
383 try w.print("{s}: {s}\r\n", .{ header.name, header.value });
384 }
385 }
406 h.appendSliceAssumeCapacity("\r\n");
386407
387 if (res.request.method == .HEAD) {
388 res.transfer_encoding = .none;
408 return .{
409 .stream = request.server.connection.stream,
410 .send_buffer = options.send_buffer,
411 .send_buffer_start = 0,
412 .send_buffer_end = h.items.len,
413 .content_length = options.content_length,
414 .elide_body = request.head.method == .HEAD,
415 .chunk_len = 0,
416 };
389417 }
390418
391 try w.writeAll("\r\n");
392
393 try buffered.flush();
394}
419 pub const ReadError = net.Stream.ReadError;
395420
396const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;
421 fn read_cl(context: *const anyopaque, buffer: []u8) ReadError!usize {
422 const request: *Request = @constCast(@alignCast(@ptrCast(context)));
423 const s = request.server;
424 assert(s.state == .receiving_body);
397425
398const TransferReader = std.io.Reader(*Server, TransferReadError, transferRead);
399
400fn transferReader(res: *Server) TransferReader {
401 return .{ .context = res };
402}
426 const remaining_content_length = &request.reader_state.remaining_content_length;
403427
404fn transferRead(res: *Server, buf: []u8) TransferReadError!usize {
405 if (res.request.parser.done) return 0;
428 if (remaining_content_length.* == 0) {
429 s.state = .ready;
430 return 0;
431 }
406432
407 var index: usize = 0;
408 while (index == 0) {
409 const amt = try res.request.parser.read(&res.connection, buf[index..], false);
410 if (amt == 0 and res.request.parser.done) break;
411 index += amt;
433 const available_bytes = s.read_buffer_len - request.head_end;
434 if (available_bytes == 0)
435 s.read_buffer_len += try s.connection.stream.read(s.read_buffer[request.head_end..]);
436
437 const available_buf = s.read_buffer[request.head_end..s.read_buffer_len];
438 const len = @min(remaining_content_length.*, available_buf.len, buffer.len);
439 @memcpy(buffer[0..len], available_buf[0..len]);
440 remaining_content_length.* -= len;
441 if (remaining_content_length.* == 0)
442 s.state = .ready;
443 return len;
412444 }
413445
414 return index;
415}
416
417pub const WaitError = Connection.ReadError ||
418 proto.HeadersParser.CheckCompleteHeadError || Request.ParseError ||
419 error{CompressionUnsupported};
420
421/// Wait for the client to send a complete request head.
422///
423/// For correct behavior, the following rules must be followed:
424///
425/// * If this returns any error in `Connection.ReadError`, you MUST
426/// immediately close the connection by calling `deinit`.
427/// * If this returns `error.HttpHeadersInvalid`, you MAY immediately close
428/// the connection by calling `deinit`.
429/// * If this returns `error.HttpHeadersOversize`, you MUST
430/// respond with a 431 status code and then call `deinit`.
431/// * If this returns any error in `Request.ParseError`, you MUST respond
432/// with a 400 status code and then call `deinit`.
433/// * If this returns any other error, you MUST respond with a 400 status
434/// code and then call `deinit`.
435/// * If the request has an Expect header containing 100-continue, you MUST either:
436/// * Respond with a 100 status code, then call `wait` again.
437/// * Respond with a 417 status code.
438pub fn wait(res: *Server) WaitError!void {
439 switch (res.state) {
440 .first, .start => res.state = .waited,
441 .waited, .responded, .finished => unreachable,
446 fn read_chunked(context: *const anyopaque, buffer: []u8) ReadError!usize {
447 const request: *Request = @constCast(@alignCast(@ptrCast(context)));
448 const s = request.server;
449 assert(s.state == .receiving_body);
450 _ = buffer;
451 @panic("TODO");
442452 }
443453
444 while (true) {
445 try res.connection.fill();
454 pub const ReadAllError = ReadError || error{HttpBodyOversize};
446455
447 const nchecked = try res.request.parser.checkCompleteHead(res.connection.peek());
448 res.connection.drop(@intCast(nchecked));
456 pub fn reader(request: *Request) std.io.AnyReader {
457 const s = request.server;
458 assert(s.state == .received_head);
459 s.state = .receiving_body;
460 switch (request.head.transfer_encoding) {
461 .chunked => return .{
462 .readFn = read_chunked,
463 .context = request,
464 },
465 .none => {
466 request.reader_state = .{
467 .remaining_content_length = request.head.content_length orelse 0,
468 };
469 return .{
470 .readFn = read_cl,
471 .context = request,
472 };
473 },
474 }
475 }
449476
450 if (res.request.parser.state.isContent()) break;
477 /// Returns whether the connection: keep-alive header should be sent to the client.
478 /// If it would fail, it instead sets the Server state to `receiving_body`
479 /// and returns false.
480 fn discardBody(request: *Request, keep_alive: bool) bool {
481 // Prepare to receive another request on the same connection.
482 // There are two factors to consider:
483 // * Any body the client sent must be discarded.
484 // * The Server's read_buffer may already have some bytes in it from
485 // whatever came after the head, which may be the next HTTP request
486 // or the request body.
487 // If the connection won't be kept alive, then none of this matters
488 // because the connection will be severed after the response is sent.
489 const s = request.server;
490 if (keep_alive and request.head.keep_alive) switch (s.state) {
491 .received_head => {
492 s.state = .receiving_body;
493 switch (request.head.transfer_encoding) {
494 .none => t: {
495 const len = request.head.content_length orelse break :t;
496 const head_end = request.head_end;
497 var total_body_discarded: usize = 0;
498 while (true) {
499 const available_bytes = s.read_buffer_len - head_end;
500 const remaining_len = len - total_body_discarded;
501 if (available_bytes >= remaining_len) {
502 s.next_request_start = head_end + remaining_len;
503 break :t;
504 }
505 total_body_discarded += available_bytes;
506 // Preserve request header memory until receiveHead is called.
507 const buf = s.read_buffer[head_end..];
508 const read_n = s.connection.stream.read(buf) catch return false;
509 s.read_buffer_len = head_end + read_n;
510 }
511 },
512 .chunked => {
513 @panic("TODO");
514 },
515 }
516 s.state = .ready;
517 return true;
518 },
519 .receiving_body, .ready => return true,
520 else => unreachable,
521 } else {
522 s.state = .closing;
523 return false;
524 }
451525 }
526};
452527
453 try res.request.parse(res.request.parser.get());
528pub const Response = struct {
529 stream: net.Stream,
530 send_buffer: []u8,
531 /// Index of the first byte in `send_buffer`.
532 /// This is 0 unless a short write happens in `write`.
533 send_buffer_start: usize,
534 /// Index of the last byte + 1 in `send_buffer`.
535 send_buffer_end: usize,
536 /// `null` means transfer-encoding: chunked.
537 /// As a debugging utility, counts down to zero as bytes are written.
538 content_length: ?u64,
539 elide_body: bool,
540 /// Indicates how much of the end of the `send_buffer` corresponds to a
541 /// chunk. This amount of data will be wrapped by an HTTP chunk header.
542 chunk_len: usize,
543
544 pub const WriteError = net.Stream.WriteError;
545
546 /// When using content-length, asserts that the amount of data sent matches
547 /// the value sent in the header, then calls `flush`.
548 /// Otherwise, transfer-encoding: chunked is being used, and it writes the
549 /// end-of-stream message, then flushes the stream to the system.
550 /// When request method is HEAD, does not write anything to the stream.
551 pub fn end(r: *Response) WriteError!void {
552 if (r.content_length) |len| {
553 assert(len == 0); // Trips when end() called before all bytes written.
554 return flush_cl(r);
555 }
556 if (!r.elide_body) {
557 return flush_chunked(r, &.{});
558 }
559 r.* = undefined;
560 }
454561
455 switch (res.request.transfer_encoding) {
456 .none => {
457 if (res.request.content_length) |len| {
458 res.request.parser.next_chunk_length = len;
562 pub const EndChunkedOptions = struct {
563 trailers: []const http.Header = &.{},
564 };
459565
460 if (len == 0) res.request.parser.done = true;
461 } else {
462 res.request.parser.done = true;
463 }
464 },
465 .chunked => {
466 res.request.parser.next_chunk_length = 0;
467 res.request.parser.state = .chunk_head_size;
468 },
566 /// Asserts that the Response is using transfer-encoding: chunked.
567 /// Writes the end-of-stream message and any optional trailers, then
568 /// flushes the stream to the system.
569 /// When request method is HEAD, does not write anything to the stream.
570 /// Asserts there are at most 25 trailers.
571 pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void {
572 assert(r.content_length == null);
573 if (r.elide_body) return;
574 try flush_chunked(r, options.trailers);
575 r.* = undefined;
469576 }
470577
471 if (!res.request.parser.done) {
472 switch (res.request.transfer_compression) {
473 .identity => res.request.compression = .none,
474 .compress, .@"x-compress" => return error.CompressionUnsupported,
475 .deflate => res.request.compression = .{
476 .deflate = std.compress.zlib.decompressor(res.transferReader()),
477 },
478 .gzip, .@"x-gzip" => res.request.compression = .{
479 .gzip = std.compress.gzip.decompressor(res.transferReader()),
480 },
481 .zstd => {
482 // https://github.com/ziglang/zig/issues/18937
483 return error.CompressionUnsupported;
484 },
578 /// If using content-length, asserts that writing these bytes to the client
579 /// would not exceed the content-length value sent in the HTTP header.
580 /// May return 0, which does not indicate end of stream. The caller decides
581 /// when the end of stream occurs by calling `end`.
582 pub fn write(r: *Response, bytes: []const u8) WriteError!usize {
583 if (r.content_length != null) {
584 return write_cl(r, bytes);
585 } else {
586 return write_chunked(r, bytes);
485587 }
486588 }
487}
488589
489pub const ReadError = TransferReadError || proto.HeadersParser.CheckCompleteHeadError || error{ DecompressionFailure, InvalidTrailers };
590 fn write_cl(context: *const anyopaque, bytes: []const u8) WriteError!usize {
591 const r: *Response = @constCast(@alignCast(@ptrCast(context)));
592 const len = &r.content_length.?;
593 if (r.elide_body) {
594 len.* -= bytes.len;
595 return bytes.len;
596 }
490597
491pub const Reader = std.io.Reader(*Server, ReadError, read);
598 if (bytes.len + r.send_buffer_end > r.send_buffer.len) {
599 const send_buffer_len = r.send_buffer_end - r.send_buffer_start;
600 var iovecs: [2]std.posix.iovec_const = .{
601 .{
602 .iov_base = r.send_buffer.ptr + r.send_buffer_start,
603 .iov_len = send_buffer_len,
604 },
605 .{
606 .iov_base = bytes.ptr,
607 .iov_len = bytes.len,
608 },
609 };
610 const n = try r.stream.writev(&iovecs);
611
612 if (n >= send_buffer_len) {
613 // It was enough to reset the buffer.
614 r.send_buffer_start = 0;
615 r.send_buffer_end = 0;
616 const bytes_n = n - send_buffer_len;
617 len.* -= bytes_n;
618 return bytes_n;
619 }
492620
493pub fn reader(res: *Server) Reader {
494 return .{ .context = res };
495}
621 // It didn't even make it through the existing buffer, let
622 // alone the new bytes provided.
623 r.send_buffer_start += n;
624 return 0;
625 }
496626
497/// Reads data from the response body. Must be called after `wait`.
498pub fn read(res: *Server, buffer: []u8) ReadError!usize {
499 switch (res.state) {
500 .waited, .responded, .finished => {},
501 .first, .start => unreachable,
627 // All bytes can be stored in the remaining space of the buffer.
628 @memcpy(r.send_buffer[r.send_buffer_end..][0..bytes.len], bytes);
629 r.send_buffer_end += bytes.len;
630 len.* -= bytes.len;
631 return bytes.len;
502632 }
503633
504 const out_index = switch (res.request.compression) {
505 .deflate => |*deflate| deflate.read(buffer) catch return error.DecompressionFailure,
506 .gzip => |*gzip| gzip.read(buffer) catch return error.DecompressionFailure,
507 // https://github.com/ziglang/zig/issues/18937
508 //.zstd => |*zstd| zstd.read(buffer) catch return error.DecompressionFailure,
509 else => try res.transferRead(buffer),
510 };
511
512 if (out_index == 0) {
513 const has_trail = !res.request.parser.state.isContent();
634 fn write_chunked(context: *const anyopaque, bytes: []const u8) WriteError!usize {
635 const r: *Response = @constCast(@alignCast(@ptrCast(context)));
636 assert(r.content_length == null);
514637
515 while (!res.request.parser.state.isContent()) { // read trailing headers
516 try res.connection.fill();
638 if (r.elide_body)
639 return bytes.len;
517640
518 const nchecked = try res.request.parser.checkCompleteHead(res.connection.peek());
519 res.connection.drop(@intCast(nchecked));
641 if (bytes.len + r.send_buffer_end > r.send_buffer.len) {
642 const send_buffer_len = r.send_buffer_end - r.send_buffer_start;
643 const chunk_len = r.chunk_len + bytes.len;
644 var header_buf: [18]u8 = undefined;
645 const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{chunk_len}) catch unreachable;
646
647 var iovecs: [5]std.posix.iovec_const = .{
648 .{
649 .iov_base = r.send_buffer.ptr + r.send_buffer_start,
650 .iov_len = send_buffer_len - r.chunk_len,
651 },
652 .{
653 .iov_base = chunk_header.ptr,
654 .iov_len = chunk_header.len,
655 },
656 .{
657 .iov_base = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len,
658 .iov_len = r.chunk_len,
659 },
660 .{
661 .iov_base = bytes.ptr,
662 .iov_len = bytes.len,
663 },
664 .{
665 .iov_base = "\r\n",
666 .iov_len = 2,
667 },
668 };
669 // TODO make this writev instead of writevAll, which involves
670 // complicating the logic of this function.
671 try r.stream.writevAll(&iovecs);
672 r.send_buffer_start = 0;
673 r.send_buffer_end = 0;
674 r.chunk_len = 0;
675 return bytes.len;
520676 }
521677
522 if (has_trail) {
523 // The response headers before the trailers are already
524 // guaranteed to be valid, so they will always be parsed again
525 // and cannot return an error.
526 // This will *only* fail for a malformed trailer.
527 res.request.parse(res.request.parser.get()) catch return error.InvalidTrailers;
678 // All bytes can be stored in the remaining space of the buffer.
679 @memcpy(r.send_buffer[r.send_buffer_end..][0..bytes.len], bytes);
680 r.send_buffer_end += bytes.len;
681 r.chunk_len += bytes.len;
682 return bytes.len;
683 }
684
685 /// If using content-length, asserts that writing these bytes to the client
686 /// would not exceed the content-length value sent in the HTTP header.
687 pub fn writeAll(r: *Response, bytes: []const u8) WriteError!void {
688 var index: usize = 0;
689 while (index < bytes.len) {
690 index += try write(r, bytes[index..]);
528691 }
529692 }
530693
531 return out_index;
532}
694 /// Sends all buffered data to the client.
695 /// This is redundant after calling `end`.
696 pub fn flush(r: *Response) WriteError!void {
697 if (r.content_length != null) {
698 return flush_cl(r);
699 } else {
700 return flush_chunked(r, null);
701 }
702 }
533703
534/// Reads data from the response body. Must be called after `wait`.
535pub fn readAll(res: *Server, buffer: []u8) !usize {
536 var index: usize = 0;
537 while (index < buffer.len) {
538 const amt = try read(res, buffer[index..]);
539 if (amt == 0) break;
540 index += amt;
704 fn flush_cl(r: *Response) WriteError!void {
705 assert(r.content_length != null);
706 try r.stream.writeAll(r.send_buffer[r.send_buffer_start..r.send_buffer_end]);
707 r.send_buffer_start = 0;
708 r.send_buffer_end = 0;
541709 }
542 return index;
543}
544710
545pub const WriteError = Connection.WriteError || error{ NotWriteable, MessageTooLong };
711 fn flush_chunked(r: *Response, end_trailers: ?[]const http.Header) WriteError!void {
712 const max_trailers = 25;
713 if (end_trailers) |trailers| assert(trailers.len <= max_trailers);
714 assert(r.content_length == null);
715 const send_buffer_len = r.send_buffer_end - r.send_buffer_start;
716 var header_buf: [18]u8 = undefined;
717 const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{r.chunk_len}) catch unreachable;
546718
547pub const Writer = std.io.Writer(*Server, WriteError, write);
719 var iovecs: [max_trailers * 4 + 5]std.posix.iovec_const = undefined;
720 var iovecs_len: usize = 0;
548721
549pub fn writer(res: *Server) Writer {
550 return .{ .context = res };
551}
722 iovecs[iovecs_len] = .{
723 .iov_base = r.send_buffer.ptr + r.send_buffer_start,
724 .iov_len = send_buffer_len - r.chunk_len,
725 };
726 iovecs_len += 1;
552727
553/// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent.
554/// Must be called after `send` and before `finish`.
555pub fn write(res: *Server, bytes: []const u8) WriteError!usize {
556 switch (res.state) {
557 .responded => {},
558 .first, .waited, .start, .finished => unreachable,
559 }
728 iovecs[iovecs_len] = .{
729 .iov_base = chunk_header.ptr,
730 .iov_len = chunk_header.len,
731 };
732 iovecs_len += 1;
560733
561 switch (res.transfer_encoding) {
562 .chunked => {
563 if (bytes.len > 0) {
564 try res.connection.writer().print("{x}\r\n", .{bytes.len});
565 try res.connection.writeAll(bytes);
566 try res.connection.writeAll("\r\n");
567 }
734 iovecs[iovecs_len] = .{
735 .iov_base = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len,
736 .iov_len = r.chunk_len,
737 };
738 iovecs_len += 1;
568739
569 return bytes.len;
570 },
571 .content_length => |*len| {
572 if (len.* < bytes.len) return error.MessageTooLong;
573
574 const amt = try res.connection.write(bytes);
575 len.* -= amt;
576 return amt;
577 },
578 .none => return error.NotWriteable,
579 }
580}
740 if (end_trailers) |trailers| {
741 if (r.chunk_len > 0) {
742 iovecs[iovecs_len] = .{
743 .iov_base = "\r\n0\r\n",
744 .iov_len = 5,
745 };
746 iovecs_len += 1;
747 }
581748
582/// Write `bytes` to the server. The `transfer_encoding` request header determines how data will be sent.
583/// Must be called after `send` and before `finish`.
584pub fn writeAll(req: *Server, bytes: []const u8) WriteError!void {
585 var index: usize = 0;
586 while (index < bytes.len) {
587 index += try write(req, bytes[index..]);
588 }
589}
749 for (trailers) |trailer| {
750 iovecs[iovecs_len] = .{
751 .iov_base = trailer.name.ptr,
752 .iov_len = trailer.name.len,
753 };
754 iovecs_len += 1;
755
756 iovecs[iovecs_len] = .{
757 .iov_base = ": ",
758 .iov_len = 2,
759 };
760 iovecs_len += 1;
761
762 iovecs[iovecs_len] = .{
763 .iov_base = trailer.value.ptr,
764 .iov_len = trailer.value.len,
765 };
766 iovecs_len += 1;
767
768 iovecs[iovecs_len] = .{
769 .iov_base = "\r\n",
770 .iov_len = 2,
771 };
772 iovecs_len += 1;
773 }
590774
591pub const FinishError = Connection.WriteError || error{MessageNotCompleted};
775 iovecs[iovecs_len] = .{
776 .iov_base = "\r\n",
777 .iov_len = 2,
778 };
779 iovecs_len += 1;
780 } else if (r.chunk_len > 0) {
781 iovecs[iovecs_len] = .{
782 .iov_base = "\r\n",
783 .iov_len = 2,
784 };
785 iovecs_len += 1;
786 }
592787
593/// Finish the body of a request. This notifies the server that you have no more data to send.
594/// Must be called after `send`.
595pub fn finish(res: *Server) FinishError!void {
596 switch (res.state) {
597 .responded => res.state = .finished,
598 .first, .waited, .start, .finished => unreachable,
788 try r.stream.writevAll(iovecs[0..iovecs_len]);
789 r.send_buffer_start = 0;
790 r.send_buffer_end = 0;
791 r.chunk_len = 0;
599792 }
600793
601 switch (res.transfer_encoding) {
602 .chunked => try res.connection.writeAll("0\r\n\r\n"),
603 .content_length => |len| if (len != 0) return error.MessageNotCompleted,
604 .none => {},
794 pub fn writer(r: *Response) std.io.AnyWriter {
795 return .{
796 .writeFn = if (r.content_length != null) write_cl else write_chunked,
797 .context = r,
798 };
605799 }
606}
800};
607801
608const builtin = @import("builtin");
609802const std = @import("../std.zig");
610const testing = std.testing;
611803const http = std.http;
612804const mem = std.mem;
613805const net = std.net;
......@@ -615,4 +807,3 @@ const Uri = std.Uri;
615807const assert = std.debug.assert;
616808
617809const Server = @This();
618const proto = @import("protocol.zig");
lib/std/http/Server/Connection.zig deleted-119
......@@ -1,119 +0,0 @@
1stream: std.net.Stream,
2
3read_buf: [buffer_size]u8,
4read_start: u16,
5read_end: u16,
6
7pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
8
9pub fn rawReadAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
10 return conn.stream.readAtLeast(buffer, len) catch |err| {
11 switch (err) {
12 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
13 else => return error.UnexpectedReadFailure,
14 }
15 };
16}
17
18pub fn fill(conn: *Connection) ReadError!void {
19 if (conn.read_end != conn.read_start) return;
20
21 const nread = try conn.rawReadAtLeast(conn.read_buf[0..], 1);
22 if (nread == 0) return error.EndOfStream;
23 conn.read_start = 0;
24 conn.read_end = @intCast(nread);
25}
26
27pub fn peek(conn: *Connection) []const u8 {
28 return conn.read_buf[conn.read_start..conn.read_end];
29}
30
31pub fn drop(conn: *Connection, num: u16) void {
32 conn.read_start += num;
33}
34
35pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
36 assert(len <= buffer.len);
37
38 var out_index: u16 = 0;
39 while (out_index < len) {
40 const available_read = conn.read_end - conn.read_start;
41 const available_buffer = buffer.len - out_index;
42
43 if (available_read > available_buffer) { // partially read buffered data
44 @memcpy(buffer[out_index..], conn.read_buf[conn.read_start..conn.read_end][0..available_buffer]);
45 out_index += @as(u16, @intCast(available_buffer));
46 conn.read_start += @as(u16, @intCast(available_buffer));
47
48 break;
49 } else if (available_read > 0) { // fully read buffered data
50 @memcpy(buffer[out_index..][0..available_read], conn.read_buf[conn.read_start..conn.read_end]);
51 out_index += available_read;
52 conn.read_start += available_read;
53
54 if (out_index >= len) break;
55 }
56
57 const leftover_buffer = available_buffer - available_read;
58 const leftover_len = len - out_index;
59
60 if (leftover_buffer > conn.read_buf.len) {
61 // skip the buffer if the output is large enough
62 return conn.rawReadAtLeast(buffer[out_index..], leftover_len);
63 }
64
65 try conn.fill();
66 }
67
68 return out_index;
69}
70
71pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {
72 return conn.readAtLeast(buffer, 1);
73}
74
75pub const ReadError = error{
76 ConnectionTimedOut,
77 ConnectionResetByPeer,
78 UnexpectedReadFailure,
79 EndOfStream,
80};
81
82pub const Reader = std.io.Reader(*Connection, ReadError, read);
83
84pub fn reader(conn: *Connection) Reader {
85 return .{ .context = conn };
86}
87
88pub fn writeAll(conn: *Connection, buffer: []const u8) WriteError!void {
89 return conn.stream.writeAll(buffer) catch |err| switch (err) {
90 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
91 else => return error.UnexpectedWriteFailure,
92 };
93}
94
95pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize {
96 return conn.stream.write(buffer) catch |err| switch (err) {
97 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
98 else => return error.UnexpectedWriteFailure,
99 };
100}
101
102pub const WriteError = error{
103 ConnectionResetByPeer,
104 UnexpectedWriteFailure,
105};
106
107pub const Writer = std.io.Writer(*Connection, WriteError, write);
108
109pub fn writer(conn: *Connection) Writer {
110 return .{ .context = conn };
111}
112
113pub fn close(conn: *Connection) void {
114 conn.stream.close();
115}
116
117const Connection = @This();
118const std = @import("../../std.zig");
119const assert = std.debug.assert;
lib/std/http/protocol.zig+21-343
......@@ -73,339 +73,28 @@ pub const HeadersParser = struct {
7373 return hp.header_bytes_buffer[0..hp.header_bytes_len];
7474 }
7575
76 /// Returns the number of bytes consumed by headers. This is always less
77 /// than or equal to `bytes.len`.
78 /// You should check `r.state.isContent()` after this to check if the
79 /// headers are done.
80 ///
81 /// If the amount returned is less than `bytes.len`, you may assume that
82 /// the parser is in a content state and the
83 /// first byte of content is located at `bytes[result]`.
8476 pub fn findHeadersEnd(r: *HeadersParser, bytes: []const u8) u32 {
85 const vector_len: comptime_int = @max(std.simd.suggestVectorLength(u8) orelse 1, 8);
86 const len: u32 = @intCast(bytes.len);
87 var index: u32 = 0;
88
89 while (true) {
90 switch (r.state) {
91 .invalid => unreachable,
92 .finished => return index,
93 .start => switch (len - index) {
94 0 => return index,
95 1 => {
96 switch (bytes[index]) {
97 '\r' => r.state = .seen_r,
98 '\n' => r.state = .seen_n,
99 else => {},
100 }
101
102 return index + 1;
103 },
104 2 => {
105 const b16 = int16(bytes[index..][0..2]);
106 const b8 = intShift(u8, b16);
107
108 switch (b8) {
109 '\r' => r.state = .seen_r,
110 '\n' => r.state = .seen_n,
111 else => {},
112 }
113
114 switch (b16) {
115 int16("\r\n") => r.state = .seen_rn,
116 int16("\n\n") => r.state = .finished,
117 else => {},
118 }
119
120 return index + 2;
121 },
122 3 => {
123 const b24 = int24(bytes[index..][0..3]);
124 const b16 = intShift(u16, b24);
125 const b8 = intShift(u8, b24);
126
127 switch (b8) {
128 '\r' => r.state = .seen_r,
129 '\n' => r.state = .seen_n,
130 else => {},
131 }
132
133 switch (b16) {
134 int16("\r\n") => r.state = .seen_rn,
135 int16("\n\n") => r.state = .finished,
136 else => {},
137 }
138
139 switch (b24) {
140 int24("\r\n\r") => r.state = .seen_rnr,
141 else => {},
142 }
143
144 return index + 3;
145 },
146 4...vector_len - 1 => {
147 const b32 = int32(bytes[index..][0..4]);
148 const b24 = intShift(u24, b32);
149 const b16 = intShift(u16, b32);
150 const b8 = intShift(u8, b32);
151
152 switch (b8) {
153 '\r' => r.state = .seen_r,
154 '\n' => r.state = .seen_n,
155 else => {},
156 }
157
158 switch (b16) {
159 int16("\r\n") => r.state = .seen_rn,
160 int16("\n\n") => r.state = .finished,
161 else => {},
162 }
163
164 switch (b24) {
165 int24("\r\n\r") => r.state = .seen_rnr,
166 else => {},
167 }
168
169 switch (b32) {
170 int32("\r\n\r\n") => r.state = .finished,
171 else => {},
172 }
173
174 index += 4;
175 continue;
176 },
177 else => {
178 const chunk = bytes[index..][0..vector_len];
179 const matches = if (use_vectors) matches: {
180 const Vector = @Vector(vector_len, u8);
181 // const BoolVector = @Vector(vector_len, bool);
182 const BitVector = @Vector(vector_len, u1);
183 const SizeVector = @Vector(vector_len, u8);
184
185 const v: Vector = chunk.*;
186 const matches_r: BitVector = @bitCast(v == @as(Vector, @splat('\r')));
187 const matches_n: BitVector = @bitCast(v == @as(Vector, @splat('\n')));
188 const matches_or: SizeVector = matches_r | matches_n;
189
190 break :matches @reduce(.Add, matches_or);
191 } else matches: {
192 var matches: u8 = 0;
193 for (chunk) |byte| switch (byte) {
194 '\r', '\n' => matches += 1,
195 else => {},
196 };
197 break :matches matches;
198 };
199 switch (matches) {
200 0 => {},
201 1 => switch (chunk[vector_len - 1]) {
202 '\r' => r.state = .seen_r,
203 '\n' => r.state = .seen_n,
204 else => {},
205 },
206 2 => {
207 const b16 = int16(chunk[vector_len - 2 ..][0..2]);
208 const b8 = intShift(u8, b16);
209
210 switch (b8) {
211 '\r' => r.state = .seen_r,
212 '\n' => r.state = .seen_n,
213 else => {},
214 }
215
216 switch (b16) {
217 int16("\r\n") => r.state = .seen_rn,
218 int16("\n\n") => r.state = .finished,
219 else => {},
220 }
221 },
222 3 => {
223 const b24 = int24(chunk[vector_len - 3 ..][0..3]);
224 const b16 = intShift(u16, b24);
225 const b8 = intShift(u8, b24);
226
227 switch (b8) {
228 '\r' => r.state = .seen_r,
229 '\n' => r.state = .seen_n,
230 else => {},
231 }
232
233 switch (b16) {
234 int16("\r\n") => r.state = .seen_rn,
235 int16("\n\n") => r.state = .finished,
236 else => {},
237 }
238
239 switch (b24) {
240 int24("\r\n\r") => r.state = .seen_rnr,
241 else => {},
242 }
243 },
244 4...vector_len => {
245 inline for (0..vector_len - 3) |i_usize| {
246 const i = @as(u32, @truncate(i_usize));
247
248 const b32 = int32(chunk[i..][0..4]);
249 const b16 = intShift(u16, b32);
250
251 if (b32 == int32("\r\n\r\n")) {
252 r.state = .finished;
253 return index + i + 4;
254 } else if (b16 == int16("\n\n")) {
255 r.state = .finished;
256 return index + i + 2;
257 }
258 }
259
260 const b24 = int24(chunk[vector_len - 3 ..][0..3]);
261 const b16 = intShift(u16, b24);
262 const b8 = intShift(u8, b24);
263
264 switch (b8) {
265 '\r' => r.state = .seen_r,
266 '\n' => r.state = .seen_n,
267 else => {},
268 }
269
270 switch (b16) {
271 int16("\r\n") => r.state = .seen_rn,
272 int16("\n\n") => r.state = .finished,
273 else => {},
274 }
275
276 switch (b24) {
277 int24("\r\n\r") => r.state = .seen_rnr,
278 else => {},
279 }
280 },
281 else => unreachable,
282 }
283
284 index += vector_len;
285 continue;
286 },
287 },
288 .seen_n => switch (len - index) {
289 0 => return index,
290 else => {
291 switch (bytes[index]) {
292 '\n' => r.state = .finished,
293 else => r.state = .start,
294 }
295
296 index += 1;
297 continue;
298 },
299 },
300 .seen_r => switch (len - index) {
301 0 => return index,
302 1 => {
303 switch (bytes[index]) {
304 '\n' => r.state = .seen_rn,
305 '\r' => r.state = .seen_r,
306 else => r.state = .start,
307 }
308
309 return index + 1;
310 },
311 2 => {
312 const b16 = int16(bytes[index..][0..2]);
313 const b8 = intShift(u8, b16);
314
315 switch (b8) {
316 '\r' => r.state = .seen_r,
317 '\n' => r.state = .seen_rn,
318 else => r.state = .start,
319 }
320
321 switch (b16) {
322 int16("\r\n") => r.state = .seen_rn,
323 int16("\n\r") => r.state = .seen_rnr,
324 int16("\n\n") => r.state = .finished,
325 else => {},
326 }
327
328 return index + 2;
329 },
330 else => {
331 const b24 = int24(bytes[index..][0..3]);
332 const b16 = intShift(u16, b24);
333 const b8 = intShift(u8, b24);
334
335 switch (b8) {
336 '\r' => r.state = .seen_r,
337 '\n' => r.state = .seen_n,
338 else => r.state = .start,
339 }
340
341 switch (b16) {
342 int16("\r\n") => r.state = .seen_rn,
343 int16("\n\n") => r.state = .finished,
344 else => {},
345 }
346
347 switch (b24) {
348 int24("\n\r\n") => r.state = .finished,
349 else => {},
350 }
351
352 index += 3;
353 continue;
354 },
355 },
356 .seen_rn => switch (len - index) {
357 0 => return index,
358 1 => {
359 switch (bytes[index]) {
360 '\r' => r.state = .seen_rnr,
361 '\n' => r.state = .seen_n,
362 else => r.state = .start,
363 }
364
365 return index + 1;
366 },
367 else => {
368 const b16 = int16(bytes[index..][0..2]);
369 const b8 = intShift(u8, b16);
370
371 switch (b8) {
372 '\r' => r.state = .seen_rnr,
373 '\n' => r.state = .seen_n,
374 else => r.state = .start,
375 }
376
377 switch (b16) {
378 int16("\r\n") => r.state = .finished,
379 int16("\n\n") => r.state = .finished,
380 else => {},
381 }
382
383 index += 2;
384 continue;
385 },
386 },
387 .seen_rnr => switch (len - index) {
388 0 => return index,
389 else => {
390 switch (bytes[index]) {
391 '\n' => r.state = .finished,
392 else => r.state = .start,
393 }
394
395 index += 1;
396 continue;
397 },
398 },
399 .chunk_head_size => unreachable,
400 .chunk_head_ext => unreachable,
401 .chunk_head_r => unreachable,
402 .chunk_data => unreachable,
403 .chunk_data_suffix => unreachable,
404 .chunk_data_suffix_r => unreachable,
405 }
406
407 return index;
408 }
77 var hp: std.http.HeadParser = .{
78 .state = switch (r.state) {
79 .start => .start,
80 .seen_n => .seen_n,
81 .seen_r => .seen_r,
82 .seen_rn => .seen_rn,
83 .seen_rnr => .seen_rnr,
84 .finished => .finished,
85 else => unreachable,
86 },
87 };
88 const result = hp.feed(bytes);
89 r.state = switch (hp.state) {
90 .start => .start,
91 .seen_n => .seen_n,
92 .seen_r => .seen_r,
93 .seen_rn => .seen_rn,
94 .seen_rnr => .seen_rnr,
95 .finished => .finished,
96 };
97 return @intCast(result);
40998 }
41099
411100 /// Returns the number of bytes consumed by the chunk size. This is always
......@@ -775,17 +464,6 @@ const MockBufferedConnection = struct {
775464 }
776465};
777466
778test "HeadersParser.findHeadersEnd" {
779 var r: HeadersParser = undefined;
780 const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\nHello";
781
782 for (0..36) |i| {
783 r = HeadersParser.init(&.{});
784 try std.testing.expectEqual(@as(u32, @intCast(i)), r.findHeadersEnd(data[0..i]));
785 try std.testing.expectEqual(@as(u32, @intCast(35 - i)), r.findHeadersEnd(data[i..]));
786 }
787}
788
789467test "HeadersParser.findChunkedLen" {
790468 var r: HeadersParser = undefined;
791469 const data = "Ff\r\nf0f000 ; ext\n0\r\nffffffffffffffffffffffffffffffffffffffff\r\n";
lib/std/http/test.zig+40-37
......@@ -69,31 +69,35 @@ test "trailers" {
6969fn serverThread(http_server: *std.net.Server) anyerror!void {
7070 var header_buffer: [1024]u8 = undefined;
7171 var remaining: usize = 1;
72 accept: while (remaining != 0) : (remaining -= 1) {
72 while (remaining != 0) : (remaining -= 1) {
7373 const conn = try http_server.accept();
7474 defer conn.stream.close();
7575
76 var res = std.http.Server.init(conn, .{ .client_header_buffer = &header_buffer });
76 var server = std.http.Server.init(conn, &header_buffer);
7777
78 res.wait() catch |err| switch (err) {
79 error.HttpHeadersInvalid => continue :accept,
80 error.EndOfStream => continue,
81 else => return err,
82 };
83 try serve(&res);
84
85 try testing.expectEqual(.reset, res.reset());
78 try testing.expectEqual(.ready, server.state);
79 var request = try server.receiveHead();
80 try serve(&request);
81 try testing.expectEqual(.ready, server.state);
8682 }
8783}
8884
89fn serve(res: *std.http.Server) !void {
90 try testing.expectEqualStrings(res.request.target, "/trailer");
91 res.transfer_encoding = .chunked;
85fn serve(request: *std.http.Server.Request) !void {
86 try testing.expectEqualStrings(request.head.target, "/trailer");
9287
93 try res.send();
94 try res.writeAll("Hello, ");
95 try res.writeAll("World!\n");
96 try res.connection.writeAll("0\r\nX-Checksum: aaaa\r\n\r\n");
88 var send_buffer: [1024]u8 = undefined;
89 var response = request.respondStreaming(.{
90 .send_buffer = &send_buffer,
91 });
92 try response.writeAll("Hello, ");
93 try response.flush();
94 try response.writeAll("World!\n");
95 try response.flush();
96 try response.endChunked(.{
97 .trailers = &.{
98 .{ .name = "X-Checksum", .value = "aaaa" },
99 },
100 });
97101}
98102
99103test "HTTP server handles a chunked transfer coding request" {
......@@ -116,34 +120,33 @@ test "HTTP server handles a chunked transfer coding request" {
116120 const max_header_size = 8192;
117121
118122 const address = try std.net.Address.parseIp("127.0.0.1", 0);
119 var server = try address.listen(.{ .reuse_address = true });
120 defer server.deinit();
121 const server_port = server.listen_address.in.getPort();
123 var socket_server = try address.listen(.{ .reuse_address = true });
124 defer socket_server.deinit();
125 const server_port = socket_server.listen_address.in.getPort();
122126
123127 const server_thread = try std.Thread.spawn(.{}, (struct {
124 fn apply(s: *std.net.Server) !void {
128 fn apply(net_server: *std.net.Server) !void {
125129 var header_buffer: [max_header_size]u8 = undefined;
126 const conn = try s.accept();
130 const conn = try net_server.accept();
127131 defer conn.stream.close();
128 var res = std.http.Server.init(conn, .{ .client_header_buffer = &header_buffer });
129 try res.wait();
130
131 try expect(res.request.transfer_encoding == .chunked);
132 const server_body: []const u8 = "message from server!\n";
133 res.transfer_encoding = .{ .content_length = server_body.len };
134 res.extra_headers = &.{
135 .{ .name = "content-type", .value = "text/plain" },
136 };
137 res.keep_alive = false;
138 try res.send();
132
133 var server = std.http.Server.init(conn, &header_buffer);
134 var request = try server.receiveHead();
135
136 try expect(request.head.transfer_encoding == .chunked);
139137
140138 var buf: [128]u8 = undefined;
141 const n = try res.readAll(&buf);
139 const n = try request.reader().readAll(&buf);
142140 try expect(std.mem.eql(u8, buf[0..n], "ABCD"));
143 _ = try res.writer().writeAll(server_body);
144 try res.finish();
141
142 try request.respond("message from server!\n", .{
143 .extra_headers = &.{
144 .{ .name = "content-type", .value = "text/plain" },
145 },
146 .keep_alive = false,
147 });
145148 }
146 }).apply, .{&server});
149 }).apply, .{&socket_server});
147150
148151 const request_bytes =
149152 "POST / HTTP/1.1\r\n" ++