An HTTP/1.1 server for zig
0

Configure Feed

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

Add simple built-in support for Expect: Continue

https://github.com/karlseguin/http.zig/issues/187

Karl Seguin (Mar 23, 2026, 7:45 PM +0800) 86ec1ae5 844f8016

+23 -14
+20 -11
src/request.zig
··· 705 705 } 706 706 707 707 // returns true if the header has been fully parsed 708 - pub fn parse(self: *State, req_arena: Allocator, stream: *std.Io.Reader) !bool { 708 + pub fn parse(self: *State, conn: *HTTPConn, stream: *std.Io.Reader) !bool { 709 709 if (self.body != null) { 710 710 // if we have a body, then we've read the header. We want to read into 711 711 // self.body, not self.buf. ··· 734 734 if (try self.parseProtocol(buf[self.pos..len]) == false) { 735 735 return false; 736 736 } 737 - if (try self.parseHeaders(req_arena, buf[self.pos..len]) == true) { 737 + if (try self.parseHeaders(conn, buf[self.pos..len]) == true) { 738 738 return true; 739 739 } 740 740 } else if (self.url == null) { ··· 744 744 if (try self.parseProtocol(buf[self.pos..len]) == false) { 745 745 return false; 746 746 } 747 - if (try self.parseHeaders(req_arena, buf[self.pos..len]) == true) { 747 + if (try self.parseHeaders(conn, buf[self.pos..len]) == true) { 748 748 return true; 749 749 } 750 750 } else if (self.protocol == null) { 751 751 if (try self.parseProtocol(buf[self.pos..len]) == false) { 752 752 return false; 753 753 } 754 - if (try self.parseHeaders(req_arena, buf[self.pos..len]) == true) { 754 + if (try self.parseHeaders(conn, buf[self.pos..len]) == true) { 755 755 return true; 756 756 } 757 757 } else { 758 - if (try self.parseHeaders(req_arena, buf[self.pos..len]) == true) { 758 + if (try self.parseHeaders(conn, buf[self.pos..len]) == true) { 759 759 return true; 760 760 } 761 761 } ··· 894 894 return true; 895 895 } 896 896 897 - fn parseHeaders(self: *State, req_arena: Allocator, full: []u8) !bool { 897 + fn parseHeaders(self: *State, conn: *HTTPConn, full: []u8) !bool { 898 898 var buf = full; 899 899 var headers = &self.headers; 900 + const req_arena = conn.req_arena.allocator(); 901 + 900 902 line: while (buf.len > 0) { 901 903 for (buf, 0..) |bn, i| { 902 904 switch (bn) { ··· 965 967 if (buf[1] == '\n') { 966 968 // we have \r\n at the start of a line, we're done 967 969 self.pos += 2; 968 - return try self.prepareForBody(req_arena); 970 + return try self.prepareForBody(conn, req_arena); 969 971 } 970 972 // we have a \r followed by something that isn't a \n, can't be right 971 973 return error.InvalidHeaderLine; ··· 981 983 } 982 984 983 985 // we've finished reading the header 984 - fn prepareForBody(self: *State, req_arena: Allocator) !bool { 986 + fn prepareForBody(self: *State, conn: *HTTPConn, req_arena: Allocator) !bool { 985 987 const str = self.headers.get("content-length") orelse return true; 986 988 const cl = atoi(str) orelse return error.InvalidContentLength; 989 + 990 + if (self.headers.get("expect")) |expect| { 991 + if (std.ascii.eqlIgnoreCase(expect, "100-continue")) { 992 + // TODO: Maybe support an application-defined handler for this 993 + try conn.writeAll("HTTP/1.1 100 Continue\r\n\r\n"); 994 + } 995 + } 987 996 988 997 self.body_len = cl; 989 998 if (cl == 0) return true; ··· 1706 1715 var fake_reader = ctx.fakeReader(); 1707 1716 const fr = &fake_reader.interface; 1708 1717 while (true) { 1709 - const done = try conn.req_state.parse(conn.req_arena.allocator(), fr); 1718 + const done = try conn.req_state.parse(conn, fr); 1710 1719 if (done) break; 1711 1720 } 1712 1721 ··· 1784 1793 var reader = ctx.stream.reader(&.{}); 1785 1794 const r = reader.interface(); 1786 1795 while (true) { 1787 - const done = try ctx.conn.req_state.parse(ctx.conn.req_arena.allocator(), r); 1796 + const done = try ctx.conn.req_state.parse(ctx.conn, r); 1788 1797 if (done) break; 1789 1798 } 1790 1799 return Request.init(ctx.conn.req_arena.allocator(), ctx.conn); ··· 1797 1806 var reader = ctx.stream.reader(&.{}); 1798 1807 const r = reader.interface(); 1799 1808 ctx.write(input); 1800 - try t.expectError(expected, ctx.conn.req_state.parse(ctx.conn.req_arena.allocator(), r)); 1809 + try t.expectError(expected, ctx.conn.req_state.parse(ctx.conn, r)); 1801 1810 } 1802 1811 1803 1812 fn randomMethod(random: std.Random) []const u8 {
+1 -1
src/testing.zig
··· 18 18 // thereafter to change whatever properties they want. 19 19 var base_request: std.Io.Reader = .fixed("GET / HTTP/1.1\r\nContent-Length: 0\r\n\r\n"); 20 20 while (true) { 21 - const done = conn.req_state.parse(conn.req_arena.allocator(), &base_request) catch unreachable; 21 + const done = conn.req_state.parse(conn, &base_request) catch unreachable; 22 22 if (done) { 23 23 break; 24 24 }
+2 -2
src/worker.zig
··· 263 263 var is_first = true; 264 264 var reader = stream.reader(&.{}); // Request.State does its own buffering 265 265 while (true) { 266 - const done = conn.req_state.parse(conn.req_arena.allocator(), reader.interface()) catch |err| { 266 + const done = conn.req_state.parse(conn, reader.interface()) catch |err| { 267 267 switch (err) { 268 268 error.ReadFailed => { 269 269 if (reader.getError()) |e| { ··· 594 594 595 595 const stream = http_conn.stream; 596 596 var reader = stream.reader(&.{}); // Request.State does its own buffering 597 - const done = http_conn.req_state.parse(http_conn.req_arena.allocator(), reader.interface()) catch |err| { 597 + const done = http_conn.req_state.parse(http_conn, reader.interface()) catch |err| { 598 598 // maybe a write fail or something, doesn't matter, we're closing the connection 599 599 requestError(http_conn, reader.getError() orelse err) catch {}; 600 600