From fdf0e4612e4441d2f0386839078d8a7adf738a3e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 1 Aug 2025 21:30:27 -0700 Subject: [PATCH] update build system to new http.Server API --- lib/std/Build/Fuzz.zig | 39 +++++++-------- lib/std/Build/WebServer.zig | 95 ++++++++++++++++--------------------- lib/std/http/Server.zig | 59 ++++++++++++----------- 3 files changed, 90 insertions(+), 103 deletions(-) diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index a25b501755..bc10f7907a 100644 --- a/lib/std/Build/Fuzz.zig +++ b/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, - }; -} diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 9264d7473c..868aabe67e 100644 --- a/lib/std/Build/WebServer.zig +++ b/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(); diff --git a/lib/std/http/Server.zig b/lib/std/http/Server.zig index aa6c72a5b3..188f2a45e4 100644 --- a/lib/std/http/Server.zig +++ b/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); }, }