authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-31 22:36:08-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-07 10:04:28-07:00
log02908a2d8c0376fa2f9b793ac22d648632fde735
tree2f8d0a8ed2350ad53f5eca5c901e21d28ac8d353
parent8843631f7ecfe0a7756a2f1f0bc99a24b57c2fcc

std.http: rework for new std.Io API


7 files changed, 2423 insertions(+), 2808 deletions(-)

lib/std/http.zig+766-15
......@@ -1,14 +1,14 @@
11const builtin = @import("builtin");
22const std = @import("std.zig");
33const assert = std.debug.assert;
4const Writer = std.Io.Writer;
5const File = std.fs.File;
46
57pub const Client = @import("http/Client.zig");
68pub const Server = @import("http/Server.zig");
7pub const protocol = @import("http/protocol.zig");
89pub const HeadParser = @import("http/HeadParser.zig");
910pub const ChunkParser = @import("http/ChunkParser.zig");
1011pub const HeaderIterator = @import("http/HeaderIterator.zig");
11pub const WebSocket = @import("http/WebSocket.zig");
1212
1313pub const Version = enum {
1414 @"HTTP/1.0",
......@@ -42,7 +42,7 @@ pub const Method = enum(u64) {
4242 return x;
4343 }
4444
45 pub fn format(self: Method, w: *std.io.Writer) std.io.Writer.Error!void {
45 pub fn format(self: Method, w: *Writer) Writer.Error!void {
4646 const bytes: []const u8 = @ptrCast(&@intFromEnum(self));
4747 const str = std.mem.sliceTo(bytes, 0);
4848 try w.writeAll(str);
......@@ -296,13 +296,24 @@ pub const TransferEncoding = enum {
296296};
297297
298298pub const ContentEncoding = enum {
299 identity,
300 compress,
301 @"x-compress",
302 deflate,
303 gzip,
304 @"x-gzip",
305299 zstd,
300 gzip,
301 deflate,
302 compress,
303 identity,
304
305 pub fn fromString(s: []const u8) ?ContentEncoding {
306 const map = std.StaticStringMap(ContentEncoding).initComptime(.{
307 .{ "zstd", .zstd },
308 .{ "gzip", .gzip },
309 .{ "x-gzip", .gzip },
310 .{ "deflate", .deflate },
311 .{ "compress", .compress },
312 .{ "x-compress", .compress },
313 .{ "identity", .identity },
314 });
315 return map.get(s);
316 }
306317};
307318
308319pub const Connection = enum {
......@@ -315,15 +326,755 @@ pub const Header = struct {
315326 value: []const u8,
316327};
317328
329pub const Reader = struct {
330 in: *std.Io.Reader,
331 /// This is preallocated memory that might be used by `bodyReader`. That
332 /// function might return a pointer to this field, or a different
333 /// `*std.Io.Reader`. Advisable to not access this field directly.
334 interface: std.Io.Reader,
335 /// Keeps track of whether the stream is ready to accept a new request,
336 /// making invalid API usage cause assertion failures rather than HTTP
337 /// protocol violations.
338 state: State,
339 /// HTTP trailer bytes. These are at the end of a transfer-encoding:
340 /// chunked message. This data is available only after calling one of the
341 /// "end" functions and points to data inside the buffer of `in`, and is
342 /// therefore invalidated on the next call to `receiveHead`, or any other
343 /// read from `in`.
344 trailers: []const u8 = &.{},
345 body_err: ?BodyError = null,
346 /// Stolen from `in`.
347 head_buffer: []u8 = &.{},
348
349 pub const max_chunk_header_len = 22;
350
351 pub const RemainingChunkLen = enum(u64) {
352 head = 0,
353 n = 1,
354 rn = 2,
355 _,
356
357 pub fn init(integer: u64) RemainingChunkLen {
358 return @enumFromInt(integer);
359 }
360
361 pub fn int(rcl: RemainingChunkLen) u64 {
362 return @intFromEnum(rcl);
363 }
364 };
365
366 pub const State = union(enum) {
367 /// The stream is available to be used for the first time, or reused.
368 ready,
369 received_head,
370 /// The stream goes until the connection is closed.
371 body_none,
372 body_remaining_content_length: u64,
373 body_remaining_chunk_len: RemainingChunkLen,
374 /// The stream would be eligible for another HTTP request, however the
375 /// client and server did not negotiate a persistent connection.
376 closing,
377 };
378
379 pub const BodyError = error{
380 HttpChunkInvalid,
381 HttpChunkTruncated,
382 HttpHeadersOversize,
383 };
384
385 pub const HeadError = error{
386 /// Too many bytes of HTTP headers.
387 ///
388 /// The HTTP specification suggests to respond with a 431 status code
389 /// before closing the connection.
390 HttpHeadersOversize,
391 /// Partial HTTP request was received but the connection was closed
392 /// before fully receiving the headers.
393 HttpRequestTruncated,
394 /// The client sent 0 bytes of headers before closing the stream. This
395 /// happens when a keep-alive connection is finally closed.
396 HttpConnectionClosing,
397 /// Transitive error occurred reading from `in`.
398 ReadFailed,
399 };
400
401 pub fn restituteHeadBuffer(reader: *Reader) void {
402 reader.in.restitute(reader.head_buffer.len);
403 reader.head_buffer.len = 0;
404 }
405
406 /// Buffers the entire head into `head_buffer`, invalidating the previous
407 /// `head_buffer`, if any.
408 pub fn receiveHead(reader: *Reader) HeadError!void {
409 reader.trailers = &.{};
410 const in = reader.in;
411 in.restitute(reader.head_buffer.len);
412 reader.head_buffer.len = 0;
413 in.rebase();
414 var hp: HeadParser = .{};
415 var head_end: usize = 0;
416 while (true) {
417 if (head_end >= in.buffer.len) return error.HttpHeadersOversize;
418 in.fillMore() catch |err| switch (err) {
419 error.EndOfStream => switch (head_end) {
420 0 => return error.HttpConnectionClosing,
421 else => return error.HttpRequestTruncated,
422 },
423 error.ReadFailed => return error.ReadFailed,
424 };
425 head_end += hp.feed(in.buffered()[head_end..]);
426 if (hp.state == .finished) {
427 reader.head_buffer = in.steal(head_end);
428 reader.state = .received_head;
429 return;
430 }
431 }
432 }
433
434 /// If compressed body has been negotiated this will return compressed bytes.
435 ///
436 /// Asserts only called once and after `receiveHead`.
437 ///
438 /// See also:
439 /// * `interfaceDecompressing`
440 pub fn bodyReader(
441 reader: *Reader,
442 buffer: []u8,
443 transfer_encoding: TransferEncoding,
444 content_length: ?u64,
445 ) *std.Io.Reader {
446 assert(reader.state == .received_head);
447 switch (transfer_encoding) {
448 .chunked => {
449 reader.state = .{ .body_remaining_chunk_len = .head };
450 reader.interface = .{
451 .buffer = buffer,
452 .seek = 0,
453 .end = 0,
454 .vtable = &.{
455 .stream = chunkedStream,
456 .discard = chunkedDiscard,
457 },
458 };
459 return &reader.interface;
460 },
461 .none => {
462 if (content_length) |len| {
463 reader.state = .{ .body_remaining_content_length = len };
464 reader.interface = .{
465 .buffer = buffer,
466 .seek = 0,
467 .end = 0,
468 .vtable = &.{
469 .stream = contentLengthStream,
470 .discard = contentLengthDiscard,
471 },
472 };
473 return &reader.interface;
474 } else {
475 reader.state = .body_none;
476 return reader.in;
477 }
478 },
479 }
480 }
481
482 /// If compressed body has been negotiated this will return decompressed bytes.
483 ///
484 /// Asserts only called once and after `receiveHead`.
485 ///
486 /// See also:
487 /// * `interface`
488 pub fn bodyReaderDecompressing(
489 reader: *Reader,
490 transfer_encoding: TransferEncoding,
491 content_length: ?u64,
492 content_encoding: ContentEncoding,
493 decompressor: *Decompressor,
494 decompression_buffer: []u8,
495 ) *std.Io.Reader {
496 if (transfer_encoding == .none and content_length == null) {
497 assert(reader.state == .received_head);
498 reader.state = .body_none;
499 switch (content_encoding) {
500 .identity => {
501 return reader.in;
502 },
503 .deflate => {
504 decompressor.* = .{ .flate = .init(reader.in, .raw, decompression_buffer) };
505 return &decompressor.flate.reader;
506 },
507 .gzip => {
508 decompressor.* = .{ .flate = .init(reader.in, .gzip, decompression_buffer) };
509 return &decompressor.flate.reader;
510 },
511 .zstd => {
512 decompressor.* = .{ .zstd = .init(reader.in, decompression_buffer, .{ .verify_checksum = false }) };
513 return &decompressor.zstd.reader;
514 },
515 .compress => unreachable,
516 }
517 }
518 const transfer_reader = bodyReader(reader, &.{}, transfer_encoding, content_length);
519 return decompressor.init(transfer_reader, decompression_buffer, content_encoding);
520 }
521
522 fn contentLengthStream(
523 io_r: *std.Io.Reader,
524 w: *Writer,
525 limit: std.Io.Limit,
526 ) std.Io.Reader.StreamError!usize {
527 const reader: *Reader = @fieldParentPtr("interface", io_r);
528 const remaining_content_length = &reader.state.body_remaining_content_length;
529 const remaining = remaining_content_length.*;
530 if (remaining == 0) {
531 reader.state = .ready;
532 return error.EndOfStream;
533 }
534 const n = try reader.in.stream(w, limit.min(.limited(remaining)));
535 remaining_content_length.* = remaining - n;
536 return n;
537 }
538
539 fn contentLengthDiscard(io_r: *std.Io.Reader, limit: std.Io.Limit) std.Io.Reader.Error!usize {
540 const reader: *Reader = @fieldParentPtr("interface", io_r);
541 const remaining_content_length = &reader.state.body_remaining_content_length;
542 const remaining = remaining_content_length.*;
543 if (remaining == 0) {
544 reader.state = .ready;
545 return error.EndOfStream;
546 }
547 const n = try reader.in.discard(limit.min(.limited(remaining)));
548 remaining_content_length.* = remaining - n;
549 return n;
550 }
551
552 fn chunkedStream(io_r: *std.Io.Reader, w: *Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
553 const reader: *Reader = @fieldParentPtr("interface", io_r);
554 const chunk_len_ptr = switch (reader.state) {
555 .ready => return error.EndOfStream,
556 .body_remaining_chunk_len => |*x| x,
557 else => unreachable,
558 };
559 return chunkedReadEndless(reader, w, limit, chunk_len_ptr) catch |err| switch (err) {
560 error.ReadFailed => return error.ReadFailed,
561 error.WriteFailed => return error.WriteFailed,
562 error.EndOfStream => {
563 reader.body_err = error.HttpChunkTruncated;
564 return error.ReadFailed;
565 },
566 else => |e| {
567 reader.body_err = e;
568 return error.ReadFailed;
569 },
570 };
571 }
572
573 fn chunkedReadEndless(
574 reader: *Reader,
575 w: *Writer,
576 limit: std.Io.Limit,
577 chunk_len_ptr: *RemainingChunkLen,
578 ) (BodyError || std.Io.Reader.StreamError)!usize {
579 const in = reader.in;
580 len: switch (chunk_len_ptr.*) {
581 .head => {
582 var cp: ChunkParser = .init;
583 while (true) {
584 const i = cp.feed(in.buffered());
585 switch (cp.state) {
586 .invalid => return error.HttpChunkInvalid,
587 .data => {
588 in.toss(i);
589 break;
590 },
591 else => {
592 in.toss(i);
593 try in.fillMore();
594 continue;
595 },
596 }
597 }
598 if (cp.chunk_len == 0) return parseTrailers(reader, 0);
599 const n = try in.stream(w, limit.min(.limited(cp.chunk_len)));
600 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);
601 return n;
602 },
603 .n => {
604 if ((try in.peekByte()) != '\n') return error.HttpChunkInvalid;
605 in.toss(1);
606 continue :len .head;
607 },
608 .rn => {
609 const rn = try in.peekArray(2);
610 if (rn[0] != '\r' or rn[1] != '\n') return error.HttpChunkInvalid;
611 in.toss(2);
612 continue :len .head;
613 },
614 else => |remaining_chunk_len| {
615 const n = try in.stream(w, limit.min(.limited(@intFromEnum(remaining_chunk_len) - 2)));
616 chunk_len_ptr.* = .init(@intFromEnum(remaining_chunk_len) - n);
617 return n;
618 },
619 }
620 }
621
622 fn chunkedDiscard(io_r: *std.Io.Reader, limit: std.Io.Limit) std.Io.Reader.Error!usize {
623 const reader: *Reader = @fieldParentPtr("interface", io_r);
624 const chunk_len_ptr = switch (reader.state) {
625 .ready => return error.EndOfStream,
626 .body_remaining_chunk_len => |*x| x,
627 else => unreachable,
628 };
629 return chunkedDiscardEndless(reader, limit, chunk_len_ptr) catch |err| switch (err) {
630 error.ReadFailed => return error.ReadFailed,
631 error.EndOfStream => {
632 reader.body_err = error.HttpChunkTruncated;
633 return error.ReadFailed;
634 },
635 else => |e| {
636 reader.body_err = e;
637 return error.ReadFailed;
638 },
639 };
640 }
641
642 fn chunkedDiscardEndless(
643 reader: *Reader,
644 limit: std.Io.Limit,
645 chunk_len_ptr: *RemainingChunkLen,
646 ) (BodyError || std.Io.Reader.Error)!usize {
647 const in = reader.in;
648 len: switch (chunk_len_ptr.*) {
649 .head => {
650 var cp: ChunkParser = .init;
651 while (true) {
652 const i = cp.feed(in.buffered());
653 switch (cp.state) {
654 .invalid => return error.HttpChunkInvalid,
655 .data => {
656 in.toss(i);
657 break;
658 },
659 else => {
660 in.toss(i);
661 try in.fillMore();
662 continue;
663 },
664 }
665 }
666 if (cp.chunk_len == 0) return parseTrailers(reader, 0);
667 const n = try in.discard(limit.min(.limited(cp.chunk_len)));
668 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);
669 return n;
670 },
671 .n => {
672 if ((try in.peekByte()) != '\n') return error.HttpChunkInvalid;
673 in.toss(1);
674 continue :len .head;
675 },
676 .rn => {
677 const rn = try in.peekArray(2);
678 if (rn[0] != '\r' or rn[1] != '\n') return error.HttpChunkInvalid;
679 in.toss(2);
680 continue :len .head;
681 },
682 else => |remaining_chunk_len| {
683 const n = try in.discard(limit.min(.limited(remaining_chunk_len.int() - 2)));
684 chunk_len_ptr.* = .init(remaining_chunk_len.int() - n);
685 return n;
686 },
687 }
688 }
689
690 /// Called when next bytes in the stream are trailers, or "\r\n" to indicate
691 /// end of chunked body.
692 fn parseTrailers(reader: *Reader, amt_read: usize) (BodyError || std.Io.Reader.Error)!usize {
693 const in = reader.in;
694 const rn = try in.peekArray(2);
695 if (rn[0] == '\r' and rn[1] == '\n') {
696 in.toss(2);
697 reader.state = .ready;
698 assert(reader.trailers.len == 0);
699 return amt_read;
700 }
701 var hp: HeadParser = .{ .state = .seen_rn };
702 var trailers_len: usize = 2;
703 while (true) {
704 if (in.buffer.len - trailers_len == 0) return error.HttpHeadersOversize;
705 const remaining = in.buffered()[trailers_len..];
706 if (remaining.len == 0) {
707 try in.fillMore();
708 continue;
709 }
710 trailers_len += hp.feed(remaining);
711 if (hp.state == .finished) {
712 reader.state = .ready;
713 reader.trailers = in.buffered()[0..trailers_len];
714 in.toss(trailers_len);
715 return amt_read;
716 }
717 }
718 }
719};
720
721pub const Decompressor = union(enum) {
722 flate: std.compress.flate.Decompress,
723 zstd: std.compress.zstd.Decompress,
724 none: *std.Io.Reader,
725
726 pub fn init(
727 decompressor: *Decompressor,
728 transfer_reader: *std.Io.Reader,
729 buffer: []u8,
730 content_encoding: ContentEncoding,
731 ) *std.Io.Reader {
732 switch (content_encoding) {
733 .identity => {
734 decompressor.* = .{ .none = transfer_reader };
735 return transfer_reader;
736 },
737 .deflate => {
738 decompressor.* = .{ .flate = .init(transfer_reader, .raw, buffer) };
739 return &decompressor.flate.reader;
740 },
741 .gzip => {
742 decompressor.* = .{ .flate = .init(transfer_reader, .gzip, buffer) };
743 return &decompressor.flate.reader;
744 },
745 .zstd => {
746 decompressor.* = .{ .zstd = .init(transfer_reader, buffer, .{ .verify_checksum = false }) };
747 return &decompressor.zstd.reader;
748 },
749 .compress => unreachable,
750 }
751 }
752};
753
754/// Request or response body.
755pub const BodyWriter = struct {
756 /// Until the lifetime of `BodyWriter` ends, it is illegal to modify the
757 /// state of this other than via methods of `BodyWriter`.
758 http_protocol_output: *Writer,
759 state: State,
760 writer: Writer,
761
762 pub const Error = Writer.Error;
763
764 /// How many zeroes to reserve for hex-encoded chunk length.
765 const chunk_len_digits = 8;
766 const max_chunk_len: usize = std.math.pow(usize, 16, chunk_len_digits) - 1;
767 const chunk_header_template = ("0" ** chunk_len_digits) ++ "\r\n";
768
769 comptime {
770 assert(max_chunk_len == std.math.maxInt(u32));
771 }
772
773 pub const State = union(enum) {
774 /// End of connection signals the end of the stream.
775 none,
776 /// As a debugging utility, counts down to zero as bytes are written.
777 content_length: u64,
778 /// Each chunk is wrapped in a header and trailer.
779 chunked: Chunked,
780 /// Cleanly finished stream; connection can be reused.
781 end,
782
783 pub const Chunked = union(enum) {
784 /// Index to the start of the hex-encoded chunk length in the chunk
785 /// header within the buffer of `BodyWriter.http_protocol_output`.
786 /// Buffered chunk data starts here plus length of `chunk_header_template`.
787 offset: usize,
788 /// We are in the middle of a chunk and this is how many bytes are
789 /// left until the next header. This includes +2 for "\r"\n", and
790 /// is zero for the beginning of the stream.
791 chunk_len: usize,
792
793 pub const init: Chunked = .{ .chunk_len = 0 };
794 };
795 };
796
797 pub fn isEliding(w: *const BodyWriter) bool {
798 return w.writer.vtable.drain == Writer.discardingDrain;
799 }
800
801 /// Sends all buffered data across `BodyWriter.http_protocol_output`.
802 pub fn flush(w: *BodyWriter) Error!void {
803 const out = w.http_protocol_output;
804 switch (w.state) {
805 .end, .none, .content_length => return out.flush(),
806 .chunked => |*chunked| switch (chunked.*) {
807 .offset => |offset| {
808 const chunk_len = out.end - offset - chunk_header_template.len;
809 if (chunk_len > 0) {
810 writeHex(out.buffer[offset..][0..chunk_len_digits], chunk_len);
811 chunked.* = .{ .chunk_len = 2 };
812 } else {
813 out.end = offset;
814 chunked.* = .{ .chunk_len = 0 };
815 }
816 try out.flush();
817 },
818 .chunk_len => return out.flush(),
819 },
820 }
821 }
822
823 /// When using content-length, asserts that the amount of data sent matches
824 /// the value sent in the header, then flushes.
825 ///
826 /// When using transfer-encoding: chunked, writes the end-of-stream message
827 /// with empty trailers, then flushes the stream to the system. Asserts any
828 /// started chunk has been completely finished.
829 ///
830 /// Respects the value of `isEliding` to omit all data after the headers.
831 ///
832 /// See also:
833 /// * `endUnflushed`
834 /// * `endChunked`
835 pub fn end(w: *BodyWriter) Error!void {
836 try endUnflushed(w);
837 try w.http_protocol_output.flush();
838 }
839
840 /// When using content-length, asserts that the amount of data sent matches
841 /// the value sent in the header.
842 ///
843 /// Otherwise, transfer-encoding: chunked is being used, and it writes the
844 /// end-of-stream message with empty trailers.
845 ///
846 /// Respects the value of `isEliding` to omit all data after the headers.
847 ///
848 /// See also:
849 /// * `end`
850 /// * `endChunked`
851 pub fn endUnflushed(w: *BodyWriter) Error!void {
852 switch (w.state) {
853 .end => unreachable,
854 .content_length => |len| {
855 assert(len == 0); // Trips when end() called before all bytes written.
856 w.state = .end;
857 },
858 .none => {},
859 .chunked => return endChunkedUnflushed(w, .{}),
860 }
861 }
862
863 pub const EndChunkedOptions = struct {
864 trailers: []const Header = &.{},
865 };
866
867 /// Writes the end-of-stream message and any optional trailers, flushing
868 /// the underlying stream.
869 ///
870 /// Asserts that the BodyWriter is using transfer-encoding: chunked.
871 ///
872 /// Respects the value of `isEliding` to omit all data after the headers.
873 ///
874 /// See also:
875 /// * `endChunkedUnflushed`
876 /// * `end`
877 pub fn endChunked(w: *BodyWriter, options: EndChunkedOptions) Error!void {
878 try endChunkedUnflushed(w, options);
879 try w.http_protocol_output.flush();
880 }
881
882 /// Writes the end-of-stream message and any optional trailers.
883 ///
884 /// Does not flush.
885 ///
886 /// Asserts that the BodyWriter is using transfer-encoding: chunked.
887 ///
888 /// Respects the value of `isEliding` to omit all data after the headers.
889 ///
890 /// See also:
891 /// * `endChunked`
892 /// * `endUnflushed`
893 /// * `end`
894 pub fn endChunkedUnflushed(w: *BodyWriter, options: EndChunkedOptions) Error!void {
895 const chunked = &w.state.chunked;
896 if (w.isEliding()) {
897 w.state = .end;
898 return;
899 }
900 const bw = w.http_protocol_output;
901 switch (chunked.*) {
902 .offset => |offset| {
903 const chunk_len = bw.end - offset - chunk_header_template.len;
904 writeHex(bw.buffer[offset..][0..chunk_len_digits], chunk_len);
905 try bw.writeAll("\r\n");
906 },
907 .chunk_len => |chunk_len| switch (chunk_len) {
908 0 => {},
909 1 => try bw.writeByte('\n'),
910 2 => try bw.writeAll("\r\n"),
911 else => unreachable, // An earlier write call indicated more data would follow.
912 },
913 }
914 try bw.writeAll("0\r\n");
915 for (options.trailers) |trailer| {
916 try bw.writeAll(trailer.name);
917 try bw.writeAll(": ");
918 try bw.writeAll(trailer.value);
919 try bw.writeAll("\r\n");
920 }
921 try bw.writeAll("\r\n");
922 w.state = .end;
923 }
924
925 pub fn contentLengthDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
926 const bw: *BodyWriter = @fieldParentPtr("writer", w);
927 assert(!bw.isEliding());
928 const out = bw.http_protocol_output;
929 const n = try out.writeSplatHeader(w.buffered(), data, splat);
930 bw.state.content_length -= n;
931 return w.consume(n);
932 }
933
934 pub fn noneDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
935 const bw: *BodyWriter = @fieldParentPtr("writer", w);
936 assert(!bw.isEliding());
937 const out = bw.http_protocol_output;
938 const n = try out.writeSplatHeader(w.buffered(), data, splat);
939 return w.consume(n);
940 }
941
942 /// Returns `null` if size cannot be computed without making any syscalls.
943 pub fn noneSendFile(w: *Writer, file_reader: *File.Reader, limit: std.Io.Limit) Writer.FileError!usize {
944 const bw: *BodyWriter = @fieldParentPtr("writer", w);
945 assert(!bw.isEliding());
946 const out = bw.http_protocol_output;
947 const n = try out.sendFileHeader(w.buffered(), file_reader, limit);
948 return w.consume(n);
949 }
950
951 pub fn contentLengthSendFile(w: *Writer, file_reader: *File.Reader, limit: std.Io.Limit) Writer.FileError!usize {
952 const bw: *BodyWriter = @fieldParentPtr("writer", w);
953 assert(!bw.isEliding());
954 const out = bw.http_protocol_output;
955 const n = try out.sendFileHeader(w.buffered(), file_reader, limit);
956 bw.state.content_length -= n;
957 return w.consume(n);
958 }
959
960 pub fn chunkedSendFile(w: *Writer, file_reader: *File.Reader, limit: std.Io.Limit) Writer.FileError!usize {
961 const bw: *BodyWriter = @fieldParentPtr("writer", w);
962 assert(!bw.isEliding());
963 const data_len = Writer.countSendFileLowerBound(w.end, file_reader, limit) orelse {
964 // If the file size is unknown, we cannot lower to a `sendFile` since we would
965 // have to flush the chunk header before knowing the chunk length.
966 return error.Unimplemented;
967 };
968 const out = bw.http_protocol_output;
969 const chunked = &bw.state.chunked;
970 state: switch (chunked.*) {
971 .offset => |off| {
972 // TODO: is it better perf to read small files into the buffer?
973 const buffered_len = out.end - off - chunk_header_template.len;
974 const chunk_len = data_len + buffered_len;
975 writeHex(out.buffer[off..][0..chunk_len_digits], chunk_len);
976 const n = try out.sendFileHeader(w.buffered(), file_reader, limit);
977 chunked.* = .{ .chunk_len = data_len + 2 - n };
978 return w.consume(n);
979 },
980 .chunk_len => |chunk_len| l: switch (chunk_len) {
981 0 => {
982 const off = out.end;
983 const header_buf = try out.writableArray(chunk_header_template.len);
984 @memcpy(header_buf, chunk_header_template);
985 chunked.* = .{ .offset = off };
986 continue :state .{ .offset = off };
987 },
988 1 => {
989 try out.writeByte('\n');
990 chunked.chunk_len = 0;
991 continue :l 0;
992 },
993 2 => {
994 try out.writeByte('\r');
995 chunked.chunk_len = 1;
996 continue :l 1;
997 },
998 else => {
999 const new_limit = limit.min(.limited(chunk_len - 2));
1000 const n = try out.sendFileHeader(w.buffered(), file_reader, new_limit);
1001 chunked.chunk_len = chunk_len - n;
1002 return w.consume(n);
1003 },
1004 },
1005 }
1006 }
1007
1008 pub fn chunkedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
1009 const bw: *BodyWriter = @fieldParentPtr("writer", w);
1010 assert(!bw.isEliding());
1011 const out = bw.http_protocol_output;
1012 const data_len = w.end + Writer.countSplat(data, splat);
1013 const chunked = &bw.state.chunked;
1014 state: switch (chunked.*) {
1015 .offset => |offset| {
1016 if (out.unusedCapacityLen() >= data_len) {
1017 return w.consume(out.writeSplatHeader(w.buffered(), data, splat) catch unreachable);
1018 }
1019 const buffered_len = out.end - offset - chunk_header_template.len;
1020 const chunk_len = data_len + buffered_len;
1021 writeHex(out.buffer[offset..][0..chunk_len_digits], chunk_len);
1022 const n = try out.writeSplatHeader(w.buffered(), data, splat);
1023 chunked.* = .{ .chunk_len = data_len + 2 - n };
1024 return w.consume(n);
1025 },
1026 .chunk_len => |chunk_len| l: switch (chunk_len) {
1027 0 => {
1028 const offset = out.end;
1029 const header_buf = try out.writableArray(chunk_header_template.len);
1030 @memcpy(header_buf, chunk_header_template);
1031 chunked.* = .{ .offset = offset };
1032 continue :state .{ .offset = offset };
1033 },
1034 1 => {
1035 try out.writeByte('\n');
1036 chunked.chunk_len = 0;
1037 continue :l 0;
1038 },
1039 2 => {
1040 try out.writeByte('\r');
1041 chunked.chunk_len = 1;
1042 continue :l 1;
1043 },
1044 else => {
1045 const n = try out.writeSplatHeaderLimit(w.buffered(), data, splat, .limited(chunk_len - 2));
1046 chunked.chunk_len = chunk_len - n;
1047 return w.consume(n);
1048 },
1049 },
1050 }
1051 }
1052
1053 /// Writes an integer as base 16 to `buf`, right-aligned, assuming the
1054 /// buffer has already been filled with zeroes.
1055 fn writeHex(buf: []u8, x: usize) void {
1056 assert(std.mem.allEqual(u8, buf, '0'));
1057 const base = 16;
1058 var index: usize = buf.len;
1059 var a = x;
1060 while (a > 0) {
1061 const digit = a % base;
1062 index -= 1;
1063 buf[index] = std.fmt.digitToChar(@intCast(digit), .lower);
1064 a /= base;
1065 }
1066 }
1067};
1068
3181069test {
1070 _ = Server;
1071 _ = Status;
1072 _ = Method;
1073 _ = ChunkParser;
1074 _ = HeadParser;
1075
3191076 if (builtin.os.tag != .wasi) {
3201077 _ = Client;
321 _ = Method;
322 _ = Server;
323 _ = Status;
324 _ = HeadParser;
325 _ = ChunkParser;
326 _ = WebSocket;
3271078 _ = @import("http/test.zig");
3281079 }
3291080}
lib/std/http/ChunkParser.zig+3-3
......@@ -1,5 +1,8 @@
11//! Parser for transfer-encoding: chunked.
22
3const ChunkParser = @This();
4const std = @import("std");
5
36state: State,
47chunk_len: u64,
58
......@@ -97,9 +100,6 @@ pub fn feed(p: *ChunkParser, bytes: []const u8) usize {
97100 return bytes.len;
98101}
99102
100const ChunkParser = @This();
101const std = @import("std");
102
103103test feed {
104104 const testing = std.testing;
105105
lib/std/http/Client.zig+997-1011
......@@ -13,9 +13,10 @@ const net = std.net;
1313const Uri = std.Uri;
1414const Allocator = mem.Allocator;
1515const assert = std.debug.assert;
16const Writer = std.io.Writer;
17const Reader = std.io.Reader;
1618
1719const Client = @This();
18const proto = @import("protocol.zig");
1920
2021pub const disable_tls = std.options.http_disable_tls;
2122
......@@ -24,6 +25,12 @@ allocator: Allocator,
2425
2526ca_bundle: if (disable_tls) void else std.crypto.Certificate.Bundle = if (disable_tls) {} else .{},
2627ca_bundle_mutex: std.Thread.Mutex = .{},
28/// Used both for the reader and writer buffers.
29tls_buffer_size: if (disable_tls) u0 else usize = if (disable_tls) 0 else std.crypto.tls.Client.min_buffer_len,
30/// If non-null, ssl secrets are logged to a stream. Creating such a stream
31/// allows other processes with access to that stream to decrypt all
32/// traffic over connections created with this `Client`.
33ssl_key_log: ?*std.crypto.tls.Client.SslKeyLog = null,
2734
2835/// When this is `true`, the next time this client performs an HTTPS request,
2936/// it will first rescan the system for root certificates.
......@@ -31,6 +38,13 @@ next_https_rescan_certs: bool = true,
3138
3239/// The pool of connections that can be reused (and currently in use).
3340connection_pool: ConnectionPool = .{},
41/// Each `Connection` allocates this amount for the reader buffer.
42///
43/// If the entire HTTP header cannot fit in this amount of bytes,
44/// `error.HttpHeadersOversize` will be returned from `Request.wait`.
45read_buffer_size: usize = 4096,
46/// Each `Connection` allocates this amount for the writer buffer.
47write_buffer_size: usize = 1024,
3448
3549/// If populated, all http traffic travels through this third party.
3650/// This field cannot be modified while the client has active connections.
......@@ -41,7 +55,7 @@ http_proxy: ?*Proxy = null,
4155/// Pointer to externally-owned memory.
4256https_proxy: ?*Proxy = null,
4357
44/// A set of linked lists of connections that can be reused.
58/// A Least-Recently-Used cache of open connections to be reused.
4559pub const ConnectionPool = struct {
4660 mutex: std.Thread.Mutex = .{},
4761 /// Open connections that are currently in use.
......@@ -55,11 +69,13 @@ pub const ConnectionPool = struct {
5569 pub const Criteria = struct {
5670 host: []const u8,
5771 port: u16,
58 protocol: Connection.Protocol,
72 protocol: Protocol,
5973 };
6074
61 /// Finds and acquires a connection from the connection pool matching the criteria. This function is threadsafe.
75 /// Finds and acquires a connection from the connection pool matching the criteria.
6276 /// If no connection is found, null is returned.
77 ///
78 /// Threadsafe.
6379 pub fn findConnection(pool: *ConnectionPool, criteria: Criteria) ?*Connection {
6480 pool.mutex.lock();
6581 defer pool.mutex.unlock();
......@@ -71,7 +87,7 @@ pub const ConnectionPool = struct {
7187 if (connection.port != criteria.port) continue;
7288
7389 // Domain names are case-insensitive (RFC 5890, Section 2.3.2.4)
74 if (!std.ascii.eqlIgnoreCase(connection.host, criteria.host)) continue;
90 if (!std.ascii.eqlIgnoreCase(connection.host(), criteria.host)) continue;
7591
7692 pool.acquireUnsafe(connection);
7793 return connection;
......@@ -96,28 +112,25 @@ pub const ConnectionPool = struct {
96112 return pool.acquireUnsafe(connection);
97113 }
98114
99 /// Tries to release a connection back to the connection pool. This function is threadsafe.
115 /// Tries to release a connection back to the connection pool.
100116 /// If the connection is marked as closing, it will be closed instead.
101117 ///
102 /// The allocator must be the owner of all nodes in this pool.
103 /// The allocator must be the owner of all resources associated with the connection.
104 pub fn release(pool: *ConnectionPool, allocator: Allocator, connection: *Connection) void {
118 /// `allocator` must be the same one used to create `connection`.
119 ///
120 /// Threadsafe.
121 pub fn release(pool: *ConnectionPool, connection: *Connection) void {
105122 pool.mutex.lock();
106123 defer pool.mutex.unlock();
107124
108125 pool.used.remove(&connection.pool_node);
109126
110 if (connection.closing or pool.free_size == 0) {
111 connection.close(allocator);
112 return allocator.destroy(connection);
113 }
127 if (connection.closing or pool.free_size == 0) return connection.destroy();
114128
115129 if (pool.free_len >= pool.free_size) {
116130 const popped: *Connection = @fieldParentPtr("pool_node", pool.free.popFirst().?);
117131 pool.free_len -= 1;
118132
119 popped.close(allocator);
120 allocator.destroy(popped);
133 popped.destroy();
121134 }
122135
123136 if (connection.proxied) {
......@@ -138,9 +151,11 @@ pub const ConnectionPool = struct {
138151 pool.used.append(&connection.pool_node);
139152 }
140153
141 /// Resizes the connection pool. This function is threadsafe.
154 /// Resizes the connection pool.
142155 ///
143156 /// If the new size is smaller than the current size, then idle connections will be closed until the pool is the new size.
157 ///
158 /// Threadsafe.
144159 pub fn resize(pool: *ConnectionPool, allocator: Allocator, new_size: usize) void {
145160 pool.mutex.lock();
146161 defer pool.mutex.unlock();
......@@ -158,538 +173,586 @@ pub const ConnectionPool = struct {
158173 pool.free_size = new_size;
159174 }
160175
161 /// Frees the connection pool and closes all connections within. This function is threadsafe.
176 /// Frees the connection pool and closes all connections within.
162177 ///
163178 /// All future operations on the connection pool will deadlock.
164 pub fn deinit(pool: *ConnectionPool, allocator: Allocator) void {
179 ///
180 /// Threadsafe.
181 pub fn deinit(pool: *ConnectionPool) void {
165182 pool.mutex.lock();
166183
167184 var next = pool.free.first;
168185 while (next) |node| {
169186 const connection: *Connection = @fieldParentPtr("pool_node", node);
170187 next = node.next;
171 connection.close(allocator);
172 allocator.destroy(connection);
188 connection.destroy();
173189 }
174190
175191 next = pool.used.first;
176192 while (next) |node| {
177193 const connection: *Connection = @fieldParentPtr("pool_node", node);
178194 next = node.next;
179 connection.close(allocator);
180 allocator.destroy(node);
195 connection.destroy();
181196 }
182197
183198 pool.* = undefined;
184199 }
185200};
186201
187/// An interface to either a plain or TLS connection.
188pub const Connection = struct {
189 stream: net.Stream,
190 /// undefined unless protocol is tls.
191 tls_client: if (!disable_tls) *std.crypto.tls.Client else void,
192
193 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.
194 pool_node: std.DoublyLinkedList.Node,
195
196 /// The protocol that this connection is using.
197 protocol: Protocol,
198
199 /// The host that this connection is connected to.
200 host: []u8,
201
202 /// The port that this connection is connected to.
203 port: u16,
204
205 /// Whether this connection is proxied and is not directly connected.
206 proxied: bool = false,
207
208 /// Whether this connection is closing when we're done with it.
209 closing: bool = false,
210
211 read_start: BufferSize = 0,
212 read_end: BufferSize = 0,
213 write_end: BufferSize = 0,
214 read_buf: [buffer_size]u8 = undefined,
215 write_buf: [buffer_size]u8 = undefined,
216
217 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
218 const BufferSize = std.math.IntFittingRange(0, buffer_size);
219
220 pub const Protocol = enum { plain, tls };
221
222 pub fn readvDirectTls(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize {
223 return conn.tls_client.readv(conn.stream, buffers) catch |err| {
224 // https://github.com/ziglang/zig/issues/2473
225 if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert;
226
227 switch (err) {
228 error.TlsConnectionTruncated, error.TlsRecordOverflow, error.TlsDecodeError, error.TlsBadRecordMac, error.TlsBadLength, error.TlsIllegalParameter, error.TlsUnexpectedMessage => return error.TlsFailure,
229 error.ConnectionTimedOut => return error.ConnectionTimedOut,
230 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
231 else => return error.UnexpectedReadFailure,
232 }
233 };
234 }
235
236 pub fn readvDirect(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize {
237 if (conn.protocol == .tls) {
238 if (disable_tls) unreachable;
202pub const Protocol = enum {
203 plain,
204 tls,
239205
240 return conn.readvDirectTls(buffers);
241 }
242
243 return conn.stream.readv(buffers) catch |err| switch (err) {
244 error.ConnectionTimedOut => return error.ConnectionTimedOut,
245 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
246 else => return error.UnexpectedReadFailure,
206 fn port(protocol: Protocol) u16 {
207 return switch (protocol) {
208 .plain => 80,
209 .tls => 443,
247210 };
248211 }
249212
250 /// Refills the read buffer with data from the connection.
251 pub fn fill(conn: *Connection) ReadError!void {
252 if (conn.read_end != conn.read_start) return;
253
254 var iovecs = [1]std.posix.iovec{
255 .{ .base = &conn.read_buf, .len = conn.read_buf.len },
256 };
257 const nread = try conn.readvDirect(&iovecs);
258 if (nread == 0) return error.EndOfStream;
259 conn.read_start = 0;
260 conn.read_end = @intCast(nread);
213 pub fn fromScheme(scheme: []const u8) ?Protocol {
214 const protocol_map = std.StaticStringMap(Protocol).initComptime(.{
215 .{ "http", .plain },
216 .{ "ws", .plain },
217 .{ "https", .tls },
218 .{ "wss", .tls },
219 });
220 return protocol_map.get(scheme);
261221 }
262222
263 /// Returns the current slice of buffered data.
264 pub fn peek(conn: *Connection) []const u8 {
265 return conn.read_buf[conn.read_start..conn.read_end];
223 pub fn fromUri(uri: Uri) ?Protocol {
224 return fromScheme(uri.scheme);
266225 }
226};
267227
268 /// Discards the given number of bytes from the read buffer.
269 pub fn drop(conn: *Connection, num: BufferSize) void {
270 conn.read_start += num;
271 }
228pub const Connection = struct {
229 client: *Client,
230 stream_writer: net.Stream.Writer,
231 stream_reader: net.Stream.Reader,
232 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.
233 pool_node: std.DoublyLinkedList.Node,
234 port: u16,
235 host_len: u8,
236 proxied: bool,
237 closing: bool,
238 protocol: Protocol,
272239
273 /// Reads data from the connection into the given buffer.
274 pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {
275 const available_read = conn.read_end - conn.read_start;
276 const available_buffer = buffer.len;
240 const Plain = struct {
241 connection: Connection,
242
243 fn create(
244 client: *Client,
245 remote_host: []const u8,
246 port: u16,
247 stream: net.Stream,
248 ) error{OutOfMemory}!*Plain {
249 const gpa = client.allocator;
250 const alloc_len = allocLen(client, remote_host.len);
251 const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len);
252 errdefer gpa.free(base);
253 const host_buffer = base[@sizeOf(Plain)..][0..remote_host.len];
254 const socket_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.read_buffer_size];
255 const socket_write_buffer = socket_read_buffer.ptr[socket_read_buffer.len..][0..client.write_buffer_size];
256 assert(base.ptr + alloc_len == socket_write_buffer.ptr + socket_write_buffer.len);
257 @memcpy(host_buffer, remote_host);
258 const plain: *Plain = @ptrCast(base);
259 plain.* = .{
260 .connection = .{
261 .client = client,
262 .stream_writer = stream.writer(socket_write_buffer),
263 .stream_reader = stream.reader(socket_read_buffer),
264 .pool_node = .{},
265 .port = port,
266 .host_len = @intCast(remote_host.len),
267 .proxied = false,
268 .closing = false,
269 .protocol = .plain,
270 },
271 };
272 return plain;
273 }
277274
278 if (available_read > available_buffer) { // partially read buffered data
279 @memcpy(buffer[0..available_buffer], conn.read_buf[conn.read_start..conn.read_end][0..available_buffer]);
280 conn.read_start += @intCast(available_buffer);
275 fn destroy(plain: *Plain) void {
276 const c = &plain.connection;
277 const gpa = c.client.allocator;
278 const base: [*]align(@alignOf(Plain)) u8 = @ptrCast(plain);
279 gpa.free(base[0..allocLen(c.client, c.host_len)]);
280 }
281281
282 return available_buffer;
283 } else if (available_read > 0) { // fully read buffered data
284 @memcpy(buffer[0..available_read], conn.read_buf[conn.read_start..conn.read_end]);
285 conn.read_start += available_read;
282 fn allocLen(client: *Client, host_len: usize) usize {
283 return @sizeOf(Plain) + host_len + client.read_buffer_size + client.write_buffer_size;
284 }
286285
287 return available_read;
286 fn host(plain: *Plain) []u8 {
287 const base: [*]u8 = @ptrCast(plain);
288 return base[@sizeOf(Plain)..][0..plain.connection.host_len];
288289 }
290 };
289291
290 var iovecs = [2]std.posix.iovec{
291 .{ .base = buffer.ptr, .len = buffer.len },
292 .{ .base = &conn.read_buf, .len = conn.read_buf.len },
293 };
294 const nread = try conn.readvDirect(&iovecs);
292 const Tls = struct {
293 client: std.crypto.tls.Client,
294 connection: Connection,
295
296 fn create(
297 client: *Client,
298 remote_host: []const u8,
299 port: u16,
300 stream: net.Stream,
301 ) error{ OutOfMemory, TlsInitializationFailed }!*Tls {
302 const gpa = client.allocator;
303 const alloc_len = allocLen(client, remote_host.len);
304 const base = try gpa.alignedAlloc(u8, .of(Tls), alloc_len);
305 errdefer gpa.free(base);
306 const host_buffer = base[@sizeOf(Tls)..][0..remote_host.len];
307 const tls_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.tls_buffer_size];
308 const tls_write_buffer = tls_read_buffer.ptr[tls_read_buffer.len..][0..client.tls_buffer_size];
309 const socket_write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size];
310 assert(base.ptr + alloc_len == socket_write_buffer.ptr + socket_write_buffer.len);
311 @memcpy(host_buffer, remote_host);
312 const tls: *Tls = @ptrCast(base);
313 tls.* = .{
314 .connection = .{
315 .client = client,
316 .stream_writer = stream.writer(socket_write_buffer),
317 .stream_reader = stream.reader(&.{}),
318 .pool_node = .{},
319 .port = port,
320 .host_len = @intCast(remote_host.len),
321 .proxied = false,
322 .closing = false,
323 .protocol = .tls,
324 },
325 // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true
326 .client = std.crypto.tls.Client.init(
327 tls.connection.stream_reader.interface(),
328 &tls.connection.stream_writer.interface,
329 .{
330 .host = .{ .explicit = remote_host },
331 .ca = .{ .bundle = client.ca_bundle },
332 .ssl_key_log = client.ssl_key_log,
333 .read_buffer = tls_read_buffer,
334 .write_buffer = tls_write_buffer,
335 // This is appropriate for HTTPS because the HTTP headers contain
336 // the content length which is used to detect truncation attacks.
337 .allow_truncation_attacks = true,
338 },
339 ) catch return error.TlsInitializationFailed,
340 };
341 return tls;
342 }
295343
296 if (nread > buffer.len) {
297 conn.read_start = 0;
298 conn.read_end = @intCast(nread - buffer.len);
299 return buffer.len;
344 fn destroy(tls: *Tls) void {
345 const c = &tls.connection;
346 const gpa = c.client.allocator;
347 const base: [*]align(@alignOf(Tls)) u8 = @ptrCast(tls);
348 gpa.free(base[0..allocLen(c.client, c.host_len)]);
300349 }
301350
302 return nread;
303 }
351 fn allocLen(client: *Client, host_len: usize) usize {
352 return @sizeOf(Tls) + host_len + client.tls_buffer_size + client.tls_buffer_size + client.write_buffer_size;
353 }
304354
305 pub const ReadError = error{
306 TlsFailure,
307 TlsAlert,
308 ConnectionTimedOut,
309 ConnectionResetByPeer,
310 UnexpectedReadFailure,
311 EndOfStream,
355 fn host(tls: *Tls) []u8 {
356 const base: [*]u8 = @ptrCast(tls);
357 return base[@sizeOf(Tls)..][0..tls.connection.host_len];
358 }
312359 };
313360
314 pub const Reader = std.io.GenericReader(*Connection, ReadError, read);
315
316 pub fn reader(conn: *Connection) Reader {
317 return Reader{ .context = conn };
361 fn getStream(c: *Connection) net.Stream {
362 return c.stream_reader.getStream();
318363 }
319364
320 pub fn writeAllDirectTls(conn: *Connection, buffer: []const u8) WriteError!void {
321 return conn.tls_client.writeAll(conn.stream, buffer) catch |err| switch (err) {
322 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
323 else => return error.UnexpectedWriteFailure,
324 };
325 }
326
327 pub fn writeAllDirect(conn: *Connection, buffer: []const u8) WriteError!void {
328 if (conn.protocol == .tls) {
329 if (disable_tls) unreachable;
330
331 return conn.writeAllDirectTls(buffer);
332 }
333
334 return conn.stream.writeAll(buffer) catch |err| switch (err) {
335 error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
336 else => return error.UnexpectedWriteFailure,
365 fn host(c: *Connection) []u8 {
366 return switch (c.protocol) {
367 .tls => {
368 if (disable_tls) unreachable;
369 const tls: *Tls = @fieldParentPtr("connection", c);
370 return tls.host();
371 },
372 .plain => {
373 const plain: *Plain = @fieldParentPtr("connection", c);
374 return plain.host();
375 },
337376 };
338377 }
339378
340 /// Writes the given buffer to the connection.
341 pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize {
342 if (conn.write_buf.len - conn.write_end < buffer.len) {
343 try conn.flush();
344
345 if (buffer.len > conn.write_buf.len) {
346 try conn.writeAllDirect(buffer);
347 return buffer.len;
348 }
379 /// If this is called without calling `flush` or `end`, data will be
380 /// dropped unsent.
381 pub fn destroy(c: *Connection) void {
382 c.getStream().close();
383 switch (c.protocol) {
384 .tls => {
385 if (disable_tls) unreachable;
386 const tls: *Tls = @fieldParentPtr("connection", c);
387 tls.destroy();
388 },
389 .plain => {
390 const plain: *Plain = @fieldParentPtr("connection", c);
391 plain.destroy();
392 },
349393 }
350
351 @memcpy(conn.write_buf[conn.write_end..][0..buffer.len], buffer);
352 conn.write_end += @intCast(buffer.len);
353
354 return buffer.len;
355394 }
356395
357 /// Returns a buffer to be filled with exactly len bytes to write to the connection.
358 pub fn allocWriteBuffer(conn: *Connection, len: BufferSize) WriteError![]u8 {
359 if (conn.write_buf.len - conn.write_end < len) try conn.flush();
360 defer conn.write_end += len;
361 return conn.write_buf[conn.write_end..][0..len];
396 /// HTTP protocol from client to server.
397 /// This either goes directly to `stream_writer`, or to a TLS client.
398 pub fn writer(c: *Connection) *Writer {
399 return switch (c.protocol) {
400 .tls => {
401 if (disable_tls) unreachable;
402 const tls: *Tls = @fieldParentPtr("connection", c);
403 return &tls.client.writer;
404 },
405 .plain => &c.stream_writer.interface,
406 };
362407 }
363408
364 /// Flushes the write buffer to the connection.
365 pub fn flush(conn: *Connection) WriteError!void {
366 if (conn.write_end == 0) return;
367
368 try conn.writeAllDirect(conn.write_buf[0..conn.write_end]);
369 conn.write_end = 0;
409 /// HTTP protocol from server to client.
410 /// This either comes directly from `stream_reader`, or from a TLS client.
411 pub fn reader(c: *Connection) *Reader {
412 return switch (c.protocol) {
413 .tls => {
414 if (disable_tls) unreachable;
415 const tls: *Tls = @fieldParentPtr("connection", c);
416 return &tls.client.reader;
417 },
418 .plain => c.stream_reader.interface(),
419 };
370420 }
371421
372 pub const WriteError = error{
373 ConnectionResetByPeer,
374 UnexpectedWriteFailure,
375 };
376
377 pub const Writer = std.io.GenericWriter(*Connection, WriteError, write);
378
379 pub fn writer(conn: *Connection) Writer {
380 return Writer{ .context = conn };
422 pub fn flush(c: *Connection) Writer.Error!void {
423 if (c.protocol == .tls) {
424 if (disable_tls) unreachable;
425 const tls: *Tls = @fieldParentPtr("connection", c);
426 try tls.client.writer.flush();
427 }
428 try c.stream_writer.interface.flush();
381429 }
382430
383 /// Closes the connection.
384 pub fn close(conn: *Connection, allocator: Allocator) void {
385 if (conn.protocol == .tls) {
431 /// If the connection is a TLS connection, sends the close_notify alert.
432 ///
433 /// Flushes all buffers.
434 pub fn end(c: *Connection) Writer.Error!void {
435 if (c.protocol == .tls) {
386436 if (disable_tls) unreachable;
387
388 // try to cleanly close the TLS connection, for any server that cares.
389 _ = conn.tls_client.writeEnd(conn.stream, "", true) catch {};
390 if (conn.tls_client.ssl_key_log) |key_log| key_log.file.close();
391 allocator.destroy(conn.tls_client);
437 const tls: *Tls = @fieldParentPtr("connection", c);
438 try tls.client.end();
439 try tls.client.writer.flush();
392440 }
393
394 conn.stream.close();
395 allocator.free(conn.host);
441 try c.stream_writer.interface.flush();
396442 }
397443};
398444
399/// The mode of transport for requests.
400pub const RequestTransfer = union(enum) {
401 content_length: u64,
402 chunked: void,
403 none: void,
404};
405
406/// The decompressor for response messages.
407pub const Compression = union(enum) {
408 //deflate: std.compress.flate.Decompress,
409 //gzip: std.compress.flate.Decompress,
410 // https://github.com/ziglang/zig/issues/18937
411 //zstd: ZstdDecompressor,
412 none: void,
413};
414
415/// A HTTP response originating from a server.
416445pub const Response = struct {
417 version: http.Version,
418 status: http.Status,
419 reason: []const u8,
420
421 /// Points into the user-provided `server_header_buffer`.
422 location: ?[]const u8 = null,
423 /// Points into the user-provided `server_header_buffer`.
424 content_type: ?[]const u8 = null,
425 /// Points into the user-provided `server_header_buffer`.
426 content_disposition: ?[]const u8 = null,
427
428 keep_alive: bool,
429
430 /// If present, the number of bytes in the response body.
431 content_length: ?u64 = null,
446 request: *Request,
447 /// Pointers in this struct are invalidated with the next call to
448 /// `receiveHead`.
449 head: Head,
450
451 pub const Head = struct {
452 bytes: []const u8,
453 version: http.Version,
454 status: http.Status,
455 reason: []const u8,
456 location: ?[]const u8 = null,
457 content_type: ?[]const u8 = null,
458 content_disposition: ?[]const u8 = null,
459
460 keep_alive: bool,
461
462 /// If present, the number of bytes in the response body.
463 content_length: ?u64 = null,
464
465 transfer_encoding: http.TransferEncoding = .none,
466 content_encoding: http.ContentEncoding = .identity,
467
468 pub const ParseError = error{
469 HttpConnectionHeaderUnsupported,
470 HttpContentEncodingUnsupported,
471 HttpHeaderContinuationsUnsupported,
472 HttpHeadersInvalid,
473 HttpTransferEncodingUnsupported,
474 InvalidContentLength,
475 };
432476
433 /// If present, the transfer encoding of the response body, otherwise none.
434 transfer_encoding: http.TransferEncoding = .none,
477 pub fn parse(bytes: []const u8) ParseError!Head {
478 var res: Head = .{
479 .bytes = bytes,
480 .status = undefined,
481 .reason = undefined,
482 .version = undefined,
483 .keep_alive = false,
484 };
485 var it = mem.splitSequence(u8, bytes, "\r\n");
435486
436 /// If present, the compression of the response body, otherwise identity (no compression).
437 transfer_compression: http.ContentEncoding = .identity,
487 const first_line = it.next().?;
488 if (first_line.len < 12) {
489 return error.HttpHeadersInvalid;
490 }
438491
439 parser: proto.HeadersParser,
440 compression: Compression = .none,
492 const version: http.Version = switch (int64(first_line[0..8])) {
493 int64("HTTP/1.0") => .@"HTTP/1.0",
494 int64("HTTP/1.1") => .@"HTTP/1.1",
495 else => return error.HttpHeadersInvalid,
496 };
497 if (first_line[8] != ' ') return error.HttpHeadersInvalid;
498 const status: http.Status = @enumFromInt(parseInt3(first_line[9..12]));
499 const reason = mem.trimLeft(u8, first_line[12..], " ");
500
501 res.version = version;
502 res.status = status;
503 res.reason = reason;
504 res.keep_alive = switch (version) {
505 .@"HTTP/1.0" => false,
506 .@"HTTP/1.1" => true,
507 };
441508
442 /// Whether the response body should be skipped. Any data read from the
443 /// response body will be discarded.
444 skip: bool = false,
509 while (it.next()) |line| {
510 if (line.len == 0) return res;
511 switch (line[0]) {
512 ' ', '\t' => return error.HttpHeaderContinuationsUnsupported,
513 else => {},
514 }
445515
446 pub const ParseError = error{
447 HttpHeadersInvalid,
448 HttpHeaderContinuationsUnsupported,
449 HttpTransferEncodingUnsupported,
450 HttpConnectionHeaderUnsupported,
451 InvalidContentLength,
452 CompressionUnsupported,
453 };
516 var line_it = mem.splitScalar(u8, line, ':');
517 const header_name = line_it.next().?;
518 const header_value = mem.trim(u8, line_it.rest(), " \t");
519 if (header_name.len == 0) return error.HttpHeadersInvalid;
520
521 if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
522 res.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close");
523 } else if (std.ascii.eqlIgnoreCase(header_name, "content-type")) {
524 res.content_type = header_value;
525 } else if (std.ascii.eqlIgnoreCase(header_name, "location")) {
526 res.location = header_value;
527 } else if (std.ascii.eqlIgnoreCase(header_name, "content-disposition")) {
528 res.content_disposition = header_value;
529 } else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) {
530 // Transfer-Encoding: second, first
531 // Transfer-Encoding: deflate, chunked
532 var iter = mem.splitBackwardsScalar(u8, header_value, ',');
533
534 const first = iter.first();
535 const trimmed_first = mem.trim(u8, first, " ");
536
537 var next: ?[]const u8 = first;
538 if (std.meta.stringToEnum(http.TransferEncoding, trimmed_first)) |transfer| {
539 if (res.transfer_encoding != .none) return error.HttpHeadersInvalid; // we already have a transfer encoding
540 res.transfer_encoding = transfer;
541
542 next = iter.next();
543 }
454544
455 pub fn parse(res: *Response, bytes: []const u8) ParseError!void {
456 var it = mem.splitSequence(u8, bytes, "\r\n");
545 if (next) |second| {
546 const trimmed_second = mem.trim(u8, second, " ");
457547
458 const first_line = it.next().?;
459 if (first_line.len < 12) {
460 return error.HttpHeadersInvalid;
461 }
548 if (http.ContentEncoding.fromString(trimmed_second)) |transfer| {
549 if (res.content_encoding != .identity) return error.HttpHeadersInvalid; // double compression is not supported
550 res.content_encoding = transfer;
551 } else {
552 return error.HttpTransferEncodingUnsupported;
553 }
554 }
462555
463 const version: http.Version = switch (int64(first_line[0..8])) {
464 int64("HTTP/1.0") => .@"HTTP/1.0",
465 int64("HTTP/1.1") => .@"HTTP/1.1",
466 else => return error.HttpHeadersInvalid,
467 };
468 if (first_line[8] != ' ') return error.HttpHeadersInvalid;
469 const status: http.Status = @enumFromInt(parseInt3(first_line[9..12]));
470 const reason = mem.trimStart(u8, first_line[12..], " ");
471
472 res.version = version;
473 res.status = status;
474 res.reason = reason;
475 res.keep_alive = switch (version) {
476 .@"HTTP/1.0" => false,
477 .@"HTTP/1.1" => true,
478 };
556 if (iter.next()) |_| return error.HttpTransferEncodingUnsupported;
557 } else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) {
558 const content_length = std.fmt.parseInt(u64, header_value, 10) catch return error.InvalidContentLength;
479559
480 while (it.next()) |line| {
481 if (line.len == 0) return;
482 switch (line[0]) {
483 ' ', '\t' => return error.HttpHeaderContinuationsUnsupported,
484 else => {},
485 }
560 if (res.content_length != null and res.content_length != content_length) return error.HttpHeadersInvalid;
486561
487 var line_it = mem.splitScalar(u8, line, ':');
488 const header_name = line_it.next().?;
489 const header_value = mem.trim(u8, line_it.rest(), " \t");
490 if (header_name.len == 0) return error.HttpHeadersInvalid;
491
492 if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
493 res.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close");
494 } else if (std.ascii.eqlIgnoreCase(header_name, "content-type")) {
495 res.content_type = header_value;
496 } else if (std.ascii.eqlIgnoreCase(header_name, "location")) {
497 res.location = header_value;
498 } else if (std.ascii.eqlIgnoreCase(header_name, "content-disposition")) {
499 res.content_disposition = header_value;
500 } else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) {
501 // Transfer-Encoding: second, first
502 // Transfer-Encoding: deflate, chunked
503 var iter = mem.splitBackwardsScalar(u8, header_value, ',');
504
505 const first = iter.first();
506 const trimmed_first = mem.trim(u8, first, " ");
507
508 var next: ?[]const u8 = first;
509 if (std.meta.stringToEnum(http.TransferEncoding, trimmed_first)) |transfer| {
510 if (res.transfer_encoding != .none) return error.HttpHeadersInvalid; // we already have a transfer encoding
511 res.transfer_encoding = transfer;
512
513 next = iter.next();
514 }
562 res.content_length = content_length;
563 } else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) {
564 if (res.content_encoding != .identity) return error.HttpHeadersInvalid;
515565
516 if (next) |second| {
517 const trimmed_second = mem.trim(u8, second, " ");
566 const trimmed = mem.trim(u8, header_value, " ");
518567
519 if (std.meta.stringToEnum(http.ContentEncoding, trimmed_second)) |transfer| {
520 if (res.transfer_compression != .identity) return error.HttpHeadersInvalid; // double compression is not supported
521 res.transfer_compression = transfer;
568 if (http.ContentEncoding.fromString(trimmed)) |ce| {
569 res.content_encoding = ce;
522570 } else {
523 return error.HttpTransferEncodingUnsupported;
571 return error.HttpContentEncodingUnsupported;
524572 }
525573 }
526
527 if (iter.next()) |_| return error.HttpTransferEncodingUnsupported;
528 } else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) {
529 const content_length = std.fmt.parseInt(u64, header_value, 10) catch return error.InvalidContentLength;
530
531 if (res.content_length != null and res.content_length != content_length) return error.HttpHeadersInvalid;
532
533 res.content_length = content_length;
534 } else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) {
535 if (res.transfer_compression != .identity) return error.HttpHeadersInvalid;
536
537 const trimmed = mem.trim(u8, header_value, " ");
538
539 if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
540 res.transfer_compression = ce;
541 } else {
542 return error.HttpTransferEncodingUnsupported;
543 }
544574 }
575 return error.HttpHeadersInvalid; // missing empty line
545576 }
546 return error.HttpHeadersInvalid; // missing empty line
547 }
548
549 test parse {
550 const response_bytes = "HTTP/1.1 200 OK\r\n" ++
551 "LOcation:url\r\n" ++
552 "content-tYpe: text/plain\r\n" ++
553 "content-disposition:attachment; filename=example.txt \r\n" ++
554 "content-Length:10\r\n" ++
555 "TRansfer-encoding:\tdeflate, chunked \r\n" ++
556 "connectioN:\t keep-alive \r\n\r\n";
557
558 var header_buffer: [1024]u8 = undefined;
559 var res = Response{
560 .status = undefined,
561 .reason = undefined,
562 .version = undefined,
563 .keep_alive = false,
564 .parser = .init(&header_buffer),
565 };
566577
567 @memcpy(header_buffer[0..response_bytes.len], response_bytes);
568 res.parser.header_bytes_len = response_bytes.len;
578 test parse {
579 const response_bytes = "HTTP/1.1 200 OK\r\n" ++
580 "LOcation:url\r\n" ++
581 "content-tYpe: text/plain\r\n" ++
582 "content-disposition:attachment; filename=example.txt \r\n" ++
583 "content-Length:10\r\n" ++
584 "TRansfer-encoding:\tdeflate, chunked \r\n" ++
585 "connectioN:\t keep-alive \r\n\r\n";
586
587 const head = try Head.parse(response_bytes);
588
589 try testing.expectEqual(.@"HTTP/1.1", head.version);
590 try testing.expectEqualStrings("OK", head.reason);
591 try testing.expectEqual(.ok, head.status);
592
593 try testing.expectEqualStrings("url", head.location.?);
594 try testing.expectEqualStrings("text/plain", head.content_type.?);
595 try testing.expectEqualStrings("attachment; filename=example.txt", head.content_disposition.?);
596
597 try testing.expectEqual(true, head.keep_alive);
598 try testing.expectEqual(10, head.content_length.?);
599 try testing.expectEqual(.chunked, head.transfer_encoding);
600 try testing.expectEqual(.deflate, head.content_encoding);
601 }
569602
570 try res.parse(response_bytes);
603 pub fn iterateHeaders(h: Head) http.HeaderIterator {
604 return .init(h.bytes);
605 }
571606
572 try testing.expectEqual(.@"HTTP/1.1", res.version);
573 try testing.expectEqualStrings("OK", res.reason);
574 try testing.expectEqual(.ok, res.status);
607 test iterateHeaders {
608 const response_bytes = "HTTP/1.1 200 OK\r\n" ++
609 "LOcation:url\r\n" ++
610 "content-tYpe: text/plain\r\n" ++
611 "content-disposition:attachment; filename=example.txt \r\n" ++
612 "content-Length:10\r\n" ++
613 "TRansfer-encoding:\tdeflate, chunked \r\n" ++
614 "connectioN:\t keep-alive \r\n\r\n";
615
616 const head = try Head.parse(response_bytes);
617 var it = head.iterateHeaders();
618 {
619 const header = it.next().?;
620 try testing.expectEqualStrings("LOcation", header.name);
621 try testing.expectEqualStrings("url", header.value);
622 try testing.expect(!it.is_trailer);
623 }
624 {
625 const header = it.next().?;
626 try testing.expectEqualStrings("content-tYpe", header.name);
627 try testing.expectEqualStrings("text/plain", header.value);
628 try testing.expect(!it.is_trailer);
629 }
630 {
631 const header = it.next().?;
632 try testing.expectEqualStrings("content-disposition", header.name);
633 try testing.expectEqualStrings("attachment; filename=example.txt", header.value);
634 try testing.expect(!it.is_trailer);
635 }
636 {
637 const header = it.next().?;
638 try testing.expectEqualStrings("content-Length", header.name);
639 try testing.expectEqualStrings("10", header.value);
640 try testing.expect(!it.is_trailer);
641 }
642 {
643 const header = it.next().?;
644 try testing.expectEqualStrings("TRansfer-encoding", header.name);
645 try testing.expectEqualStrings("deflate, chunked", header.value);
646 try testing.expect(!it.is_trailer);
647 }
648 {
649 const header = it.next().?;
650 try testing.expectEqualStrings("connectioN", header.name);
651 try testing.expectEqualStrings("keep-alive", header.value);
652 try testing.expect(!it.is_trailer);
653 }
654 try testing.expectEqual(null, it.next());
655 }
575656
576 try testing.expectEqualStrings("url", res.location.?);
577 try testing.expectEqualStrings("text/plain", res.content_type.?);
578 try testing.expectEqualStrings("attachment; filename=example.txt", res.content_disposition.?);
657 inline fn int64(array: *const [8]u8) u64 {
658 return @bitCast(array.*);
659 }
579660
580 try testing.expectEqual(true, res.keep_alive);
581 try testing.expectEqual(10, res.content_length.?);
582 try testing.expectEqual(.chunked, res.transfer_encoding);
583 try testing.expectEqual(.deflate, res.transfer_compression);
584 }
661 fn parseInt3(text: *const [3]u8) u10 {
662 const nnn: @Vector(3, u8) = text.*;
663 const zero: @Vector(3, u8) = .{ '0', '0', '0' };
664 const mmm: @Vector(3, u10) = .{ 100, 10, 1 };
665 return @reduce(.Add, (nnn -% zero) *% mmm);
666 }
585667
586 inline fn int64(array: *const [8]u8) u64 {
587 return @bitCast(array.*);
588 }
668 test parseInt3 {
669 const expectEqual = testing.expectEqual;
670 try expectEqual(@as(u10, 0), parseInt3("000"));
671 try expectEqual(@as(u10, 418), parseInt3("418"));
672 try expectEqual(@as(u10, 999), parseInt3("999"));
673 }
674 };
589675
590 fn parseInt3(text: *const [3]u8) u10 {
591 const nnn: @Vector(3, u8) = text.*;
592 const zero: @Vector(3, u8) = .{ '0', '0', '0' };
593 const mmm: @Vector(3, u10) = .{ 100, 10, 1 };
594 return @reduce(.Add, (nnn -% zero) *% mmm);
676 /// If compressed body has been negotiated this will return compressed bytes.
677 ///
678 /// If the returned `Reader` returns `error.ReadFailed` the error is
679 /// available via `bodyErr`.
680 ///
681 /// Asserts that this function is only called once.
682 ///
683 /// See also:
684 /// * `readerDecompressing`
685 pub fn reader(response: *Response, buffer: []u8) *Reader {
686 const req = response.request;
687 if (!req.method.responseHasBody()) return .ending;
688 const head = &response.head;
689 return req.reader.bodyReader(buffer, head.transfer_encoding, head.content_length);
595690 }
596691
597 test parseInt3 {
598 const expectEqual = testing.expectEqual;
599 try expectEqual(@as(u10, 0), parseInt3("000"));
600 try expectEqual(@as(u10, 418), parseInt3("418"));
601 try expectEqual(@as(u10, 999), parseInt3("999"));
692 /// If compressed body has been negotiated this will return decompressed bytes.
693 ///
694 /// If the returned `Reader` returns `error.ReadFailed` the error is
695 /// available via `bodyErr`.
696 ///
697 /// Asserts that this function is only called once.
698 ///
699 /// See also:
700 /// * `reader`
701 pub fn readerDecompressing(
702 response: *Response,
703 decompressor: *http.Decompressor,
704 decompression_buffer: []u8,
705 ) *Reader {
706 const head = &response.head;
707 return response.request.reader.bodyReaderDecompressing(
708 head.transfer_encoding,
709 head.content_length,
710 head.content_encoding,
711 decompressor,
712 decompression_buffer,
713 );
602714 }
603715
604 pub fn iterateHeaders(r: Response) http.HeaderIterator {
605 return .init(r.parser.get());
716 /// After receiving `error.ReadFailed` from the `Reader` returned by
717 /// `reader` or `readerDecompressing`, this function accesses the
718 /// more specific error code.
719 pub fn bodyErr(response: *const Response) ?http.Reader.BodyError {
720 return response.request.reader.body_err;
606721 }
607722
608 test iterateHeaders {
609 const response_bytes = "HTTP/1.1 200 OK\r\n" ++
610 "LOcation:url\r\n" ++
611 "content-tYpe: text/plain\r\n" ++
612 "content-disposition:attachment; filename=example.txt \r\n" ++
613 "content-Length:10\r\n" ++
614 "TRansfer-encoding:\tdeflate, chunked \r\n" ++
615 "connectioN:\t keep-alive \r\n\r\n";
616
617 var header_buffer: [1024]u8 = undefined;
618 var res = Response{
619 .status = undefined,
620 .reason = undefined,
621 .version = undefined,
622 .keep_alive = false,
623 .parser = .init(&header_buffer),
723 pub fn iterateTrailers(response: *const Response) http.HeaderIterator {
724 const r = &response.request.reader;
725 assert(r.state == .ready);
726 return .{
727 .bytes = r.trailers,
728 .index = 0,
729 .is_trailer = true,
624730 };
625
626 @memcpy(header_buffer[0..response_bytes.len], response_bytes);
627 res.parser.header_bytes_len = response_bytes.len;
628
629 var it = res.iterateHeaders();
630 {
631 const header = it.next().?;
632 try testing.expectEqualStrings("LOcation", header.name);
633 try testing.expectEqualStrings("url", header.value);
634 try testing.expect(!it.is_trailer);
635 }
636 {
637 const header = it.next().?;
638 try testing.expectEqualStrings("content-tYpe", header.name);
639 try testing.expectEqualStrings("text/plain", header.value);
640 try testing.expect(!it.is_trailer);
641 }
642 {
643 const header = it.next().?;
644 try testing.expectEqualStrings("content-disposition", header.name);
645 try testing.expectEqualStrings("attachment; filename=example.txt", header.value);
646 try testing.expect(!it.is_trailer);
647 }
648 {
649 const header = it.next().?;
650 try testing.expectEqualStrings("content-Length", header.name);
651 try testing.expectEqualStrings("10", header.value);
652 try testing.expect(!it.is_trailer);
653 }
654 {
655 const header = it.next().?;
656 try testing.expectEqualStrings("TRansfer-encoding", header.name);
657 try testing.expectEqualStrings("deflate, chunked", header.value);
658 try testing.expect(!it.is_trailer);
659 }
660 {
661 const header = it.next().?;
662 try testing.expectEqualStrings("connectioN", header.name);
663 try testing.expectEqualStrings("keep-alive", header.value);
664 try testing.expect(!it.is_trailer);
665 }
666 try testing.expectEqual(null, it.next());
667731 }
668732};
669733
670/// A HTTP request that has been sent.
671///
672/// Order of operations: open -> send[ -> write -> finish] -> wait -> read
673734pub const Request = struct {
735 /// This field is provided so that clients can observe redirected URIs.
736 ///
737 /// Its backing memory is externally provided by API users when creating a
738 /// request, and then again provided externally via `redirect_buffer` to
739 /// `receiveHead`.
674740 uri: Uri,
675741 client: *Client,
676742 /// This is null when the connection is released.
677743 connection: ?*Connection,
744 reader: http.Reader,
678745 keep_alive: bool,
679746
680747 method: http.Method,
681748 version: http.Version = .@"HTTP/1.1",
682 transfer_encoding: RequestTransfer,
749 transfer_encoding: TransferEncoding,
683750 redirect_behavior: RedirectBehavior,
751 accept_encoding: @TypeOf(default_accept_encoding) = default_accept_encoding,
684752
685753 /// Whether the request should handle a 100-continue response before sending the request body.
686754 handle_continue: bool,
687755
688 /// The response associated with this request.
689 ///
690 /// This field is undefined until `wait` is called.
691 response: Response,
692
693756 /// Standard headers that have default, but overridable, behavior.
694757 headers: Headers,
695758
......@@ -703,6 +766,20 @@ pub const Request = struct {
703766 /// Externally-owned; must outlive the Request.
704767 privileged_headers: []const http.Header,
705768
769 pub const default_accept_encoding: [@typeInfo(http.ContentEncoding).@"enum".fields.len]bool = b: {
770 var result: [@typeInfo(http.ContentEncoding).@"enum".fields.len]bool = @splat(false);
771 result[@intFromEnum(http.ContentEncoding.gzip)] = true;
772 result[@intFromEnum(http.ContentEncoding.deflate)] = true;
773 result[@intFromEnum(http.ContentEncoding.identity)] = true;
774 break :b result;
775 };
776
777 pub const TransferEncoding = union(enum) {
778 content_length: u64,
779 chunked: void,
780 none: void,
781 };
782
706783 pub const Headers = struct {
707784 host: Value = .default,
708785 authorization: Value = .default,
......@@ -742,98 +819,102 @@ pub const Request = struct {
742819 }
743820 };
744821
745 /// Frees all resources associated with the request.
746 pub fn deinit(req: *Request) void {
747 if (req.connection) |connection| {
748 if (!req.response.parser.done) {
749 // If the response wasn't fully read, then we need to close the connection.
750 connection.closing = true;
751 }
752 req.client.connection_pool.release(req.client.allocator, connection);
822 /// Returns the request's `Connection` back to the pool of the `Client`.
823 pub fn deinit(r: *Request) void {
824 r.reader.restituteHeadBuffer();
825 if (r.connection) |connection| {
826 connection.closing = connection.closing or switch (r.reader.state) {
827 .ready => false,
828 .received_head => r.method.requestHasBody(),
829 else => true,
830 };
831 r.client.connection_pool.release(connection);
753832 }
754 req.* = undefined;
833 r.* = undefined;
755834 }
756835
757 // This function must deallocate all resources associated with the request,
758 // or keep those which will be used.
759 // This needs to be kept in sync with deinit and request.
760 fn redirect(req: *Request, uri: Uri) !void {
761 assert(req.response.parser.done);
762
763 req.client.connection_pool.release(req.client.allocator, req.connection.?);
764 req.connection = null;
765
766 var server_header: std.heap.FixedBufferAllocator = .init(req.response.parser.header_bytes_buffer);
767 defer req.response.parser.header_bytes_buffer = server_header.buffer[server_header.end_index..];
768 const protocol, const valid_uri = try validateUri(uri, server_header.allocator());
769
770 const new_host = valid_uri.host.?.raw;
771 const prev_host = req.uri.host.?.raw;
772 const keep_privileged_headers =
773 std.ascii.eqlIgnoreCase(valid_uri.scheme, req.uri.scheme) and
774 std.ascii.endsWithIgnoreCase(new_host, prev_host) and
775 (new_host.len == prev_host.len or new_host[new_host.len - prev_host.len - 1] == '.');
776 if (!keep_privileged_headers) {
777 // When redirecting to a different domain, strip privileged headers.
778 req.privileged_headers = &.{};
779 }
780
781 if (switch (req.response.status) {
782 .see_other => true,
783 .moved_permanently, .found => req.method == .POST,
784 else => false,
785 }) {
786 // A redirect to a GET must change the method and remove the body.
787 req.method = .GET;
788 req.transfer_encoding = .none;
789 req.headers.content_type = .omit;
790 }
791
792 if (req.transfer_encoding != .none) {
793 // The request body has already been sent. The request is
794 // still in a valid state, but the redirect must be handled
795 // manually.
796 return error.RedirectRequiresResend;
797 }
798
799 req.uri = valid_uri;
800 req.connection = try req.client.connect(new_host, uriPort(valid_uri, protocol), protocol);
801 req.redirect_behavior.subtractOne();
802 req.response.parser.reset();
803
804 req.response = .{
805 .version = undefined,
806 .status = undefined,
807 .reason = undefined,
808 .keep_alive = undefined,
809 .parser = req.response.parser,
810 };
836 /// Sends and flushes a complete request as only HTTP head, no body.
837 pub fn sendBodiless(r: *Request) Writer.Error!void {
838 try sendBodilessUnflushed(r);
839 try r.connection.?.flush();
811840 }
812841
813 pub const SendError = Connection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding };
842 /// Sends but does not flush a complete request as only HTTP head, no body.
843 pub fn sendBodilessUnflushed(r: *Request) Writer.Error!void {
844 assert(r.transfer_encoding == .none);
845 assert(!r.method.requestHasBody());
846 try sendHead(r);
847 }
814848
815 /// Send the HTTP request headers to the server.
816 pub fn send(req: *Request) SendError!void {
817 if (!req.method.requestHasBody() and req.transfer_encoding != .none)
818 return error.UnsupportedTransferEncoding;
849 /// Transfers the HTTP head over the connection and flushes.
850 ///
851 /// See also:
852 /// * `sendBodyUnflushed`
853 pub fn sendBody(r: *Request, buffer: []u8) Writer.Error!http.BodyWriter {
854 const result = try sendBodyUnflushed(r, buffer);
855 try r.connection.?.flush();
856 return result;
857 }
819858
820 const connection = req.connection.?;
821 var connection_writer_adapter = connection.writer().adaptToNewApi();
822 const w = &connection_writer_adapter.new_interface;
823 sendAdapted(req, connection, w) catch |err| switch (err) {
824 error.WriteFailed => return connection_writer_adapter.err.?,
825 else => |e| return e,
859 /// Transfers the HTTP head over the connection, which is not flushed until
860 /// `BodyWriter.flush` or `BodyWriter.end` is called.
861 ///
862 /// See also:
863 /// * `sendBody`
864 pub fn sendBodyUnflushed(r: *Request, buffer: []u8) Writer.Error!http.BodyWriter {
865 assert(r.method.requestHasBody());
866 try sendHead(r);
867 const http_protocol_output = r.connection.?.writer();
868 return switch (r.transfer_encoding) {
869 .chunked => .{
870 .http_protocol_output = http_protocol_output,
871 .state = .{ .chunked = .init },
872 .writer = .{
873 .buffer = buffer,
874 .vtable = &.{
875 .drain = http.BodyWriter.chunkedDrain,
876 .sendFile = http.BodyWriter.chunkedSendFile,
877 },
878 },
879 },
880 .content_length => |len| .{
881 .http_protocol_output = http_protocol_output,
882 .state = .{ .content_length = len },
883 .writer = .{
884 .buffer = buffer,
885 .vtable = &.{
886 .drain = http.BodyWriter.contentLengthDrain,
887 .sendFile = http.BodyWriter.contentLengthSendFile,
888 },
889 },
890 },
891 .none => .{
892 .http_protocol_output = http_protocol_output,
893 .state = .none,
894 .writer = .{
895 .buffer = buffer,
896 .vtable = &.{
897 .drain = http.BodyWriter.noneDrain,
898 .sendFile = http.BodyWriter.noneSendFile,
899 },
900 },
901 },
826902 };
827903 }
828904
829 fn sendAdapted(req: *Request, connection: *Connection, w: *std.io.Writer) !void {
830 try req.method.format(w);
905 /// Sends HTTP headers without flushing.
906 fn sendHead(r: *Request) Writer.Error!void {
907 const uri = r.uri;
908 const connection = r.connection.?;
909 const w = connection.writer();
910
911 try r.method.write(w);
831912 try w.writeByte(' ');
832913
833 if (req.method == .CONNECT) {
834 try req.uri.writeToStream(w, .{ .authority = true });
914 if (r.method == .CONNECT) {
915 try uri.writeToStream(.{ .authority = true }, w);
835916 } else {
836 try req.uri.writeToStream(w, .{
917 try uri.writeToStream(.{
837918 .scheme = connection.proxied,
838919 .authentication = connection.proxied,
839920 .authority = connection.proxied,
......@@ -842,58 +923,64 @@ pub const Request = struct {
842923 });
843924 }
844925 try w.writeByte(' ');
845 try w.writeAll(@tagName(req.version));
926 try w.writeAll(@tagName(r.version));
846927 try w.writeAll("\r\n");
847928
848 if (try emitOverridableHeader("host: ", req.headers.host, w)) {
929 if (try emitOverridableHeader("host: ", r.headers.host, w)) {
849930 try w.writeAll("host: ");
850 try req.uri.writeToStream(w, .{ .authority = true });
931 try uri.writeToStream(.{ .authority = true }, w);
851932 try w.writeAll("\r\n");
852933 }
853934
854 if (try emitOverridableHeader("authorization: ", req.headers.authorization, w)) {
855 if (req.uri.user != null or req.uri.password != null) {
935 if (try emitOverridableHeader("authorization: ", r.headers.authorization, w)) {
936 if (uri.user != null or uri.password != null) {
856937 try w.writeAll("authorization: ");
857 const authorization = try connection.allocWriteBuffer(
858 @intCast(basic_authorization.valueLengthFromUri(req.uri)),
859 );
860 assert(basic_authorization.value(req.uri, authorization).len == authorization.len);
938 try basic_authorization.write(uri, w);
861939 try w.writeAll("\r\n");
862940 }
863941 }
864942
865 if (try emitOverridableHeader("user-agent: ", req.headers.user_agent, w)) {
943 if (try emitOverridableHeader("user-agent: ", r.headers.user_agent, w)) {
866944 try w.writeAll("user-agent: zig/");
867945 try w.writeAll(builtin.zig_version_string);
868946 try w.writeAll(" (std.http)\r\n");
869947 }
870948
871 if (try emitOverridableHeader("connection: ", req.headers.connection, w)) {
872 if (req.keep_alive) {
949 if (try emitOverridableHeader("connection: ", r.headers.connection, w)) {
950 if (r.keep_alive) {
873951 try w.writeAll("connection: keep-alive\r\n");
874952 } else {
875953 try w.writeAll("connection: close\r\n");
876954 }
877955 }
878956
879 if (try emitOverridableHeader("accept-encoding: ", req.headers.accept_encoding, w)) {
880 // https://github.com/ziglang/zig/issues/18937
881 //try w.writeAll("accept-encoding: gzip, deflate, zstd\r\n");
882 try w.writeAll("accept-encoding: gzip, deflate\r\n");
957 if (try emitOverridableHeader("accept-encoding: ", r.headers.accept_encoding, w)) {
958 try w.writeAll("accept-encoding: ");
959 for (r.accept_encoding, 0..) |enabled, i| {
960 if (!enabled) continue;
961 const tag: http.ContentEncoding = @enumFromInt(i);
962 if (tag == .identity) continue;
963 const tag_name = @tagName(tag);
964 try w.ensureUnusedCapacity(tag_name.len + 2);
965 try w.writeAll(tag_name);
966 try w.writeAll(", ");
967 }
968 w.undo(2);
969 try w.writeAll("\r\n");
883970 }
884971
885 switch (req.transfer_encoding) {
972 switch (r.transfer_encoding) {
886973 .chunked => try w.writeAll("transfer-encoding: chunked\r\n"),
887974 .content_length => |len| try w.print("content-length: {d}\r\n", .{len}),
888975 .none => {},
889976 }
890977
891 if (try emitOverridableHeader("content-type: ", req.headers.content_type, w)) {
978 if (try emitOverridableHeader("content-type: ", r.headers.content_type, w)) {
892979 // The default is to omit content-type if not provided because
893980 // "application/octet-stream" is redundant.
894981 }
895982
896 for (req.extra_headers) |header| {
983 for (r.extra_headers) |header| {
897984 assert(header.name.len != 0);
898985
899986 try w.writeAll(header.name);
......@@ -904,8 +991,8 @@ pub const Request = struct {
904991
905992 if (connection.proxied) proxy: {
906993 const proxy = switch (connection.protocol) {
907 .plain => req.client.http_proxy,
908 .tls => req.client.https_proxy,
994 .plain => r.client.http_proxy,
995 .tls => r.client.https_proxy,
909996 } orelse break :proxy;
910997
911998 const authorization = proxy.authorization orelse break :proxy;
......@@ -915,282 +1002,198 @@ pub const Request = struct {
9151002 }
9161003
9171004 try w.writeAll("\r\n");
918
919 try connection.flush();
9201005 }
9211006
922 /// Returns true if the default behavior is required, otherwise handles
923 /// writing (or not writing) the header.
924 fn emitOverridableHeader(prefix: []const u8, v: Headers.Value, w: anytype) !bool {
925 switch (v) {
926 .default => return true,
927 .omit => return false,
928 .override => |x| {
929 try w.writeAll(prefix);
930 try w.writeAll(x);
931 try w.writeAll("\r\n");
932 return false;
933 },
934 }
935 }
936
937 const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError;
938
939 const TransferReader = std.io.GenericReader(*Request, TransferReadError, transferRead);
940
941 fn transferReader(req: *Request) TransferReader {
942 return .{ .context = req };
943 }
944
945 fn transferRead(req: *Request, buf: []u8) TransferReadError!usize {
946 if (req.response.parser.done) return 0;
947
948 var index: usize = 0;
949 while (index == 0) {
950 const amt = try req.response.parser.read(req.connection.?, buf[index..], req.response.skip);
951 if (amt == 0 and req.response.parser.done) break;
952 index += amt;
953 }
954
955 return index;
956 }
1007 pub const ReceiveHeadError = http.Reader.HeadError || ConnectError || error{
1008 /// Server sent headers that did not conform to the HTTP protocol.
1009 ///
1010 /// To find out more detailed diagnostics, `http.Reader.head_buffer` can be
1011 /// passed directly to `Request.Head.parse`.
1012 HttpHeadersInvalid,
1013 TooManyHttpRedirects,
1014 /// This can be avoided by calling `receiveHead` before sending the
1015 /// request body.
1016 RedirectRequiresResend,
1017 HttpRedirectLocationMissing,
1018 HttpRedirectLocationOversize,
1019 HttpRedirectLocationInvalid,
1020 HttpContentEncodingUnsupported,
1021 HttpChunkInvalid,
1022 HttpChunkTruncated,
1023 HttpHeadersOversize,
1024 UnsupportedUriScheme,
9571025
958 pub const WaitError = RequestError || SendError || TransferReadError ||
959 proto.HeadersParser.CheckCompleteHeadError || Response.ParseError ||
960 error{
961 TooManyHttpRedirects,
962 RedirectRequiresResend,
963 HttpRedirectLocationMissing,
964 HttpRedirectLocationInvalid,
965 CompressionInitializationFailed,
966 CompressionUnsupported,
967 };
1026 /// Sending the request failed. Error code can be found on the
1027 /// `Connection` object.
1028 WriteFailed,
1029 };
9681030
969 /// Waits for a response from the server and parses any headers that are sent.
970 /// This function will block until the final response is received.
971 ///
9721031 /// If handling redirects and the request has no payload, then this
973 /// function will automatically follow redirects. If a request payload is
974 /// present, then this function will error with
975 /// error.RedirectRequiresResend.
1032 /// function will automatically follow redirects.
9761033 ///
977 /// Must be called after `send` and, if any data was written to the request
978 /// body, then also after `finish`.
979 pub fn wait(req: *Request) WaitError!void {
1034 /// If a request payload is present, then this function will error with
1035 /// `error.RedirectRequiresResend`.
1036 ///
1037 /// This function takes an auxiliary buffer to store the arbitrarily large
1038 /// URI which may need to be merged with the previous URI, and that data
1039 /// needs to survive across different connections, which is where the input
1040 /// buffer lives.
1041 ///
1042 /// `redirect_buffer` must outlive accesses to `Request.uri`. If this
1043 /// buffer capacity would be exceeded, `error.HttpRedirectLocationOversize`
1044 /// is returned instead. This buffer may be empty if no redirects are to be
1045 /// handled.
1046 pub fn receiveHead(r: *Request, redirect_buffer: []u8) ReceiveHeadError!Response {
1047 var aux_buf = redirect_buffer;
9801048 while (true) {
981 // This while loop is for handling redirects, which means the request's
982 // connection may be different than the previous iteration. However, it
983 // is still guaranteed to be non-null with each iteration of this loop.
984 const connection = req.connection.?;
985
986 while (true) { // read headers
987 try connection.fill();
988
989 const nchecked = try req.response.parser.checkCompleteHead(connection.peek());
990 connection.drop(@intCast(nchecked));
1049 try r.reader.receiveHead();
1050 const response: Response = .{
1051 .request = r,
1052 .head = Response.Head.parse(r.reader.head_buffer) catch return error.HttpHeadersInvalid,
1053 };
1054 const head = &response.head;
9911055
992 if (req.response.parser.state.isContent()) break;
1056 if (head.status == .@"continue") {
1057 if (r.handle_continue) continue;
1058 return response; // we're not handling the 100-continue
9931059 }
9941060
995 try req.response.parse(req.response.parser.get());
996
997 if (req.response.status == .@"continue") {
998 // We're done parsing the continue response; reset to prepare
999 // for the real response.
1000 req.response.parser.done = true;
1001 req.response.parser.reset();
1002
1003 if (req.handle_continue)
1004 continue;
1005
1006 return; // we're not handling the 100-continue
1007 }
1061 // This while loop is for handling redirects, which means the request's
1062 // connection may be different than the previous iteration. However, it
1063 // is still guaranteed to be non-null with each iteration of this loop.
1064 const connection = r.connection.?;
10081065
1009 // we're switching protocols, so this connection is no longer doing http
1010 if (req.method == .CONNECT and req.response.status.class() == .success) {
1066 if (r.method == .CONNECT and head.status.class() == .success) {
1067 // This connection is no longer doing HTTP.
10111068 connection.closing = false;
1012 req.response.parser.done = true;
1013 return; // the connection is not HTTP past this point
1069 return response;
10141070 }
10151071
1016 connection.closing = !req.response.keep_alive or !req.keep_alive;
1072 connection.closing = !head.keep_alive or !r.keep_alive;
10171073
10181074 // Any response to a HEAD request and any response with a 1xx
10191075 // (Informational), 204 (No Content), or 304 (Not Modified) status
10201076 // code is always terminated by the first empty line after the
10211077 // header fields, regardless of the header fields present in the
10221078 // message.
1023 if (req.method == .HEAD or req.response.status.class() == .informational or
1024 req.response.status == .no_content or req.response.status == .not_modified)
1079 if (r.method == .HEAD or head.status.class() == .informational or
1080 head.status == .no_content or head.status == .not_modified)
10251081 {
1026 req.response.parser.done = true;
1027 return; // The response is empty; no further setup or redirection is necessary.
1028 }
1029
1030 switch (req.response.transfer_encoding) {
1031 .none => {
1032 if (req.response.content_length) |cl| {
1033 req.response.parser.next_chunk_length = cl;
1034
1035 if (cl == 0) req.response.parser.done = true;
1036 } else {
1037 // read until the connection is closed
1038 req.response.parser.next_chunk_length = std.math.maxInt(u64);
1039 }
1040 },
1041 .chunked => {
1042 req.response.parser.next_chunk_length = 0;
1043 req.response.parser.state = .chunk_head_size;
1044 },
1082 return response;
10451083 }
10461084
1047 if (req.response.status.class() == .redirect and req.redirect_behavior != .unhandled) {
1048 // skip the body of the redirect response, this will at least
1049 // leave the connection in a known good state.
1050 req.response.skip = true;
1051 assert(try req.transferRead(&.{}) == 0); // we're skipping, no buffer is necessary
1052
1053 if (req.redirect_behavior == .not_allowed) return error.TooManyHttpRedirects;
1054
1055 const location = req.response.location orelse
1056 return error.HttpRedirectLocationMissing;
1057
1058 // This mutates the beginning of header_bytes_buffer and uses that
1059 // for the backing memory of the returned Uri.
1060 try req.redirect(req.uri.resolve_inplace(
1061 location,
1062 &req.response.parser.header_bytes_buffer,
1063 ) catch |err| switch (err) {
1064 error.UnexpectedCharacter,
1065 error.InvalidFormat,
1066 error.InvalidPort,
1067 => return error.HttpRedirectLocationInvalid,
1068 error.NoSpaceLeft => return error.HttpHeadersOversize,
1069 });
1070 try req.send();
1071 } else {
1072 req.response.skip = false;
1073 if (!req.response.parser.done) {
1074 switch (req.response.transfer_compression) {
1075 .identity => req.response.compression = .none,
1076 .compress, .@"x-compress" => return error.CompressionUnsupported,
1077 // I'm about to upstream my http.Client rewrite
1078 .deflate => return error.CompressionUnsupported,
1079 // I'm about to upstream my http.Client rewrite
1080 .gzip, .@"x-gzip" => return error.CompressionUnsupported,
1081 // https://github.com/ziglang/zig/issues/18937
1082 //.zstd => req.response.compression = .{
1083 // .zstd = std.compress.zstd.decompressStream(req.client.allocator, req.transferReader()),
1084 //},
1085 .zstd => return error.CompressionUnsupported,
1086 }
1085 if (head.status.class() == .redirect and r.redirect_behavior != .unhandled) {
1086 if (r.redirect_behavior == .not_allowed) {
1087 // Connection can still be reused by skipping the body.
1088 const reader = r.reader.bodyReader(&.{}, head.transfer_encoding, head.content_length);
1089 _ = reader.discardRemaining() catch |err| switch (err) {
1090 error.ReadFailed => connection.closing = true,
1091 };
1092 return error.TooManyHttpRedirects;
10871093 }
1088
1089 break;
1094 try r.redirect(head, &aux_buf);
1095 try r.sendBodiless();
1096 continue;
10901097 }
1091 }
1092 }
1093
1094 pub const ReadError = TransferReadError || proto.HeadersParser.CheckCompleteHeadError ||
1095 error{ DecompressionFailure, InvalidTrailers };
10961098
1097 pub const Reader = std.io.GenericReader(*Request, ReadError, read);
1099 if (!r.accept_encoding[@intFromEnum(head.content_encoding)])
1100 return error.HttpContentEncodingUnsupported;
10981101
1099 pub fn reader(req: *Request) Reader {
1100 return .{ .context = req };
1101 }
1102
1103 /// Reads data from the response body. Must be called after `wait`.
1104 pub fn read(req: *Request, buffer: []u8) ReadError!usize {
1105 const out_index = switch (req.response.compression) {
1106 // I'm about to upstream my http client rewrite
1107 //.deflate => |*deflate| deflate.readSlice(buffer) catch return error.DecompressionFailure,
1108 //.gzip => |*gzip| gzip.read(buffer) catch return error.DecompressionFailure,
1109 // https://github.com/ziglang/zig/issues/18937
1110 //.zstd => |*zstd| zstd.read(buffer) catch return error.DecompressionFailure,
1111 else => try req.transferRead(buffer),
1112 };
1113 if (out_index > 0) return out_index;
1114
1115 while (!req.response.parser.state.isContent()) { // read trailing headers
1116 try req.connection.?.fill();
1117
1118 const nchecked = try req.response.parser.checkCompleteHead(req.connection.?.peek());
1119 req.connection.?.drop(@intCast(nchecked));
1102 return response;
11201103 }
1121
1122 return 0;
11231104 }
11241105
1125 /// Reads data from the response body. Must be called after `wait`.
1126 pub fn readAll(req: *Request, buffer: []u8) !usize {
1127 var index: usize = 0;
1128 while (index < buffer.len) {
1129 const amt = try read(req, buffer[index..]);
1130 if (amt == 0) break;
1131 index += amt;
1106 /// This function takes an auxiliary buffer to store the arbitrarily large
1107 /// URI which may need to be merged with the previous URI, and that data
1108 /// needs to survive across different connections, which is where the input
1109 /// buffer lives.
1110 ///
1111 /// `aux_buf` must outlive accesses to `Request.uri`.
1112 fn redirect(r: *Request, head: *const Response.Head, aux_buf: *[]u8) !void {
1113 const new_location = head.location orelse return error.HttpRedirectLocationMissing;
1114 if (new_location.len > aux_buf.*.len) return error.HttpRedirectLocationOversize;
1115 const location = aux_buf.*[0..new_location.len];
1116 @memcpy(location, new_location);
1117 {
1118 // Skip the body of the redirect response to leave the connection in
1119 // the correct state. This causes `new_location` to be invalidated.
1120 const reader = r.reader.bodyReader(&.{}, head.transfer_encoding, head.content_length);
1121 _ = reader.discardRemaining() catch |err| switch (err) {
1122 error.ReadFailed => return r.reader.body_err.?,
1123 };
1124 r.reader.restituteHeadBuffer();
11321125 }
1133 return index;
1134 }
1135
1136 pub const WriteError = Connection.WriteError || error{ NotWriteable, MessageTooLong };
1137
1138 pub const Writer = std.io.GenericWriter(*Request, WriteError, write);
1126 const new_uri = r.uri.resolveInPlace(location.len, aux_buf) catch |err| switch (err) {
1127 error.UnexpectedCharacter => return error.HttpRedirectLocationInvalid,
1128 error.InvalidFormat => return error.HttpRedirectLocationInvalid,
1129 error.InvalidPort => return error.HttpRedirectLocationInvalid,
1130 error.NoSpaceLeft => return error.HttpRedirectLocationOversize,
1131 };
11391132
1140 pub fn writer(req: *Request) Writer {
1141 return .{ .context = req };
1142 }
1133 const protocol = Protocol.fromUri(new_uri) orelse return error.UnsupportedUriScheme;
1134 const old_connection = r.connection.?;
1135 const old_host = old_connection.host();
1136 var new_host_name_buffer: [Uri.host_name_max]u8 = undefined;
1137 const new_host = try new_uri.getHost(&new_host_name_buffer);
1138 const keep_privileged_headers =
1139 std.ascii.eqlIgnoreCase(r.uri.scheme, new_uri.scheme) and
1140 sameParentDomain(old_host, new_host);
11431141
1144 /// Write `bytes` to the server. The `transfer_encoding` field determines how data will be sent.
1145 /// Must be called after `send` and before `finish`.
1146 pub fn write(req: *Request, bytes: []const u8) WriteError!usize {
1147 switch (req.transfer_encoding) {
1148 .chunked => {
1149 if (bytes.len > 0) {
1150 try req.connection.?.writer().print("{x}\r\n", .{bytes.len});
1151 try req.connection.?.writer().writeAll(bytes);
1152 try req.connection.?.writer().writeAll("\r\n");
1153 }
1142 r.client.connection_pool.release(old_connection);
1143 r.connection = null;
11541144
1155 return bytes.len;
1156 },
1157 .content_length => |*len| {
1158 if (len.* < bytes.len) return error.MessageTooLong;
1145 if (!keep_privileged_headers) {
1146 // When redirecting to a different domain, strip privileged headers.
1147 r.privileged_headers = &.{};
1148 }
11591149
1160 const amt = try req.connection.?.write(bytes);
1161 len.* -= amt;
1162 return amt;
1163 },
1164 .none => return error.NotWriteable,
1150 if (switch (head.status) {
1151 .see_other => true,
1152 .moved_permanently, .found => r.method == .POST,
1153 else => false,
1154 }) {
1155 // A redirect to a GET must change the method and remove the body.
1156 r.method = .GET;
1157 r.transfer_encoding = .none;
1158 r.headers.content_type = .omit;
11651159 }
1166 }
11671160
1168 /// Write `bytes` to the server. The `transfer_encoding` field determines how data will be sent.
1169 /// Must be called after `send` and before `finish`.
1170 pub fn writeAll(req: *Request, bytes: []const u8) WriteError!void {
1171 var index: usize = 0;
1172 while (index < bytes.len) {
1173 index += try write(req, bytes[index..]);
1161 if (r.transfer_encoding != .none) {
1162 // The request body has already been sent. The request is
1163 // still in a valid state, but the redirect must be handled
1164 // manually.
1165 return error.RedirectRequiresResend;
11741166 }
1175 }
11761167
1177 pub const FinishError = WriteError || error{MessageNotCompleted};
1168 const new_connection = try r.client.connect(new_host, uriPort(new_uri, protocol), protocol);
1169 r.uri = new_uri;
1170 r.connection = new_connection;
1171 r.reader = .{
1172 .in = new_connection.reader(),
1173 .state = .ready,
1174 // Populated when `http.Reader.bodyReader` is called.
1175 .interface = undefined,
1176 };
1177 r.redirect_behavior.subtractOne();
1178 }
11781179
1179 /// Finish the body of a request. This notifies the server that you have no more data to send.
1180 /// Must be called after `send`.
1181 pub fn finish(req: *Request) FinishError!void {
1182 switch (req.transfer_encoding) {
1183 .chunked => try req.connection.?.writer().writeAll("0\r\n\r\n"),
1184 .content_length => |len| if (len != 0) return error.MessageNotCompleted,
1185 .none => {},
1180 /// Returns true if the default behavior is required, otherwise handles
1181 /// writing (or not writing) the header.
1182 fn emitOverridableHeader(prefix: []const u8, v: Headers.Value, bw: *Writer) Writer.Error!bool {
1183 switch (v) {
1184 .default => return true,
1185 .omit => return false,
1186 .override => |x| {
1187 var vecs: [3][]const u8 = .{ prefix, x, "\r\n" };
1188 try bw.writeVecAll(&vecs);
1189 return false;
1190 },
11861191 }
1187
1188 try req.connection.?.flush();
11891192 }
11901193};
11911194
11921195pub const Proxy = struct {
1193 protocol: Connection.Protocol,
1196 protocol: Protocol,
11941197 host: []const u8,
11951198 authorization: ?[]const u8,
11961199 port: u16,
......@@ -1204,10 +1207,8 @@ pub const Proxy = struct {
12041207pub fn deinit(client: *Client) void {
12051208 assert(client.connection_pool.used.first == null); // There are still active requests.
12061209
1207 client.connection_pool.deinit(client.allocator);
1208
1209 if (!disable_tls)
1210 client.ca_bundle.deinit(client.allocator);
1210 client.connection_pool.deinit();
1211 if (!disable_tls) client.ca_bundle.deinit(client.allocator);
12111212
12121213 client.* = undefined;
12131214}
......@@ -1249,24 +1250,21 @@ fn createProxyFromEnvVar(arena: Allocator, env_var_names: []const []const u8) !?
12491250 } else return null;
12501251
12511252 const uri = Uri.parse(content) catch try Uri.parseAfterScheme("http", content);
1252 const protocol, const valid_uri = validateUri(uri, arena) catch |err| switch (err) {
1253 error.UnsupportedUriScheme => return null,
1254 error.UriMissingHost => return error.HttpProxyMissingHost,
1255 error.OutOfMemory => |e| return e,
1256 };
1253 const protocol = Protocol.fromUri(uri) orelse return null;
1254 const raw_host = try uri.getHostAlloc(arena);
12571255
1258 const authorization: ?[]const u8 = if (valid_uri.user != null or valid_uri.password != null) a: {
1259 const authorization = try arena.alloc(u8, basic_authorization.valueLengthFromUri(valid_uri));
1260 assert(basic_authorization.value(valid_uri, authorization).len == authorization.len);
1256 const authorization: ?[]const u8 = if (uri.user != null or uri.password != null) a: {
1257 const authorization = try arena.alloc(u8, basic_authorization.valueLengthFromUri(uri));
1258 assert(basic_authorization.value(uri, authorization).len == authorization.len);
12611259 break :a authorization;
12621260 } else null;
12631261
12641262 const proxy = try arena.create(Proxy);
12651263 proxy.* = .{
12661264 .protocol = protocol,
1267 .host = valid_uri.host.?.raw,
1265 .host = raw_host,
12681266 .authorization = authorization,
1269 .port = uriPort(valid_uri, protocol),
1267 .port = uriPort(uri, protocol),
12701268 .supports_connect = true,
12711269 };
12721270 return proxy;
......@@ -1277,10 +1275,8 @@ pub const basic_authorization = struct {
12771275 pub const max_password_len = 255;
12781276 pub const max_value_len = valueLength(max_user_len, max_password_len);
12791277
1280 const prefix = "Basic ";
1281
12821278 pub fn valueLength(user_len: usize, password_len: usize) usize {
1283 return prefix.len + std.base64.standard.Encoder.calcSize(user_len + 1 + password_len);
1279 return "Basic ".len + std.base64.standard.Encoder.calcSize(user_len + 1 + password_len);
12841280 }
12851281
12861282 pub fn valueLengthFromUri(uri: Uri) usize {
......@@ -1300,37 +1296,69 @@ pub const basic_authorization = struct {
13001296 }
13011297
13021298 pub fn value(uri: Uri, out: []u8) []u8 {
1303 const user: Uri.Component = uri.user orelse .empty;
1304 const password: Uri.Component = uri.password orelse .empty;
1299 var bw: Writer = .fixed(out);
1300 write(uri, &bw) catch unreachable;
1301 return bw.getWritten();
1302 }
13051303
1304 pub fn write(uri: Uri, out: *Writer) Writer.Error!void {
13061305 var buf: [max_user_len + ":".len + max_password_len]u8 = undefined;
1307 var w: std.io.Writer = .fixed(&buf);
1308 user.formatUser(&w) catch unreachable; // fixed
1309 password.formatPassword(&w) catch unreachable; // fixed
1310
1311 @memcpy(out[0..prefix.len], prefix);
1312 const base64 = std.base64.standard.Encoder.encode(out[prefix.len..], w.buffered());
1313 return out[0 .. prefix.len + base64.len];
1306 var w: Writer = .fixed(&buf);
1307 w.print("{fuser}:{fpassword}", .{
1308 uri.user orelse Uri.Component.empty,
1309 uri.password orelse Uri.Component.empty,
1310 }) catch unreachable;
1311 try out.print("Basic {b64}", .{w.buffered()});
13141312 }
13151313};
13161314
1317pub const ConnectTcpError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed };
1315pub const ConnectTcpError = Allocator.Error || error{
1316 ConnectionRefused,
1317 NetworkUnreachable,
1318 ConnectionTimedOut,
1319 ConnectionResetByPeer,
1320 TemporaryNameServerFailure,
1321 NameServerFailure,
1322 UnknownHostName,
1323 HostLacksNetworkAddresses,
1324 UnexpectedConnectFailure,
1325 TlsInitializationFailed,
1326};
13181327
1319/// Connect to `host:port` using the specified protocol. This will reuse a connection if one is already open.
1328/// Reuses a `Connection` if one matching `host` and `port` is already open.
13201329///
1321/// This function is threadsafe.
1322pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectTcpError!*Connection {
1323 if (client.connection_pool.findConnection(.{
1324 .host = host,
1325 .port = port,
1326 .protocol = protocol,
1327 })) |node| return node;
1330/// Threadsafe.
1331pub fn connectTcp(
1332 client: *Client,
1333 host: []const u8,
1334 port: u16,
1335 protocol: Protocol,
1336) ConnectTcpError!*Connection {
1337 return connectTcpOptions(client, .{ .host = host, .port = port, .protocol = protocol });
1338}
1339
1340pub const ConnectTcpOptions = struct {
1341 host: []const u8,
1342 port: u16,
1343 protocol: Protocol,
13281344
1329 if (disable_tls and protocol == .tls)
1330 return error.TlsInitializationFailed;
1345 proxied_host: ?[]const u8 = null,
1346 proxied_port: ?u16 = null,
1347};
13311348
1332 const conn = try client.allocator.create(Connection);
1333 errdefer client.allocator.destroy(conn);
1349pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection {
1350 const host = options.host;
1351 const port = options.port;
1352 const protocol = options.protocol;
1353
1354 const proxied_host = options.proxied_host orelse host;
1355 const proxied_port = options.proxied_port orelse port;
1356
1357 if (client.connection_pool.findConnection(.{
1358 .host = proxied_host,
1359 .port = proxied_port,
1360 .protocol = protocol,
1361 })) |conn| return conn;
13341362
13351363 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) {
13361364 error.ConnectionRefused => return error.ConnectionRefused,
......@@ -1345,53 +1373,19 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
13451373 };
13461374 errdefer stream.close();
13471375
1348 conn.* = .{
1349 .stream = stream,
1350 .tls_client = undefined,
1351
1352 .protocol = protocol,
1353 .host = try client.allocator.dupe(u8, host),
1354 .port = port,
1355
1356 .pool_node = .{},
1357 };
1358 errdefer client.allocator.free(conn.host);
1359
1360 if (protocol == .tls) {
1361 if (disable_tls) unreachable;
1362
1363 conn.tls_client = try client.allocator.create(std.crypto.tls.Client);
1364 errdefer client.allocator.destroy(conn.tls_client);
1365
1366 const ssl_key_log_file: ?std.fs.File = if (std.options.http_enable_ssl_key_log_file) ssl_key_log_file: {
1367 const ssl_key_log_path = std.process.getEnvVarOwned(client.allocator, "SSLKEYLOGFILE") catch |err| switch (err) {
1368 error.EnvironmentVariableNotFound, error.InvalidWtf8 => break :ssl_key_log_file null,
1369 error.OutOfMemory => return error.OutOfMemory,
1370 };
1371 defer client.allocator.free(ssl_key_log_path);
1372 break :ssl_key_log_file std.fs.cwd().createFile(ssl_key_log_path, .{
1373 .truncate = false,
1374 .mode = switch (builtin.os.tag) {
1375 .windows, .wasi => 0,
1376 else => 0o600,
1377 },
1378 }) catch null;
1379 } else null;
1380 errdefer if (ssl_key_log_file) |key_log_file| key_log_file.close();
1381
1382 conn.tls_client.* = std.crypto.tls.Client.init(stream, .{
1383 .host = .{ .explicit = host },
1384 .ca = .{ .bundle = client.ca_bundle },
1385 .ssl_key_log_file = ssl_key_log_file,
1386 }) catch return error.TlsInitializationFailed;
1387 // This is appropriate for HTTPS because the HTTP headers contain
1388 // the content length which is used to detect truncation attacks.
1389 conn.tls_client.allow_truncation_attacks = true;
1376 switch (protocol) {
1377 .tls => {
1378 if (disable_tls) return error.TlsInitializationFailed;
1379 const tc = try Connection.Tls.create(client, proxied_host, proxied_port, stream);
1380 client.connection_pool.addUsed(&tc.connection);
1381 return &tc.connection;
1382 },
1383 .plain => {
1384 const pc = try Connection.Plain.create(client, proxied_host, proxied_port, stream);
1385 client.connection_pool.addUsed(&pc.connection);
1386 return &pc.connection;
1387 },
13901388 }
1391
1392 client.connection_pool.addUsed(conn);
1393
1394 return conn;
13951389}
13961390
13971391pub const ConnectUnixError = Allocator.Error || std.posix.SocketError || error{NameTooLong} || std.posix.ConnectError;
......@@ -1429,69 +1423,67 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti
14291423 return &conn.data;
14301424}
14311425
1432/// Connect to `tunnel_host:tunnel_port` using the specified proxy with HTTP
1426/// Connect to `proxied_host:proxied_port` using the specified proxy with HTTP
14331427/// CONNECT. This will reuse a connection if one is already open.
14341428///
14351429/// This function is threadsafe.
1436pub fn connectTunnel(
1430pub fn connectProxied(
14371431 client: *Client,
14381432 proxy: *Proxy,
1439 tunnel_host: []const u8,
1440 tunnel_port: u16,
1433 proxied_host: []const u8,
1434 proxied_port: u16,
14411435) !*Connection {
14421436 if (!proxy.supports_connect) return error.TunnelNotSupported;
14431437
14441438 if (client.connection_pool.findConnection(.{
1445 .host = tunnel_host,
1446 .port = tunnel_port,
1439 .host = proxied_host,
1440 .port = proxied_port,
14471441 .protocol = proxy.protocol,
1448 })) |node|
1449 return node;
1442 })) |node| return node;
14501443
14511444 var maybe_valid = false;
14521445 (tunnel: {
1453 const conn = try client.connectTcp(proxy.host, proxy.port, proxy.protocol);
1446 const connection = try client.connectTcpOptions(.{
1447 .host = proxy.host,
1448 .port = proxy.port,
1449 .protocol = proxy.protocol,
1450 .proxied_host = proxied_host,
1451 .proxied_port = proxied_port,
1452 });
14541453 errdefer {
1455 conn.closing = true;
1456 client.connection_pool.release(client.allocator, conn);
1454 connection.closing = true;
1455 client.connection_pool.release(connection);
14571456 }
14581457
1459 var buffer: [8096]u8 = undefined;
1460 var req = client.open(.CONNECT, .{
1458 var req = client.request(.CONNECT, .{
14611459 .scheme = "http",
1462 .host = .{ .raw = tunnel_host },
1463 .port = tunnel_port,
1460 .host = .{ .raw = proxied_host },
1461 .port = proxied_port,
14641462 }, .{
14651463 .redirect_behavior = .unhandled,
1466 .connection = conn,
1467 .server_header_buffer = &buffer,
1464 .connection = connection,
14681465 }) catch |err| {
1469 std.log.debug("err {}", .{err});
14701466 break :tunnel err;
14711467 };
14721468 defer req.deinit();
14731469
1474 req.send() catch |err| break :tunnel err;
1475 req.wait() catch |err| break :tunnel err;
1470 req.sendBodiless() catch |err| break :tunnel err;
1471 const response = req.receiveHead(&.{}) catch |err| break :tunnel err;
14761472
1477 if (req.response.status.class() == .server_error) {
1473 if (response.head.status.class() == .server_error) {
14781474 maybe_valid = true;
14791475 break :tunnel error.ServerError;
14801476 }
14811477
1482 if (req.response.status != .ok) break :tunnel error.ConnectionRefused;
1478 if (response.head.status != .ok) break :tunnel error.ConnectionRefused;
14831479
1484 // this connection is now a tunnel, so we can't use it for anything else, it will only be released when the client is de-initialized.
1480 // this connection is now a tunnel, so we can't use it for anything
1481 // else, it will only be released when the client is de-initialized.
14851482 req.connection = null;
14861483
1487 client.allocator.free(conn.host);
1488 conn.host = try client.allocator.dupe(u8, tunnel_host);
1489 errdefer client.allocator.free(conn.host);
1484 connection.closing = false;
14901485
1491 conn.port = tunnel_port;
1492 conn.closing = false;
1493
1494 return conn;
1486 return connection;
14951487 }) catch {
14961488 // something went wrong with the tunnel
14971489 proxy.supports_connect = maybe_valid;
......@@ -1499,12 +1491,11 @@ pub fn connectTunnel(
14991491 };
15001492}
15011493
1502// Prevents a dependency loop in open()
1503const ConnectErrorPartial = ConnectTcpError || error{ UnsupportedUriScheme, ConnectionRefused };
1504pub const ConnectError = ConnectErrorPartial || RequestError;
1494pub const ConnectError = ConnectTcpError || RequestError;
15051495
15061496/// Connect to `host:port` using the specified protocol. This will reuse a
15071497/// connection if one is already open.
1498///
15081499/// If a proxy is configured for the client, then the proxy will be used to
15091500/// connect to the host.
15101501///
......@@ -1513,7 +1504,7 @@ pub fn connect(
15131504 client: *Client,
15141505 host: []const u8,
15151506 port: u16,
1516 protocol: Connection.Protocol,
1507 protocol: Protocol,
15171508) ConnectError!*Connection {
15181509 const proxy = switch (protocol) {
15191510 .plain => client.http_proxy,
......@@ -1528,32 +1519,24 @@ pub fn connect(
15281519 }
15291520
15301521 if (proxy.supports_connect) tunnel: {
1531 return connectTunnel(client, proxy, host, port) catch |err| switch (err) {
1522 return connectProxied(client, proxy, host, port) catch |err| switch (err) {
15321523 error.TunnelNotSupported => break :tunnel,
15331524 else => |e| return e,
15341525 };
15351526 }
15361527
15371528 // fall back to using the proxy as a normal http proxy
1538 const conn = try client.connectTcp(proxy.host, proxy.port, proxy.protocol);
1539 errdefer {
1540 conn.closing = true;
1541 client.connection_pool.release(conn);
1542 }
1543
1544 conn.proxied = true;
1545 return conn;
1529 const connection = try client.connectTcp(proxy.host, proxy.port, proxy.protocol);
1530 connection.proxied = true;
1531 return connection;
15461532}
15471533
1548pub const RequestError = ConnectTcpError || ConnectErrorPartial || Request.SendError ||
1549 std.fmt.ParseIntError || Connection.WriteError ||
1550 error{
1551 UnsupportedUriScheme,
1552 UriMissingHost,
1553
1554 CertificateBundleLoadFailure,
1555 UnsupportedTransferEncoding,
1556 };
1534pub const RequestError = ConnectTcpError || error{
1535 UnsupportedUriScheme,
1536 UriMissingHost,
1537 UriHostTooLong,
1538 CertificateBundleLoadFailure,
1539};
15571540
15581541pub const RequestOptions = struct {
15591542 version: http.Version = .@"HTTP/1.1",
......@@ -1578,11 +1561,6 @@ pub const RequestOptions = struct {
15781561 /// payload or the server has acknowledged the payload).
15791562 redirect_behavior: Request.RedirectBehavior = @enumFromInt(3),
15801563
1581 /// Externally-owned memory used to store the server's entire HTTP header.
1582 /// `error.HttpHeadersOversize` is returned from read() when a
1583 /// client sends too many bytes of HTTP headers.
1584 server_header_buffer: []u8,
1585
15861564 /// Must be an already acquired connection.
15871565 connection: ?*Connection = null,
15881566
......@@ -1598,38 +1576,17 @@ pub const RequestOptions = struct {
15981576 privileged_headers: []const http.Header = &.{},
15991577};
16001578
1601fn validateUri(uri: Uri, arena: Allocator) !struct { Connection.Protocol, Uri } {
1602 const protocol_map = std.StaticStringMap(Connection.Protocol).initComptime(.{
1603 .{ "http", .plain },
1604 .{ "ws", .plain },
1605 .{ "https", .tls },
1606 .{ "wss", .tls },
1607 });
1608 const protocol = protocol_map.get(uri.scheme) orelse return error.UnsupportedUriScheme;
1609 var valid_uri = uri;
1610 // The host is always going to be needed as a raw string for hostname resolution anyway.
1611 valid_uri.host = .{
1612 .raw = try (uri.host orelse return error.UriMissingHost).toRawMaybeAlloc(arena),
1613 };
1614 return .{ protocol, valid_uri };
1615}
1616
1617fn uriPort(uri: Uri, protocol: Connection.Protocol) u16 {
1618 return uri.port orelse switch (protocol) {
1619 .plain => 80,
1620 .tls => 443,
1621 };
1579fn uriPort(uri: Uri, protocol: Protocol) u16 {
1580 return uri.port orelse protocol.port();
16221581}
16231582
16241583/// Open a connection to the host specified by `uri` and prepare to send a HTTP request.
16251584///
1626/// `uri` must remain alive during the entire request.
1627///
16281585/// The caller is responsible for calling `deinit()` on the `Request`.
16291586/// This function is threadsafe.
16301587///
16311588/// Asserts that "\r\n" does not occur in any header name or value.
1632pub fn open(
1589pub fn request(
16331590 client: *Client,
16341591 method: http.Method,
16351592 uri: Uri,
......@@ -1649,59 +1606,58 @@ pub fn open(
16491606 }
16501607 }
16511608
1652 var server_header: std.heap.FixedBufferAllocator = .init(options.server_header_buffer);
1653 const protocol, const valid_uri = try validateUri(uri, server_header.allocator());
1609 const protocol = Protocol.fromUri(uri) orelse return error.UnsupportedUriScheme;
16541610
1655 if (protocol == .tls and @atomicLoad(bool, &client.next_https_rescan_certs, .acquire)) {
1611 if (protocol == .tls) {
16561612 if (disable_tls) unreachable;
1657
1658 client.ca_bundle_mutex.lock();
1659 defer client.ca_bundle_mutex.unlock();
1660
1661 if (client.next_https_rescan_certs) {
1662 client.ca_bundle.rescan(client.allocator) catch
1663 return error.CertificateBundleLoadFailure;
1664 @atomicStore(bool, &client.next_https_rescan_certs, false, .release);
1613 if (@atomicLoad(bool, &client.next_https_rescan_certs, .acquire)) {
1614 client.ca_bundle_mutex.lock();
1615 defer client.ca_bundle_mutex.unlock();
1616
1617 if (client.next_https_rescan_certs) {
1618 client.ca_bundle.rescan(client.allocator) catch
1619 return error.CertificateBundleLoadFailure;
1620 @atomicStore(bool, &client.next_https_rescan_certs, false, .release);
1621 }
16651622 }
16661623 }
16671624
1668 const conn = options.connection orelse
1669 try client.connect(valid_uri.host.?.raw, uriPort(valid_uri, protocol), protocol);
1625 const connection = options.connection orelse c: {
1626 var host_name_buffer: [Uri.host_name_max]u8 = undefined;
1627 const host_name = try uri.getHost(&host_name_buffer);
1628 break :c try client.connect(host_name, uriPort(uri, protocol), protocol);
1629 };
16701630
1671 var req: Request = .{
1672 .uri = valid_uri,
1631 return .{
1632 .uri = uri,
16731633 .client = client,
1674 .connection = conn,
1634 .connection = connection,
1635 .reader = .{
1636 .in = connection.reader(),
1637 .state = .ready,
1638 // Populated when `http.Reader.bodyReader` is called.
1639 .interface = undefined,
1640 },
16751641 .keep_alive = options.keep_alive,
16761642 .method = method,
16771643 .version = options.version,
16781644 .transfer_encoding = .none,
16791645 .redirect_behavior = options.redirect_behavior,
16801646 .handle_continue = options.handle_continue,
1681 .response = .{
1682 .version = undefined,
1683 .status = undefined,
1684 .reason = undefined,
1685 .keep_alive = undefined,
1686 .parser = .init(server_header.buffer[server_header.end_index..]),
1687 },
16881647 .headers = options.headers,
16891648 .extra_headers = options.extra_headers,
16901649 .privileged_headers = options.privileged_headers,
16911650 };
1692 errdefer req.deinit();
1693
1694 return req;
16951651}
16961652
16971653pub const FetchOptions = struct {
1698 server_header_buffer: ?[]u8 = null,
1654 /// `null` means it will be heap-allocated.
1655 redirect_buffer: ?[]u8 = null,
1656 /// `null` means it will be heap-allocated.
1657 decompress_buffer: ?[]u8 = null,
16991658 redirect_behavior: ?Request.RedirectBehavior = null,
1700
1701 /// If the server sends a body, it will be appended to this ArrayList.
1702 /// `max_append_size` provides an upper limit for how much they can grow.
1703 response_storage: ResponseStorage = .ignore,
1704 max_append_size: ?usize = null,
1659 /// If the server sends a body, it will be stored here.
1660 response_storage: ?ResponseStorage = null,
17051661
17061662 location: Location,
17071663 method: ?http.Method = null,
......@@ -1725,11 +1681,11 @@ pub const FetchOptions = struct {
17251681 uri: Uri,
17261682 };
17271683
1728 pub const ResponseStorage = union(enum) {
1729 ignore,
1730 /// Only the existing capacity will be used.
1731 static: *std.ArrayListUnmanaged(u8),
1732 dynamic: *std.ArrayList(u8),
1684 pub const ResponseStorage = struct {
1685 list: *std.ArrayListUnmanaged(u8),
1686 /// If null then only the existing capacity will be used.
1687 allocator: ?Allocator = null,
1688 append_limit: std.io.Limit = .unlimited,
17331689 };
17341690};
17351691
......@@ -1737,23 +1693,28 @@ pub const FetchResult = struct {
17371693 status: http.Status,
17381694};
17391695
1696pub const FetchError = Uri.ParseError || RequestError || Request.ReceiveHeadError || error{
1697 StreamTooLong,
1698 /// TODO provide optional diagnostics when this occurs or break into more error codes
1699 WriteFailed,
1700};
1701
17401702/// Perform a one-shot HTTP request with the provided options.
17411703///
17421704/// This function is threadsafe.
1743pub fn fetch(client: *Client, options: FetchOptions) !FetchResult {
1705pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {
17441706 const uri = switch (options.location) {
17451707 .url => |u| try Uri.parse(u),
17461708 .uri => |u| u,
17471709 };
1748 var server_header_buffer: [16 * 1024]u8 = undefined;
1749
17501710 const method: http.Method = options.method orelse
17511711 if (options.payload != null) .POST else .GET;
17521712
1753 var req = try open(client, method, uri, .{
1754 .server_header_buffer = options.server_header_buffer orelse &server_header_buffer,
1755 .redirect_behavior = options.redirect_behavior orelse
1756 if (options.payload == null) @enumFromInt(3) else .unhandled,
1713 const redirect_behavior: Request.RedirectBehavior = options.redirect_behavior orelse
1714 if (options.payload == null) @enumFromInt(3) else .unhandled;
1715
1716 var req = try request(client, method, uri, .{
1717 .redirect_behavior = redirect_behavior,
17571718 .headers = options.headers,
17581719 .extra_headers = options.extra_headers,
17591720 .privileged_headers = options.privileged_headers,
......@@ -1761,44 +1722,69 @@ pub fn fetch(client: *Client, options: FetchOptions) !FetchResult {
17611722 });
17621723 defer req.deinit();
17631724
1764 if (options.payload) |payload| req.transfer_encoding = .{ .content_length = payload.len };
1725 if (options.payload) |payload| {
1726 req.transfer_encoding = .{ .content_length = payload.len };
1727 var body = try req.sendBody(&.{});
1728 try body.writer.writeAll(payload);
1729 try body.end();
1730 } else {
1731 try req.sendBodiless();
1732 }
17651733
1766 try req.send();
1734 const redirect_buffer: []u8 = if (redirect_behavior == .unhandled) &.{} else options.redirect_buffer orelse
1735 try client.allocator.alloc(u8, 8 * 1024);
1736 defer if (options.redirect_buffer == null) client.allocator.free(redirect_buffer);
17671737
1768 if (options.payload) |payload| try req.writeAll(payload);
1738 var response = try req.receiveHead(redirect_buffer);
17691739
1770 try req.finish();
1771 try req.wait();
1740 const storage = options.response_storage orelse {
1741 const reader = response.reader(&.{});
1742 _ = reader.discardRemaining() catch |err| switch (err) {
1743 error.ReadFailed => return response.bodyErr().?,
1744 };
1745 return .{ .status = response.head.status };
1746 };
17721747
1773 switch (options.response_storage) {
1774 .ignore => {
1775 // Take advantage of request internals to discard the response body
1776 // and make the connection available for another request.
1777 req.response.skip = true;
1778 assert(try req.transferRead(&.{}) == 0); // No buffer is necessary when skipping.
1779 },
1780 .dynamic => |list| {
1781 const max_append_size = options.max_append_size orelse 2 * 1024 * 1024;
1782 try req.reader().readAllArrayList(list, max_append_size);
1783 },
1784 .static => |list| {
1785 const buf = b: {
1786 const buf = list.unusedCapacitySlice();
1787 if (options.max_append_size) |len| {
1788 if (len < buf.len) break :b buf[0..len];
1789 }
1790 break :b buf;
1791 };
1792 list.items.len += try req.reader().readAll(buf);
1793 },
1748 const decompress_buffer: []u8 = switch (response.head.content_encoding) {
1749 .identity => &.{},
1750 .zstd => options.decompress_buffer orelse try client.allocator.alloc(u8, std.compress.zstd.default_window_len),
1751 else => options.decompress_buffer orelse try client.allocator.alloc(u8, 8 * 1024),
1752 };
1753 defer if (options.decompress_buffer == null) client.allocator.free(decompress_buffer);
1754
1755 var decompressor: http.Decompressor = undefined;
1756 const reader = response.readerDecompressing(&decompressor, decompress_buffer);
1757 const list = storage.list;
1758
1759 if (storage.allocator) |allocator| {
1760 reader.appendRemaining(allocator, null, list, storage.append_limit) catch |err| switch (err) {
1761 error.ReadFailed => return response.bodyErr().?,
1762 else => |e| return e,
1763 };
1764 } else {
1765 const buf = storage.append_limit.slice(list.unusedCapacitySlice());
1766 list.items.len += reader.readSliceShort(buf) catch |err| switch (err) {
1767 error.ReadFailed => return response.bodyErr().?,
1768 };
17941769 }
17951770
1796 return .{
1797 .status = req.response.status,
1798 };
1771 return .{ .status = response.head.status };
1772}
1773
1774pub fn sameParentDomain(parent_host: []const u8, child_host: []const u8) bool {
1775 if (!std.ascii.endsWithIgnoreCase(child_host, parent_host)) return false;
1776 if (child_host.len == parent_host.len) return true;
1777 if (parent_host.len > child_host.len) return false;
1778 return child_host[child_host.len - parent_host.len - 1] == '.';
1779}
1780
1781test sameParentDomain {
1782 try testing.expect(!sameParentDomain("foo.com", "bar.com"));
1783 try testing.expect(sameParentDomain("foo.com", "foo.com"));
1784 try testing.expect(sameParentDomain("foo.com", "bar.foo.com"));
1785 try testing.expect(!sameParentDomain("bar.foo.com", "foo.com"));
17991786}
18001787
18011788test {
18021789 _ = Response;
1803 _ = &initDefaultProxies;
18041790}
lib/std/http/Server.zig+385-747
......@@ -1,139 +1,70 @@
1//! Blocking HTTP server implementation.
2//! Handles a single connection's lifecycle.
3
4connection: net.Server.Connection,
5/// Keeps track of whether the Server is ready to accept a new request on the
6/// same connection, and makes invalid API usage cause assertion failures
7/// rather than HTTP protocol violations.
8state: State,
9/// User-provided buffer that must outlive this Server.
10/// Used to store the client's entire HTTP header.
11read_buffer: []u8,
12/// Amount of available data inside read_buffer.
13read_buffer_len: usize,
14/// Index into `read_buffer` of the first byte of the next HTTP request.
15next_request_start: usize,
16
17pub const State = enum {
18 /// The connection is available to be used for the first time, or reused.
19 ready,
20 /// An error occurred in `receiveHead`.
21 receiving_head,
22 /// A Request object has been obtained and from there a Response can be
23 /// opened.
24 received_head,
25 /// The client is uploading something to this Server.
26 receiving_body,
27 /// The connection is eligible for another HTTP request, however the client
28 /// and server did not negotiate a persistent connection.
29 closing,
30};
1//! Handles a single connection lifecycle.
2
3const std = @import("../std.zig");
4const http = std.http;
5const mem = std.mem;
6const Uri = std.Uri;
7const assert = std.debug.assert;
8const testing = std.testing;
9const Writer = std.io.Writer;
10
11const Server = @This();
12
13/// Data from the HTTP server to the HTTP client.
14out: *Writer,
15reader: http.Reader,
3116
3217/// Initialize an HTTP server that can respond to multiple requests on the same
3318/// connection.
19///
20/// The buffer of `in` must be large enough to store the client's entire HTTP
21/// header, otherwise `receiveHead` returns `error.HttpHeadersOversize`.
22///
3423/// The returned `Server` is ready for `receiveHead` to be called.
35pub fn init(connection: net.Server.Connection, read_buffer: []u8) Server {
24pub fn init(in: *std.io.Reader, out: *Writer) Server {
3625 return .{
37 .connection = connection,
38 .state = .ready,
39 .read_buffer = read_buffer,
40 .read_buffer_len = 0,
41 .next_request_start = 0,
26 .reader = .{
27 .in = in,
28 .state = .ready,
29 // Populated when `http.Reader.bodyReader` is called.
30 .interface = undefined,
31 },
32 .out = out,
4233 };
4334}
4435
45pub const ReceiveHeadError = error{
46 /// Client sent too many bytes of HTTP headers.
47 /// The HTTP specification suggests to respond with a 431 status code
48 /// before closing the connection.
49 HttpHeadersOversize,
36pub fn deinit(s: *Server) void {
37 s.reader.restituteHeadBuffer();
38}
39
40pub const ReceiveHeadError = http.Reader.HeadError || error{
5041 /// Client sent headers that did not conform to the HTTP protocol.
42 ///
43 /// To find out more detailed diagnostics, `http.Reader.head_buffer` can be
44 /// passed directly to `Request.Head.parse`.
5145 HttpHeadersInvalid,
52 /// A low level I/O error occurred trying to read the headers.
53 HttpHeadersUnreadable,
54 /// Partial HTTP request was received but the connection was closed before
55 /// fully receiving the headers.
56 HttpRequestTruncated,
57 /// The client sent 0 bytes of headers before closing the stream.
58 /// In other words, a keep-alive connection was finally closed.
59 HttpConnectionClosing,
6046};
6147
62/// The header bytes reference the read buffer that Server was initialized with
63/// and remain alive until the next call to receiveHead.
6448pub fn receiveHead(s: *Server) ReceiveHeadError!Request {
65 assert(s.state == .ready);
66 s.state = .received_head;
67 errdefer s.state = .receiving_head;
68
69 // In case of a reused connection, move the next request's bytes to the
70 // beginning of the buffer.
71 if (s.next_request_start > 0) {
72 if (s.read_buffer_len > s.next_request_start) {
73 rebase(s, 0);
74 } else {
75 s.read_buffer_len = 0;
76 }
77 }
78
79 var hp: http.HeadParser = .{};
80
81 if (s.read_buffer_len > 0) {
82 const bytes = s.read_buffer[0..s.read_buffer_len];
83 const end = hp.feed(bytes);
84 if (hp.state == .finished)
85 return finishReceivingHead(s, end);
86 }
87
88 while (true) {
89 const buf = s.read_buffer[s.read_buffer_len..];
90 if (buf.len == 0)
91 return error.HttpHeadersOversize;
92 const read_n = s.connection.stream.read(buf) catch
93 return error.HttpHeadersUnreadable;
94 if (read_n == 0) {
95 if (s.read_buffer_len > 0) {
96 return error.HttpRequestTruncated;
97 } else {
98 return error.HttpConnectionClosing;
99 }
100 }
101 s.read_buffer_len += read_n;
102 const bytes = buf[0..read_n];
103 const end = hp.feed(bytes);
104 if (hp.state == .finished)
105 return finishReceivingHead(s, s.read_buffer_len - bytes.len + end);
106 }
107}
108
109fn finishReceivingHead(s: *Server, head_end: usize) ReceiveHeadError!Request {
49 try s.reader.receiveHead();
11050 return .{
11151 .server = s,
112 .head_end = head_end,
113 .head = Request.Head.parse(s.read_buffer[0..head_end]) catch
114 return error.HttpHeadersInvalid,
115 .reader_state = undefined,
52 // No need to track the returned error here since users can repeat the
53 // parse with the header buffer to get detailed diagnostics.
54 .head = Request.Head.parse(s.reader.head_buffer) catch return error.HttpHeadersInvalid,
11655 };
11756}
11857
11958pub const Request = struct {
12059 server: *Server,
121 /// Index into Server's read_buffer.
122 head_end: usize,
60 /// Pointers in this struct are invalidated with the next call to
61 /// `receiveHead`.
12362 head: Head,
124 reader_state: union {
125 remaining_content_length: u64,
126 chunk_parser: http.ChunkParser,
127 },
128
129 pub const Compression = union(enum) {
130 pub const DeflateDecompressor = std.compress.zlib.Decompressor(std.io.AnyReader);
131 pub const GzipDecompressor = std.compress.gzip.Decompressor(std.io.AnyReader);
132
133 deflate: std.compress.flate.Decompress,
134 gzip: std.compress.flate.Decompress,
135 zstd: std.compress.zstd.Decompress,
136 none: void,
63 respond_err: ?RespondError = null,
64
65 pub const RespondError = error{
66 /// The request contained an `expect` header with an unrecognized value.
67 HttpExpectationFailed,
13768 };
13869
13970 pub const Head = struct {
......@@ -146,7 +77,6 @@ pub const Request = struct {
14677 transfer_encoding: http.TransferEncoding,
14778 transfer_compression: http.ContentEncoding,
14879 keep_alive: bool,
149 compression: Compression,
15080
15181 pub const ParseError = error{
15282 UnknownHttpMethod,
......@@ -200,7 +130,6 @@ pub const Request = struct {
200130 .@"HTTP/1.0" => false,
201131 .@"HTTP/1.1" => true,
202132 },
203 .compression = .none,
204133 };
205134
206135 while (it.next()) |line| {
......@@ -230,7 +159,7 @@ pub const Request = struct {
230159
231160 const trimmed = mem.trim(u8, header_value, " ");
232161
233 if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
162 if (http.ContentEncoding.fromString(trimmed)) |ce| {
234163 head.transfer_compression = ce;
235164 } else {
236165 return error.HttpTransferEncodingUnsupported;
......@@ -255,7 +184,7 @@ pub const Request = struct {
255184 if (next) |second| {
256185 const trimmed_second = mem.trim(u8, second, " ");
257186
258 if (std.meta.stringToEnum(http.ContentEncoding, trimmed_second)) |transfer| {
187 if (http.ContentEncoding.fromString(trimmed_second)) |transfer| {
259188 if (head.transfer_compression != .identity)
260189 return error.HttpHeadersInvalid; // double compression is not supported
261190 head.transfer_compression = transfer;
......@@ -299,7 +228,8 @@ pub const Request = struct {
299228 };
300229
301230 pub fn iterateHeaders(r: *Request) http.HeaderIterator {
302 return http.HeaderIterator.init(r.server.read_buffer[0..r.head_end]);
231 assert(r.server.reader.state == .received_head);
232 return http.HeaderIterator.init(r.server.reader.head_buffer);
303233 }
304234
305235 test iterateHeaders {
......@@ -310,22 +240,19 @@ pub const Request = struct {
310240 "TRansfer-encoding:\tdeflate, chunked \r\n" ++
311241 "connectioN:\t keep-alive \r\n\r\n";
312242
313 var read_buffer: [500]u8 = undefined;
314 @memcpy(read_buffer[0..request_bytes.len], request_bytes);
315
316243 var server: Server = .{
317 .connection = undefined,
318 .state = .ready,
319 .read_buffer = &read_buffer,
320 .read_buffer_len = request_bytes.len,
321 .next_request_start = 0,
244 .reader = .{
245 .in = undefined,
246 .state = .received_head,
247 .head_buffer = @constCast(request_bytes),
248 .interface = undefined,
249 },
250 .out = undefined,
322251 };
323252
324253 var request: Request = .{
325254 .server = &server,
326 .head_end = request_bytes.len,
327255 .head = undefined,
328 .reader_state = undefined,
329256 };
330257
331258 var it = request.iterateHeaders();
......@@ -384,16 +311,22 @@ pub const Request = struct {
384311 /// no error is surfaced.
385312 ///
386313 /// Asserts status is not `continue`.
387 /// Asserts there are at most 25 extra_headers.
388314 /// Asserts that "\r\n" does not occur in any header name or value.
389315 pub fn respond(
390316 request: *Request,
391317 content: []const u8,
392318 options: RespondOptions,
393 ) Response.WriteError!void {
394 const max_extra_headers = 25;
319 ) ExpectContinueError!void {
320 try respondUnflushed(request, content, options);
321 try request.server.out.flush();
322 }
323
324 pub fn respondUnflushed(
325 request: *Request,
326 content: []const u8,
327 options: RespondOptions,
328 ) ExpectContinueError!void {
395329 assert(options.status != .@"continue");
396 assert(options.extra_headers.len <= max_extra_headers);
397330 if (std.debug.runtime_safety) {
398331 for (options.extra_headers) |header| {
399332 assert(header.name.len != 0);
......@@ -402,6 +335,7 @@ pub const Request = struct {
402335 assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null);
403336 }
404337 }
338 try writeExpectContinue(request);
405339
406340 const transfer_encoding_none = (options.transfer_encoding orelse .chunked) == .none;
407341 const server_keep_alive = !transfer_encoding_none and options.keep_alive;
......@@ -409,130 +343,42 @@ pub const Request = struct {
409343
410344 const phrase = options.reason orelse options.status.phrase() orelse "";
411345
412 var first_buffer: [500]u8 = undefined;
413 var h = std.ArrayListUnmanaged(u8).initBuffer(&first_buffer);
414 if (request.head.expect != null) {
415 // reader() and hence discardBody() above sets expect to null if it
416 // is handled. So the fact that it is not null here means unhandled.
417 h.appendSliceAssumeCapacity("HTTP/1.1 417 Expectation Failed\r\n");
418 if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n");
419 h.appendSliceAssumeCapacity("content-length: 0\r\n\r\n");
420 try request.server.connection.stream.writeAll(h.items);
421 return;
422 }
423 h.fixedWriter().print("{s} {d} {s}\r\n", .{
346 const out = request.server.out;
347 try out.print("{s} {d} {s}\r\n", .{
424348 @tagName(options.version), @intFromEnum(options.status), phrase,
425 }) catch unreachable;
349 });
426350
427351 switch (options.version) {
428 .@"HTTP/1.0" => if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n"),
429 .@"HTTP/1.1" => if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n"),
352 .@"HTTP/1.0" => if (keep_alive) try out.writeAll("connection: keep-alive\r\n"),
353 .@"HTTP/1.1" => if (!keep_alive) try out.writeAll("connection: close\r\n"),
430354 }
431355
432356 if (options.transfer_encoding) |transfer_encoding| switch (transfer_encoding) {
433357 .none => {},
434 .chunked => h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n"),
358 .chunked => try out.writeAll("transfer-encoding: chunked\r\n"),
435359 } else {
436 h.fixedWriter().print("content-length: {d}\r\n", .{content.len}) catch unreachable;
360 try out.print("content-length: {d}\r\n", .{content.len});
437361 }
438362
439 var chunk_header_buffer: [18]u8 = undefined;
440 var iovecs: [max_extra_headers * 4 + 3]std.posix.iovec_const = undefined;
441 var iovecs_len: usize = 0;
442
443 iovecs[iovecs_len] = .{
444 .base = h.items.ptr,
445 .len = h.items.len,
446 };
447 iovecs_len += 1;
448
449363 for (options.extra_headers) |header| {
450 iovecs[iovecs_len] = .{
451 .base = header.name.ptr,
452 .len = header.name.len,
453 };
454 iovecs_len += 1;
455
456 iovecs[iovecs_len] = .{
457 .base = ": ",
458 .len = 2,
459 };
460 iovecs_len += 1;
461
462 if (header.value.len != 0) {
463 iovecs[iovecs_len] = .{
464 .base = header.value.ptr,
465 .len = header.value.len,
466 };
467 iovecs_len += 1;
468 }
469
470 iovecs[iovecs_len] = .{
471 .base = "\r\n",
472 .len = 2,
473 };
474 iovecs_len += 1;
364 var vecs: [4][]const u8 = .{ header.name, ": ", header.value, "\r\n" };
365 try out.writeVecAll(&vecs);
475366 }
476367
477 iovecs[iovecs_len] = .{
478 .base = "\r\n",
479 .len = 2,
480 };
481 iovecs_len += 1;
368 try out.writeAll("\r\n");
482369
483370 if (request.head.method != .HEAD) {
484371 const is_chunked = (options.transfer_encoding orelse .none) == .chunked;
485372 if (is_chunked) {
486 if (content.len > 0) {
487 const chunk_header = std.fmt.bufPrint(
488 &chunk_header_buffer,
489 "{x}\r\n",
490 .{content.len},
491 ) catch unreachable;
492
493 iovecs[iovecs_len] = .{
494 .base = chunk_header.ptr,
495 .len = chunk_header.len,
496 };
497 iovecs_len += 1;
498
499 iovecs[iovecs_len] = .{
500 .base = content.ptr,
501 .len = content.len,
502 };
503 iovecs_len += 1;
504
505 iovecs[iovecs_len] = .{
506 .base = "\r\n",
507 .len = 2,
508 };
509 iovecs_len += 1;
510 }
511
512 iovecs[iovecs_len] = .{
513 .base = "0\r\n\r\n",
514 .len = 5,
515 };
516 iovecs_len += 1;
373 if (content.len > 0) try out.print("{x}\r\n{s}\r\n", .{ content.len, content });
374 try out.writeAll("0\r\n\r\n");
517375 } else if (content.len > 0) {
518 iovecs[iovecs_len] = .{
519 .base = content.ptr,
520 .len = content.len,
521 };
522 iovecs_len += 1;
376 try out.writeAll(content);
523377 }
524378 }
525
526 try request.server.connection.stream.writevAll(iovecs[0..iovecs_len]);
527379 }
528380
529381 pub const RespondStreamingOptions = struct {
530 /// An externally managed slice of memory used to batch bytes before
531 /// sending. `respondStreaming` asserts this is large enough to store
532 /// the full HTTP response head.
533 ///
534 /// Must outlive the returned Response.
535 send_buffer: []u8,
536382 /// If provided, the response will use the content-length header;
537383 /// otherwise it will use transfer-encoding: chunked.
538384 content_length: ?u64 = null,
......@@ -540,254 +386,221 @@ pub const Request = struct {
540386 respond_options: RespondOptions = .{},
541387 };
542388
543 /// The header is buffered but not sent until Response.flush is called.
389 /// The header is not guaranteed to be sent until `BodyWriter.flush` or
390 /// `BodyWriter.end` is called.
544391 ///
545392 /// If the request contains a body and the connection is to be reused,
546393 /// discards the request body, leaving the Server in the `ready` state. If
547394 /// this discarding fails, the connection is marked as not to be reused and
548395 /// no error is surfaced.
549396 ///
550 /// HEAD requests are handled transparently by setting a flag on the
551 /// returned Response to omit the body. However it may be worth noticing
397 /// HEAD requests are handled transparently by setting the
398 /// `BodyWriter.elide` flag on the returned `BodyWriter`, causing
399 /// the response stream to omit the body. However, it may be worth noticing
552400 /// that flag and skipping any expensive work that would otherwise need to
553401 /// be done to satisfy the request.
554402 ///
555 /// Asserts `send_buffer` is large enough to store the entire response header.
556403 /// Asserts status is not `continue`.
557 pub fn respondStreaming(request: *Request, options: RespondStreamingOptions) Response {
404 pub fn respondStreaming(
405 request: *Request,
406 buffer: []u8,
407 options: RespondStreamingOptions,
408 ) ExpectContinueError!http.BodyWriter {
409 try writeExpectContinue(request);
558410 const o = options.respond_options;
559411 assert(o.status != .@"continue");
560412 const transfer_encoding_none = (o.transfer_encoding orelse .chunked) == .none;
561413 const server_keep_alive = !transfer_encoding_none and o.keep_alive;
562414 const keep_alive = request.discardBody(server_keep_alive);
563415 const phrase = o.reason orelse o.status.phrase() orelse "";
416 const out = request.server.out;
564417
565 var h = std.ArrayListUnmanaged(u8).initBuffer(options.send_buffer);
566
567 const elide_body = if (request.head.expect != null) eb: {
568 // reader() and hence discardBody() above sets expect to null if it
569 // is handled. So the fact that it is not null here means unhandled.
570 h.appendSliceAssumeCapacity("HTTP/1.1 417 Expectation Failed\r\n");
571 if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n");
572 h.appendSliceAssumeCapacity("content-length: 0\r\n\r\n");
573 break :eb true;
574 } else eb: {
575 h.fixedWriter().print("{s} {d} {s}\r\n", .{
576 @tagName(o.version), @intFromEnum(o.status), phrase,
577 }) catch unreachable;
578
579 switch (o.version) {
580 .@"HTTP/1.0" => if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n"),
581 .@"HTTP/1.1" => if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n"),
582 }
418 try out.print("{s} {d} {s}\r\n", .{
419 @tagName(o.version), @intFromEnum(o.status), phrase,
420 });
583421
584 if (o.transfer_encoding) |transfer_encoding| switch (transfer_encoding) {
585 .chunked => h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n"),
586 .none => {},
587 } else if (options.content_length) |len| {
588 h.fixedWriter().print("content-length: {d}\r\n", .{len}) catch unreachable;
589 } else {
590 h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n");
591 }
422 switch (o.version) {
423 .@"HTTP/1.0" => if (keep_alive) try out.writeAll("connection: keep-alive\r\n"),
424 .@"HTTP/1.1" => if (!keep_alive) try out.writeAll("connection: close\r\n"),
425 }
592426
593 for (o.extra_headers) |header| {
594 assert(header.name.len != 0);
595 h.appendSliceAssumeCapacity(header.name);
596 h.appendSliceAssumeCapacity(": ");
597 h.appendSliceAssumeCapacity(header.value);
598 h.appendSliceAssumeCapacity("\r\n");
599 }
427 if (o.transfer_encoding) |transfer_encoding| switch (transfer_encoding) {
428 .chunked => try out.writeAll("transfer-encoding: chunked\r\n"),
429 .none => {},
430 } else if (options.content_length) |len| {
431 try out.print("content-length: {d}\r\n", .{len});
432 } else {
433 try out.writeAll("transfer-encoding: chunked\r\n");
434 }
600435
601 h.appendSliceAssumeCapacity("\r\n");
602 break :eb request.head.method == .HEAD;
603 };
436 for (o.extra_headers) |header| {
437 assert(header.name.len != 0);
438 try out.writeAll(header.name);
439 try out.writeAll(": ");
440 try out.writeAll(header.value);
441 try out.writeAll("\r\n");
442 }
604443
605 return .{
606 .stream = request.server.connection.stream,
607 .send_buffer = options.send_buffer,
608 .send_buffer_start = 0,
609 .send_buffer_end = h.items.len,
610 .transfer_encoding = if (o.transfer_encoding) |te| switch (te) {
611 .chunked => .chunked,
612 .none => .none,
613 } else if (options.content_length) |len| .{
614 .content_length = len,
615 } else .chunked,
616 .elide_body = elide_body,
617 .chunk_len = 0,
444 try out.writeAll("\r\n");
445 const elide_body = request.head.method == .HEAD;
446 const state: http.BodyWriter.State = if (o.transfer_encoding) |te| switch (te) {
447 .chunked => .{ .chunked = .init },
448 .none => .none,
449 } else if (options.content_length) |len| .{
450 .content_length = len,
451 } else .{ .chunked = .init };
452
453 return if (elide_body) .{
454 .http_protocol_output = request.server.out,
455 .state = state,
456 .writer = .discarding(buffer),
457 } else .{
458 .http_protocol_output = request.server.out,
459 .state = state,
460 .writer = .{
461 .buffer = buffer,
462 .vtable = switch (state) {
463 .none => &.{
464 .drain = http.BodyWriter.noneDrain,
465 .sendFile = http.BodyWriter.noneSendFile,
466 },
467 .content_length => &.{
468 .drain = http.BodyWriter.contentLengthDrain,
469 .sendFile = http.BodyWriter.contentLengthSendFile,
470 },
471 .chunked => &.{
472 .drain = http.BodyWriter.chunkedDrain,
473 .sendFile = http.BodyWriter.chunkedSendFile,
474 },
475 .end => unreachable,
476 },
477 },
618478 };
619479 }
620480
621 pub const ReadError = net.Stream.ReadError || error{
622 HttpChunkInvalid,
623 HttpHeadersOversize,
481 pub const UpgradeRequest = union(enum) {
482 websocket: ?[]const u8,
483 other: []const u8,
484 none,
624485 };
625486
626 fn read_cl(context: *const anyopaque, buffer: []u8) ReadError!usize {
627 const request: *Request = @ptrCast(@alignCast(@constCast(context)));
628 const s = request.server;
629
630 const remaining_content_length = &request.reader_state.remaining_content_length;
631 if (remaining_content_length.* == 0) {
632 s.state = .ready;
633 return 0;
487 pub fn upgradeRequested(request: *const Request) UpgradeRequest {
488 switch (request.head.version) {
489 .@"HTTP/1.0" => return null,
490 .@"HTTP/1.1" => if (request.head.method != .GET) return null,
634491 }
635 assert(s.state == .receiving_body);
636 const available = try fill(s, request.head_end);
637 const len = @min(remaining_content_length.*, available.len, buffer.len);
638 @memcpy(buffer[0..len], available[0..len]);
639 remaining_content_length.* -= len;
640 s.next_request_start += len;
641 if (remaining_content_length.* == 0)
642 s.state = .ready;
643 return len;
644 }
645492
646 fn fill(s: *Server, head_end: usize) ReadError![]u8 {
647 const available = s.read_buffer[s.next_request_start..s.read_buffer_len];
648 if (available.len > 0) return available;
649 s.next_request_start = head_end;
650 s.read_buffer_len = head_end + try s.connection.stream.read(s.read_buffer[head_end..]);
651 return s.read_buffer[head_end..s.read_buffer_len];
652 }
653
654 fn read_chunked(context: *const anyopaque, buffer: []u8) ReadError!usize {
655 const request: *Request = @ptrCast(@alignCast(@constCast(context)));
656 const s = request.server;
657
658 const cp = &request.reader_state.chunk_parser;
659 const head_end = request.head_end;
660
661 // Protect against returning 0 before the end of stream.
662 var out_end: usize = 0;
663 while (out_end == 0) {
664 switch (cp.state) {
665 .invalid => return 0,
666 .data => {
667 assert(s.state == .receiving_body);
668 const available = try fill(s, head_end);
669 const len = @min(cp.chunk_len, available.len, buffer.len);
670 @memcpy(buffer[0..len], available[0..len]);
671 cp.chunk_len -= len;
672 if (cp.chunk_len == 0)
673 cp.state = .data_suffix;
674 out_end += len;
675 s.next_request_start += len;
676 continue;
677 },
678 else => {
679 assert(s.state == .receiving_body);
680 const available = try fill(s, head_end);
681 const n = cp.feed(available);
682 switch (cp.state) {
683 .invalid => return error.HttpChunkInvalid,
684 .data => {
685 if (cp.chunk_len == 0) {
686 // The next bytes in the stream are trailers,
687 // or \r\n to indicate end of chunked body.
688 //
689 // This function must append the trailers at
690 // head_end so that headers and trailers are
691 // together.
692 //
693 // Since returning 0 would indicate end of
694 // stream, this function must read all the
695 // trailers before returning.
696 if (s.next_request_start > head_end) rebase(s, head_end);
697 var hp: http.HeadParser = .{};
698 {
699 const bytes = s.read_buffer[head_end..s.read_buffer_len];
700 const end = hp.feed(bytes);
701 if (hp.state == .finished) {
702 cp.state = .invalid;
703 s.state = .ready;
704 s.next_request_start = s.read_buffer_len - bytes.len + end;
705 return out_end;
706 }
707 }
708 while (true) {
709 const buf = s.read_buffer[s.read_buffer_len..];
710 if (buf.len == 0)
711 return error.HttpHeadersOversize;
712 const read_n = try s.connection.stream.read(buf);
713 s.read_buffer_len += read_n;
714 const bytes = buf[0..read_n];
715 const end = hp.feed(bytes);
716 if (hp.state == .finished) {
717 cp.state = .invalid;
718 s.state = .ready;
719 s.next_request_start = s.read_buffer_len - bytes.len + end;
720 return out_end;
721 }
722 }
723 }
724 const data = available[n..];
725 const len = @min(cp.chunk_len, data.len, buffer.len);
726 @memcpy(buffer[0..len], data[0..len]);
727 cp.chunk_len -= len;
728 if (cp.chunk_len == 0)
729 cp.state = .data_suffix;
730 out_end += len;
731 s.next_request_start += n + len;
732 continue;
733 },
734 else => continue,
735 }
736 },
493 var sec_websocket_key: ?[]const u8 = null;
494 var upgrade_name: ?[]const u8 = null;
495 var it = request.iterateHeaders();
496 while (it.next()) |header| {
497 if (std.ascii.eqlIgnoreCase(header.name, "sec-websocket-key")) {
498 sec_websocket_key = header.value;
499 } else if (std.ascii.eqlIgnoreCase(header.name, "upgrade")) {
500 upgrade_name = header.value;
737501 }
738502 }
739 return out_end;
503
504 const name = upgrade_name orelse return .none;
505 if (std.ascii.eqlIgnoreCase(name, "websocket")) return .{ .websocket = sec_websocket_key };
506 return .{ .other = name };
740507 }
741508
742 pub const ReaderError = Response.WriteError || error{
743 /// The client sent an expect HTTP header value other than
744 /// "100-continue".
745 HttpExpectationFailed,
509 pub const WebSocketOptions = struct {
510 /// The value from `UpgradeRequest.websocket` (sec-websocket-key header value).
511 key: []const u8,
512 reason: ?[]const u8 = null,
513 extra_headers: []const http.Header = &.{},
746514 };
747515
516 /// The header is not guaranteed to be sent until `WebSocket.flush` is
517 /// called on the returned struct.
518 pub fn respondWebSocket(request: *Request, options: WebSocketOptions) Writer.Error!WebSocket {
519 if (request.head.expect != null) return error.HttpExpectationFailed;
520
521 const out = request.server.out;
522 const version: http.Version = .@"HTTP/1.1";
523 const status: http.Status = .switching_protocols;
524 const phrase = options.reason orelse status.phrase() orelse "";
525
526 assert(request.head.version == version);
527 assert(request.head.method == .GET);
528
529 var sha1 = std.crypto.hash.Sha1.init(.{});
530 sha1.update(options.key);
531 sha1.update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
532 var digest: [std.crypto.hash.Sha1.digest_length]u8 = undefined;
533 sha1.final(&digest);
534 try out.print("{s} {d} {s}\r\n", .{ @tagName(version), @intFromEnum(status), phrase });
535 try out.writeAll("connection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: ");
536 const base64_digest = try out.writableArray(28);
537 assert(std.base64.standard.Encoder.encode(&base64_digest, &digest).len == base64_digest.len);
538 out.advance(base64_digest.len);
539 try out.writeAll("\r\n");
540
541 for (options.extra_headers) |header| {
542 assert(header.name.len != 0);
543 try out.writeAll(header.name);
544 try out.writeAll(": ");
545 try out.writeAll(header.value);
546 try out.writeAll("\r\n");
547 }
548
549 try out.writeAll("\r\n");
550
551 return .{
552 .input = request.server.reader.in,
553 .output = request.server.out,
554 .key = options.key,
555 };
556 }
557
748558 /// In the case that the request contains "expect: 100-continue", this
749559 /// function writes the continuation header, which means it can fail with a
750560 /// write error. After sending the continuation header, it sets the
751561 /// request's expect field to `null`.
752562 ///
753563 /// Asserts that this function is only called once.
754 pub fn reader(request: *Request) ReaderError!std.io.AnyReader {
755 const s = request.server;
756 assert(s.state == .received_head);
757 s.state = .receiving_body;
758 s.next_request_start = request.head_end;
759
760 if (request.head.expect) |expect| {
761 if (mem.eql(u8, expect, "100-continue")) {
762 try request.server.connection.stream.writeAll("HTTP/1.1 100 Continue\r\n\r\n");
763 request.head.expect = null;
764 } else {
765 return error.HttpExpectationFailed;
766 }
767 }
564 ///
565 /// See `readerExpectNone` for an infallible alternative that cannot write
566 /// to the server output stream.
567 pub fn readerExpectContinue(request: *Request, buffer: []u8) ExpectContinueError!*std.io.Reader {
568 const flush = request.head.expect != null;
569 try writeExpectContinue(request);
570 if (flush) try request.server.out.flush();
571 return readerExpectNone(request, buffer);
572 }
768573
769 switch (request.head.transfer_encoding) {
770 .chunked => {
771 request.reader_state = .{ .chunk_parser = http.ChunkParser.init };
772 return .{
773 .readFn = read_chunked,
774 .context = request,
775 };
776 },
777 .none => {
778 request.reader_state = .{
779 .remaining_content_length = request.head.content_length orelse 0,
780 };
781 return .{
782 .readFn = read_cl,
783 .context = request,
784 };
785 },
786 }
574 /// Asserts the expect header is `null`. The caller must handle the
575 /// expectation manually and then set the value to `null` prior to calling
576 /// this function.
577 ///
578 /// Asserts that this function is only called once.
579 pub fn readerExpectNone(request: *Request, buffer: []u8) *std.io.Reader {
580 assert(request.server.reader.state == .received_head);
581 assert(request.head.expect == null);
582 if (!request.head.method.requestHasBody()) return .ending;
583 return request.server.reader.bodyReader(buffer, request.head.transfer_encoding, request.head.content_length);
584 }
585
586 pub const ExpectContinueError = error{
587 /// Failed to write "HTTP/1.1 100 Continue\r\n\r\n" to the stream.
588 WriteFailed,
589 /// The client sent an expect HTTP header value other than
590 /// "100-continue".
591 HttpExpectationFailed,
592 };
593
594 pub fn writeExpectContinue(request: *Request) ExpectContinueError!void {
595 const expect = request.head.expect orelse return;
596 if (!mem.eql(u8, expect, "100-continue")) return error.HttpExpectationFailed;
597 try request.server.out.writeAll("HTTP/1.1 100 Continue\r\n\r\n");
598 request.head.expect = null;
787599 }
788600
789601 /// Returns whether the connection should remain persistent.
790 /// If it would fail, it instead sets the Server state to `receiving_body`
602 ///
603 /// If it would fail, it instead sets the Server state to receiving body
791604 /// and returns false.
792605 fn discardBody(request: *Request, keep_alive: bool) bool {
793606 // Prepare to receive another request on the same connection.
......@@ -798,350 +611,175 @@ pub const Request = struct {
798611 // or the request body.
799612 // If the connection won't be kept alive, then none of this matters
800613 // because the connection will be severed after the response is sent.
801 const s = request.server;
802 if (keep_alive and request.head.keep_alive) switch (s.state) {
614 const r = &request.server.reader;
615 if (keep_alive and request.head.keep_alive) switch (r.state) {
803616 .received_head => {
804 const r = request.reader() catch return false;
805 _ = r.discard() catch return false;
806 assert(s.state == .ready);
617 if (request.head.method.requestHasBody()) {
618 assert(request.head.transfer_encoding != .none or request.head.content_length != null);
619 const reader_interface = request.readerExpectContinue(&.{}) catch return false;
620 _ = reader_interface.discardRemaining() catch return false;
621 assert(r.state == .ready);
622 } else {
623 r.state = .ready;
624 }
807625 return true;
808626 },
809 .receiving_body, .ready => return true,
627 .body_remaining_content_length, .body_remaining_chunk_len, .body_none, .ready => return true,
810628 else => unreachable,
811629 };
812630
813631 // Avoid clobbering the state in case a reading stream already exists.
814 switch (s.state) {
815 .received_head => s.state = .closing,
632 switch (r.state) {
633 .received_head => r.state = .closing,
816634 else => {},
817635 }
818636 return false;
819637 }
820638};
821639
822pub const Response = struct {
823 stream: net.Stream,
824 send_buffer: []u8,
825 /// Index of the first byte in `send_buffer`.
826 /// This is 0 unless a short write happens in `write`.
827 send_buffer_start: usize,
828 /// Index of the last byte + 1 in `send_buffer`.
829 send_buffer_end: usize,
830 /// `null` means transfer-encoding: chunked.
831 /// As a debugging utility, counts down to zero as bytes are written.
832 transfer_encoding: TransferEncoding,
833 elide_body: bool,
834 /// Indicates how much of the end of the `send_buffer` corresponds to a
835 /// chunk. This amount of data will be wrapped by an HTTP chunk header.
836 chunk_len: usize,
837
838 pub const TransferEncoding = union(enum) {
839 /// End of connection signals the end of the stream.
840 none,
841 /// As a debugging utility, counts down to zero as bytes are written.
842 content_length: u64,
843 /// Each chunk is wrapped in a header and trailer.
844 chunked,
640/// See https://tools.ietf.org/html/rfc6455
641pub const WebSocket = struct {
642 key: []const u8,
643 input: *std.io.Reader,
644 output: *Writer,
645
646 pub const Header0 = packed struct(u8) {
647 opcode: Opcode,
648 rsv3: u1 = 0,
649 rsv2: u1 = 0,
650 rsv1: u1 = 0,
651 fin: bool,
845652 };
846653
847 pub const WriteError = net.Stream.WriteError;
848
849 /// When using content-length, asserts that the amount of data sent matches
850 /// the value sent in the header, then calls `flush`.
851 /// Otherwise, transfer-encoding: chunked is being used, and it writes the
852 /// end-of-stream message, then flushes the stream to the system.
853 /// Respects the value of `elide_body` to omit all data after the headers.
854 pub fn end(r: *Response) WriteError!void {
855 switch (r.transfer_encoding) {
856 .content_length => |len| {
857 assert(len == 0); // Trips when end() called before all bytes written.
858 try flush_cl(r);
859 },
860 .none => {
861 try flush_cl(r);
862 },
863 .chunked => {
864 try flush_chunked(r, &.{});
865 },
866 }
867 r.* = undefined;
868 }
869
870 pub const EndChunkedOptions = struct {
871 trailers: []const http.Header = &.{},
654 pub const Header1 = packed struct(u8) {
655 payload_len: enum(u7) {
656 len16 = 126,
657 len64 = 127,
658 _,
659 },
660 mask: bool,
872661 };
873662
874 /// Asserts that the Response is using transfer-encoding: chunked.
875 /// Writes the end-of-stream message and any optional trailers, then
876 /// flushes the stream to the system.
877 /// Respects the value of `elide_body` to omit all data after the headers.
878 /// Asserts there are at most 25 trailers.
879 pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void {
880 assert(r.transfer_encoding == .chunked);
881 try flush_chunked(r, options.trailers);
882 r.* = undefined;
883 }
884
885 /// If using content-length, asserts that writing these bytes to the client
886 /// would not exceed the content-length value sent in the HTTP header.
887 /// May return 0, which does not indicate end of stream. The caller decides
888 /// when the end of stream occurs by calling `end`.
889 pub fn write(r: *Response, bytes: []const u8) WriteError!usize {
890 switch (r.transfer_encoding) {
891 .content_length, .none => return write_cl(r, bytes),
892 .chunked => return write_chunked(r, bytes),
893 }
894 }
895
896 fn write_cl(context: *const anyopaque, bytes: []const u8) WriteError!usize {
897 const r: *Response = @ptrCast(@alignCast(@constCast(context)));
663 pub const Opcode = enum(u4) {
664 continuation = 0,
665 text = 1,
666 binary = 2,
667 connection_close = 8,
668 ping = 9,
669 /// "A Pong frame MAY be sent unsolicited. This serves as a unidirectional
670 /// heartbeat. A response to an unsolicited Pong frame is not expected."
671 pong = 10,
672 _,
673 };
898674
899 var trash: u64 = std.math.maxInt(u64);
900 const len = switch (r.transfer_encoding) {
901 .content_length => |*len| len,
902 else => &trash,
903 };
675 pub const ReadSmallTextMessageError = error{
676 ConnectionClose,
677 UnexpectedOpCode,
678 MessageTooBig,
679 MissingMaskBit,
680 };
904681
905 if (r.elide_body) {
906 len.* -= bytes.len;
907 return bytes.len;
908 }
682 pub const SmallMessage = struct {
683 /// Can be text, binary, or ping.
684 opcode: Opcode,
685 data: []u8,
686 };
909687
910 if (bytes.len + r.send_buffer_end > r.send_buffer.len) {
911 const send_buffer_len = r.send_buffer_end - r.send_buffer_start;
912 var iovecs: [2]std.posix.iovec_const = .{
913 .{
914 .base = r.send_buffer.ptr + r.send_buffer_start,
915 .len = send_buffer_len,
916 },
917 .{
918 .base = bytes.ptr,
919 .len = bytes.len,
920 },
921 };
922 const n = try r.stream.writev(&iovecs);
923
924 if (n >= send_buffer_len) {
925 // It was enough to reset the buffer.
926 r.send_buffer_start = 0;
927 r.send_buffer_end = 0;
928 const bytes_n = n - send_buffer_len;
929 len.* -= bytes_n;
930 return bytes_n;
688 /// Reads the next message from the WebSocket stream, failing if the
689 /// message does not fit into the input buffer. The returned memory points
690 /// into the input buffer and is invalidated on the next read.
691 pub fn readSmallMessage(ws: *WebSocket) ReadSmallTextMessageError!SmallMessage {
692 const in = ws.input;
693 while (true) {
694 const h0 = in.takeStruct(Header0);
695 const h1 = in.takeStruct(Header1);
696
697 switch (h0.opcode) {
698 .text, .binary, .pong, .ping => {},
699 .connection_close => return error.ConnectionClose,
700 .continuation => return error.UnexpectedOpCode,
701 _ => return error.UnexpectedOpCode,
931702 }
932703
933 // It didn't even make it through the existing buffer, let
934 // alone the new bytes provided.
935 r.send_buffer_start += n;
936 return 0;
937 }
938
939 // All bytes can be stored in the remaining space of the buffer.
940 @memcpy(r.send_buffer[r.send_buffer_end..][0..bytes.len], bytes);
941 r.send_buffer_end += bytes.len;
942 len.* -= bytes.len;
943 return bytes.len;
944 }
704 if (!h0.fin) return error.MessageTooBig;
705 if (!h1.mask) return error.MissingMaskBit;
945706
946 fn write_chunked(context: *const anyopaque, bytes: []const u8) WriteError!usize {
947 const r: *Response = @ptrCast(@alignCast(@constCast(context)));
948 assert(r.transfer_encoding == .chunked);
949
950 if (r.elide_body)
951 return bytes.len;
952
953 if (bytes.len + r.send_buffer_end > r.send_buffer.len) {
954 const send_buffer_len = r.send_buffer_end - r.send_buffer_start;
955 const chunk_len = r.chunk_len + bytes.len;
956 var header_buf: [18]u8 = undefined;
957 const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{chunk_len}) catch unreachable;
958
959 var iovecs: [5]std.posix.iovec_const = .{
960 .{
961 .base = r.send_buffer.ptr + r.send_buffer_start,
962 .len = send_buffer_len - r.chunk_len,
963 },
964 .{
965 .base = chunk_header.ptr,
966 .len = chunk_header.len,
967 },
968 .{
969 .base = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len,
970 .len = r.chunk_len,
971 },
972 .{
973 .base = bytes.ptr,
974 .len = bytes.len,
975 },
976 .{
977 .base = "\r\n",
978 .len = 2,
979 },
707 const len: usize = switch (h1.payload_len) {
708 .len16 => try in.takeInt(u16, .big),
709 .len64 => std.math.cast(usize, try in.takeInt(u64, .big)) orelse return error.MessageTooBig,
710 else => @intFromEnum(h1.payload_len),
711 };
712 if (len > in.buffer.len) return error.MessageTooBig;
713 const mask: u32 = @bitCast((try in.takeArray(4)).*);
714 const payload = try in.take(len);
715
716 // Skip pongs.
717 if (h0.opcode == .pong) continue;
718
719 // The last item may contain a partial word of unused data.
720 const floored_len = (payload.len / 4) * 4;
721 const u32_payload: []align(1) u32 = @ptrCast(payload[0..floored_len]);
722 for (u32_payload) |*elem| elem.* ^= mask;
723 const mask_bytes: []const u8 = @ptrCast(&mask);
724 for (payload[floored_len..], mask_bytes[0 .. payload.len - floored_len]) |*leftover, m|
725 leftover.* ^= m;
726
727 return .{
728 .opcode = h0.opcode,
729 .data = payload,
980730 };
981 // TODO make this writev instead of writevAll, which involves
982 // complicating the logic of this function.
983 try r.stream.writevAll(&iovecs);
984 r.send_buffer_start = 0;
985 r.send_buffer_end = 0;
986 r.chunk_len = 0;
987 return bytes.len;
988731 }
989
990 // All bytes can be stored in the remaining space of the buffer.
991 @memcpy(r.send_buffer[r.send_buffer_end..][0..bytes.len], bytes);
992 r.send_buffer_end += bytes.len;
993 r.chunk_len += bytes.len;
994 return bytes.len;
995732 }
996733
997 /// If using content-length, asserts that writing these bytes to the client
998 /// would not exceed the content-length value sent in the HTTP header.
999 pub fn writeAll(r: *Response, bytes: []const u8) WriteError!void {
1000 var index: usize = 0;
1001 while (index < bytes.len) {
1002 index += try write(r, bytes[index..]);
1003 }
734 pub fn writeMessage(ws: *WebSocket, data: []const u8, op: Opcode) Writer.Error!void {
735 try writeMessageVecUnflushed(ws, &.{data}, op);
736 try ws.output.flush();
1004737 }
1005738
1006 /// Sends all buffered data to the client.
1007 /// This is redundant after calling `end`.
1008 /// Respects the value of `elide_body` to omit all data after the headers.
1009 pub fn flush(r: *Response) WriteError!void {
1010 switch (r.transfer_encoding) {
1011 .none, .content_length => return flush_cl(r),
1012 .chunked => return flush_chunked(r, null),
1013 }
739 pub fn writeMessageUnflushed(ws: *WebSocket, data: []const u8, op: Opcode) Writer.Error!void {
740 try writeMessageVecUnflushed(ws, &.{data}, op);
1014741 }
1015742
1016 fn flush_cl(r: *Response) WriteError!void {
1017 try r.stream.writeAll(r.send_buffer[r.send_buffer_start..r.send_buffer_end]);
1018 r.send_buffer_start = 0;
1019 r.send_buffer_end = 0;
743 pub fn writeMessageVec(ws: *WebSocket, data: []const []const u8, op: Opcode) Writer.Error!void {
744 try writeMessageVecUnflushed(ws, data, op);
745 try ws.output.flush();
1020746 }
1021747
1022 fn flush_chunked(r: *Response, end_trailers: ?[]const http.Header) WriteError!void {
1023 const max_trailers = 25;
1024 if (end_trailers) |trailers| assert(trailers.len <= max_trailers);
1025 assert(r.transfer_encoding == .chunked);
1026
1027 const http_headers = r.send_buffer[r.send_buffer_start .. r.send_buffer_end - r.chunk_len];
1028
1029 if (r.elide_body) {
1030 try r.stream.writeAll(http_headers);
1031 r.send_buffer_start = 0;
1032 r.send_buffer_end = 0;
1033 r.chunk_len = 0;
1034 return;
1035 }
1036
1037 var header_buf: [18]u8 = undefined;
1038 const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{r.chunk_len}) catch unreachable;
1039
1040 var iovecs: [max_trailers * 4 + 5]std.posix.iovec_const = undefined;
1041 var iovecs_len: usize = 0;
1042
1043 iovecs[iovecs_len] = .{
1044 .base = http_headers.ptr,
1045 .len = http_headers.len,
748 pub fn writeMessageVecUnflushed(ws: *WebSocket, data: []const []const u8, op: Opcode) Writer.Error!void {
749 const total_len = l: {
750 var total_len: u64 = 0;
751 for (data) |iovec| total_len += iovec.len;
752 break :l total_len;
1046753 };
1047 iovecs_len += 1;
1048
1049 if (r.chunk_len > 0) {
1050 iovecs[iovecs_len] = .{
1051 .base = chunk_header.ptr,
1052 .len = chunk_header.len,
1053 };
1054 iovecs_len += 1;
1055
1056 iovecs[iovecs_len] = .{
1057 .base = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len,
1058 .len = r.chunk_len,
1059 };
1060 iovecs_len += 1;
1061
1062 iovecs[iovecs_len] = .{
1063 .base = "\r\n",
1064 .len = 2,
1065 };
1066 iovecs_len += 1;
1067 }
1068
1069 if (end_trailers) |trailers| {
1070 iovecs[iovecs_len] = .{
1071 .base = "0\r\n",
1072 .len = 3,
1073 };
1074 iovecs_len += 1;
1075
1076 for (trailers) |trailer| {
1077 iovecs[iovecs_len] = .{
1078 .base = trailer.name.ptr,
1079 .len = trailer.name.len,
1080 };
1081 iovecs_len += 1;
1082
1083 iovecs[iovecs_len] = .{
1084 .base = ": ",
1085 .len = 2,
1086 };
1087 iovecs_len += 1;
1088
1089 if (trailer.value.len != 0) {
1090 iovecs[iovecs_len] = .{
1091 .base = trailer.value.ptr,
1092 .len = trailer.value.len,
1093 };
1094 iovecs_len += 1;
1095 }
1096
1097 iovecs[iovecs_len] = .{
1098 .base = "\r\n",
1099 .len = 2,
1100 };
1101 iovecs_len += 1;
1102 }
1103
1104 iovecs[iovecs_len] = .{
1105 .base = "\r\n",
1106 .len = 2,
1107 };
1108 iovecs_len += 1;
754 const out = ws.output;
755 try out.writeStruct(@as(Header0, .{
756 .opcode = op,
757 .fin = true,
758 }));
759 switch (total_len) {
760 0...125 => try out.writeStruct(@as(Header1, .{
761 .payload_len = @enumFromInt(total_len),
762 .mask = false,
763 })),
764 126...0xffff => {
765 try out.writeStruct(@as(Header1, .{
766 .payload_len = .len16,
767 .mask = false,
768 }));
769 try out.writeInt(u16, @intCast(total_len), .big);
770 },
771 else => {
772 try out.writeStruct(@as(Header1, .{
773 .payload_len = .len64,
774 .mask = false,
775 }));
776 try out.writeInt(u64, total_len, .big);
777 },
1109778 }
1110
1111 try r.stream.writevAll(iovecs[0..iovecs_len]);
1112 r.send_buffer_start = 0;
1113 r.send_buffer_end = 0;
1114 r.chunk_len = 0;
779 try out.writeVecAll(data);
1115780 }
1116781
1117 pub fn writer(r: *Response) std.io.AnyWriter {
1118 return .{
1119 .writeFn = switch (r.transfer_encoding) {
1120 .none, .content_length => write_cl,
1121 .chunked => write_chunked,
1122 },
1123 .context = r,
1124 };
782 pub fn flush(ws: *WebSocket) Writer.Error!void {
783 try ws.output.flush();
1125784 }
1126785};
1127
1128fn rebase(s: *Server, index: usize) void {
1129 const leftover = s.read_buffer[s.next_request_start..s.read_buffer_len];
1130 const dest = s.read_buffer[index..][0..leftover.len];
1131 if (leftover.len <= s.next_request_start - index) {
1132 @memcpy(dest, leftover);
1133 } else {
1134 mem.copyBackwards(u8, dest, leftover);
1135 }
1136 s.read_buffer_len = index + leftover.len;
1137}
1138
1139const std = @import("../std.zig");
1140const http = std.http;
1141const mem = std.mem;
1142const net = std.net;
1143const Uri = std.Uri;
1144const assert = std.debug.assert;
1145const testing = std.testing;
1146
1147const Server = @This();
lib/std/http/WebSocket.zig deleted-246
......@@ -1,246 +0,0 @@
1//! See https://tools.ietf.org/html/rfc6455
2
3const builtin = @import("builtin");
4const std = @import("std");
5const WebSocket = @This();
6const assert = std.debug.assert;
7const native_endian = builtin.cpu.arch.endian();
8
9key: []const u8,
10request: *std.http.Server.Request,
11recv_fifo: std.fifo.LinearFifo(u8, .Slice),
12reader: std.io.AnyReader,
13response: std.http.Server.Response,
14/// Number of bytes that have been peeked but not discarded yet.
15outstanding_len: usize,
16
17pub const InitError = error{WebSocketUpgradeMissingKey} ||
18 std.http.Server.Request.ReaderError;
19
20pub fn init(
21 request: *std.http.Server.Request,
22 send_buffer: []u8,
23 recv_buffer: []align(4) u8,
24) InitError!?WebSocket {
25 switch (request.head.version) {
26 .@"HTTP/1.0" => return null,
27 .@"HTTP/1.1" => if (request.head.method != .GET) return null,
28 }
29
30 var sec_websocket_key: ?[]const u8 = null;
31 var upgrade_websocket: bool = false;
32 var it = request.iterateHeaders();
33 while (it.next()) |header| {
34 if (std.ascii.eqlIgnoreCase(header.name, "sec-websocket-key")) {
35 sec_websocket_key = header.value;
36 } else if (std.ascii.eqlIgnoreCase(header.name, "upgrade")) {
37 if (!std.ascii.eqlIgnoreCase(header.value, "websocket"))
38 return null;
39 upgrade_websocket = true;
40 }
41 }
42 if (!upgrade_websocket)
43 return null;
44
45 const key = sec_websocket_key orelse return error.WebSocketUpgradeMissingKey;
46
47 var sha1 = std.crypto.hash.Sha1.init(.{});
48 sha1.update(key);
49 sha1.update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
50 var digest: [std.crypto.hash.Sha1.digest_length]u8 = undefined;
51 sha1.final(&digest);
52 var base64_digest: [28]u8 = undefined;
53 assert(std.base64.standard.Encoder.encode(&base64_digest, &digest).len == base64_digest.len);
54
55 request.head.content_length = std.math.maxInt(u64);
56
57 return .{
58 .key = key,
59 .recv_fifo = std.fifo.LinearFifo(u8, .Slice).init(recv_buffer),
60 .reader = try request.reader(),
61 .response = request.respondStreaming(.{
62 .send_buffer = send_buffer,
63 .respond_options = .{
64 .status = .switching_protocols,
65 .extra_headers = &.{
66 .{ .name = "upgrade", .value = "websocket" },
67 .{ .name = "connection", .value = "upgrade" },
68 .{ .name = "sec-websocket-accept", .value = &base64_digest },
69 },
70 .transfer_encoding = .none,
71 },
72 }),
73 .request = request,
74 .outstanding_len = 0,
75 };
76}
77
78pub const Header0 = packed struct(u8) {
79 opcode: Opcode,
80 rsv3: u1 = 0,
81 rsv2: u1 = 0,
82 rsv1: u1 = 0,
83 fin: bool,
84};
85
86pub const Header1 = packed struct(u8) {
87 payload_len: enum(u7) {
88 len16 = 126,
89 len64 = 127,
90 _,
91 },
92 mask: bool,
93};
94
95pub const Opcode = enum(u4) {
96 continuation = 0,
97 text = 1,
98 binary = 2,
99 connection_close = 8,
100 ping = 9,
101 /// "A Pong frame MAY be sent unsolicited. This serves as a unidirectional
102 /// heartbeat. A response to an unsolicited Pong frame is not expected."
103 pong = 10,
104 _,
105};
106
107pub const ReadSmallTextMessageError = error{
108 ConnectionClose,
109 UnexpectedOpCode,
110 MessageTooBig,
111 MissingMaskBit,
112} || RecvError;
113
114pub const SmallMessage = struct {
115 /// Can be text, binary, or ping.
116 opcode: Opcode,
117 data: []u8,
118};
119
120/// Reads the next message from the WebSocket stream, failing if the message does not fit
121/// into `recv_buffer`.
122pub fn readSmallMessage(ws: *WebSocket) ReadSmallTextMessageError!SmallMessage {
123 while (true) {
124 const header_bytes = (try recv(ws, 2))[0..2];
125 const h0: Header0 = @bitCast(header_bytes[0]);
126 const h1: Header1 = @bitCast(header_bytes[1]);
127
128 switch (h0.opcode) {
129 .text, .binary, .pong, .ping => {},
130 .connection_close => return error.ConnectionClose,
131 .continuation => return error.UnexpectedOpCode,
132 _ => return error.UnexpectedOpCode,
133 }
134
135 if (!h0.fin) return error.MessageTooBig;
136 if (!h1.mask) return error.MissingMaskBit;
137
138 const len: usize = switch (h1.payload_len) {
139 .len16 => try recvReadInt(ws, u16),
140 .len64 => std.math.cast(usize, try recvReadInt(ws, u64)) orelse return error.MessageTooBig,
141 else => @intFromEnum(h1.payload_len),
142 };
143 if (len > ws.recv_fifo.buf.len) return error.MessageTooBig;
144
145 const mask: u32 = @bitCast((try recv(ws, 4))[0..4].*);
146 const payload = try recv(ws, len);
147
148 // Skip pongs.
149 if (h0.opcode == .pong) continue;
150
151 // The last item may contain a partial word of unused data.
152 const floored_len = (payload.len / 4) * 4;
153 const u32_payload: []align(1) u32 = @alignCast(std.mem.bytesAsSlice(u32, payload[0..floored_len]));
154 for (u32_payload) |*elem| elem.* ^= mask;
155 const mask_bytes = std.mem.asBytes(&mask)[0 .. payload.len - floored_len];
156 for (payload[floored_len..], mask_bytes) |*leftover, m| leftover.* ^= m;
157
158 return .{
159 .opcode = h0.opcode,
160 .data = payload,
161 };
162 }
163}
164
165const RecvError = std.http.Server.Request.ReadError || error{EndOfStream};
166
167fn recv(ws: *WebSocket, len: usize) RecvError![]u8 {
168 ws.recv_fifo.discard(ws.outstanding_len);
169 assert(len <= ws.recv_fifo.buf.len);
170 if (len > ws.recv_fifo.count) {
171 const small_buf = ws.recv_fifo.writableSlice(0);
172 const needed = len - ws.recv_fifo.count;
173 const buf = if (small_buf.len >= needed) small_buf else b: {
174 ws.recv_fifo.realign();
175 break :b ws.recv_fifo.writableSlice(0);
176 };
177 const n = try @as(RecvError!usize, @errorCast(ws.reader.readAtLeast(buf, needed)));
178 if (n < needed) return error.EndOfStream;
179 ws.recv_fifo.update(n);
180 }
181 ws.outstanding_len = len;
182 // TODO: improve the std lib API so this cast isn't necessary.
183 return @constCast(ws.recv_fifo.readableSliceOfLen(len));
184}
185
186fn recvReadInt(ws: *WebSocket, comptime I: type) !I {
187 const unswapped: I = @bitCast((try recv(ws, @sizeOf(I)))[0..@sizeOf(I)].*);
188 return switch (native_endian) {
189 .little => @byteSwap(unswapped),
190 .big => unswapped,
191 };
192}
193
194pub const WriteError = std.http.Server.Response.WriteError;
195
196pub fn writeMessage(ws: *WebSocket, message: []const u8, opcode: Opcode) WriteError!void {
197 const iovecs: [1]std.posix.iovec_const = .{
198 .{ .base = message.ptr, .len = message.len },
199 };
200 return writeMessagev(ws, &iovecs, opcode);
201}
202
203pub fn writeMessagev(ws: *WebSocket, message: []const std.posix.iovec_const, opcode: Opcode) WriteError!void {
204 const total_len = l: {
205 var total_len: u64 = 0;
206 for (message) |iovec| total_len += iovec.len;
207 break :l total_len;
208 };
209
210 var header_buf: [2 + 8]u8 = undefined;
211 header_buf[0] = @bitCast(@as(Header0, .{
212 .opcode = opcode,
213 .fin = true,
214 }));
215 const header = switch (total_len) {
216 0...125 => blk: {
217 header_buf[1] = @bitCast(@as(Header1, .{
218 .payload_len = @enumFromInt(total_len),
219 .mask = false,
220 }));
221 break :blk header_buf[0..2];
222 },
223 126...0xffff => blk: {
224 header_buf[1] = @bitCast(@as(Header1, .{
225 .payload_len = .len16,
226 .mask = false,
227 }));
228 std.mem.writeInt(u16, header_buf[2..4], @intCast(total_len), .big);
229 break :blk header_buf[0..4];
230 },
231 else => blk: {
232 header_buf[1] = @bitCast(@as(Header1, .{
233 .payload_len = .len64,
234 .mask = false,
235 }));
236 std.mem.writeInt(u64, header_buf[2..10], total_len, .big);
237 break :blk header_buf[0..10];
238 },
239 };
240
241 const response = &ws.response;
242 try response.writeAll(header);
243 for (message) |iovec|
244 try response.writeAll(iovec.base[0..iovec.len]);
245 try response.flush();
246}
lib/std/http/protocol.zig deleted-464
......@@ -1,464 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const testing = std.testing;
4const mem = std.mem;
5
6const assert = std.debug.assert;
7
8pub const State = enum {
9 invalid,
10
11 // Begin header and trailer parsing states.
12
13 start,
14 seen_n,
15 seen_r,
16 seen_rn,
17 seen_rnr,
18 finished,
19
20 // Begin transfer-encoding: chunked parsing states.
21
22 chunk_head_size,
23 chunk_head_ext,
24 chunk_head_r,
25 chunk_data,
26 chunk_data_suffix,
27 chunk_data_suffix_r,
28
29 /// Returns true if the parser is in a content state (ie. not waiting for more headers).
30 pub fn isContent(self: State) bool {
31 return switch (self) {
32 .invalid, .start, .seen_n, .seen_r, .seen_rn, .seen_rnr => false,
33 .finished, .chunk_head_size, .chunk_head_ext, .chunk_head_r, .chunk_data, .chunk_data_suffix, .chunk_data_suffix_r => true,
34 };
35 }
36};
37
38pub const HeadersParser = struct {
39 state: State = .start,
40 /// A fixed buffer of len `max_header_bytes`.
41 /// Pointers into this buffer are not stable until after a message is complete.
42 header_bytes_buffer: []u8,
43 header_bytes_len: u32,
44 next_chunk_length: u64,
45 /// `false`: headers. `true`: trailers.
46 done: bool,
47
48 /// Initializes the parser with a provided buffer `buf`.
49 pub fn init(buf: []u8) HeadersParser {
50 return .{
51 .header_bytes_buffer = buf,
52 .header_bytes_len = 0,
53 .done = false,
54 .next_chunk_length = 0,
55 };
56 }
57
58 /// Reinitialize the parser.
59 /// Asserts the parser is in the "done" state.
60 pub fn reset(hp: *HeadersParser) void {
61 assert(hp.done);
62 hp.* = .{
63 .state = .start,
64 .header_bytes_buffer = hp.header_bytes_buffer,
65 .header_bytes_len = 0,
66 .done = false,
67 .next_chunk_length = 0,
68 };
69 }
70
71 pub fn get(hp: HeadersParser) []u8 {
72 return hp.header_bytes_buffer[0..hp.header_bytes_len];
73 }
74
75 pub fn findHeadersEnd(r: *HeadersParser, bytes: []const u8) u32 {
76 var hp: std.http.HeadParser = .{
77 .state = switch (r.state) {
78 .start => .start,
79 .seen_n => .seen_n,
80 .seen_r => .seen_r,
81 .seen_rn => .seen_rn,
82 .seen_rnr => .seen_rnr,
83 .finished => .finished,
84 else => unreachable,
85 },
86 };
87 const result = hp.feed(bytes);
88 r.state = switch (hp.state) {
89 .start => .start,
90 .seen_n => .seen_n,
91 .seen_r => .seen_r,
92 .seen_rn => .seen_rn,
93 .seen_rnr => .seen_rnr,
94 .finished => .finished,
95 };
96 return @intCast(result);
97 }
98
99 pub fn findChunkedLen(r: *HeadersParser, bytes: []const u8) u32 {
100 var cp: std.http.ChunkParser = .{
101 .state = switch (r.state) {
102 .chunk_head_size => .head_size,
103 .chunk_head_ext => .head_ext,
104 .chunk_head_r => .head_r,
105 .chunk_data => .data,
106 .chunk_data_suffix => .data_suffix,
107 .chunk_data_suffix_r => .data_suffix_r,
108 .invalid => .invalid,
109 else => unreachable,
110 },
111 .chunk_len = r.next_chunk_length,
112 };
113 const result = cp.feed(bytes);
114 r.state = switch (cp.state) {
115 .head_size => .chunk_head_size,
116 .head_ext => .chunk_head_ext,
117 .head_r => .chunk_head_r,
118 .data => .chunk_data,
119 .data_suffix => .chunk_data_suffix,
120 .data_suffix_r => .chunk_data_suffix_r,
121 .invalid => .invalid,
122 };
123 r.next_chunk_length = cp.chunk_len;
124 return @intCast(result);
125 }
126
127 /// Returns whether or not the parser has finished parsing a complete
128 /// message. A message is only complete after the entire body has been read
129 /// and any trailing headers have been parsed.
130 pub fn isComplete(r: *HeadersParser) bool {
131 return r.done and r.state == .finished;
132 }
133
134 pub const CheckCompleteHeadError = error{HttpHeadersOversize};
135
136 /// Pushes `in` into the parser. Returns the number of bytes consumed by
137 /// the header. Any header bytes are appended to `header_bytes_buffer`.
138 pub fn checkCompleteHead(hp: *HeadersParser, in: []const u8) CheckCompleteHeadError!u32 {
139 if (hp.state.isContent()) return 0;
140
141 const i = hp.findHeadersEnd(in);
142 const data = in[0..i];
143 if (hp.header_bytes_len + data.len > hp.header_bytes_buffer.len)
144 return error.HttpHeadersOversize;
145
146 @memcpy(hp.header_bytes_buffer[hp.header_bytes_len..][0..data.len], data);
147 hp.header_bytes_len += @intCast(data.len);
148
149 return i;
150 }
151
152 pub const ReadError = error{
153 HttpChunkInvalid,
154 };
155
156 /// Reads the body of the message into `buffer`. Returns the number of
157 /// bytes placed in the buffer.
158 ///
159 /// If `skip` is true, the buffer will be unused and the body will be skipped.
160 ///
161 /// See `std.http.Client.Connection for an example of `conn`.
162 pub fn read(r: *HeadersParser, conn: anytype, buffer: []u8, skip: bool) !usize {
163 assert(r.state.isContent());
164 if (r.done) return 0;
165
166 var out_index: usize = 0;
167 while (true) {
168 switch (r.state) {
169 .invalid, .start, .seen_n, .seen_r, .seen_rn, .seen_rnr => unreachable,
170 .finished => {
171 const data_avail = r.next_chunk_length;
172
173 if (skip) {
174 conn.fill() catch |err| switch (err) {
175 error.EndOfStream => {
176 r.done = true;
177 return 0;
178 },
179 else => |e| return e,
180 };
181
182 const nread = @min(conn.peek().len, data_avail);
183 conn.drop(@intCast(nread));
184 r.next_chunk_length -= nread;
185
186 if (r.next_chunk_length == 0 or nread == 0) r.done = true;
187
188 return out_index;
189 } else if (out_index < buffer.len) {
190 const out_avail = buffer.len - out_index;
191
192 const can_read = @as(usize, @intCast(@min(data_avail, out_avail)));
193 const nread = try conn.read(buffer[0..can_read]);
194 r.next_chunk_length -= nread;
195
196 if (r.next_chunk_length == 0 or nread == 0) r.done = true;
197
198 return nread;
199 } else {
200 return out_index;
201 }
202 },
203 .chunk_data_suffix, .chunk_data_suffix_r, .chunk_head_size, .chunk_head_ext, .chunk_head_r => {
204 conn.fill() catch |err| switch (err) {
205 error.EndOfStream => {
206 r.done = true;
207 return 0;
208 },
209 else => |e| return e,
210 };
211
212 const i = r.findChunkedLen(conn.peek());
213 conn.drop(@intCast(i));
214
215 switch (r.state) {
216 .invalid => return error.HttpChunkInvalid,
217 .chunk_data => if (r.next_chunk_length == 0) {
218 if (std.mem.eql(u8, conn.peek(), "\r\n")) {
219 r.state = .finished;
220 conn.drop(2);
221 } else {
222 // The trailer section is formatted identically
223 // to the header section.
224 r.state = .seen_rn;
225 }
226 r.done = true;
227
228 return out_index;
229 },
230 else => return out_index,
231 }
232
233 continue;
234 },
235 .chunk_data => {
236 const data_avail = r.next_chunk_length;
237 const out_avail = buffer.len - out_index;
238
239 if (skip) {
240 conn.fill() catch |err| switch (err) {
241 error.EndOfStream => {
242 r.done = true;
243 return 0;
244 },
245 else => |e| return e,
246 };
247
248 const nread = @min(conn.peek().len, data_avail);
249 conn.drop(@intCast(nread));
250 r.next_chunk_length -= nread;
251 } else if (out_avail > 0) {
252 const can_read: usize = @intCast(@min(data_avail, out_avail));
253 const nread = try conn.read(buffer[out_index..][0..can_read]);
254 r.next_chunk_length -= nread;
255 out_index += nread;
256 }
257
258 if (r.next_chunk_length == 0) {
259 r.state = .chunk_data_suffix;
260 continue;
261 }
262
263 return out_index;
264 },
265 }
266 }
267 }
268};
269
270inline fn int16(array: *const [2]u8) u16 {
271 return @as(u16, @bitCast(array.*));
272}
273
274inline fn int24(array: *const [3]u8) u24 {
275 return @as(u24, @bitCast(array.*));
276}
277
278inline fn int32(array: *const [4]u8) u32 {
279 return @as(u32, @bitCast(array.*));
280}
281
282inline fn intShift(comptime T: type, x: anytype) T {
283 switch (@import("builtin").cpu.arch.endian()) {
284 .little => return @as(T, @truncate(x >> (@bitSizeOf(@TypeOf(x)) - @bitSizeOf(T)))),
285 .big => return @as(T, @truncate(x)),
286 }
287}
288
289/// A buffered (and peekable) Connection.
290const MockBufferedConnection = struct {
291 pub const buffer_size = 0x2000;
292
293 conn: std.io.FixedBufferStream([]const u8),
294 buf: [buffer_size]u8 = undefined,
295 start: u16 = 0,
296 end: u16 = 0,
297
298 pub fn fill(conn: *MockBufferedConnection) ReadError!void {
299 if (conn.end != conn.start) return;
300
301 const nread = try conn.conn.read(conn.buf[0..]);
302 if (nread == 0) return error.EndOfStream;
303 conn.start = 0;
304 conn.end = @as(u16, @truncate(nread));
305 }
306
307 pub fn peek(conn: *MockBufferedConnection) []const u8 {
308 return conn.buf[conn.start..conn.end];
309 }
310
311 pub fn drop(conn: *MockBufferedConnection, num: u16) void {
312 conn.start += num;
313 }
314
315 pub fn readAtLeast(conn: *MockBufferedConnection, buffer: []u8, len: usize) ReadError!usize {
316 var out_index: u16 = 0;
317 while (out_index < len) {
318 const available = conn.end - conn.start;
319 const left = buffer.len - out_index;
320
321 if (available > 0) {
322 const can_read = @as(u16, @truncate(@min(available, left)));
323
324 @memcpy(buffer[out_index..][0..can_read], conn.buf[conn.start..][0..can_read]);
325 out_index += can_read;
326 conn.start += can_read;
327
328 continue;
329 }
330
331 if (left > conn.buf.len) {
332 // skip the buffer if the output is large enough
333 return conn.conn.read(buffer[out_index..]);
334 }
335
336 try conn.fill();
337 }
338
339 return out_index;
340 }
341
342 pub fn read(conn: *MockBufferedConnection, buffer: []u8) ReadError!usize {
343 return conn.readAtLeast(buffer, 1);
344 }
345
346 pub const ReadError = std.io.FixedBufferStream([]const u8).ReadError || error{EndOfStream};
347 pub const Reader = std.io.GenericReader(*MockBufferedConnection, ReadError, read);
348
349 pub fn reader(conn: *MockBufferedConnection) Reader {
350 return Reader{ .context = conn };
351 }
352
353 pub fn writeAll(conn: *MockBufferedConnection, buffer: []const u8) WriteError!void {
354 return conn.conn.writeAll(buffer);
355 }
356
357 pub fn write(conn: *MockBufferedConnection, buffer: []const u8) WriteError!usize {
358 return conn.conn.write(buffer);
359 }
360
361 pub const WriteError = std.io.FixedBufferStream([]const u8).WriteError;
362 pub const Writer = std.io.GenericWriter(*MockBufferedConnection, WriteError, write);
363
364 pub fn writer(conn: *MockBufferedConnection) Writer {
365 return Writer{ .context = conn };
366 }
367};
368
369test "HeadersParser.read length" {
370 // mock BufferedConnection for read
371 var headers_buf: [256]u8 = undefined;
372
373 var r = HeadersParser.init(&headers_buf);
374 const data = "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 5\r\n\r\nHello";
375
376 var conn: MockBufferedConnection = .{
377 .conn = std.io.fixedBufferStream(data),
378 };
379
380 while (true) { // read headers
381 try conn.fill();
382
383 const nchecked = try r.checkCompleteHead(conn.peek());
384 conn.drop(@intCast(nchecked));
385
386 if (r.state.isContent()) break;
387 }
388
389 var buf: [8]u8 = undefined;
390
391 r.next_chunk_length = 5;
392 const len = try r.read(&conn, &buf, false);
393 try std.testing.expectEqual(@as(usize, 5), len);
394 try std.testing.expectEqualStrings("Hello", buf[0..len]);
395
396 try std.testing.expectEqualStrings("GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 5\r\n\r\n", r.get());
397}
398
399test "HeadersParser.read chunked" {
400 // mock BufferedConnection for read
401
402 var headers_buf: [256]u8 = undefined;
403 var r = HeadersParser.init(&headers_buf);
404 const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n2\r\nHe\r\n2\r\nll\r\n1\r\no\r\n0\r\n\r\n";
405
406 var conn: MockBufferedConnection = .{
407 .conn = std.io.fixedBufferStream(data),
408 };
409
410 while (true) { // read headers
411 try conn.fill();
412
413 const nchecked = try r.checkCompleteHead(conn.peek());
414 conn.drop(@intCast(nchecked));
415
416 if (r.state.isContent()) break;
417 }
418 var buf: [8]u8 = undefined;
419
420 r.state = .chunk_head_size;
421 const len = try r.read(&conn, &buf, false);
422 try std.testing.expectEqual(@as(usize, 5), len);
423 try std.testing.expectEqualStrings("Hello", buf[0..len]);
424
425 try std.testing.expectEqualStrings("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n", r.get());
426}
427
428test "HeadersParser.read chunked trailer" {
429 // mock BufferedConnection for read
430
431 var headers_buf: [256]u8 = undefined;
432 var r = HeadersParser.init(&headers_buf);
433 const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n2\r\nHe\r\n2\r\nll\r\n1\r\no\r\n0\r\nContent-Type: text/plain\r\n\r\n";
434
435 var conn: MockBufferedConnection = .{
436 .conn = std.io.fixedBufferStream(data),
437 };
438
439 while (true) { // read headers
440 try conn.fill();
441
442 const nchecked = try r.checkCompleteHead(conn.peek());
443 conn.drop(@intCast(nchecked));
444
445 if (r.state.isContent()) break;
446 }
447 var buf: [8]u8 = undefined;
448
449 r.state = .chunk_head_size;
450 const len = try r.read(&conn, &buf, false);
451 try std.testing.expectEqual(@as(usize, 5), len);
452 try std.testing.expectEqualStrings("Hello", buf[0..len]);
453
454 while (true) { // read headers
455 try conn.fill();
456
457 const nchecked = try r.checkCompleteHead(conn.peek());
458 conn.drop(@intCast(nchecked));
459
460 if (r.state.isContent()) break;
461 }
462
463 try std.testing.expectEqualStrings("GET / HTTP/1.1\r\nHost: localhost\r\n\r\nContent-Type: text/plain\r\n\r\n", r.get());
464}
lib/std/http/test.zig+272-322
......@@ -10,32 +10,33 @@ const expectError = std.testing.expectError;
1010
1111test "trailers" {
1212 const test_server = try createTestServer(struct {
13 fn run(net_server: *std.net.Server) anyerror!void {
14 var header_buffer: [1024]u8 = undefined;
13 fn run(test_server: *TestServer) anyerror!void {
14 const net_server = &test_server.net_server;
15 var recv_buffer: [1024]u8 = undefined;
16 var send_buffer: [1024]u8 = undefined;
1517 var remaining: usize = 1;
1618 while (remaining != 0) : (remaining -= 1) {
17 const conn = try net_server.accept();
18 defer conn.stream.close();
19 const connection = try net_server.accept();
20 defer connection.stream.close();
1921
20 var server = http.Server.init(conn, &header_buffer);
22 var connection_br = connection.stream.reader(&recv_buffer);
23 var connection_bw = connection.stream.writer(&send_buffer);
24 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);
2125
22 try expectEqual(.ready, server.state);
26 try expectEqual(.ready, server.reader.state);
2327 var request = try server.receiveHead();
2428 try serve(&request);
25 try expectEqual(.ready, server.state);
29 try expectEqual(.ready, server.reader.state);
2630 }
2731 }
2832
2933 fn serve(request: *http.Server.Request) !void {
3034 try expectEqualStrings(request.head.target, "/trailer");
3135
32 var send_buffer: [1024]u8 = undefined;
33 var response = request.respondStreaming(.{
34 .send_buffer = &send_buffer,
35 });
36 try response.writeAll("Hello, ");
36 var response = try request.respondStreaming(&.{}, .{});
37 try response.writer.writeAll("Hello, ");
3738 try response.flush();
38 try response.writeAll("World!\n");
39 try response.writer.writeAll("World!\n");
3940 try response.flush();
4041 try response.endChunked(.{
4142 .trailers = &.{
......@@ -58,34 +59,33 @@ test "trailers" {
5859 const uri = try std.Uri.parse(location);
5960
6061 {
61 var server_header_buffer: [1024]u8 = undefined;
62 var req = try client.open(.GET, uri, .{
63 .server_header_buffer = &server_header_buffer,
64 });
62 var req = try client.request(.GET, uri, .{});
6563 defer req.deinit();
6664
67 try req.send();
68 try req.wait();
65 try req.sendBodiless();
66 var response = try req.receiveHead(&.{});
6967
70 const body = try req.reader().readAllAlloc(gpa, 8192);
68 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
7169 defer gpa.free(body);
7270
7371 try expectEqualStrings("Hello, World!\n", body);
7472
75 var it = req.response.iterateHeaders();
7673 {
74 var it = response.head.iterateHeaders();
7775 const header = it.next().?;
7876 try expect(!it.is_trailer);
7977 try expectEqualStrings("transfer-encoding", header.name);
8078 try expectEqualStrings("chunked", header.value);
79 try expectEqual(null, it.next());
8180 }
8281 {
82 var it = response.iterateTrailers();
8383 const header = it.next().?;
8484 try expect(it.is_trailer);
8585 try expectEqualStrings("X-Checksum", header.name);
8686 try expectEqualStrings("aaaa", header.value);
87 try expectEqual(null, it.next());
8788 }
88 try expectEqual(null, it.next());
8989 }
9090
9191 // connection has been kept alive
......@@ -94,19 +94,24 @@ test "trailers" {
9494
9595test "HTTP server handles a chunked transfer coding request" {
9696 const test_server = try createTestServer(struct {
97 fn run(net_server: *std.net.Server) !void {
98 var header_buffer: [8192]u8 = undefined;
99 const conn = try net_server.accept();
100 defer conn.stream.close();
101
102 var server = http.Server.init(conn, &header_buffer);
97 fn run(test_server: *TestServer) anyerror!void {
98 const net_server = &test_server.net_server;
99 var recv_buffer: [8192]u8 = undefined;
100 var send_buffer: [500]u8 = undefined;
101 const connection = try net_server.accept();
102 defer connection.stream.close();
103
104 var connection_br = connection.stream.reader(&recv_buffer);
105 var connection_bw = connection.stream.writer(&send_buffer);
106 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);
103107 var request = try server.receiveHead();
104108
105109 try expect(request.head.transfer_encoding == .chunked);
106110
107111 var buf: [128]u8 = undefined;
108 const n = try (try request.reader()).readAll(&buf);
109 try expect(mem.eql(u8, buf[0..n], "ABCD"));
112 var br = try request.readerExpectContinue(&.{});
113 const n = try br.readSliceShort(&buf);
114 try expectEqualStrings("ABCD", buf[0..n]);
110115
111116 try request.respond("message from server!\n", .{
112117 .extra_headers = &.{
......@@ -154,16 +159,20 @@ test "HTTP server handles a chunked transfer coding request" {
154159
155160test "echo content server" {
156161 const test_server = try createTestServer(struct {
157 fn run(net_server: *std.net.Server) anyerror!void {
158 var read_buffer: [1024]u8 = undefined;
162 fn run(test_server: *TestServer) anyerror!void {
163 const net_server = &test_server.net_server;
164 var recv_buffer: [1024]u8 = undefined;
165 var send_buffer: [100]u8 = undefined;
159166
160 accept: while (true) {
161 const conn = try net_server.accept();
162 defer conn.stream.close();
167 accept: while (!test_server.shutting_down) {
168 const connection = try net_server.accept();
169 defer connection.stream.close();
163170
164 var http_server = http.Server.init(conn, &read_buffer);
171 var connection_br = connection.stream.reader(&recv_buffer);
172 var connection_bw = connection.stream.writer(&send_buffer);
173 var http_server = http.Server.init(connection_br.interface(), &connection_bw.interface);
165174
166 while (http_server.state == .ready) {
175 while (http_server.reader.state == .ready) {
167176 var request = http_server.receiveHead() catch |err| switch (err) {
168177 error.HttpConnectionClosing => continue :accept,
169178 else => |e| return e,
......@@ -173,7 +182,7 @@ test "echo content server" {
173182 }
174183 if (request.head.expect) |expect_header_value| {
175184 if (mem.eql(u8, expect_header_value, "garbage")) {
176 try expectError(error.HttpExpectationFailed, request.reader());
185 try expectError(error.HttpExpectationFailed, request.readerExpectContinue(&.{}));
177186 try request.respond("", .{ .keep_alive = false });
178187 continue;
179188 }
......@@ -195,16 +204,14 @@ test "echo content server" {
195204 // request.head.target,
196205 //});
197206
198 const body = try (try request.reader()).readAllAlloc(std.testing.allocator, 8192);
207 const body = try (try request.readerExpectContinue(&.{})).allocRemaining(std.testing.allocator, .limited(8192));
199208 defer std.testing.allocator.free(body);
200209
201210 try expect(mem.startsWith(u8, request.head.target, "/echo-content"));
202211 try expectEqualStrings("Hello, World!\n", body);
203212 try expectEqualStrings("text/plain", request.head.content_type.?);
204213
205 var send_buffer: [100]u8 = undefined;
206 var response = request.respondStreaming(.{
207 .send_buffer = &send_buffer,
214 var response = try request.respondStreaming(&.{}, .{
208215 .content_length = switch (request.head.transfer_encoding) {
209216 .chunked => null,
210217 .none => len: {
......@@ -213,9 +220,8 @@ test "echo content server" {
213220 },
214221 },
215222 });
216
217223 try response.flush(); // Test an early flush to send the HTTP headers before the body.
218 const w = response.writer();
224 const w = &response.writer;
219225 try w.writeAll("Hello, ");
220226 try w.writeAll("World!\n");
221227 try response.end();
......@@ -241,35 +247,36 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
241247 // In this case, the response is expected to stream until the connection is
242248 // closed, indicating the end of the body.
243249 const test_server = try createTestServer(struct {
244 fn run(net_server: *std.net.Server) anyerror!void {
245 var header_buffer: [1000]u8 = undefined;
250 fn run(test_server: *TestServer) anyerror!void {
251 const net_server = &test_server.net_server;
252 var recv_buffer: [1000]u8 = undefined;
253 var send_buffer: [500]u8 = undefined;
246254 var remaining: usize = 1;
247255 while (remaining != 0) : (remaining -= 1) {
248 const conn = try net_server.accept();
249 defer conn.stream.close();
256 const connection = try net_server.accept();
257 defer connection.stream.close();
250258
251 var server = http.Server.init(conn, &header_buffer);
259 var connection_br = connection.stream.reader(&recv_buffer);
260 var connection_bw = connection.stream.writer(&send_buffer);
261 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);
252262
253 try expectEqual(.ready, server.state);
263 try expectEqual(.ready, server.reader.state);
254264 var request = try server.receiveHead();
255265 try expectEqualStrings(request.head.target, "/foo");
256 var send_buffer: [500]u8 = undefined;
257 var response = request.respondStreaming(.{
258 .send_buffer = &send_buffer,
266 var buf: [30]u8 = undefined;
267 var response = try request.respondStreaming(&buf, .{
259268 .respond_options = .{
260269 .transfer_encoding = .none,
261270 },
262271 });
263 var total: usize = 0;
272 const w = &response.writer;
264273 for (0..500) |i| {
265 var buf: [30]u8 = undefined;
266 const line = try std.fmt.bufPrint(&buf, "{d}, ah ha ha!\n", .{i});
267 try response.writeAll(line);
268 total += line.len;
274 try w.print("{d}, ah ha ha!\n", .{i});
269275 }
270 try expectEqual(7390, total);
276 try expectEqual(7390, w.count);
277 try w.flush();
271278 try response.end();
272 try expectEqual(.closing, server.state);
279 try expectEqual(.closing, server.reader.state);
273280 }
274281 }
275282 });
......@@ -308,15 +315,20 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
308315
309316test "receiving arbitrary http headers from the client" {
310317 const test_server = try createTestServer(struct {
311 fn run(net_server: *std.net.Server) anyerror!void {
312 var read_buffer: [666]u8 = undefined;
318 fn run(test_server: *TestServer) anyerror!void {
319 const net_server = &test_server.net_server;
320 var recv_buffer: [666]u8 = undefined;
321 var send_buffer: [777]u8 = undefined;
313322 var remaining: usize = 1;
314323 while (remaining != 0) : (remaining -= 1) {
315 const conn = try net_server.accept();
316 defer conn.stream.close();
324 const connection = try net_server.accept();
325 defer connection.stream.close();
326
327 var connection_br = connection.stream.reader(&recv_buffer);
328 var connection_bw = connection.stream.writer(&send_buffer);
329 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);
317330
318 var server = http.Server.init(conn, &read_buffer);
319 try expectEqual(.ready, server.state);
331 try expectEqual(.ready, server.reader.state);
320332 var request = try server.receiveHead();
321333 try expectEqualStrings("/bar", request.head.target);
322334 var it = request.iterateHeaders();
......@@ -368,19 +380,21 @@ test "general client/server API coverage" {
368380 return error.SkipZigTest;
369381 }
370382
371 const global = struct {
372 var handle_new_requests = true;
373 };
374383 const test_server = try createTestServer(struct {
375 fn run(net_server: *std.net.Server) anyerror!void {
376 var client_header_buffer: [1024]u8 = undefined;
377 outer: while (global.handle_new_requests) {
384 fn run(test_server: *TestServer) anyerror!void {
385 const net_server = &test_server.net_server;
386 var recv_buffer: [1024]u8 = undefined;
387 var send_buffer: [100]u8 = undefined;
388
389 outer: while (!test_server.shutting_down) {
378390 var connection = try net_server.accept();
379391 defer connection.stream.close();
380392
381 var http_server = http.Server.init(connection, &client_header_buffer);
393 var connection_br = connection.stream.reader(&recv_buffer);
394 var connection_bw = connection.stream.writer(&send_buffer);
395 var http_server = http.Server.init(connection_br.interface(), &connection_bw.interface);
382396
383 while (http_server.state == .ready) {
397 while (http_server.reader.state == .ready) {
384398 var request = http_server.receiveHead() catch |err| switch (err) {
385399 error.HttpConnectionClosing => continue :outer,
386400 else => |e| return e,
......@@ -399,14 +413,11 @@ test "general client/server API coverage" {
399413 });
400414
401415 const gpa = std.testing.allocator;
402 const body = try (try request.reader()).readAllAlloc(gpa, 8192);
416 const body = try (try request.readerExpectContinue(&.{})).allocRemaining(gpa, .limited(8192));
403417 defer gpa.free(body);
404418
405 var send_buffer: [100]u8 = undefined;
406
407419 if (mem.startsWith(u8, request.head.target, "/get")) {
408 var response = request.respondStreaming(.{
409 .send_buffer = &send_buffer,
420 var response = try request.respondStreaming(&.{}, .{
410421 .content_length = if (mem.indexOf(u8, request.head.target, "?chunked") == null)
411422 14
412423 else
......@@ -417,20 +428,19 @@ test "general client/server API coverage" {
417428 },
418429 },
419430 });
420 const w = response.writer();
431 const w = &response.writer;
421432 try w.writeAll("Hello, ");
422433 try w.writeAll("World!\n");
423434 try response.end();
424435 // Writing again would cause an assertion failure.
425436 } else if (mem.startsWith(u8, request.head.target, "/large")) {
426 var response = request.respondStreaming(.{
427 .send_buffer = &send_buffer,
437 var response = try request.respondStreaming(&.{}, .{
428438 .content_length = 14 * 1024 + 14 * 10,
429439 });
430440
431441 try response.flush(); // Test an early flush to send the HTTP headers before the body.
432442
433 const w = response.writer();
443 const w = &response.writer;
434444
435445 var i: u32 = 0;
436446 while (i < 5) : (i += 1) {
......@@ -446,8 +456,7 @@ test "general client/server API coverage" {
446456
447457 try response.end();
448458 } else if (mem.eql(u8, request.head.target, "/redirect/1")) {
449 var response = request.respondStreaming(.{
450 .send_buffer = &send_buffer,
459 var response = try request.respondStreaming(&.{}, .{
451460 .respond_options = .{
452461 .status = .found,
453462 .extra_headers = &.{
......@@ -456,7 +465,7 @@ test "general client/server API coverage" {
456465 },
457466 });
458467
459 const w = response.writer();
468 const w = &response.writer;
460469 try w.writeAll("Hello, ");
461470 try w.writeAll("Redirected!\n");
462471 try response.end();
......@@ -524,17 +533,13 @@ test "general client/server API coverage" {
524533 return s.listen_address.in.getPort();
525534 }
526535 });
527 defer {
528 global.handle_new_requests = false;
529 test_server.destroy();
530 }
536 defer test_server.destroy();
531537
532538 const log = std.log.scoped(.client);
533539
534540 const gpa = std.testing.allocator;
535541 var client: http.Client = .{ .allocator = gpa };
536 errdefer client.deinit();
537 // defer client.deinit(); handled below
542 defer client.deinit();
538543
539544 const port = test_server.port();
540545
......@@ -544,20 +549,18 @@ test "general client/server API coverage" {
544549 const uri = try std.Uri.parse(location);
545550
546551 log.info("{s}", .{location});
547 var server_header_buffer: [1024]u8 = undefined;
548 var req = try client.open(.GET, uri, .{
549 .server_header_buffer = &server_header_buffer,
550 });
552 var redirect_buffer: [1024]u8 = undefined;
553 var req = try client.request(.GET, uri, .{});
551554 defer req.deinit();
552555
553 try req.send();
554 try req.wait();
556 try req.sendBodiless();
557 var response = try req.receiveHead(&redirect_buffer);
555558
556 const body = try req.reader().readAllAlloc(gpa, 8192);
559 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
557560 defer gpa.free(body);
558561
559562 try expectEqualStrings("Hello, World!\n", body);
560 try expectEqualStrings("text/plain", req.response.content_type.?);
563 try expectEqualStrings("text/plain", response.head.content_type.?);
561564 }
562565
563566 // connection has been kept alive
......@@ -569,16 +572,14 @@ test "general client/server API coverage" {
569572 const uri = try std.Uri.parse(location);
570573
571574 log.info("{s}", .{location});
572 var server_header_buffer: [1024]u8 = undefined;
573 var req = try client.open(.GET, uri, .{
574 .server_header_buffer = &server_header_buffer,
575 });
575 var redirect_buffer: [1024]u8 = undefined;
576 var req = try client.request(.GET, uri, .{});
576577 defer req.deinit();
577578
578 try req.send();
579 try req.wait();
579 try req.sendBodiless();
580 var response = try req.receiveHead(&redirect_buffer);
580581
581 const body = try req.reader().readAllAlloc(gpa, 8192 * 1024);
582 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192 * 1024));
582583 defer gpa.free(body);
583584
584585 try expectEqual(@as(usize, 14 * 1024 + 14 * 10), body.len);
......@@ -593,21 +594,19 @@ test "general client/server API coverage" {
593594 const uri = try std.Uri.parse(location);
594595
595596 log.info("{s}", .{location});
596 var server_header_buffer: [1024]u8 = undefined;
597 var req = try client.open(.HEAD, uri, .{
598 .server_header_buffer = &server_header_buffer,
599 });
597 var redirect_buffer: [1024]u8 = undefined;
598 var req = try client.request(.HEAD, uri, .{});
600599 defer req.deinit();
601600
602 try req.send();
603 try req.wait();
601 try req.sendBodiless();
602 var response = try req.receiveHead(&redirect_buffer);
604603
605 const body = try req.reader().readAllAlloc(gpa, 8192);
604 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
606605 defer gpa.free(body);
607606
608607 try expectEqualStrings("", body);
609 try expectEqualStrings("text/plain", req.response.content_type.?);
610 try expectEqual(14, req.response.content_length.?);
608 try expectEqualStrings("text/plain", response.head.content_type.?);
609 try expectEqual(14, response.head.content_length.?);
611610 }
612611
613612 // connection has been kept alive
......@@ -619,20 +618,18 @@ test "general client/server API coverage" {
619618 const uri = try std.Uri.parse(location);
620619
621620 log.info("{s}", .{location});
622 var server_header_buffer: [1024]u8 = undefined;
623 var req = try client.open(.GET, uri, .{
624 .server_header_buffer = &server_header_buffer,
625 });
621 var redirect_buffer: [1024]u8 = undefined;
622 var req = try client.request(.GET, uri, .{});
626623 defer req.deinit();
627624
628 try req.send();
629 try req.wait();
625 try req.sendBodiless();
626 var response = try req.receiveHead(&redirect_buffer);
630627
631 const body = try req.reader().readAllAlloc(gpa, 8192);
628 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
632629 defer gpa.free(body);
633630
634631 try expectEqualStrings("Hello, World!\n", body);
635 try expectEqualStrings("text/plain", req.response.content_type.?);
632 try expectEqualStrings("text/plain", response.head.content_type.?);
636633 }
637634
638635 // connection has been kept alive
......@@ -644,21 +641,19 @@ test "general client/server API coverage" {
644641 const uri = try std.Uri.parse(location);
645642
646643 log.info("{s}", .{location});
647 var server_header_buffer: [1024]u8 = undefined;
648 var req = try client.open(.HEAD, uri, .{
649 .server_header_buffer = &server_header_buffer,
650 });
644 var redirect_buffer: [1024]u8 = undefined;
645 var req = try client.request(.HEAD, uri, .{});
651646 defer req.deinit();
652647
653 try req.send();
654 try req.wait();
648 try req.sendBodiless();
649 var response = try req.receiveHead(&redirect_buffer);
655650
656 const body = try req.reader().readAllAlloc(gpa, 8192);
651 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
657652 defer gpa.free(body);
658653
659654 try expectEqualStrings("", body);
660 try expectEqualStrings("text/plain", req.response.content_type.?);
661 try expect(req.response.transfer_encoding == .chunked);
655 try expectEqualStrings("text/plain", response.head.content_type.?);
656 try expect(response.head.transfer_encoding == .chunked);
662657 }
663658
664659 // connection has been kept alive
......@@ -670,21 +665,20 @@ test "general client/server API coverage" {
670665 const uri = try std.Uri.parse(location);
671666
672667 log.info("{s}", .{location});
673 var server_header_buffer: [1024]u8 = undefined;
674 var req = try client.open(.GET, uri, .{
675 .server_header_buffer = &server_header_buffer,
668 var redirect_buffer: [1024]u8 = undefined;
669 var req = try client.request(.GET, uri, .{
676670 .keep_alive = false,
677671 });
678672 defer req.deinit();
679673
680 try req.send();
681 try req.wait();
674 try req.sendBodiless();
675 var response = try req.receiveHead(&redirect_buffer);
682676
683 const body = try req.reader().readAllAlloc(gpa, 8192);
677 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
684678 defer gpa.free(body);
685679
686680 try expectEqualStrings("Hello, World!\n", body);
687 try expectEqualStrings("text/plain", req.response.content_type.?);
681 try expectEqualStrings("text/plain", response.head.content_type.?);
688682 }
689683
690684 // connection has been closed
......@@ -696,26 +690,25 @@ test "general client/server API coverage" {
696690 const uri = try std.Uri.parse(location);
697691
698692 log.info("{s}", .{location});
699 var server_header_buffer: [1024]u8 = undefined;
700 var req = try client.open(.GET, uri, .{
701 .server_header_buffer = &server_header_buffer,
693 var redirect_buffer: [1024]u8 = undefined;
694 var req = try client.request(.GET, uri, .{
702695 .extra_headers = &.{
703696 .{ .name = "empty", .value = "" },
704697 },
705698 });
706699 defer req.deinit();
707700
708 try req.send();
709 try req.wait();
701 try req.sendBodiless();
702 var response = try req.receiveHead(&redirect_buffer);
710703
711 try std.testing.expectEqual(.ok, req.response.status);
704 try std.testing.expectEqual(.ok, response.head.status);
712705
713 const body = try req.reader().readAllAlloc(gpa, 8192);
706 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
714707 defer gpa.free(body);
715708
716709 try expectEqualStrings("", body);
717710
718 var it = req.response.iterateHeaders();
711 var it = response.head.iterateHeaders();
719712 {
720713 const header = it.next().?;
721714 try expect(!it.is_trailer);
......@@ -740,16 +733,14 @@ test "general client/server API coverage" {
740733 const uri = try std.Uri.parse(location);
741734
742735 log.info("{s}", .{location});
743 var server_header_buffer: [1024]u8 = undefined;
744 var req = try client.open(.GET, uri, .{
745 .server_header_buffer = &server_header_buffer,
746 });
736 var redirect_buffer: [1024]u8 = undefined;
737 var req = try client.request(.GET, uri, .{});
747738 defer req.deinit();
748739
749 try req.send();
750 try req.wait();
740 try req.sendBodiless();
741 var response = try req.receiveHead(&redirect_buffer);
751742
752 const body = try req.reader().readAllAlloc(gpa, 8192);
743 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
753744 defer gpa.free(body);
754745
755746 try expectEqualStrings("Hello, World!\n", body);
......@@ -764,16 +755,14 @@ test "general client/server API coverage" {
764755 const uri = try std.Uri.parse(location);
765756
766757 log.info("{s}", .{location});
767 var server_header_buffer: [1024]u8 = undefined;
768 var req = try client.open(.GET, uri, .{
769 .server_header_buffer = &server_header_buffer,
770 });
758 var redirect_buffer: [1024]u8 = undefined;
759 var req = try client.request(.GET, uri, .{});
771760 defer req.deinit();
772761
773 try req.send();
774 try req.wait();
762 try req.sendBodiless();
763 var response = try req.receiveHead(&redirect_buffer);
775764
776 const body = try req.reader().readAllAlloc(gpa, 8192);
765 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
777766 defer gpa.free(body);
778767
779768 try expectEqualStrings("Hello, World!\n", body);
......@@ -788,16 +777,14 @@ test "general client/server API coverage" {
788777 const uri = try std.Uri.parse(location);
789778
790779 log.info("{s}", .{location});
791 var server_header_buffer: [1024]u8 = undefined;
792 var req = try client.open(.GET, uri, .{
793 .server_header_buffer = &server_header_buffer,
794 });
780 var redirect_buffer: [1024]u8 = undefined;
781 var req = try client.request(.GET, uri, .{});
795782 defer req.deinit();
796783
797 try req.send();
798 try req.wait();
784 try req.sendBodiless();
785 var response = try req.receiveHead(&redirect_buffer);
799786
800 const body = try req.reader().readAllAlloc(gpa, 8192);
787 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
801788 defer gpa.free(body);
802789
803790 try expectEqualStrings("Hello, World!\n", body);
......@@ -812,17 +799,17 @@ test "general client/server API coverage" {
812799 const uri = try std.Uri.parse(location);
813800
814801 log.info("{s}", .{location});
815 var server_header_buffer: [1024]u8 = undefined;
816 var req = try client.open(.GET, uri, .{
817 .server_header_buffer = &server_header_buffer,
818 });
802 var redirect_buffer: [1024]u8 = undefined;
803 var req = try client.request(.GET, uri, .{});
819804 defer req.deinit();
820805
821 try req.send();
822 req.wait() catch |err| switch (err) {
806 try req.sendBodiless();
807 if (req.receiveHead(&redirect_buffer)) |_| {
808 return error.TestFailed;
809 } else |err| switch (err) {
823810 error.TooManyHttpRedirects => {},
824811 else => return err,
825 };
812 }
826813 }
827814
828815 { // redirect to encoded url
......@@ -831,16 +818,14 @@ test "general client/server API coverage" {
831818 const uri = try std.Uri.parse(location);
832819
833820 log.info("{s}", .{location});
834 var server_header_buffer: [1024]u8 = undefined;
835 var req = try client.open(.GET, uri, .{
836 .server_header_buffer = &server_header_buffer,
837 });
821 var redirect_buffer: [1024]u8 = undefined;
822 var req = try client.request(.GET, uri, .{});
838823 defer req.deinit();
839824
840 try req.send();
841 try req.wait();
825 try req.sendBodiless();
826 var response = try req.receiveHead(&redirect_buffer);
842827
843 const body = try req.reader().readAllAlloc(gpa, 8192);
828 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
844829 defer gpa.free(body);
845830
846831 try expectEqualStrings("Encoded redirect successful!\n", body);
......@@ -855,14 +840,12 @@ test "general client/server API coverage" {
855840 const uri = try std.Uri.parse(location);
856841
857842 log.info("{s}", .{location});
858 var server_header_buffer: [1024]u8 = undefined;
859 var req = try client.open(.GET, uri, .{
860 .server_header_buffer = &server_header_buffer,
861 });
843 var redirect_buffer: [1024]u8 = undefined;
844 var req = try client.request(.GET, uri, .{});
862845 defer req.deinit();
863846
864 try req.send();
865 const result = req.wait();
847 try req.sendBodiless();
848 const result = req.receiveHead(&redirect_buffer);
866849
867850 // a proxy without an upstream is likely to return a 5xx status.
868851 if (client.http_proxy == null) {
......@@ -872,77 +855,40 @@ test "general client/server API coverage" {
872855
873856 // connection has been kept alive
874857 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
875
876 { // issue 16282 *** This test leaves the client in an invalid state, it must be last ***
877 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/get", .{port});
878 defer gpa.free(location);
879 const uri = try std.Uri.parse(location);
880
881 const total_connections = client.connection_pool.free_size + 64;
882 var requests = try gpa.alloc(http.Client.Request, total_connections);
883 defer gpa.free(requests);
884
885 var header_bufs = std.ArrayList([]u8).init(gpa);
886 defer header_bufs.deinit();
887 defer for (header_bufs.items) |item| gpa.free(item);
888
889 for (0..total_connections) |i| {
890 const headers_buf = try gpa.alloc(u8, 1024);
891 try header_bufs.append(headers_buf);
892 var req = try client.open(.GET, uri, .{
893 .server_header_buffer = headers_buf,
894 });
895 req.response.parser.done = true;
896 req.connection.?.closing = false;
897 requests[i] = req;
898 }
899
900 for (0..total_connections) |i| {
901 requests[i].deinit();
902 }
903
904 // free connections should be full now
905 try expect(client.connection_pool.free_len == client.connection_pool.free_size);
906 }
907
908 client.deinit();
909
910 {
911 global.handle_new_requests = false;
912
913 const conn = try std.net.tcpConnectToAddress(test_server.net_server.listen_address);
914 conn.close();
915 }
916858}
917859
918860test "Server streams both reading and writing" {
919861 const test_server = try createTestServer(struct {
920 fn run(net_server: *std.net.Server) anyerror!void {
921 var header_buffer: [1024]u8 = undefined;
922 const conn = try net_server.accept();
923 defer conn.stream.close();
862 fn run(test_server: *TestServer) anyerror!void {
863 const net_server = &test_server.net_server;
864 var recv_buffer: [1024]u8 = undefined;
865 var send_buffer: [777]u8 = undefined;
924866
925 var server = http.Server.init(conn, &header_buffer);
926 var request = try server.receiveHead();
927 const reader = try request.reader();
867 const connection = try net_server.accept();
868 defer connection.stream.close();
928869
929 var send_buffer: [777]u8 = undefined;
930 var response = request.respondStreaming(.{
931 .send_buffer = &send_buffer,
870 var connection_br = connection.stream.reader(&recv_buffer);
871 var connection_bw = connection.stream.writer(&send_buffer);
872 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);
873 var request = try server.receiveHead();
874 var read_buffer: [100]u8 = undefined;
875 var br = try request.readerExpectContinue(&read_buffer);
876 var response = try request.respondStreaming(&.{}, .{
932877 .respond_options = .{
933878 .transfer_encoding = .none, // Causes keep_alive=false
934879 },
935880 });
936 const writer = response.writer();
881 const w = &response.writer;
937882
938883 while (true) {
939884 try response.flush();
940 var buf: [100]u8 = undefined;
941 const n = try reader.read(&buf);
942 if (n == 0) break;
943 const sub_buf = buf[0..n];
944 for (sub_buf) |*b| b.* = std.ascii.toUpper(b.*);
945 try writer.writeAll(sub_buf);
885 const buf = br.peekGreedy(1) catch |err| switch (err) {
886 error.EndOfStream => break,
887 error.ReadFailed => return error.ReadFailed,
888 };
889 br.toss(buf.len);
890 for (buf) |*b| b.* = std.ascii.toUpper(b.*);
891 try w.writeAll(buf);
946892 }
947893 try response.end();
948894 }
......@@ -952,27 +898,24 @@ test "Server streams both reading and writing" {
952898 var client: http.Client = .{ .allocator = std.testing.allocator };
953899 defer client.deinit();
954900
955 var server_header_buffer: [555]u8 = undefined;
956 var req = try client.open(.POST, .{
901 var redirect_buffer: [555]u8 = undefined;
902 var req = try client.request(.POST, .{
957903 .scheme = "http",
958904 .host = .{ .raw = "127.0.0.1" },
959905 .port = test_server.port(),
960906 .path = .{ .percent_encoded = "/" },
961 }, .{
962 .server_header_buffer = &server_header_buffer,
963 });
907 }, .{});
964908 defer req.deinit();
965909
966910 req.transfer_encoding = .chunked;
967 try req.send();
968 try req.wait();
911 var body_writer = try req.sendBody(&.{});
912 var response = try req.receiveHead(&redirect_buffer);
969913
970 try req.writeAll("one ");
971 try req.writeAll("fish");
914 try body_writer.writer.writeAll("one ");
915 try body_writer.writer.writeAll("fish");
916 try body_writer.end();
972917
973 try req.finish();
974
975 const body = try req.reader().readAllAlloc(std.testing.allocator, 8192);
918 const body = try response.reader(&.{}).allocRemaining(std.testing.allocator, .limited(8192));
976919 defer std.testing.allocator.free(body);
977920
978921 try expectEqualStrings("ONE FISH", body);
......@@ -987,9 +930,8 @@ fn echoTests(client: *http.Client, port: u16) !void {
987930 defer gpa.free(location);
988931 const uri = try std.Uri.parse(location);
989932
990 var server_header_buffer: [1024]u8 = undefined;
991 var req = try client.open(.POST, uri, .{
992 .server_header_buffer = &server_header_buffer,
933 var redirect_buffer: [1024]u8 = undefined;
934 var req = try client.request(.POST, uri, .{
993935 .extra_headers = &.{
994936 .{ .name = "content-type", .value = "text/plain" },
995937 },
......@@ -998,14 +940,14 @@ fn echoTests(client: *http.Client, port: u16) !void {
998940
999941 req.transfer_encoding = .{ .content_length = 14 };
1000942
1001 try req.send();
1002 try req.writeAll("Hello, ");
1003 try req.writeAll("World!\n");
1004 try req.finish();
943 var body_writer = try req.sendBody(&.{});
944 try body_writer.writer.writeAll("Hello, ");
945 try body_writer.writer.writeAll("World!\n");
946 try body_writer.end();
1005947
1006 try req.wait();
948 var response = try req.receiveHead(&redirect_buffer);
1007949
1008 const body = try req.reader().readAllAlloc(gpa, 8192);
950 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
1009951 defer gpa.free(body);
1010952
1011953 try expectEqualStrings("Hello, World!\n", body);
......@@ -1021,9 +963,8 @@ fn echoTests(client: *http.Client, port: u16) !void {
1021963 .{port},
1022964 ));
1023965
1024 var server_header_buffer: [1024]u8 = undefined;
1025 var req = try client.open(.POST, uri, .{
1026 .server_header_buffer = &server_header_buffer,
966 var redirect_buffer: [1024]u8 = undefined;
967 var req = try client.request(.POST, uri, .{
1027968 .extra_headers = &.{
1028969 .{ .name = "content-type", .value = "text/plain" },
1029970 },
......@@ -1032,14 +973,14 @@ fn echoTests(client: *http.Client, port: u16) !void {
1032973
1033974 req.transfer_encoding = .chunked;
1034975
1035 try req.send();
1036 try req.writeAll("Hello, ");
1037 try req.writeAll("World!\n");
1038 try req.finish();
976 var body_writer = try req.sendBody(&.{});
977 try body_writer.writer.writeAll("Hello, ");
978 try body_writer.writer.writeAll("World!\n");
979 try body_writer.end();
1039980
1040 try req.wait();
981 var response = try req.receiveHead(&redirect_buffer);
1041982
1042 const body = try req.reader().readAllAlloc(gpa, 8192);
983 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
1043984 defer gpa.free(body);
1044985
1045986 try expectEqualStrings("Hello, World!\n", body);
......@@ -1053,8 +994,8 @@ fn echoTests(client: *http.Client, port: u16) !void {
1053994 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/echo-content#fetch", .{port});
1054995 defer gpa.free(location);
1055996
1056 var body = std.ArrayList(u8).init(gpa);
1057 defer body.deinit();
997 var body: std.ArrayListUnmanaged(u8) = .empty;
998 defer body.deinit(gpa);
1058999
10591000 const res = try client.fetch(.{
10601001 .location = .{ .url = location },
......@@ -1063,7 +1004,7 @@ fn echoTests(client: *http.Client, port: u16) !void {
10631004 .extra_headers = &.{
10641005 .{ .name = "content-type", .value = "text/plain" },
10651006 },
1066 .response_storage = .{ .dynamic = &body },
1007 .response_storage = .{ .allocator = gpa, .list = &body },
10671008 });
10681009 try expectEqual(.ok, res.status);
10691010 try expectEqualStrings("Hello, World!\n", body.items);
......@@ -1074,9 +1015,8 @@ fn echoTests(client: *http.Client, port: u16) !void {
10741015 defer gpa.free(location);
10751016 const uri = try std.Uri.parse(location);
10761017
1077 var server_header_buffer: [1024]u8 = undefined;
1078 var req = try client.open(.POST, uri, .{
1079 .server_header_buffer = &server_header_buffer,
1018 var redirect_buffer: [1024]u8 = undefined;
1019 var req = try client.request(.POST, uri, .{
10801020 .extra_headers = &.{
10811021 .{ .name = "expect", .value = "100-continue" },
10821022 .{ .name = "content-type", .value = "text/plain" },
......@@ -1086,15 +1026,15 @@ fn echoTests(client: *http.Client, port: u16) !void {
10861026
10871027 req.transfer_encoding = .chunked;
10881028
1089 try req.send();
1090 try req.writeAll("Hello, ");
1091 try req.writeAll("World!\n");
1092 try req.finish();
1029 var body_writer = try req.sendBody(&.{});
1030 try body_writer.writer.writeAll("Hello, ");
1031 try body_writer.writer.writeAll("World!\n");
1032 try body_writer.end();
10931033
1094 try req.wait();
1095 try expectEqual(.ok, req.response.status);
1034 var response = try req.receiveHead(&redirect_buffer);
1035 try expectEqual(.ok, response.head.status);
10961036
1097 const body = try req.reader().readAllAlloc(gpa, 8192);
1037 const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192));
10981038 defer gpa.free(body);
10991039
11001040 try expectEqualStrings("Hello, World!\n", body);
......@@ -1105,9 +1045,8 @@ fn echoTests(client: *http.Client, port: u16) !void {
11051045 defer gpa.free(location);
11061046 const uri = try std.Uri.parse(location);
11071047
1108 var server_header_buffer: [1024]u8 = undefined;
1109 var req = try client.open(.POST, uri, .{
1110 .server_header_buffer = &server_header_buffer,
1048 var redirect_buffer: [1024]u8 = undefined;
1049 var req = try client.request(.POST, uri, .{
11111050 .extra_headers = &.{
11121051 .{ .name = "content-type", .value = "text/plain" },
11131052 .{ .name = "expect", .value = "garbage" },
......@@ -1117,23 +1056,24 @@ fn echoTests(client: *http.Client, port: u16) !void {
11171056
11181057 req.transfer_encoding = .chunked;
11191058
1120 try req.send();
1121 try req.wait();
1122 try expectEqual(.expectation_failed, req.response.status);
1059 var body_writer = try req.sendBody(&.{});
1060 try body_writer.flush();
1061 var response = try req.receiveHead(&redirect_buffer);
1062 try expectEqual(.expectation_failed, response.head.status);
1063 _ = try response.reader(&.{}).discardRemaining();
11231064 }
1124
1125 _ = try client.fetch(.{
1126 .location = .{
1127 .url = try std.fmt.bufPrint(&location_buffer, "http://127.0.0.1:{d}/end", .{port}),
1128 },
1129 });
11301065}
11311066
11321067const TestServer = struct {
1068 shutting_down: bool,
11331069 server_thread: std.Thread,
11341070 net_server: std.net.Server,
11351071
11361072 fn destroy(self: *@This()) void {
1073 self.shutting_down = true;
1074 const conn = std.net.tcpConnectToAddress(self.net_server.listen_address) catch @panic("shutdown failure");
1075 conn.close();
1076
11371077 self.server_thread.join();
11381078 self.net_server.deinit();
11391079 std.testing.allocator.destroy(self);
......@@ -1153,20 +1093,27 @@ fn createTestServer(S: type) !*TestServer {
11531093
11541094 const address = try std.net.Address.parseIp("127.0.0.1", 0);
11551095 const test_server = try std.testing.allocator.create(TestServer);
1156 test_server.net_server = try address.listen(.{ .reuse_address = true });
1157 test_server.server_thread = try std.Thread.spawn(.{}, S.run, .{&test_server.net_server});
1096 test_server.* = .{
1097 .net_server = try address.listen(.{ .reuse_address = true }),
1098 .server_thread = try std.Thread.spawn(.{}, S.run, .{test_server}),
1099 .shutting_down = false,
1100 };
11581101 return test_server;
11591102}
11601103
11611104test "redirect to different connection" {
11621105 const test_server_new = try createTestServer(struct {
1163 fn run(net_server: *std.net.Server) anyerror!void {
1164 var header_buffer: [888]u8 = undefined;
1106 fn run(test_server: *TestServer) anyerror!void {
1107 const net_server = &test_server.net_server;
1108 var recv_buffer: [888]u8 = undefined;
1109 var send_buffer: [777]u8 = undefined;
11651110
1166 const conn = try net_server.accept();
1167 defer conn.stream.close();
1111 const connection = try net_server.accept();
1112 defer connection.stream.close();
11681113
1169 var server = http.Server.init(conn, &header_buffer);
1114 var connection_br = connection.stream.reader(&recv_buffer);
1115 var connection_bw = connection.stream.writer(&send_buffer);
1116 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);
11701117 var request = try server.receiveHead();
11711118 try expectEqualStrings(request.head.target, "/ok");
11721119 try request.respond("good job, you pass", .{});
......@@ -1180,18 +1127,22 @@ test "redirect to different connection" {
11801127 global.other_port = test_server_new.port();
11811128
11821129 const test_server_orig = try createTestServer(struct {
1183 fn run(net_server: *std.net.Server) anyerror!void {
1184 var header_buffer: [999]u8 = undefined;
1130 fn run(test_server: *TestServer) anyerror!void {
1131 const net_server = &test_server.net_server;
1132 var recv_buffer: [999]u8 = undefined;
11851133 var send_buffer: [100]u8 = undefined;
11861134
1187 const conn = try net_server.accept();
1188 defer conn.stream.close();
1135 const connection = try net_server.accept();
1136 defer connection.stream.close();
11891137
1190 const new_loc = try std.fmt.bufPrint(&send_buffer, "http://127.0.0.1:{d}/ok", .{
1138 var loc_buf: [50]u8 = undefined;
1139 const new_loc = try std.fmt.bufPrint(&loc_buf, "http://127.0.0.1:{d}/ok", .{
11911140 global.other_port.?,
11921141 });
11931142
1194 var server = http.Server.init(conn, &header_buffer);
1143 var connection_br = connection.stream.reader(&recv_buffer);
1144 var connection_bw = connection.stream.writer(&send_buffer);
1145 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);
11951146 var request = try server.receiveHead();
11961147 try expectEqualStrings(request.head.target, "/help");
11971148 try request.respond("", .{
......@@ -1216,16 +1167,15 @@ test "redirect to different connection" {
12161167 const uri = try std.Uri.parse(location);
12171168
12181169 {
1219 var server_header_buffer: [666]u8 = undefined;
1220 var req = try client.open(.GET, uri, .{
1221 .server_header_buffer = &server_header_buffer,
1222 });
1170 var redirect_buffer: [666]u8 = undefined;
1171 var req = try client.request(.GET, uri, .{});
12231172 defer req.deinit();
12241173
1225 try req.send();
1226 try req.wait();
1174 try req.sendBodiless();
1175 var response = try req.receiveHead(&redirect_buffer);
1176 var reader = response.reader(&.{});
12271177
1228 const body = try req.reader().readAllAlloc(gpa, 8192);
1178 const body = try reader.allocRemaining(gpa, .limited(8192));
12291179 defer gpa.free(body);
12301180
12311181 try expectEqualStrings("good job, you pass", body);