An HTTP/1.1 server for zig
0

Configure Feed

Select the types of activity you want to include in your feed.

fix response writer double buffering (#154)

* fix worker KQueue signal() and stop() implementation

* fix response writer double buffering

authored by

philipmv and committed by
GitHub
(Sep 6, 2025, 9:13 AM +0800) 1d4e256b b61f3460

+29 -126
+4 -6
examples/01_basic.zig
··· 82 82 83 83 const name = req.param("name").?; 84 84 85 - var w = res.writer(); 86 85 try std.json.Stringify.value( 87 86 .{ .name = name }, 88 87 .{ .whitespace = .indent_4 }, 89 - &w.interface, 88 + res.writer(), 90 89 ); 91 90 } 92 91 93 92 fn metrics(_: *httpz.Request, res: *httpz.Response) !void { 94 93 // httpz exposes some prometheus-style metrics 95 - var w = res.writer(); 96 - return httpz.writeMetrics(&w.interface); 94 + return httpz.writeMetrics(res.writer()); 97 95 } 98 96 99 97 fn formShow(_: *httpz.Request, res: *httpz.Response) !void { ··· 112 110 113 111 res.content_type = .TEXT; 114 112 115 - var w = res.writer(); 113 + const w = res.writer(); 116 114 while (it.next()) |kv| { 117 - try w.interface.print("{s}={s}\n", .{ kv.key, kv.value }); 115 + try w.print("{s}={s}\n", .{ kv.key, kv.value }); 118 116 } 119 117 } 120 118
+2 -2
examples/09_shutdown.zig
··· 52 52 } 53 53 54 54 fn index(_: *httpz.Request, res: *httpz.Response) !void { 55 - var writer = res.writer(); 56 - return writer.interface.print("To shutdown, run:\nkill -s int {d}", .{std.c.getpid()}); 55 + const writer = res.writer(); 56 + return writer.print("To shutdown, run:\nkill -s int {d}", .{std.c.getpid()}); 57 57 }
+4 -4
src/httpz.zig
··· 1671 1671 } 1672 1672 1673 1673 fn dispatchedAction(_: *Request, res: *Response) !void { 1674 - var writer = res.writer(&.{}); 1675 - return writer.interface.writeAll("action"); 1674 + const writer = res.writer(); 1675 + return writer.writeAll("action"); 1676 1676 } 1677 1677 1678 1678 fn middlewares(req: *Request, res: *Response) !void { ··· 1804 1804 const TestHandlerHandle = struct { 1805 1805 pub fn handle(_: TestHandlerHandle, req: *Request, res: *Response) void { 1806 1806 const query = req.query() catch unreachable; 1807 - var writer = res.writer(&.{}); 1808 - writer.interface.print("hello {s}", .{query.get("name") orelse "world"}) catch unreachable; 1807 + const writer = res.writer(); 1808 + writer.print("hello {s}", .{query.get("name") orelse "world"}) catch unreachable; 1809 1809 } 1810 1810 }; 1811 1811
+19 -114
src/response.zig
··· 12 12 const Stream = std.net.Stream; 13 13 const Allocator = mem.Allocator; 14 14 const ArenaAllocator = std.heap.ArenaAllocator; 15 + const Writer = std.io.Writer; 15 16 16 17 const Self = @This(); 17 18 ··· 54 55 55 56 // When the body is written to via the writer API (the json helper wraps 56 57 // the writer api) 57 - buffer: Buffer, 58 + buffer: Writer.Allocating, 58 59 59 60 pub const State = Self.State; 60 - 61 - const Buffer = struct { 62 - pos: usize, 63 - data: []u8, 64 - }; 65 61 66 62 // Should not be called directly, but initialized through a pool 67 63 pub fn init(arena: Allocator, conn: *HTTPConn) Response { ··· 71 67 .conn = conn, 72 68 .status = 200, 73 69 .arena = arena, 74 - .buffer = Buffer{ .pos = 0, .data = "" }, 70 + .buffer = .init(arena), 75 71 .chunked = false, 76 72 .written = false, 77 73 .keepalive = true, ··· 87 83 88 84 pub fn json(self: *Response, value: anytype, options: std.json.Stringify.Options) !void { 89 85 const json_formatter = std.json.fmt(value, options); 90 - var w = Writer.init(self, &.{}); 91 - try json_formatter.format(&w.interface); 86 + try json_formatter.format(&self.buffer.writer); 92 87 self.content_type = httpz.ContentType.JSON; 93 88 } 94 89 ··· 158 153 var buf: [16]u8 = undefined; 159 154 buf[0] = '\r'; 160 155 buf[1] = '\n'; 161 - var w = std.Io.Writer.fixed(buf[2..]); 156 + var w = Writer.fixed(buf[2..]); 162 157 try w.printInt(data.len, 16, .upper, .{}); 163 158 164 159 const len = 2 + w.buffered().len; ··· 173 168 } 174 169 175 170 pub fn clearWriter(self: *Response) void { 176 - self.buffer.pos = 0; 171 + _ = self.buffer.writer.consumeAll(); 177 172 } 178 173 179 - pub fn writer(self: *Response, buf: []u8) Writer { 180 - return Writer.init(self, buf); 174 + pub fn writer(self: *Response) *Writer { 175 + return &self.buffer.writer; 181 176 } 182 177 183 178 pub fn writeHeader(self: *Response) !void { ··· 201 196 202 197 const header_buf = try self.prepareHeader(); 203 198 204 - const dyn = self.buffer; 205 - const body = if (dyn.pos > 0) dyn.data[0..dyn.pos] else self.body; 199 + const buffered = self.buffer.writer.buffered(); 200 + const body = if (buffered.len > 0) buffered else self.body; 206 201 207 202 var vec = [2]std.posix.iovec_const{ 208 203 .{ .len = header_buf.len, .base = header_buf.ptr }, ··· 308 303 } 309 304 } 310 305 311 - const buffer_pos = self.buffer.pos; 306 + const buffer_pos = self.buffer.writer.end; 312 307 const body_len = if (buffer_pos > 0) buffer_pos else self.body.len; 313 308 if (body_len > 0) { 314 309 const CONTENT_LENGTH = "Content-Length: "; ··· 335 330 @memcpy(buf[pos..end], fin); 336 331 return buf[0..end]; 337 332 } 338 - 339 - // std.io.Writer. 340 - pub const Writer = struct { 341 - res: *Response, 342 - interface: std.Io.Writer, 343 - 344 - pub const Error = Allocator.Error; 345 - 346 - fn init(res: *Response, buf: []u8) Writer { 347 - return .{ .res = res, .interface = .{ 348 - .buffer = buf, 349 - .vtable = &.{ .drain = Writer.drain }, 350 - } }; 351 - } 352 - 353 - fn drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize { 354 - const self: *@This() = @fieldParentPtr("interface", io_w); 355 - const buffered = io_w.buffered(); 356 - var n: usize = 0; 357 - if (buffered.len > 0) 358 - n = self.writeAll(buffered) catch return error.WriteFailed; 359 - for (data[0 .. data.len - 1]) |d| { 360 - if (d.len == 0) continue; 361 - n += (self.writeAll(d) catch return error.WriteFailed); 362 - } 363 - const pattern = data[data.len - 1]; 364 - if (splat > 0 and pattern.len > 0) { 365 - for (0..splat) |_| { 366 - n += (self.writeAll(pattern) catch return error.WriteFailed); 367 - } 368 - } 369 - return io_w.consume(n); 370 - } 371 - 372 - pub fn adaptToNewApi(self: Writer) Adapter { 373 - return .{ .new_interface = self.interface }; 374 - } 375 - 376 - pub const Adapter = struct { 377 - err: ?Error = null, 378 - new_interface: std.Io.Writer, 379 - }; 380 - 381 - fn writeAll(self: Writer, data: []const u8) !usize { 382 - var buf = try self.ensureSpace(data.len); 383 - const pos = buf.pos; 384 - const end_pos = pos + data.len; 385 - @memcpy(buf.data[pos..end_pos], data); 386 - buf.pos = end_pos; 387 - return data.len; 388 - } 389 - 390 - fn write(self: Writer, data: []const u8) Allocator.Error!usize { 391 - try self.writeAll(data); 392 - return data.len; 393 - } 394 - 395 - fn print(self: Writer, comptime format: []const u8, args: anytype) Allocator.Error!void { 396 - return std.fmt.format(self, format, args); 397 - } 398 - 399 - fn ensureSpace(self: Writer, n: usize) !*Buffer { 400 - const res = self.res; 401 - var buf = &res.buffer; 402 - const pos = buf.pos; 403 - const required_capacity = pos + n; 404 - 405 - const data = buf.data; 406 - if (data.len > required_capacity) { 407 - return buf; 408 - } 409 - 410 - var new_capacity = data.len; 411 - while (true) { 412 - new_capacity +|= new_capacity / 2 + 8; 413 - if (new_capacity >= required_capacity) break; 414 - } 415 - 416 - const new = try res.arena.alloc(u8, new_capacity); 417 - if (pos > 0) { 418 - @memcpy(new[0..pos], data[0..pos]); 419 - // reasonable chance that our last allocation was buf, so we 420 - // might as well try freeing it (ArenaAllocator's free is a noop 421 - // unless you're frenig the last allocation) 422 - res.arena.free(data); 423 - } 424 - buf.data = new; 425 - return buf; 426 - } 427 - }; 428 333 }; 429 334 430 335 pub const CookieOpts = struct { ··· 569 474 var buf: [10]u8 = undefined; 570 475 var tst: [10]u8 = undefined; 571 476 for (0..100_009) |i| { 572 - var writer = std.Io.Writer.fixed(&tst); 477 + var writer = Writer.fixed(&tst); 573 478 try writer.printInt(i, 10, .lower, .{}); 574 479 const l = writeInt(&buf, @intCast(i)); 575 480 try t.expectString(tst[0..writer.end], buf[0..l]); ··· 730 635 defer ctx.deinit(); 731 636 var res = ctx.response(); 732 637 { 733 - var w = res.writer(&.{}); 734 - try w.interface.writeAll("a" ** 5000); 638 + const w = res.writer(); 639 + try w.writeAll("a" ** 5000); 735 640 } 736 641 { 737 - var w = res.writer(&.{}); 738 - try w.interface.writeAll("z" ** 10); 642 + const w = res.writer(); 643 + try w.writeAll("z" ** 10); 739 644 } 740 645 try res.write(); 741 646 try ctx.expect("HTTP/1.1 200 \r\nContent-Length: 5010\r\n\r\n" ++ ("a" ** 5000) ++ ("z" ** 10)); ··· 764 669 defer ctx.deinit(); 765 670 766 671 var res = ctx.response(); 767 - var writer = res.writer(&.{}); 672 + const writer = res.writer(); 768 673 769 - try writer.interface.writeAll("abc"); 674 + try writer.writeAll("abc"); 770 675 res.clearWriter(); 771 - try writer.interface.writeAll("123"); 676 + try writer.writeAll("123"); 772 677 773 678 try res.write(); 774 679 try ctx.expect("HTTP/1.1 200 \r\nContent-Length: 3\r\n\r\n123");