authorgravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-05-03 14:34:10-05:00
committergravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-05-06 21:35:16-05:00
log1b3ebfefd8d8fd05152d55c431f741880d1ce2a7
tree65e23769c1a247340b4a7f9ab163741018411a4e
parent5f219a2d118cac1410888fb2c0abc0cc91d092de
signaturelock-open Commit is signed but in an unrecognized format.

fix keepalive and large buffered writes


3 files changed, 129 insertions(+), 28 deletions(-)

lib/std/http/Client.zig+10-12
......@@ -71,7 +71,7 @@ pub const ConnectionPool = struct {
7171 while (next) |node| : (next = node.prev) {
7272 if ((node.data.buffered.conn.protocol == .tls) != criteria.is_tls) continue;
7373 if (node.data.port != criteria.port) continue;
74 if (mem.eql(u8, node.data.host, criteria.host)) continue;
74 if (!mem.eql(u8, node.data.host, criteria.host)) continue;
7575
7676 pool.acquireUnsafe(node);
7777 return node;
......@@ -317,32 +317,29 @@ pub const BufferedConnection = struct {
317317 }
318318
319319 pub fn writeAll(bconn: *BufferedConnection, buffer: []const u8) WriteError!void {
320 if (bconn.write_buf.len - bconn.write_end <= buffer.len) {
321 @memcpy(bconn.write_buf[bconn.write_end..], buffer);
320 if (bconn.write_buf.len - bconn.write_end >= buffer.len) {
321 @memcpy(bconn.write_buf[bconn.write_end..][0..buffer.len], buffer);
322322 bconn.write_end += @intCast(u16, buffer.len);
323323 } else {
324 try bconn.conn.writeAll(bconn.write_buf[0..bconn.write_end]);
325 bconn.write_end = 0;
326
324 try bconn.flush();
327325 try bconn.conn.writeAll(buffer);
328326 }
329327 }
330328
331329 pub fn write(bconn: *BufferedConnection, buffer: []const u8) WriteError!usize {
332 if (bconn.write_buf.len - bconn.write_end <= buffer.len) {
333 @memcpy(bconn.write_buf[bconn.write_end..], buffer);
330 if (bconn.write_buf.len - bconn.write_end >= buffer.len) {
331 @memcpy(bconn.write_buf[bconn.write_end..][0..buffer.len], buffer);
334332 bconn.write_end += @intCast(u16, buffer.len);
335333
336334 return buffer.len;
337335 } else {
338 try bconn.conn.writeAll(bconn.write_buf[0..bconn.write_end]);
339 bconn.write_end = 0;
340
336 try bconn.flush();
341337 return try bconn.conn.write(buffer);
342338 }
343339 }
344340
345341 pub fn flush(bconn: *BufferedConnection) WriteError!void {
342 defer bconn.write_end = 0;
346343 return bconn.conn.writeAll(bconn.write_buf[0..bconn.write_end]);
347344 }
348345
......@@ -720,12 +717,13 @@ pub const Request = struct {
720717 req.response.parser.done = true;
721718 }
722719
720 // we default to using keep-alive if not provided
723721 const req_connection = req.headers.getFirstValue("connection");
724722 const req_keepalive = req_connection != null and !std.ascii.eqlIgnoreCase("close", req_connection.?);
725723
726724 const res_connection = req.response.headers.getFirstValue("connection");
727725 const res_keepalive = res_connection != null and !std.ascii.eqlIgnoreCase("close", res_connection.?);
728 if (req_keepalive and res_keepalive) {
726 if (res_keepalive and (req_keepalive or req_connection == null)) {
729727 req.connection.data.closing = false;
730728 } else {
731729 req.connection.data.closing = true;
lib/std/http/Server.zig+11-12
......@@ -161,32 +161,29 @@ pub const BufferedConnection = struct {
161161 }
162162
163163 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..], buffer);
164 if (bconn.write_buf.len - bconn.write_end >= buffer.len) {
165 @memcpy(bconn.write_buf[bconn.write_end..][0..buffer.len], buffer);
166166 bconn.write_end += @intCast(u16, buffer.len);
167167 } else {
168 try bconn.conn.writeAll(bconn.write_buf[0..bconn.write_end]);
169 bconn.write_end = 0;
170
168 try bconn.flush();
171169 try bconn.conn.writeAll(buffer);
172170 }
173171 }
174172
175173 pub fn write(bconn: *BufferedConnection, buffer: []const u8) WriteError!usize {
176 if (bconn.write_buf.len - bconn.write_end <= buffer.len) {
177 @memcpy(bconn.write_buf[bconn.write_end..], buffer);
174 if (bconn.write_buf.len - bconn.write_end >= buffer.len) {
175 @memcpy(bconn.write_buf[bconn.write_end..][0..buffer.len], buffer);
178176 bconn.write_end += @intCast(u16, buffer.len);
179177
180178 return buffer.len;
181179 } else {
182 try bconn.conn.writeAll(bconn.write_buf[0..bconn.write_end]);
183 bconn.write_end = 0;
184
180 try bconn.flush();
185181 return try bconn.conn.write(buffer);
186182 }
187183 }
188184
189185 pub fn flush(bconn: *BufferedConnection) WriteError!void {
186 defer bconn.write_end = 0;
190187 return bconn.conn.writeAll(bconn.write_buf[0..bconn.write_end]);
191188 }
192189
......@@ -397,12 +394,14 @@ pub const Response = struct {
397394
398395 // A connection is only keep-alive if the Connection header is present and it's value is not "close".
399396 // The server and client must both agree
397 //
398 // do() defaults to using keep-alive if the client requests it.
400399 const res_connection = res.headers.getFirstValue("connection");
401400 const res_keepalive = res_connection != null and !std.ascii.eqlIgnoreCase("close", res_connection.?);
402401
403402 const req_connection = res.request.headers.getFirstValue("connection");
404403 const req_keepalive = req_connection != null and !std.ascii.eqlIgnoreCase("close", req_connection.?);
405 if (res_keepalive and req_keepalive) {
404 if (req_keepalive and (res_keepalive or res_connection == null)) {
406405 res.connection.conn.closing = false;
407406 } else {
408407 res.connection.conn.closing = true;
......@@ -424,7 +423,7 @@ pub const Response = struct {
424423
425424 res.headers.clearRetainingCapacity();
426425
427 res.request.headers.clearRetainingCapacity();
426 res.request.headers.clearAndFree(); // FIXME: figure out why `clearRetainingCapacity` causes a leak in hash_map here
428427 res.request.parser.reset();
429428
430429 res.request = Request{
test/standalone/http.zig+108-4
......@@ -9,8 +9,8 @@ const testing = std.testing;
99
1010const max_header_size = 8192;
1111
12var gpa_server = std.heap.GeneralPurposeAllocator(.{}){};
13var gpa_client = std.heap.GeneralPurposeAllocator(.{}){};
12var gpa_server = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 12 }){};
13var gpa_client = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 12 }){};
1414
1515const salloc = gpa_server.allocator();
1616const calloc = gpa_client.allocator();
......@@ -44,6 +44,24 @@ fn handleRequest(res: *Server.Response) !void {
4444 try res.writeAll("World!\n");
4545 try res.finish();
4646 }
47 } else if (mem.startsWith(u8, res.request.target, "/large")) {
48 res.transfer_encoding = .{ .content_length = 14 * 1024 + 14 * 10 };
49
50 try res.do();
51
52 var i: u32 = 0;
53 while (i < 5) : (i += 1) {
54 try res.writeAll("Hello, World!\n");
55 }
56
57 try res.writeAll("Hello, World!\n" ** 1024);
58
59 i = 0;
60 while (i < 5) : (i += 1) {
61 try res.writeAll("Hello, World!\n");
62 }
63
64 try res.finish();
4765 } else if (mem.eql(u8, res.request.target, "/echo-content")) {
4866 try testing.expectEqualStrings("Hello, World!\n", body);
4967 try testing.expectEqualStrings("text/plain", res.request.headers.getFirstValue("content-type").?);
......@@ -68,6 +86,7 @@ fn handleRequest(res: *Server.Response) !void {
6886 try res.writeAll("World!\n");
6987 // try res.finish();
7088 try res.connection.writeAll("0\r\nX-Checksum: aaaa\r\n\r\n");
89 try res.connection.flush();
7190 } else if (mem.eql(u8, res.request.target, "/redirect/1")) {
7291 res.transfer_encoding = .chunked;
7392
......@@ -177,8 +196,7 @@ pub fn main() !void {
177196 const server_thread = try std.Thread.spawn(.{}, serverThread, .{&server});
178197
179198 var client = Client{ .allocator = calloc };
180
181 defer client.deinit();
199 // defer client.deinit(); handled below
182200
183201 { // read content-length response
184202 var h = http.Headers{ .allocator = calloc };
......@@ -202,6 +220,33 @@ pub fn main() !void {
202220 try testing.expectEqualStrings("text/plain", req.response.headers.getFirstValue("content-type").?);
203221 }
204222
223 // connection has been kept alive
224 try testing.expect(client.connection_pool.free_len == 1);
225
226 { // read large content-length response
227 var h = http.Headers{ .allocator = calloc };
228 defer h.deinit();
229
230 const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/large", .{port});
231 defer calloc.free(location);
232 const uri = try std.Uri.parse(location);
233
234 log.info("{s}", .{location});
235 var req = try client.request(.GET, uri, h, .{});
236 defer req.deinit();
237
238 try req.start();
239 try req.wait();
240
241 const body = try req.reader().readAllAlloc(calloc, 8192 * 1024);
242 defer calloc.free(body);
243
244 try testing.expectEqual(@as(usize, 14 * 1024 + 14 * 10), body.len);
245 }
246
247 // connection has been kept alive
248 try testing.expect(client.connection_pool.free_len == 1);
249
205250 { // send head request and not read chunked
206251 var h = http.Headers{ .allocator = calloc };
207252 defer h.deinit();
......@@ -225,6 +270,9 @@ pub fn main() !void {
225270 try testing.expectEqualStrings("14", req.response.headers.getFirstValue("content-length").?);
226271 }
227272
273 // connection has been kept alive
274 try testing.expect(client.connection_pool.free_len == 1);
275
228276 { // read chunked response
229277 var h = http.Headers{ .allocator = calloc };
230278 defer h.deinit();
......@@ -247,6 +295,9 @@ pub fn main() !void {
247295 try testing.expectEqualStrings("text/plain", req.response.headers.getFirstValue("content-type").?);
248296 }
249297
298 // connection has been kept alive
299 try testing.expect(client.connection_pool.free_len == 1);
300
250301 { // send head request and not read chunked
251302 var h = http.Headers{ .allocator = calloc };
252303 defer h.deinit();
......@@ -270,6 +321,9 @@ pub fn main() !void {
270321 try testing.expectEqualStrings("chunked", req.response.headers.getFirstValue("transfer-encoding").?);
271322 }
272323
324 // connection has been kept alive
325 try testing.expect(client.connection_pool.free_len == 1);
326
273327 { // check trailing headers
274328 var h = http.Headers{ .allocator = calloc };
275329 defer h.deinit();
......@@ -292,6 +346,9 @@ pub fn main() !void {
292346 try testing.expectEqualStrings("aaaa", req.response.headers.getFirstValue("x-checksum").?);
293347 }
294348
349 // connection has been kept alive
350 try testing.expect(client.connection_pool.free_len == 1);
351
295352 { // send content-length request
296353 var h = http.Headers{ .allocator = calloc };
297354 defer h.deinit();
......@@ -321,6 +378,36 @@ pub fn main() !void {
321378 try testing.expectEqualStrings("Hello, World!\n", body);
322379 }
323380
381 // connection has been kept alive
382 try testing.expect(client.connection_pool.free_len == 1);
383
384 { // read content-length response with connection close
385 var h = http.Headers{ .allocator = calloc };
386 defer h.deinit();
387
388 try h.append("connection", "close");
389
390 const location = try std.fmt.allocPrint(calloc, "http://127.0.0.1:{d}/get", .{port});
391 defer calloc.free(location);
392 const uri = try std.Uri.parse(location);
393
394 log.info("{s}", .{location});
395 var req = try client.request(.GET, uri, h, .{});
396 defer req.deinit();
397
398 try req.start();
399 try req.wait();
400
401 const body = try req.reader().readAllAlloc(calloc, 8192);
402 defer calloc.free(body);
403
404 try testing.expectEqualStrings("Hello, World!\n", body);
405 try testing.expectEqualStrings("text/plain", req.response.headers.getFirstValue("content-type").?);
406 }
407
408 // connection has been closed
409 try testing.expect(client.connection_pool.free_len == 0);
410
324411 { // send chunked request
325412 var h = http.Headers{ .allocator = calloc };
326413 defer h.deinit();
......@@ -350,6 +437,9 @@ pub fn main() !void {
350437 try testing.expectEqualStrings("Hello, World!\n", body);
351438 }
352439
440 // connection has been kept alive
441 try testing.expect(client.connection_pool.free_len == 1);
442
353443 { // relative redirect
354444 var h = http.Headers{ .allocator = calloc };
355445 defer h.deinit();
......@@ -371,6 +461,9 @@ pub fn main() !void {
371461 try testing.expectEqualStrings("Hello, World!\n", body);
372462 }
373463
464 // connection has been kept alive
465 try testing.expect(client.connection_pool.free_len == 1);
466
374467 { // redirect from root
375468 var h = http.Headers{ .allocator = calloc };
376469 defer h.deinit();
......@@ -392,6 +485,9 @@ pub fn main() !void {
392485 try testing.expectEqualStrings("Hello, World!\n", body);
393486 }
394487
488 // connection has been kept alive
489 try testing.expect(client.connection_pool.free_len == 1);
490
395491 { // absolute redirect
396492 var h = http.Headers{ .allocator = calloc };
397493 defer h.deinit();
......@@ -413,6 +509,9 @@ pub fn main() !void {
413509 try testing.expectEqualStrings("Hello, World!\n", body);
414510 }
415511
512 // connection has been kept alive
513 try testing.expect(client.connection_pool.free_len == 1);
514
416515 { // too many redirects
417516 var h = http.Headers{ .allocator = calloc };
418517 defer h.deinit();
......@@ -432,6 +531,11 @@ pub fn main() !void {
432531 };
433532 }
434533
534 // connection has been kept alive
535 try testing.expect(client.connection_pool.free_len == 1);
536
537 client.deinit();
538
435539 killServer(server.socket.listen_address);
436540 server_thread.join();
437541}