lib/http/src/writer.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const Connection = @import("connection.zig").Connection;
3
4 pub const ChunkedWriter = struct {
5 conn: *Connection,
6 allocator: std.mem.Allocator,
7 headers_sent: bool,
8
9 pub fn init(conn: *Connection, allocator: std.mem.Allocator) ChunkedWriter {
10 return .{
11 .conn = conn,
12 .allocator = allocator,
13 .headers_sent = false,
14 };
15 }
16
17 pub fn writeHeader(
18 self: *ChunkedWriter,
19 status: u16,
20 status_text: []const u8,
21 headers: []const struct { name: []const u8, value: []const u8 },
22 ) !void {
23 if (self.headers_sent) return;
24
25 var buf: std.ArrayListUnmanaged(u8) = .empty;
26 defer buf.deinit(self.allocator);
27
28 try buf.writer(self.allocator).print("HTTP/1.1 {d} {s}\r\n", .{ status, status_text });
29
30 for (headers) |header| {
31 try buf.writer(self.allocator).print("{s}: {s}\r\n", .{ header.name, header.value });
32 }
33
34 try buf.appendSlice(self.allocator, "Transfer-Encoding: chunked\r\n");
35 try buf.appendSlice(self.allocator, "\r\n");
36
37 try self.conn.write(buf.items);
38 self.headers_sent = true;
39 }
40
41 pub fn chunk(self: *ChunkedWriter, data: []const u8) !void {
42 if (data.len == 0) return;
43
44 var header_buf: [32]u8 = undefined;
45 const header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{data.len}) catch unreachable;
46
47 try self.conn.write(header);
48 try self.conn.write(data);
49 try self.conn.write("\r\n");
50 }
51
52 pub fn finish(self: *ChunkedWriter) !void {
53 try self.conn.write("0\r\n\r\n");
54 }
55 };
56
57 test "ChunkedWriter chunk format" {
58 var buf: [32]u8 = undefined;
59 const header = std.fmt.bufPrint(&buf, "{x}\r\n", .{@as(usize, 256)}) catch unreachable;
60 try std.testing.expectEqualStrings("100\r\n", header);
61
62 const header2 = std.fmt.bufPrint(&buf, "{x}\r\n", .{@as(usize, 5)}) catch unreachable;
63 try std.testing.expectEqualStrings("5\r\n", header2);
64 }