| ... | ... | @@ -474,7 +474,10 @@ pub const Request = struct { |
| 474 | 474 | }) catch unreachable; |
| 475 | 475 | if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n"); |
| 476 | 476 | |
| 477 | | if (options.content_length) |len| { |
| 477 | if (o.transfer_encoding) |transfer_encoding| switch (transfer_encoding) { |
| 478 | .chunked => h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n"), |
| 479 | .none => {}, |
| 480 | } else if (options.content_length) |len| { |
| 478 | 481 | h.fixedWriter().print("content-length: {d}\r\n", .{len}) catch unreachable; |
| 479 | 482 | } else { |
| 480 | 483 | h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n"); |
| ... | ... | @@ -496,7 +499,12 @@ pub const Request = struct { |
| 496 | 499 | .send_buffer = options.send_buffer, |
| 497 | 500 | .send_buffer_start = 0, |
| 498 | 501 | .send_buffer_end = h.items.len, |
| 499 | | .content_length = options.content_length, |
| 502 | .transfer_encoding = if (o.transfer_encoding) |te| switch (te) { |
| 503 | .chunked => .chunked, |
| 504 | .none => .none, |
| 505 | } else if (options.content_length) |len| .{ |
| 506 | .content_length = len, |
| 507 | } else .chunked, |
| 500 | 508 | .elide_body = elide_body, |
| 501 | 509 | .chunk_len = 0, |
| 502 | 510 | }; |
| ... | ... | @@ -709,12 +717,21 @@ pub const Response = struct { |
| 709 | 717 | send_buffer_end: usize, |
| 710 | 718 | /// `null` means transfer-encoding: chunked. |
| 711 | 719 | /// As a debugging utility, counts down to zero as bytes are written. |
| 712 | | content_length: ?u64, |
| 720 | transfer_encoding: TransferEncoding, |
| 713 | 721 | elide_body: bool, |
| 714 | 722 | /// Indicates how much of the end of the `send_buffer` corresponds to a |
| 715 | 723 | /// chunk. This amount of data will be wrapped by an HTTP chunk header. |
| 716 | 724 | chunk_len: usize, |
| 717 | 725 | |
| 726 | pub const TransferEncoding = union(enum) { |
| 727 | /// End of connection signals the end of the stream. |
| 728 | none, |
| 729 | /// As a debugging utility, counts down to zero as bytes are written. |
| 730 | content_length: u64, |
| 731 | /// Each chunk is wrapped in a header and trailer. |
| 732 | chunked, |
| 733 | }; |
| 734 | |
| 718 | 735 | pub const WriteError = net.Stream.WriteError; |
| 719 | 736 | |
| 720 | 737 | /// When using content-length, asserts that the amount of data sent matches |
| ... | ... | @@ -723,11 +740,17 @@ pub const Response = struct { |
| 723 | 740 | /// end-of-stream message, then flushes the stream to the system. |
| 724 | 741 | /// Respects the value of `elide_body` to omit all data after the headers. |
| 725 | 742 | pub fn end(r: *Response) WriteError!void { |
| 726 | | if (r.content_length) |len| { |
| 727 | | assert(len == 0); // Trips when end() called before all bytes written. |
| 728 | | try flush_cl(r); |
| 729 | | } else { |
| 730 | | try flush_chunked(r, &.{}); |
| 743 | switch (r.transfer_encoding) { |
| 744 | .content_length => |len| { |
| 745 | assert(len == 0); // Trips when end() called before all bytes written. |
| 746 | try flush_cl(r); |
| 747 | }, |
| 748 | .none => { |
| 749 | try flush_cl(r); |
| 750 | }, |
| 751 | .chunked => { |
| 752 | try flush_chunked(r, &.{}); |
| 753 | }, |
| 731 | 754 | } |
| 732 | 755 | r.* = undefined; |
| 733 | 756 | } |
| ... | ... | @@ -752,16 +775,21 @@ pub const Response = struct { |
| 752 | 775 | /// May return 0, which does not indicate end of stream. The caller decides |
| 753 | 776 | /// when the end of stream occurs by calling `end`. |
| 754 | 777 | pub fn write(r: *Response, bytes: []const u8) WriteError!usize { |
| 755 | | if (r.content_length != null) { |
| 756 | | return write_cl(r, bytes); |
| 757 | | } else { |
| 758 | | return write_chunked(r, bytes); |
| 778 | switch (r.transfer_encoding) { |
| 779 | .content_length, .none => return write_cl(r, bytes), |
| 780 | .chunked => return write_chunked(r, bytes), |
| 759 | 781 | } |
| 760 | 782 | } |
| 761 | 783 | |
| 762 | 784 | fn write_cl(context: *const anyopaque, bytes: []const u8) WriteError!usize { |
| 763 | 785 | const r: *Response = @constCast(@alignCast(@ptrCast(context))); |
| 764 | | const len = &r.content_length.?; |
| 786 | |
| 787 | var trash: u64 = std.math.maxInt(u64); |
| 788 | const len = switch (r.transfer_encoding) { |
| 789 | .content_length => |*len| len, |
| 790 | else => &trash, |
| 791 | }; |
| 792 | |
| 765 | 793 | if (r.elide_body) { |
| 766 | 794 | len.* -= bytes.len; |
| 767 | 795 | return bytes.len; |
| ... | ... | @@ -805,7 +833,7 @@ pub const Response = struct { |
| 805 | 833 | |
| 806 | 834 | fn write_chunked(context: *const anyopaque, bytes: []const u8) WriteError!usize { |
| 807 | 835 | const r: *Response = @constCast(@alignCast(@ptrCast(context))); |
| 808 | | assert(r.content_length == null); |
| 836 | assert(r.transfer_encoding == .chunked); |
| 809 | 837 | |
| 810 | 838 | if (r.elide_body) |
| 811 | 839 | return bytes.len; |
| ... | ... | @@ -867,15 +895,13 @@ pub const Response = struct { |
| 867 | 895 | /// This is redundant after calling `end`. |
| 868 | 896 | /// Respects the value of `elide_body` to omit all data after the headers. |
| 869 | 897 | pub fn flush(r: *Response) WriteError!void { |
| 870 | | if (r.content_length != null) { |
| 871 | | return flush_cl(r); |
| 872 | | } else { |
| 873 | | return flush_chunked(r, null); |
| 898 | switch (r.transfer_encoding) { |
| 899 | .none, .content_length => return flush_cl(r), |
| 900 | .chunked => return flush_chunked(r, null), |
| 874 | 901 | } |
| 875 | 902 | } |
| 876 | 903 | |
| 877 | 904 | fn flush_cl(r: *Response) WriteError!void { |
| 878 | | assert(r.content_length != null); |
| 879 | 905 | try r.stream.writeAll(r.send_buffer[r.send_buffer_start..r.send_buffer_end]); |
| 880 | 906 | r.send_buffer_start = 0; |
| 881 | 907 | r.send_buffer_end = 0; |
| ... | ... | @@ -884,7 +910,7 @@ pub const Response = struct { |
| 884 | 910 | fn flush_chunked(r: *Response, end_trailers: ?[]const http.Header) WriteError!void { |
| 885 | 911 | const max_trailers = 25; |
| 886 | 912 | if (end_trailers) |trailers| assert(trailers.len <= max_trailers); |
| 887 | | assert(r.content_length == null); |
| 913 | assert(r.transfer_encoding == .chunked); |
| 888 | 914 | |
| 889 | 915 | const http_headers = r.send_buffer[r.send_buffer_start .. r.send_buffer_end - r.chunk_len]; |
| 890 | 916 | |
| ... | ... | @@ -976,7 +1002,10 @@ pub const Response = struct { |
| 976 | 1002 | |
| 977 | 1003 | pub fn writer(r: *Response) std.io.AnyWriter { |
| 978 | 1004 | return .{ |
| 979 | | .writeFn = if (r.content_length != null) write_cl else write_chunked, |
| 1005 | .writeFn = switch (r.transfer_encoding) { |
| 1006 | .none, .content_length => write_cl, |
| 1007 | .chunked => write_chunked, |
| 1008 | }, |
| 980 | 1009 | .context = r, |
| 981 | 1010 | }; |
| 982 | 1011 | } |