authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-01 21:30:27-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-07 10:04:29-07:00
logfef41c66dbe9e6ce3e9567b7d6d06ebf4d0cba06
tree8436017ad76255c87f55d7a5016307a2cd9d4f37
parente2d81bf6c04d73cbe236e4b497fd2c8c54916077

update build system to new http.Server API


3 files changed, 90 insertions(+), 103 deletions(-)

lib/std/Build/Fuzz.zig+16-23
......@@ -234,7 +234,7 @@ pub const Previous = struct {
234234};
235235pub fn sendUpdate(
236236 fuzz: *Fuzz,
237 socket: *std.http.WebSocket,
237 socket: *std.http.Server.WebSocket,
238238 prev: *Previous,
239239) !void {
240240 fuzz.coverage_mutex.lock();
......@@ -263,36 +263,36 @@ pub fn sendUpdate(
263263 .string_bytes_len = @intCast(coverage_map.coverage.string_bytes.items.len),
264264 .start_timestamp = coverage_map.start_timestamp,
265265 };
266 const iovecs: [5]std.posix.iovec_const = .{
267 makeIov(@ptrCast(&header)),
268 makeIov(@ptrCast(coverage_map.coverage.directories.keys())),
269 makeIov(@ptrCast(coverage_map.coverage.files.keys())),
270 makeIov(@ptrCast(coverage_map.source_locations)),
271 makeIov(coverage_map.coverage.string_bytes.items),
266 var iovecs: [5][]const u8 = .{
267 @ptrCast(&header),
268 @ptrCast(coverage_map.coverage.directories.keys()),
269 @ptrCast(coverage_map.coverage.files.keys()),
270 @ptrCast(coverage_map.source_locations),
271 coverage_map.coverage.string_bytes.items,
272272 };
273 try socket.writeMessagev(&iovecs, .binary);
273 try socket.writeMessageVec(&iovecs, .binary);
274274 }
275275
276276 const header: abi.CoverageUpdateHeader = .{
277277 .n_runs = n_runs,
278278 .unique_runs = unique_runs,
279279 };
280 const iovecs: [2]std.posix.iovec_const = .{
281 makeIov(@ptrCast(&header)),
282 makeIov(@ptrCast(seen_pcs)),
280 var iovecs: [2][]const u8 = .{
281 @ptrCast(&header),
282 @ptrCast(seen_pcs),
283283 };
284 try socket.writeMessagev(&iovecs, .binary);
284 try socket.writeMessageVec(&iovecs, .binary);
285285
286286 prev.unique_runs = unique_runs;
287287 }
288288
289289 if (prev.entry_points != coverage_map.entry_points.items.len) {
290290 const header: abi.EntryPointHeader = .init(@intCast(coverage_map.entry_points.items.len));
291 const iovecs: [2]std.posix.iovec_const = .{
292 makeIov(@ptrCast(&header)),
293 makeIov(@ptrCast(coverage_map.entry_points.items)),
291 var iovecs: [2][]const u8 = .{
292 @ptrCast(&header),
293 @ptrCast(coverage_map.entry_points.items),
294294 };
295 try socket.writeMessagev(&iovecs, .binary);
295 try socket.writeMessageVec(&iovecs, .binary);
296296
297297 prev.entry_points = coverage_map.entry_points.items.len;
298298 }
......@@ -448,10 +448,3 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte
448448 }
449449 try coverage_map.entry_points.append(fuzz.ws.gpa, @intCast(index));
450450}
451
452fn makeIov(s: []const u8) std.posix.iovec_const {
453 return .{
454 .base = s.ptr,
455 .len = s.len,
456 };
457}
lib/std/Build/WebServer.zig+42-53
......@@ -251,48 +251,44 @@ pub fn now(s: *const WebServer) i64 {
251251fn accept(ws: *WebServer, connection: std.net.Server.Connection) void {
252252 defer connection.stream.close();
253253
254 var read_buf: [0x4000]u8 = undefined;
255 var server: std.http.Server = .init(connection, &read_buf);
254 var send_buffer: [4096]u8 = undefined;
255 var recv_buffer: [4096]u8 = undefined;
256 var connection_reader = connection.stream.reader(&recv_buffer);
257 var connection_writer = connection.stream.writer(&send_buffer);
258 var server: http.Server = .init(connection_reader.interface(), &connection_writer.interface);
256259
257260 while (true) {
258261 var request = server.receiveHead() catch |err| switch (err) {
259262 error.HttpConnectionClosing => return,
260 else => {
261 log.err("failed to receive http request: {s}", .{@errorName(err)});
262 return;
263 },
263 else => return log.err("failed to receive http request: {t}", .{err}),
264264 };
265 var ws_send_buf: [0x4000]u8 = undefined;
266 var ws_recv_buf: [0x4000]u8 align(4) = undefined;
267 if (std.http.WebSocket.init(&request, &ws_send_buf, &ws_recv_buf) catch |err| {
268 log.err("failed to initialize websocket connection: {s}", .{@errorName(err)});
269 return;
270 }) |ws_init| {
271 var web_socket = ws_init;
272 ws.serveWebSocket(&web_socket) catch |err| {
273 log.err("failed to serve websocket: {s}", .{@errorName(err)});
274 return;
275 };
276 comptime unreachable;
277 } else {
278 ws.serveRequest(&request) catch |err| switch (err) {
279 error.AlreadyReported => return,
280 else => {
281 log.err("failed to serve '{s}': {s}", .{ request.head.target, @errorName(err) });
265 switch (request.upgradeRequested()) {
266 .websocket => |opt_key| {
267 const key = opt_key orelse return log.err("missing websocket key", .{});
268 var web_socket = request.respondWebSocket(.{ .key = key }) catch {
269 return log.err("failed to respond web socket: {t}", .{connection_writer.err.?});
270 };
271 ws.serveWebSocket(&web_socket) catch |err| {
272 log.err("failed to serve websocket: {t}", .{err});
282273 return;
283 },
284 };
274 };
275 comptime unreachable;
276 },
277 .other => |name| return log.err("unknown upgrade request: {s}", .{name}),
278 .none => {
279 ws.serveRequest(&request) catch |err| switch (err) {
280 error.AlreadyReported => return,
281 else => {
282 log.err("failed to serve '{s}': {t}", .{ request.head.target, err });
283 return;
284 },
285 };
286 },
285287 }
286288 }
287289}
288290
289fn makeIov(s: []const u8) std.posix.iovec_const {
290 return .{
291 .base = s.ptr,
292 .len = s.len,
293 };
294}
295fn serveWebSocket(ws: *WebServer, sock: *std.http.WebSocket) !noreturn {
291fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
296292 var prev_build_status = ws.build_status.load(.monotonic);
297293
298294 const prev_step_status_bits = try ws.gpa.alloc(u8, ws.step_status_bits.len);
......@@ -312,11 +308,8 @@ fn serveWebSocket(ws: *WebServer, sock: *std.http.WebSocket) !noreturn {
312308 .timestamp = ws.now(),
313309 .steps_len = @intCast(ws.all_steps.len),
314310 };
315 try sock.writeMessagev(&.{
316 makeIov(@ptrCast(&hello_header)),
317 makeIov(ws.step_names_trailing),
318 makeIov(prev_step_status_bits),
319 }, .binary);
311 var bufs: [3][]const u8 = .{ @ptrCast(&hello_header), ws.step_names_trailing, prev_step_status_bits };
312 try sock.writeMessageVec(&bufs, .binary);
320313 }
321314
322315 var prev_fuzz: Fuzz.Previous = .init;
......@@ -380,7 +373,7 @@ fn serveWebSocket(ws: *WebServer, sock: *std.http.WebSocket) !noreturn {
380373 std.Thread.Futex.timedWait(&ws.update_id, start_update_id, std.time.ns_per_ms * default_update_interval_ms) catch {};
381374 }
382375}
383fn recvWebSocketMessages(ws: *WebServer, sock: *std.http.WebSocket) void {
376fn recvWebSocketMessages(ws: *WebServer, sock: *http.Server.WebSocket) void {
384377 while (true) {
385378 const msg = sock.readSmallMessage() catch return;
386379 if (msg.opcode != .binary) continue;
......@@ -402,7 +395,7 @@ fn recvWebSocketMessages(ws: *WebServer, sock: *std.http.WebSocket) void {
402395 }
403396}
404397
405fn serveRequest(ws: *WebServer, req: *std.http.Server.Request) !void {
398fn serveRequest(ws: *WebServer, req: *http.Server.Request) !void {
406399 // Strip an optional leading '/debug' component from the request.
407400 const target: []const u8, const debug: bool = target: {
408401 if (mem.eql(u8, req.head.target, "/debug")) break :target .{ "/", true };
......@@ -431,7 +424,7 @@ fn serveRequest(ws: *WebServer, req: *std.http.Server.Request) !void {
431424
432425fn serveLibFile(
433426 ws: *WebServer,
434 request: *std.http.Server.Request,
427 request: *http.Server.Request,
435428 sub_path: []const u8,
436429 content_type: []const u8,
437430) !void {
......@@ -442,7 +435,7 @@ fn serveLibFile(
442435}
443436fn serveClientWasm(
444437 ws: *WebServer,
445 req: *std.http.Server.Request,
438 req: *http.Server.Request,
446439 optimize_mode: std.builtin.OptimizeMode,
447440) !void {
448441 var arena_state: std.heap.ArenaAllocator = .init(ws.gpa);
......@@ -456,12 +449,12 @@ fn serveClientWasm(
456449
457450pub fn serveFile(
458451 ws: *WebServer,
459 request: *std.http.Server.Request,
452 request: *http.Server.Request,
460453 path: Cache.Path,
461454 content_type: []const u8,
462455) !void {
463456 const gpa = ws.gpa;
464 // The desired API is actually sendfile, which will require enhancing std.http.Server.
457 // The desired API is actually sendfile, which will require enhancing http.Server.
465458 // We load the file with every request so that the user can make changes to the file
466459 // and refresh the HTML page without restarting this server.
467460 const file_contents = path.root_dir.handle.readFileAlloc(gpa, path.sub_path, 10 * 1024 * 1024) catch |err| {
......@@ -478,14 +471,13 @@ pub fn serveFile(
478471}
479472pub fn serveTarFile(
480473 ws: *WebServer,
481 request: *std.http.Server.Request,
474 request: *http.Server.Request,
482475 paths: []const Cache.Path,
483476) !void {
484477 const gpa = ws.gpa;
485478
486 var send_buf: [0x4000]u8 = undefined;
487 var response = request.respondStreaming(.{
488 .send_buffer = &send_buf,
479 var send_buffer: [0x4000]u8 = undefined;
480 var response = try request.respondStreaming(&send_buffer, .{
489481 .respond_options = .{
490482 .extra_headers = &.{
491483 .{ .name = "Content-Type", .value = "application/x-tar" },
......@@ -497,10 +489,7 @@ pub fn serveTarFile(
497489 var cached_cwd_path: ?[]const u8 = null;
498490 defer if (cached_cwd_path) |p| gpa.free(p);
499491
500 var response_buf: [1024]u8 = undefined;
501 var adapter = response.writer().adaptToNewApi();
502 adapter.new_interface.buffer = &response_buf;
503 var archiver: std.tar.Writer = .{ .underlying_writer = &adapter.new_interface };
492 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };
504493
505494 for (paths) |path| {
506495 var file = path.root_dir.handle.openFile(path.sub_path, .{}) catch |err| {
......@@ -526,7 +515,6 @@ pub fn serveTarFile(
526515 }
527516
528517 // intentionally not calling `archiver.finishPedantically`
529 try adapter.new_interface.flush();
530518 try response.end();
531519}
532520
......@@ -804,7 +792,7 @@ pub fn wait(ws: *WebServer) RunnerRequest {
804792 }
805793}
806794
807const cache_control_header: std.http.Header = .{
795const cache_control_header: http.Header = .{
808796 .name = "Cache-Control",
809797 .value = "max-age=0, must-revalidate",
810798};
......@@ -819,5 +807,6 @@ const Build = std.Build;
819807const Cache = Build.Cache;
820808const Fuzz = Build.Fuzz;
821809const abi = Build.abi;
810const http = std.http;
822811
823812const WebServer = @This();
lib/std/http/Server.zig+32-27
......@@ -7,6 +7,7 @@ const Uri = std.Uri;
77const assert = std.debug.assert;
88const testing = std.testing;
99const Writer = std.Io.Writer;
10const Reader = std.Io.Reader;
1011
1112const Server = @This();
1213
......@@ -21,7 +22,7 @@ reader: http.Reader,
2122/// header, otherwise `receiveHead` returns `error.HttpHeadersOversize`.
2223///
2324/// The returned `Server` is ready for `receiveHead` to be called.
24pub fn init(in: *std.Io.Reader, out: *Writer) Server {
25pub fn init(in: *Reader, out: *Writer) Server {
2526 return .{
2627 .reader = .{
2728 .in = in,
......@@ -225,7 +226,7 @@ pub const Request = struct {
225226 }
226227 };
227228
228 pub fn iterateHeaders(r: *Request) http.HeaderIterator {
229 pub fn iterateHeaders(r: *const Request) http.HeaderIterator {
229230 assert(r.server.reader.state == .received_head);
230231 return http.HeaderIterator.init(r.head_buffer);
231232 }
......@@ -486,10 +487,11 @@ pub const Request = struct {
486487 none,
487488 };
488489
490 /// Does not invalidate `request.head`.
489491 pub fn upgradeRequested(request: *const Request) UpgradeRequest {
490492 switch (request.head.version) {
491 .@"HTTP/1.0" => return null,
492 .@"HTTP/1.1" => if (request.head.method != .GET) return null,
493 .@"HTTP/1.0" => return .none,
494 .@"HTTP/1.1" => if (request.head.method != .GET) return .none,
493495 }
494496
495497 var sec_websocket_key: ?[]const u8 = null;
......@@ -517,7 +519,7 @@ pub const Request = struct {
517519
518520 /// The header is not guaranteed to be sent until `WebSocket.flush` is
519521 /// called on the returned struct.
520 pub fn respondWebSocket(request: *Request, options: WebSocketOptions) Writer.Error!WebSocket {
522 pub fn respondWebSocket(request: *Request, options: WebSocketOptions) ExpectContinueError!WebSocket {
521523 if (request.head.expect != null) return error.HttpExpectationFailed;
522524
523525 const out = request.server.out;
......@@ -536,16 +538,14 @@ pub const Request = struct {
536538 try out.print("{s} {d} {s}\r\n", .{ @tagName(version), @intFromEnum(status), phrase });
537539 try out.writeAll("connection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: ");
538540 const base64_digest = try out.writableArray(28);
539 assert(std.base64.standard.Encoder.encode(&base64_digest, &digest).len == base64_digest.len);
541 assert(std.base64.standard.Encoder.encode(base64_digest, &digest).len == base64_digest.len);
540542 out.advance(base64_digest.len);
541543 try out.writeAll("\r\n");
542544
543545 for (options.extra_headers) |header| {
544546 assert(header.name.len != 0);
545 try out.writeAll(header.name);
546 try out.writeAll(": ");
547 try out.writeAll(header.value);
548 try out.writeAll("\r\n");
547 var bufs: [4][]const u8 = .{ header.name, ": ", header.value, "\r\n" };
548 try out.writeVecAll(&bufs);
549549 }
550550
551551 try out.writeAll("\r\n");
......@@ -566,7 +566,7 @@ pub const Request = struct {
566566 ///
567567 /// See `readerExpectNone` for an infallible alternative that cannot write
568568 /// to the server output stream.
569 pub fn readerExpectContinue(request: *Request, buffer: []u8) ExpectContinueError!*std.Io.Reader {
569 pub fn readerExpectContinue(request: *Request, buffer: []u8) ExpectContinueError!*Reader {
570570 const flush = request.head.expect != null;
571571 try writeExpectContinue(request);
572572 if (flush) try request.server.out.flush();
......@@ -578,7 +578,7 @@ pub const Request = struct {
578578 /// this function.
579579 ///
580580 /// Asserts that this function is only called once.
581 pub fn readerExpectNone(request: *Request, buffer: []u8) *std.Io.Reader {
581 pub fn readerExpectNone(request: *Request, buffer: []u8) *Reader {
582582 assert(request.server.reader.state == .received_head);
583583 assert(request.head.expect == null);
584584 if (!request.head.method.requestHasBody()) return .ending;
......@@ -642,7 +642,7 @@ pub const Request = struct {
642642/// See https://tools.ietf.org/html/rfc6455
643643pub const WebSocket = struct {
644644 key: []const u8,
645 input: *std.Io.Reader,
645 input: *Reader,
646646 output: *Writer,
647647
648648 pub const Header0 = packed struct(u8) {
......@@ -679,6 +679,8 @@ pub const WebSocket = struct {
679679 UnexpectedOpCode,
680680 MessageTooBig,
681681 MissingMaskBit,
682 ReadFailed,
683 EndOfStream,
682684 };
683685
684686 pub const SmallMessage = struct {
......@@ -693,8 +695,9 @@ pub const WebSocket = struct {
693695 pub fn readSmallMessage(ws: *WebSocket) ReadSmallTextMessageError!SmallMessage {
694696 const in = ws.input;
695697 while (true) {
696 const h0 = in.takeStruct(Header0);
697 const h1 = in.takeStruct(Header1);
698 const header = try in.takeArray(2);
699 const h0: Header0 = @bitCast(header[0]);
700 const h1: Header1 = @bitCast(header[1]);
698701
699702 switch (h0.opcode) {
700703 .text, .binary, .pong, .ping => {},
......@@ -734,47 +737,49 @@ pub const WebSocket = struct {
734737 }
735738
736739 pub fn writeMessage(ws: *WebSocket, data: []const u8, op: Opcode) Writer.Error!void {
737 try writeMessageVecUnflushed(ws, &.{data}, op);
740 var bufs: [1][]const u8 = .{data};
741 try writeMessageVecUnflushed(ws, &bufs, op);
738742 try ws.output.flush();
739743 }
740744
741745 pub fn writeMessageUnflushed(ws: *WebSocket, data: []const u8, op: Opcode) Writer.Error!void {
742 try writeMessageVecUnflushed(ws, &.{data}, op);
746 var bufs: [1][]const u8 = .{data};
747 try writeMessageVecUnflushed(ws, &bufs, op);
743748 }
744749
745 pub fn writeMessageVec(ws: *WebSocket, data: []const []const u8, op: Opcode) Writer.Error!void {
750 pub fn writeMessageVec(ws: *WebSocket, data: [][]const u8, op: Opcode) Writer.Error!void {
746751 try writeMessageVecUnflushed(ws, data, op);
747752 try ws.output.flush();
748753 }
749754
750 pub fn writeMessageVecUnflushed(ws: *WebSocket, data: []const []const u8, op: Opcode) Writer.Error!void {
755 pub fn writeMessageVecUnflushed(ws: *WebSocket, data: [][]const u8, op: Opcode) Writer.Error!void {
751756 const total_len = l: {
752757 var total_len: u64 = 0;
753758 for (data) |iovec| total_len += iovec.len;
754759 break :l total_len;
755760 };
756761 const out = ws.output;
757 try out.writeStruct(@as(Header0, .{
762 try out.writeByte(@bitCast(@as(Header0, .{
758763 .opcode = op,
759764 .fin = true,
760 }));
765 })));
761766 switch (total_len) {
762 0...125 => try out.writeStruct(@as(Header1, .{
767 0...125 => try out.writeByte(@bitCast(@as(Header1, .{
763768 .payload_len = @enumFromInt(total_len),
764769 .mask = false,
765 })),
770 }))),
766771 126...0xffff => {
767 try out.writeStruct(@as(Header1, .{
772 try out.writeByte(@bitCast(@as(Header1, .{
768773 .payload_len = .len16,
769774 .mask = false,
770 }));
775 })));
771776 try out.writeInt(u16, @intCast(total_len), .big);
772777 },
773778 else => {
774 try out.writeStruct(@as(Header1, .{
779 try out.writeByte(@bitCast(@as(Header1, .{
775780 .payload_len = .len64,
776781 .mask = false,
777 }));
782 })));
778783 try out.writeInt(u64, total_len, .big);
779784 },
780785 }