authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-21 23:47:35-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-23 02:37:11-07:00
logabde76a808df816ea12a8a2dbf8e6b53ff9b110f
treed817c13501789ae507fc29a069f344eaed28c286
parent380916c0f8883746e4d84d5334f68d0569d76f38

std.http.Server: handle expect: 100-continue requests

The API automatically handles these requests as expected. After receiveHead(), the server has a chance to notice the expectation and do something about it. If it does not, then the Server implementation will handle it by sending the continuation header when the read stream is created. Both respond() and respondStreaming() send the continuation header as part of discarding the request body, only if the read stream has not already been created.

3 files changed, 85 insertions(+), 41 deletions(-)

lib/std/http/Server.zig+69-22
......@@ -313,11 +313,20 @@ pub const Request = struct {
313313
314314 var first_buffer: [500]u8 = undefined;
315315 var h = std.ArrayListUnmanaged(u8).initBuffer(&first_buffer);
316 if (request.head.expect != null) {
317 // reader() and hence discardBody() above sets expect to null if it
318 // is handled. So the fact that it is not null here means unhandled.
319 h.appendSliceAssumeCapacity("HTTP/1.1 417 Expectation Failed\r\n");
320 if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n");
321 h.appendSliceAssumeCapacity("content-length: 0\r\n\r\n");
322 try request.server.connection.stream.writeAll(h.items);
323 return;
324 }
316325 h.fixedWriter().print("{s} {d} {s}\r\n", .{
317326 @tagName(options.version), @intFromEnum(options.status), phrase,
318327 }) catch unreachable;
319 if (keep_alive)
320 h.appendSliceAssumeCapacity("connection: keep-alive\r\n");
328
329 if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n");
321330
322331 if (options.transfer_encoding) |transfer_encoding| switch (transfer_encoding) {
323332 .none => {},
......@@ -452,25 +461,35 @@ pub const Request = struct {
452461
453462 var h = std.ArrayListUnmanaged(u8).initBuffer(options.send_buffer);
454463
455 h.fixedWriter().print("{s} {d} {s}\r\n", .{
456 @tagName(o.version), @intFromEnum(o.status), phrase,
457 }) catch unreachable;
458 if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n");
464 const elide_body = if (request.head.expect != null) eb: {
465 // reader() and hence discardBody() above sets expect to null if it
466 // is handled. So the fact that it is not null here means unhandled.
467 h.appendSliceAssumeCapacity("HTTP/1.1 417 Expectation Failed\r\n");
468 if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n");
469 h.appendSliceAssumeCapacity("content-length: 0\r\n\r\n");
470 break :eb true;
471 } else eb: {
472 h.fixedWriter().print("{s} {d} {s}\r\n", .{
473 @tagName(o.version), @intFromEnum(o.status), phrase,
474 }) catch unreachable;
475 if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n");
476
477 if (options.content_length) |len| {
478 h.fixedWriter().print("content-length: {d}\r\n", .{len}) catch unreachable;
479 } else {
480 h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n");
481 }
459482
460 if (options.content_length) |len| {
461 h.fixedWriter().print("content-length: {d}\r\n", .{len}) catch unreachable;
462 } else {
463 h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n");
464 }
483 for (o.extra_headers) |header| {
484 h.appendSliceAssumeCapacity(header.name);
485 h.appendSliceAssumeCapacity(": ");
486 h.appendSliceAssumeCapacity(header.value);
487 h.appendSliceAssumeCapacity("\r\n");
488 }
465489
466 for (o.extra_headers) |header| {
467 h.appendSliceAssumeCapacity(header.name);
468 h.appendSliceAssumeCapacity(": ");
469 h.appendSliceAssumeCapacity(header.value);
470490 h.appendSliceAssumeCapacity("\r\n");
471 }
472
473 h.appendSliceAssumeCapacity("\r\n");
491 break :eb request.head.method == .HEAD;
492 };
474493
475494 return .{
476495 .stream = request.server.connection.stream,
......@@ -478,16 +497,20 @@ pub const Request = struct {
478497 .send_buffer_start = 0,
479498 .send_buffer_end = h.items.len,
480499 .content_length = options.content_length,
481 .elide_body = request.head.method == .HEAD,
500 .elide_body = elide_body,
482501 .chunk_len = 0,
483502 };
484503 }
485504
486 pub const ReadError = net.Stream.ReadError || error{ HttpChunkInvalid, HttpHeadersOversize };
505 pub const ReadError = net.Stream.ReadError || error{
506 HttpChunkInvalid,
507 HttpHeadersOversize,
508 };
487509
488510 fn read_cl(context: *const anyopaque, buffer: []u8) ReadError!usize {
489511 const request: *Request = @constCast(@alignCast(@ptrCast(context)));
490512 const s = request.server;
513
491514 const remaining_content_length = &request.reader_state.remaining_content_length;
492515 if (remaining_content_length.* == 0) {
493516 s.state = .ready;
......@@ -515,6 +538,7 @@ pub const Request = struct {
515538 fn read_chunked(context: *const anyopaque, buffer: []u8) ReadError!usize {
516539 const request: *Request = @constCast(@alignCast(@ptrCast(context)));
517540 const s = request.server;
541
518542 const cp = &request.reader_state.chunk_parser;
519543 const head_end = request.head_end;
520544
......@@ -599,11 +623,33 @@ pub const Request = struct {
599623 return out_end;
600624 }
601625
602 pub fn reader(request: *Request) std.io.AnyReader {
626 pub const ReaderError = Response.WriteError || error{
627 /// The client sent an expect HTTP header value other than
628 /// "100-continue".
629 HttpExpectationFailed,
630 };
631
632 /// In the case that the request contains "expect: 100-continue", this
633 /// function writes the continuation header, which means it can fail with a
634 /// write error. After sending the continuation header, it sets the
635 /// request's expect field to `null`.
636 ///
637 /// Asserts that this function is only called once.
638 pub fn reader(request: *Request) ReaderError!std.io.AnyReader {
603639 const s = request.server;
604640 assert(s.state == .received_head);
605641 s.state = .receiving_body;
606642 s.next_request_start = request.head_end;
643
644 if (request.head.expect) |expect| {
645 if (mem.eql(u8, expect, "100-continue")) {
646 try request.server.connection.stream.writeAll("HTTP/1.1 100 Continue\r\n\r\n");
647 request.head.expect = null;
648 } else {
649 return error.HttpExpectationFailed;
650 }
651 }
652
607653 switch (request.head.transfer_encoding) {
608654 .chunked => {
609655 request.reader_state = .{ .chunk_parser = http.ChunkParser.init };
......@@ -639,7 +685,8 @@ pub const Request = struct {
639685 const s = request.server;
640686 if (keep_alive and request.head.keep_alive) switch (s.state) {
641687 .received_head => {
642 _ = request.reader().discard() catch return false;
688 const r = request.reader() catch return false;
689 _ = r.discard() catch return false;
643690 assert(s.state == .ready);
644691 return true;
645692 },
lib/std/http/test.zig+15-8
......@@ -136,7 +136,7 @@ test "HTTP server handles a chunked transfer coding request" {
136136 try expect(request.head.transfer_encoding == .chunked);
137137
138138 var buf: [128]u8 = undefined;
139 const n = try request.reader().readAll(&buf);
139 const n = try (try request.reader()).readAll(&buf);
140140 try expect(std.mem.eql(u8, buf[0..n], "ABCD"));
141141
142142 try request.respond("message from server!\n", .{
......@@ -187,13 +187,13 @@ test "echo content server" {
187187
188188 const server_thread = try std.Thread.spawn(.{}, (struct {
189189 fn handleRequest(request: *std.http.Server.Request) !void {
190 std.debug.print("server received {s} {s} {s}\n", .{
191 @tagName(request.head.method),
192 @tagName(request.head.version),
193 request.head.target,
194 });
190 //std.debug.print("server received {s} {s} {s}\n", .{
191 // @tagName(request.head.method),
192 // @tagName(request.head.version),
193 // request.head.target,
194 //});
195195
196 const body = try request.reader().readAllAlloc(std.testing.allocator, 8192);
196 const body = try (try request.reader()).readAllAlloc(std.testing.allocator, 8192);
197197 defer std.testing.allocator.free(body);
198198
199199 try testing.expect(std.mem.startsWith(u8, request.head.target, "/echo-content"));
......@@ -217,7 +217,7 @@ test "echo content server" {
217217 try w.writeAll("Hello, ");
218218 try w.writeAll("World!\n");
219219 try response.end();
220 std.debug.print(" server finished responding\n", .{});
220 //std.debug.print(" server finished responding\n", .{});
221221 }
222222
223223 fn run(net_server: *std.net.Server) anyerror!void {
......@@ -237,6 +237,13 @@ test "echo content server" {
237237 if (std.mem.eql(u8, request.head.target, "/end")) {
238238 return request.respond("", .{ .keep_alive = false });
239239 }
240 if (request.head.expect) |expect| {
241 if (std.mem.eql(u8, expect, "garbage")) {
242 try testing.expectError(error.HttpExpectationFailed, request.reader());
243 try request.respond("", .{ .keep_alive = false });
244 continue;
245 }
246 }
240247 handleRequest(&request) catch |err| {
241248 // This message helps the person troubleshooting determine whether
242249 // output comes from the server thread or the client thread.
test/standalone/http.zig+1-11
......@@ -26,17 +26,7 @@ fn handleRequest(request: *http.Server.Request, listen_port: u16) !void {
2626 request.head.target,
2727 });
2828
29 if (request.head.expect) |expect| {
30 if (mem.eql(u8, expect, "100-continue")) {
31 @panic("test failure, didn't handle expect 100-continue");
32 } else {
33 return request.respond("", .{
34 .status = .expectation_failed,
35 });
36 }
37 }
38
39 const body = try request.reader().readAllAlloc(salloc, 8192);
29 const body = try (try request.reader()).readAllAlloc(salloc, 8192);
4030 defer salloc.free(body);
4131
4232 var send_buffer: [100]u8 = undefined;