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