authorgravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-05-28 09:50:51+02:00
committergravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-06-01 13:44:00-05:00
log23ccff9cce2a5264fc84998bd2c897682ac266ea
tree7dbd46f11ba4f3a73a232a2e3e2e719acb038acc
parent0e5e6cb10c12a5ae9fd83f85a82eafbe5eac1106
signaturelock-open Commit is signed but in an unrecognized format.

std.http.Server: collapse BufferedConnection into Connection


3 files changed, 88 insertions(+), 141 deletions(-)

lib/std/http/Client.zig+5-3
...@@ -184,7 +184,7 @@ pub const Connection = struct {...@@ -184,7 +184,7 @@ pub const Connection = struct {
184 pub fn fill(conn: *Connection) ReadError!void {184 pub fn fill(conn: *Connection) ReadError!void {
185 if (conn.read_end != conn.read_start) return;185 if (conn.read_end != conn.read_start) return;
186186
187 const nread = try conn.read(conn.read_buf[0..]);187 const nread = try conn.rawReadAtLeast(conn.read_buf[0..], 1);
188 if (nread == 0) return error.EndOfStream;188 if (nread == 0) return error.EndOfStream;
189 conn.read_start = 0;189 conn.read_start = 0;
190 conn.read_end = @intCast(u16, nread);190 conn.read_end = @intCast(u16, nread);
...@@ -207,13 +207,13 @@ pub const Connection = struct {...@@ -207,13 +207,13 @@ pub const Connection = struct {
207 const available_buffer = buffer.len - out_index;207 const available_buffer = buffer.len - out_index;
208208
209 if (available_read > available_buffer) { // partially read buffered data209 if (available_read > available_buffer) { // partially read buffered data
210 @memcpy(buffer[out_index..], conn.read_buf[conn.read_start..][0..available_buffer]);210 @memcpy(buffer[out_index..], conn.read_buf[conn.read_start..conn.read_end][0..available_buffer]);
211 out_index += @intCast(u16, available_buffer);211 out_index += @intCast(u16, available_buffer);
212 conn.read_start += @intCast(u16, available_buffer);212 conn.read_start += @intCast(u16, available_buffer);
213213
214 break;214 break;
215 } else if (available_read > 0) { // fully read buffered data215 } else if (available_read > 0) { // fully read buffered data
216 @memcpy(buffer[out_index..][0..available_read], conn.read_buf[conn.read_start..]);216 @memcpy(buffer[out_index..][0..available_read], conn.read_buf[conn.read_start..conn.read_end]);
217 out_index += available_read;217 out_index += available_read;
218 conn.read_start += available_read;218 conn.read_start += available_read;
219219
...@@ -608,6 +608,8 @@ pub const Request = struct {...@@ -608,6 +608,8 @@ pub const Request = struct {
608 try w.print("{}", .{req.headers});608 try w.print("{}", .{req.headers});
609609
610 try w.writeAll("\r\n");610 try w.writeAll("\r\n");
611
612 try buffered.flush();
611 }613 }
612614
613 pub const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;615 pub const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;
lib/std/http/Server.zig+83-137
...@@ -16,39 +16,92 @@ socket: net.StreamServer,...@@ -16,39 +16,92 @@ socket: net.StreamServer,
1616
17/// An interface to either a plain or TLS connection.17/// An interface to either a plain or TLS connection.
18pub const Connection = struct {18pub const Connection = struct {
19 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
20 pub const Protocol = enum { plain };
21
19 stream: net.Stream,22 stream: net.Stream,
20 protocol: Protocol,23 protocol: Protocol,
2124
22 closing: bool = true,25 closing: bool = true,
2326
24 pub const Protocol = enum { plain };27 read_buf: [buffer_size]u8 = undefined,
28 read_start: u16 = 0,
29 read_end: u16 = 0,
2530
26 pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {31 pub fn rawReadAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
27 return switch (conn.protocol) {32 return switch (conn.protocol) {
28 .plain => conn.stream.read(buffer),33 .plain => conn.stream.readAtLeast(buffer, len),
29 // .tls => return conn.tls_client.read(conn.stream, buffer),34 // .tls => conn.tls_client.readAtLeast(conn.stream, buffer, len),
30 } catch |err| switch (err) {35 } catch |err| {
31 error.ConnectionTimedOut => return error.ConnectionTimedOut,36 switch (err) {
32 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,37 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
33 else => return error.UnexpectedReadFailure,38 else => return error.UnexpectedReadFailure,
39 }
34 };40 };
35 }41 }
3642
43 pub fn fill(conn: *Connection) ReadError!void {
44 if (conn.read_end != conn.read_start) return;
45
46 const nread = try conn.rawReadAtLeast(conn.read_buf[0..], 1);
47 if (nread == 0) return error.EndOfStream;
48 conn.read_start = 0;
49 conn.read_end = @intCast(u16, nread);
50 }
51
52 pub fn peek(conn: *Connection) []const u8 {
53 return conn.read_buf[conn.read_start..conn.read_end];
54 }
55
56 pub fn drop(conn: *Connection, num: u16) void {
57 conn.read_start += num;
58 }
59
37 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {60 pub fn readAtLeast(conn: *Connection, buffer: []u8, len: usize) ReadError!usize {
38 return switch (conn.protocol) {61 assert(len <= buffer.len);
39 .plain => conn.stream.readAtLeast(buffer, len),62
40 // .tls => return conn.tls_client.readAtLeast(conn.stream, buffer, len),63 var out_index: u16 = 0;
41 } catch |err| switch (err) {64 while (out_index < len) {
42 error.ConnectionTimedOut => return error.ConnectionTimedOut,65 const available_read = conn.read_end - conn.read_start;
43 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,66 const available_buffer = buffer.len - out_index;
44 else => return error.UnexpectedReadFailure,67
45 };68 if (available_read > available_buffer) { // partially read buffered data
69 @memcpy(buffer[out_index..], conn.read_buf[conn.read_start..conn.read_end][0..available_buffer]);
70 out_index += @intCast(u16, available_buffer);
71 conn.read_start += @intCast(u16, available_buffer);
72
73 break;
74 } else if (available_read > 0) { // fully read buffered data
75 @memcpy(buffer[out_index..][0..available_read], conn.read_buf[conn.read_start..conn.read_end]);
76 out_index += available_read;
77 conn.read_start += available_read;
78
79 if (out_index >= len) break;
80 }
81
82 const leftover_buffer = available_buffer - available_read;
83 const leftover_len = len - out_index;
84
85 if (leftover_buffer > conn.read_buf.len) {
86 // skip the buffer if the output is large enough
87 return conn.rawReadAtLeast(buffer[out_index..], leftover_len);
88 }
89
90 try conn.fill();
91 }
92
93 return out_index;
94 }
95
96 pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {
97 return conn.readAtLeast(buffer, 1);
46 }98 }
4799
48 pub const ReadError = error{100 pub const ReadError = error{
49 ConnectionTimedOut,101 ConnectionTimedOut,
50 ConnectionResetByPeer,102 ConnectionResetByPeer,
51 UnexpectedReadFailure,103 UnexpectedReadFailure,
104 EndOfStream,
52 };105 };
53106
54 pub const Reader = std.io.Reader(*Connection, ReadError, read);107 pub const Reader = std.io.Reader(*Connection, ReadError, read);
...@@ -93,112 +146,6 @@ pub const Connection = struct {...@@ -93,112 +146,6 @@ pub const Connection = struct {
93 }146 }
94};147};
95148
96/// A buffered (and peekable) Connection.
97pub const BufferedConnection = struct {
98 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
99
100 conn: Connection,
101 read_buf: [buffer_size]u8 = undefined,
102 read_start: u16 = 0,
103 read_end: u16 = 0,
104
105 write_buf: [buffer_size]u8 = undefined,
106 write_end: u16 = 0,
107
108 pub fn fill(bconn: *BufferedConnection) ReadError!void {
109 if (bconn.read_end != bconn.read_start) return;
110
111 const nread = try bconn.conn.read(bconn.read_buf[0..]);
112 if (nread == 0) return error.EndOfStream;
113 bconn.read_start = 0;
114 bconn.read_end = @intCast(u16, nread);
115 }
116
117 pub fn peek(bconn: *BufferedConnection) []const u8 {
118 return bconn.read_buf[bconn.read_start..bconn.read_end];
119 }
120
121 pub fn drop(bconn: *BufferedConnection, num: u16) void {
122 bconn.read_start += num;
123 }
124
125 pub fn readAtLeast(bconn: *BufferedConnection, buffer: []u8, len: usize) ReadError!usize {
126 var out_index: u16 = 0;
127 while (out_index < len) {
128 const available = bconn.read_end - bconn.read_start;
129 const left = buffer.len - out_index;
130
131 if (available > 0) {
132 const can_read = @intCast(u16, @min(available, left));
133
134 @memcpy(buffer[out_index..][0..can_read], bconn.read_buf[bconn.read_start..][0..can_read]);
135 out_index += can_read;
136 bconn.read_start += can_read;
137
138 continue;
139 }
140
141 if (left > bconn.read_buf.len) {
142 // skip the buffer if the output is large enough
143 return bconn.conn.read(buffer[out_index..]);
144 }
145
146 try bconn.fill();
147 }
148
149 return out_index;
150 }
151
152 pub fn read(bconn: *BufferedConnection, buffer: []u8) ReadError!usize {
153 return bconn.readAtLeast(buffer, 1);
154 }
155
156 pub const ReadError = Connection.ReadError || error{EndOfStream};
157 pub const Reader = std.io.Reader(*BufferedConnection, ReadError, read);
158
159 pub fn reader(bconn: *BufferedConnection) Reader {
160 return Reader{ .context = bconn };
161 }
162
163 pub fn writeAll(bconn: *BufferedConnection, buffer: []const u8) WriteError!void {
164 if (bconn.write_buf.len - bconn.write_end >= buffer.len) {
165 @memcpy(bconn.write_buf[bconn.write_end..][0..buffer.len], buffer);
166 bconn.write_end += @intCast(u16, buffer.len);
167 } else {
168 try bconn.flush();
169 try bconn.conn.writeAll(buffer);
170 }
171 }
172
173 pub fn write(bconn: *BufferedConnection, buffer: []const u8) WriteError!usize {
174 if (bconn.write_buf.len - bconn.write_end >= buffer.len) {
175 @memcpy(bconn.write_buf[bconn.write_end..][0..buffer.len], buffer);
176 bconn.write_end += @intCast(u16, buffer.len);
177
178 return buffer.len;
179 } else {
180 try bconn.flush();
181 return try bconn.conn.write(buffer);
182 }
183 }
184
185 pub fn flush(bconn: *BufferedConnection) WriteError!void {
186 defer bconn.write_end = 0;
187 return bconn.conn.writeAll(bconn.write_buf[0..bconn.write_end]);
188 }
189
190 pub const WriteError = Connection.WriteError;
191 pub const Writer = std.io.Writer(*BufferedConnection, WriteError, write);
192
193 pub fn writer(bconn: *BufferedConnection) Writer {
194 return Writer{ .context = bconn };
195 }
196
197 pub fn close(bconn: *BufferedConnection) void {
198 bconn.conn.close();
199 }
200};
201
202/// The mode of transport for responses.149/// The mode of transport for responses.
203pub const ResponseTransfer = union(enum) {150pub const ResponseTransfer = union(enum) {
204 content_length: u64,151 content_length: u64,
...@@ -351,7 +298,7 @@ pub const Response = struct {...@@ -351,7 +298,7 @@ pub const Response = struct {
351298
352 allocator: Allocator,299 allocator: Allocator,
353 address: net.Address,300 address: net.Address,
354 connection: BufferedConnection,301 connection: Connection,
355302
356 headers: http.Headers,303 headers: http.Headers,
357 request: Request,304 request: Request,
...@@ -388,7 +335,7 @@ pub const Response = struct {...@@ -388,7 +335,7 @@ pub const Response = struct {
388335
389 if (!res.request.parser.done) {336 if (!res.request.parser.done) {
390 // If the response wasn't fully read, then we need to close the connection.337 // If the response wasn't fully read, then we need to close the connection.
391 res.connection.conn.closing = true;338 res.connection.closing = true;
392 return .closing;339 return .closing;
393 }340 }
394341
...@@ -402,9 +349,9 @@ pub const Response = struct {...@@ -402,9 +349,9 @@ pub const Response = struct {
402 const req_connection = res.request.headers.getFirstValue("connection");349 const req_connection = res.request.headers.getFirstValue("connection");
403 const req_keepalive = req_connection != null and !std.ascii.eqlIgnoreCase("close", req_connection.?);350 const req_keepalive = req_connection != null and !std.ascii.eqlIgnoreCase("close", req_connection.?);
404 if (req_keepalive and (res_keepalive or res_connection == null)) {351 if (req_keepalive and (res_keepalive or res_connection == null)) {
405 res.connection.conn.closing = false;352 res.connection.closing = false;
406 } else {353 } else {
407 res.connection.conn.closing = true;354 res.connection.closing = true;
408 }355 }
409356
410 switch (res.request.compression) {357 switch (res.request.compression) {
...@@ -434,14 +381,14 @@ pub const Response = struct {...@@ -434,14 +381,14 @@ pub const Response = struct {
434 .parser = res.request.parser,381 .parser = res.request.parser,
435 };382 };
436383
437 if (res.connection.conn.closing) {384 if (res.connection.closing) {
438 return .closing;385 return .closing;
439 } else {386 } else {
440 return .reset;387 return .reset;
441 }388 }
442 }389 }
443390
444 pub const DoError = BufferedConnection.WriteError || error{ UnsupportedTransferEncoding, InvalidContentLength };391 pub const DoError = Connection.WriteError || error{ UnsupportedTransferEncoding, InvalidContentLength };
445392
446 /// Send the response headers.393 /// Send the response headers.
447 pub fn do(res: *Response) !void {394 pub fn do(res: *Response) !void {
...@@ -450,7 +397,8 @@ pub const Response = struct {...@@ -450,7 +397,8 @@ pub const Response = struct {
450 .first, .start, .responded, .finished => unreachable,397 .first, .start, .responded, .finished => unreachable,
451 }398 }
452399
453 const w = res.connection.writer();400 var buffered = std.io.bufferedWriter(res.connection.writer());
401 const w = buffered.writer();
454402
455 try w.writeAll(@tagName(res.version));403 try w.writeAll(@tagName(res.version));
456 try w.writeByte(' ');404 try w.writeByte(' ');
...@@ -508,10 +456,10 @@ pub const Response = struct {...@@ -508,10 +456,10 @@ pub const Response = struct {
508456
509 try w.writeAll("\r\n");457 try w.writeAll("\r\n");
510458
511 try res.connection.flush();459 try buffered.flush();
512 }460 }
513461
514 pub const TransferReadError = BufferedConnection.ReadError || proto.HeadersParser.ReadError;462 pub const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;
515463
516 pub const TransferReader = std.io.Reader(*Response, TransferReadError, transferRead);464 pub const TransferReader = std.io.Reader(*Response, TransferReadError, transferRead);
517465
...@@ -532,7 +480,7 @@ pub const Response = struct {...@@ -532,7 +480,7 @@ pub const Response = struct {
532 return index;480 return index;
533 }481 }
534482
535 pub const WaitError = BufferedConnection.ReadError || proto.HeadersParser.CheckCompleteHeadError || Request.ParseError || error{ CompressionInitializationFailed, CompressionNotSupported };483 pub const WaitError = Connection.ReadError || proto.HeadersParser.CheckCompleteHeadError || Request.ParseError || error{ CompressionInitializationFailed, CompressionNotSupported };
536484
537 /// Wait for the client to send a complete request head.485 /// Wait for the client to send a complete request head.
538 pub fn wait(res: *Response) WaitError!void {486 pub fn wait(res: *Response) WaitError!void {
...@@ -637,7 +585,7 @@ pub const Response = struct {...@@ -637,7 +585,7 @@ pub const Response = struct {
637 return index;585 return index;
638 }586 }
639587
640 pub const WriteError = BufferedConnection.WriteError || error{ NotWriteable, MessageTooLong };588 pub const WriteError = Connection.WriteError || error{ NotWriteable, MessageTooLong };
641589
642 pub const Writer = std.io.Writer(*Response, WriteError, write);590 pub const Writer = std.io.Writer(*Response, WriteError, write);
643591
...@@ -692,8 +640,6 @@ pub const Response = struct {...@@ -692,8 +640,6 @@ pub const Response = struct {
692 .content_length => |len| if (len != 0) return error.MessageNotCompleted,640 .content_length => |len| if (len != 0) return error.MessageNotCompleted,
693 .none => {},641 .none => {},
694 }642 }
695
696 try res.connection.flush();
697 }643 }
698};644};
699645
...@@ -742,10 +688,10 @@ pub fn accept(server: *Server, options: AcceptOptions) AcceptError!Response {...@@ -742,10 +688,10 @@ pub fn accept(server: *Server, options: AcceptOptions) AcceptError!Response {
742 return Response{688 return Response{
743 .allocator = options.allocator,689 .allocator = options.allocator,
744 .address = in.address,690 .address = in.address,
745 .connection = .{ .conn = .{691 .connection = .{
746 .stream = in.stream,692 .stream = in.stream,
747 .protocol = .plain,693 .protocol = .plain,
748 } },694 },
749 .headers = .{ .allocator = options.allocator },695 .headers = .{ .allocator = options.allocator },
750 .request = .{696 .request = .{
751 .version = undefined,697 .version = undefined,
test/standalone/http.zig-1
...@@ -86,7 +86,6 @@ fn handleRequest(res: *Server.Response) !void {...@@ -86,7 +86,6 @@ fn handleRequest(res: *Server.Response) !void {
86 try res.writeAll("World!\n");86 try res.writeAll("World!\n");
87 // try res.finish();87 // try res.finish();
88 try res.connection.writeAll("0\r\nX-Checksum: aaaa\r\n\r\n");88 try res.connection.writeAll("0\r\nX-Checksum: aaaa\r\n\r\n");
89 try res.connection.flush();
90 } else if (mem.eql(u8, res.request.target, "/redirect/1")) {89 } else if (mem.eql(u8, res.request.target, "/redirect/1")) {
91 res.transfer_encoding = .chunked;90 res.transfer_encoding = .chunked;
9291