Commit fef41c66db

Andrew Kelley <andrew@ziglang.org>
2025-08-02 06:30:27
update build system to new http.Server API
1 parent e2d81bf
Changed files (3)
lib/std/Build/Fuzz.zig
@@ -234,7 +234,7 @@ pub const Previous = struct {
 };
 pub fn sendUpdate(
     fuzz: *Fuzz,
-    socket: *std.http.WebSocket,
+    socket: *std.http.Server.WebSocket,
     prev: *Previous,
 ) !void {
     fuzz.coverage_mutex.lock();
@@ -263,36 +263,36 @@ pub fn sendUpdate(
                 .string_bytes_len = @intCast(coverage_map.coverage.string_bytes.items.len),
                 .start_timestamp = coverage_map.start_timestamp,
             };
-            const iovecs: [5]std.posix.iovec_const = .{
-                makeIov(@ptrCast(&header)),
-                makeIov(@ptrCast(coverage_map.coverage.directories.keys())),
-                makeIov(@ptrCast(coverage_map.coverage.files.keys())),
-                makeIov(@ptrCast(coverage_map.source_locations)),
-                makeIov(coverage_map.coverage.string_bytes.items),
+            var iovecs: [5][]const u8 = .{
+                @ptrCast(&header),
+                @ptrCast(coverage_map.coverage.directories.keys()),
+                @ptrCast(coverage_map.coverage.files.keys()),
+                @ptrCast(coverage_map.source_locations),
+                coverage_map.coverage.string_bytes.items,
             };
-            try socket.writeMessagev(&iovecs, .binary);
+            try socket.writeMessageVec(&iovecs, .binary);
         }
 
         const header: abi.CoverageUpdateHeader = .{
             .n_runs = n_runs,
             .unique_runs = unique_runs,
         };
-        const iovecs: [2]std.posix.iovec_const = .{
-            makeIov(@ptrCast(&header)),
-            makeIov(@ptrCast(seen_pcs)),
+        var iovecs: [2][]const u8 = .{
+            @ptrCast(&header),
+            @ptrCast(seen_pcs),
         };
-        try socket.writeMessagev(&iovecs, .binary);
+        try socket.writeMessageVec(&iovecs, .binary);
 
         prev.unique_runs = unique_runs;
     }
 
     if (prev.entry_points != coverage_map.entry_points.items.len) {
         const header: abi.EntryPointHeader = .init(@intCast(coverage_map.entry_points.items.len));
-        const iovecs: [2]std.posix.iovec_const = .{
-            makeIov(@ptrCast(&header)),
-            makeIov(@ptrCast(coverage_map.entry_points.items)),
+        var iovecs: [2][]const u8 = .{
+            @ptrCast(&header),
+            @ptrCast(coverage_map.entry_points.items),
         };
-        try socket.writeMessagev(&iovecs, .binary);
+        try socket.writeMessageVec(&iovecs, .binary);
 
         prev.entry_points = coverage_map.entry_points.items.len;
     }
@@ -448,10 +448,3 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte
     }
     try coverage_map.entry_points.append(fuzz.ws.gpa, @intCast(index));
 }
-
-fn makeIov(s: []const u8) std.posix.iovec_const {
-    return .{
-        .base = s.ptr,
-        .len = s.len,
-    };
-}
lib/std/Build/WebServer.zig
@@ -251,48 +251,44 @@ pub fn now(s: *const WebServer) i64 {
 fn accept(ws: *WebServer, connection: std.net.Server.Connection) void {
     defer connection.stream.close();
 
-    var read_buf: [0x4000]u8 = undefined;
-    var server: std.http.Server = .init(connection, &read_buf);
+    var send_buffer: [4096]u8 = undefined;
+    var recv_buffer: [4096]u8 = undefined;
+    var connection_reader = connection.stream.reader(&recv_buffer);
+    var connection_writer = connection.stream.writer(&send_buffer);
+    var server: http.Server = .init(connection_reader.interface(), &connection_writer.interface);
 
     while (true) {
         var request = server.receiveHead() catch |err| switch (err) {
             error.HttpConnectionClosing => return,
-            else => {
-                log.err("failed to receive http request: {s}", .{@errorName(err)});
-                return;
-            },
+            else => return log.err("failed to receive http request: {t}", .{err}),
         };
-        var ws_send_buf: [0x4000]u8 = undefined;
-        var ws_recv_buf: [0x4000]u8 align(4) = undefined;
-        if (std.http.WebSocket.init(&request, &ws_send_buf, &ws_recv_buf) catch |err| {
-            log.err("failed to initialize websocket connection: {s}", .{@errorName(err)});
-            return;
-        }) |ws_init| {
-            var web_socket = ws_init;
-            ws.serveWebSocket(&web_socket) catch |err| {
-                log.err("failed to serve websocket: {s}", .{@errorName(err)});
-                return;
-            };
-            comptime unreachable;
-        } else {
-            ws.serveRequest(&request) catch |err| switch (err) {
-                error.AlreadyReported => return,
-                else => {
-                    log.err("failed to serve '{s}': {s}", .{ request.head.target, @errorName(err) });
+        switch (request.upgradeRequested()) {
+            .websocket => |opt_key| {
+                const key = opt_key orelse return log.err("missing websocket key", .{});
+                var web_socket = request.respondWebSocket(.{ .key = key }) catch {
+                    return log.err("failed to respond web socket: {t}", .{connection_writer.err.?});
+                };
+                ws.serveWebSocket(&web_socket) catch |err| {
+                    log.err("failed to serve websocket: {t}", .{err});
                     return;
-                },
-            };
+                };
+                comptime unreachable;
+            },
+            .other => |name| return log.err("unknown upgrade request: {s}", .{name}),
+            .none => {
+                ws.serveRequest(&request) catch |err| switch (err) {
+                    error.AlreadyReported => return,
+                    else => {
+                        log.err("failed to serve '{s}': {t}", .{ request.head.target, err });
+                        return;
+                    },
+                };
+            },
         }
     }
 }
 
-fn makeIov(s: []const u8) std.posix.iovec_const {
-    return .{
-        .base = s.ptr,
-        .len = s.len,
-    };
-}
-fn serveWebSocket(ws: *WebServer, sock: *std.http.WebSocket) !noreturn {
+fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn {
     var prev_build_status = ws.build_status.load(.monotonic);
 
     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 {
             .timestamp = ws.now(),
             .steps_len = @intCast(ws.all_steps.len),
         };
-        try sock.writeMessagev(&.{
-            makeIov(@ptrCast(&hello_header)),
-            makeIov(ws.step_names_trailing),
-            makeIov(prev_step_status_bits),
-        }, .binary);
+        var bufs: [3][]const u8 = .{ @ptrCast(&hello_header), ws.step_names_trailing, prev_step_status_bits };
+        try sock.writeMessageVec(&bufs, .binary);
     }
 
     var prev_fuzz: Fuzz.Previous = .init;
@@ -380,7 +373,7 @@ fn serveWebSocket(ws: *WebServer, sock: *std.http.WebSocket) !noreturn {
         std.Thread.Futex.timedWait(&ws.update_id, start_update_id, std.time.ns_per_ms * default_update_interval_ms) catch {};
     }
 }
-fn recvWebSocketMessages(ws: *WebServer, sock: *std.http.WebSocket) void {
+fn recvWebSocketMessages(ws: *WebServer, sock: *http.Server.WebSocket) void {
     while (true) {
         const msg = sock.readSmallMessage() catch return;
         if (msg.opcode != .binary) continue;
@@ -402,7 +395,7 @@ fn recvWebSocketMessages(ws: *WebServer, sock: *std.http.WebSocket) void {
     }
 }
 
-fn serveRequest(ws: *WebServer, req: *std.http.Server.Request) !void {
+fn serveRequest(ws: *WebServer, req: *http.Server.Request) !void {
     // Strip an optional leading '/debug' component from the request.
     const target: []const u8, const debug: bool = target: {
         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 {
 
 fn serveLibFile(
     ws: *WebServer,
-    request: *std.http.Server.Request,
+    request: *http.Server.Request,
     sub_path: []const u8,
     content_type: []const u8,
 ) !void {
@@ -442,7 +435,7 @@ fn serveLibFile(
 }
 fn serveClientWasm(
     ws: *WebServer,
-    req: *std.http.Server.Request,
+    req: *http.Server.Request,
     optimize_mode: std.builtin.OptimizeMode,
 ) !void {
     var arena_state: std.heap.ArenaAllocator = .init(ws.gpa);
@@ -456,12 +449,12 @@ fn serveClientWasm(
 
 pub fn serveFile(
     ws: *WebServer,
-    request: *std.http.Server.Request,
+    request: *http.Server.Request,
     path: Cache.Path,
     content_type: []const u8,
 ) !void {
     const gpa = ws.gpa;
-    // The desired API is actually sendfile, which will require enhancing std.http.Server.
+    // The desired API is actually sendfile, which will require enhancing http.Server.
     // We load the file with every request so that the user can make changes to the file
     // and refresh the HTML page without restarting this server.
     const file_contents = path.root_dir.handle.readFileAlloc(gpa, path.sub_path, 10 * 1024 * 1024) catch |err| {
@@ -478,14 +471,13 @@ pub fn serveFile(
 }
 pub fn serveTarFile(
     ws: *WebServer,
-    request: *std.http.Server.Request,
+    request: *http.Server.Request,
     paths: []const Cache.Path,
 ) !void {
     const gpa = ws.gpa;
 
-    var send_buf: [0x4000]u8 = undefined;
-    var response = request.respondStreaming(.{
-        .send_buffer = &send_buf,
+    var send_buffer: [0x4000]u8 = undefined;
+    var response = try request.respondStreaming(&send_buffer, .{
         .respond_options = .{
             .extra_headers = &.{
                 .{ .name = "Content-Type", .value = "application/x-tar" },
@@ -497,10 +489,7 @@ pub fn serveTarFile(
     var cached_cwd_path: ?[]const u8 = null;
     defer if (cached_cwd_path) |p| gpa.free(p);
 
-    var response_buf: [1024]u8 = undefined;
-    var adapter = response.writer().adaptToNewApi();
-    adapter.new_interface.buffer = &response_buf;
-    var archiver: std.tar.Writer = .{ .underlying_writer = &adapter.new_interface };
+    var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };
 
     for (paths) |path| {
         var file = path.root_dir.handle.openFile(path.sub_path, .{}) catch |err| {
@@ -526,7 +515,6 @@ pub fn serveTarFile(
     }
 
     // intentionally not calling `archiver.finishPedantically`
-    try adapter.new_interface.flush();
     try response.end();
 }
 
@@ -804,7 +792,7 @@ pub fn wait(ws: *WebServer) RunnerRequest {
     }
 }
 
-const cache_control_header: std.http.Header = .{
+const cache_control_header: http.Header = .{
     .name = "Cache-Control",
     .value = "max-age=0, must-revalidate",
 };
@@ -819,5 +807,6 @@ const Build = std.Build;
 const Cache = Build.Cache;
 const Fuzz = Build.Fuzz;
 const abi = Build.abi;
+const http = std.http;
 
 const WebServer = @This();
lib/std/http/Server.zig
@@ -7,6 +7,7 @@ const Uri = std.Uri;
 const assert = std.debug.assert;
 const testing = std.testing;
 const Writer = std.Io.Writer;
+const Reader = std.Io.Reader;
 
 const Server = @This();
 
@@ -21,7 +22,7 @@ reader: http.Reader,
 /// header, otherwise `receiveHead` returns `error.HttpHeadersOversize`.
 ///
 /// The returned `Server` is ready for `receiveHead` to be called.
-pub fn init(in: *std.Io.Reader, out: *Writer) Server {
+pub fn init(in: *Reader, out: *Writer) Server {
     return .{
         .reader = .{
             .in = in,
@@ -225,7 +226,7 @@ pub const Request = struct {
         }
     };
 
-    pub fn iterateHeaders(r: *Request) http.HeaderIterator {
+    pub fn iterateHeaders(r: *const Request) http.HeaderIterator {
         assert(r.server.reader.state == .received_head);
         return http.HeaderIterator.init(r.head_buffer);
     }
@@ -486,10 +487,11 @@ pub const Request = struct {
         none,
     };
 
+    /// Does not invalidate `request.head`.
     pub fn upgradeRequested(request: *const Request) UpgradeRequest {
         switch (request.head.version) {
-            .@"HTTP/1.0" => return null,
-            .@"HTTP/1.1" => if (request.head.method != .GET) return null,
+            .@"HTTP/1.0" => return .none,
+            .@"HTTP/1.1" => if (request.head.method != .GET) return .none,
         }
 
         var sec_websocket_key: ?[]const u8 = null;
@@ -517,7 +519,7 @@ pub const Request = struct {
 
     /// The header is not guaranteed to be sent until `WebSocket.flush` is
     /// called on the returned struct.
-    pub fn respondWebSocket(request: *Request, options: WebSocketOptions) Writer.Error!WebSocket {
+    pub fn respondWebSocket(request: *Request, options: WebSocketOptions) ExpectContinueError!WebSocket {
         if (request.head.expect != null) return error.HttpExpectationFailed;
 
         const out = request.server.out;
@@ -536,16 +538,14 @@ pub const Request = struct {
         try out.print("{s} {d} {s}\r\n", .{ @tagName(version), @intFromEnum(status), phrase });
         try out.writeAll("connection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: ");
         const base64_digest = try out.writableArray(28);
-        assert(std.base64.standard.Encoder.encode(&base64_digest, &digest).len == base64_digest.len);
+        assert(std.base64.standard.Encoder.encode(base64_digest, &digest).len == base64_digest.len);
         out.advance(base64_digest.len);
         try out.writeAll("\r\n");
 
         for (options.extra_headers) |header| {
             assert(header.name.len != 0);
-            try out.writeAll(header.name);
-            try out.writeAll(": ");
-            try out.writeAll(header.value);
-            try out.writeAll("\r\n");
+            var bufs: [4][]const u8 = .{ header.name, ": ", header.value, "\r\n" };
+            try out.writeVecAll(&bufs);
         }
 
         try out.writeAll("\r\n");
@@ -566,7 +566,7 @@ pub const Request = struct {
     ///
     /// See `readerExpectNone` for an infallible alternative that cannot write
     /// to the server output stream.
-    pub fn readerExpectContinue(request: *Request, buffer: []u8) ExpectContinueError!*std.Io.Reader {
+    pub fn readerExpectContinue(request: *Request, buffer: []u8) ExpectContinueError!*Reader {
         const flush = request.head.expect != null;
         try writeExpectContinue(request);
         if (flush) try request.server.out.flush();
@@ -578,7 +578,7 @@ pub const Request = struct {
     /// this function.
     ///
     /// Asserts that this function is only called once.
-    pub fn readerExpectNone(request: *Request, buffer: []u8) *std.Io.Reader {
+    pub fn readerExpectNone(request: *Request, buffer: []u8) *Reader {
         assert(request.server.reader.state == .received_head);
         assert(request.head.expect == null);
         if (!request.head.method.requestHasBody()) return .ending;
@@ -642,7 +642,7 @@ pub const Request = struct {
 /// See https://tools.ietf.org/html/rfc6455
 pub const WebSocket = struct {
     key: []const u8,
-    input: *std.Io.Reader,
+    input: *Reader,
     output: *Writer,
 
     pub const Header0 = packed struct(u8) {
@@ -679,6 +679,8 @@ pub const WebSocket = struct {
         UnexpectedOpCode,
         MessageTooBig,
         MissingMaskBit,
+        ReadFailed,
+        EndOfStream,
     };
 
     pub const SmallMessage = struct {
@@ -693,8 +695,9 @@ pub const WebSocket = struct {
     pub fn readSmallMessage(ws: *WebSocket) ReadSmallTextMessageError!SmallMessage {
         const in = ws.input;
         while (true) {
-            const h0 = in.takeStruct(Header0);
-            const h1 = in.takeStruct(Header1);
+            const header = try in.takeArray(2);
+            const h0: Header0 = @bitCast(header[0]);
+            const h1: Header1 = @bitCast(header[1]);
 
             switch (h0.opcode) {
                 .text, .binary, .pong, .ping => {},
@@ -734,47 +737,49 @@ pub const WebSocket = struct {
     }
 
     pub fn writeMessage(ws: *WebSocket, data: []const u8, op: Opcode) Writer.Error!void {
-        try writeMessageVecUnflushed(ws, &.{data}, op);
+        var bufs: [1][]const u8 = .{data};
+        try writeMessageVecUnflushed(ws, &bufs, op);
         try ws.output.flush();
     }
 
     pub fn writeMessageUnflushed(ws: *WebSocket, data: []const u8, op: Opcode) Writer.Error!void {
-        try writeMessageVecUnflushed(ws, &.{data}, op);
+        var bufs: [1][]const u8 = .{data};
+        try writeMessageVecUnflushed(ws, &bufs, op);
     }
 
-    pub fn writeMessageVec(ws: *WebSocket, data: []const []const u8, op: Opcode) Writer.Error!void {
+    pub fn writeMessageVec(ws: *WebSocket, data: [][]const u8, op: Opcode) Writer.Error!void {
         try writeMessageVecUnflushed(ws, data, op);
         try ws.output.flush();
     }
 
-    pub fn writeMessageVecUnflushed(ws: *WebSocket, data: []const []const u8, op: Opcode) Writer.Error!void {
+    pub fn writeMessageVecUnflushed(ws: *WebSocket, data: [][]const u8, op: Opcode) Writer.Error!void {
         const total_len = l: {
             var total_len: u64 = 0;
             for (data) |iovec| total_len += iovec.len;
             break :l total_len;
         };
         const out = ws.output;
-        try out.writeStruct(@as(Header0, .{
+        try out.writeByte(@bitCast(@as(Header0, .{
             .opcode = op,
             .fin = true,
-        }));
+        })));
         switch (total_len) {
-            0...125 => try out.writeStruct(@as(Header1, .{
+            0...125 => try out.writeByte(@bitCast(@as(Header1, .{
                 .payload_len = @enumFromInt(total_len),
                 .mask = false,
-            })),
+            }))),
             126...0xffff => {
-                try out.writeStruct(@as(Header1, .{
+                try out.writeByte(@bitCast(@as(Header1, .{
                     .payload_len = .len16,
                     .mask = false,
-                }));
+                })));
                 try out.writeInt(u16, @intCast(total_len), .big);
             },
             else => {
-                try out.writeStruct(@as(Header1, .{
+                try out.writeByte(@bitCast(@as(Header1, .{
                     .payload_len = .len64,
                     .mask = false,
-                }));
+                })));
                 try out.writeInt(u64, total_len, .big);
             },
         }