authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-05-02 21:44:08-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:28-07:00
loge4126aa213e30d4fe93981a27160acc56e3db4f8
treeeabc86ac0072c9e6ef775918fd51e1fff3efc432
parent4716c0036678cef93388a25401f32f03fb16dbc1

std.http.Server: remove the 25 header limit

no longer makes sense when there is an output buffer

1 files changed, 18 insertions(+), 59 deletions(-)

lib/std/http/Server.zig+18-59
...@@ -298,16 +298,13 @@ pub const Request = struct {...@@ -298,16 +298,13 @@ pub const Request = struct {
298 /// no error is surfaced.298 /// no error is surfaced.
299 ///299 ///
300 /// Asserts status is not `continue`.300 /// Asserts status is not `continue`.
301 /// Asserts there are at most 25 extra_headers.
302 /// Asserts that "\r\n" does not occur in any header name or value.301 /// Asserts that "\r\n" does not occur in any header name or value.
303 pub fn respond(302 pub fn respond(
304 request: *Request,303 request: *Request,
305 content: []const u8,304 content: []const u8,
306 options: RespondOptions,305 options: RespondOptions,
307 ) std.io.Writer.Error!void {306 ) std.io.Writer.Error!void {
308 const max_extra_headers = 25;
309 assert(options.status != .@"continue");307 assert(options.status != .@"continue");
310 assert(options.extra_headers.len <= max_extra_headers);
311 if (std.debug.runtime_safety) {308 if (std.debug.runtime_safety) {
312 for (options.extra_headers) |header| {309 for (options.extra_headers) |header| {
313 assert(header.name.len != 0);310 assert(header.name.len != 0);
...@@ -323,88 +320,50 @@ pub const Request = struct {...@@ -323,88 +320,50 @@ pub const Request = struct {
323320
324 const phrase = options.reason orelse options.status.phrase() orelse "";321 const phrase = options.reason orelse options.status.phrase() orelse "";
325322
326 var first_buffer: [500]u8 = undefined;323 const out = request.server.out;
327 var h = std.ArrayListUnmanaged(u8).initBuffer(&first_buffer);
328 if (request.head.expect != null) {324 if (request.head.expect != null) {
329 // reader() and hence discardBody() above sets expect to null if it325 // reader() and hence discardBody() above sets expect to null if it
330 // is handled. So the fact that it is not null here means unhandled.326 // is handled. So the fact that it is not null here means unhandled.
331 h.appendSliceAssumeCapacity("HTTP/1.1 417 Expectation Failed\r\n");327 var vecs: [3][]const u8 = .{
332 if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n");328 "HTTP/1.1 417 Expectation Failed\r\n",
333 h.appendSliceAssumeCapacity("content-length: 0\r\n\r\n");329 if (keep_alive) "" else "connection: close\r\n",
334 try request.server.out.writeAll(h.items);330 "content-length: 0\r\n\r\n",
331 };
332 try out.writeVecAll(&vecs);
335 return;333 return;
336 }334 }
337 h.printAssumeCapacity("{s} {d} {s}\r\n", .{335 try out.print("{s} {d} {s}\r\n", .{
338 @tagName(options.version), @intFromEnum(options.status), phrase,336 @tagName(options.version), @intFromEnum(options.status), phrase,
339 });337 });
340338
341 switch (options.version) {339 switch (options.version) {
342 .@"HTTP/1.0" => if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n"),340 .@"HTTP/1.0" => if (keep_alive) try out.writeAll("connection: keep-alive\r\n"),
343 .@"HTTP/1.1" => if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n"),341 .@"HTTP/1.1" => if (!keep_alive) try out.writeAll("connection: close\r\n"),
344 }342 }
345343
346 if (options.transfer_encoding) |transfer_encoding| switch (transfer_encoding) {344 if (options.transfer_encoding) |transfer_encoding| switch (transfer_encoding) {
347 .none => {},345 .none => {},
348 .chunked => h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n"),346 .chunked => try out.writeAll("transfer-encoding: chunked\r\n"),
349 } else {347 } else {
350 h.printAssumeCapacity("content-length: {d}\r\n", .{content.len});348 try out.print("content-length: {d}\r\n", .{content.len});
351 }349 }
352350
353 var chunk_header_buffer: [18]u8 = undefined;
354 var iovecs: [max_extra_headers * 4 + 3][]const u8 = undefined;
355 var iovecs_len: usize = 0;
356
357 iovecs[iovecs_len] = h.items;
358 iovecs_len += 1;
359
360 for (options.extra_headers) |header| {351 for (options.extra_headers) |header| {
361 iovecs[iovecs_len] = header.name;352 var vecs: [4][]const u8 = .{ header.name, ": ", header.value, "\r\n" };
362 iovecs_len += 1;353 try out.writeVecAll(&vecs);
363
364 iovecs[iovecs_len] = ": ";
365 iovecs_len += 1;
366
367 if (header.value.len != 0) {
368 iovecs[iovecs_len] = header.value;
369 iovecs_len += 1;
370 }
371
372 iovecs[iovecs_len] = "\r\n";
373 iovecs_len += 1;
374 }354 }
375355
376 iovecs[iovecs_len] = "\r\n";356 try out.writeAll("\r\n");
377 iovecs_len += 1;
378357
379 if (request.head.method != .HEAD) {358 if (request.head.method != .HEAD) {
380 const is_chunked = (options.transfer_encoding orelse .none) == .chunked;359 const is_chunked = (options.transfer_encoding orelse .none) == .chunked;
381 if (is_chunked) {360 if (is_chunked) {
382 if (content.len > 0) {361 if (content.len > 0) try out.print("{x}\r\n{s}\r\n", .{ content.len, content });
383 const chunk_header = std.fmt.bufPrint(362 try out.writeAll("0\r\n\r\n");
384 &chunk_header_buffer,
385 "{x}\r\n",
386 .{content.len},
387 ) catch unreachable;
388
389 iovecs[iovecs_len] = chunk_header;
390 iovecs_len += 1;
391
392 iovecs[iovecs_len] = content;
393 iovecs_len += 1;
394
395 iovecs[iovecs_len] = "\r\n";
396 iovecs_len += 1;
397 }
398
399 iovecs[iovecs_len] = "0\r\n\r\n";
400 iovecs_len += 1;
401 } else if (content.len > 0) {363 } else if (content.len > 0) {
402 iovecs[iovecs_len] = content;364 try out.writeAll(content);
403 iovecs_len += 1;
404 }365 }
405 }366 }
406
407 try request.server.out.writeVecAll(iovecs[0..iovecs_len]);
408 }367 }
409368
410 pub const RespondStreamingOptions = struct {369 pub const RespondStreamingOptions = struct {