| ... | @@ -758,22 +758,14 @@ pub const BodyWriter = struct { | ... | @@ -758,22 +758,14 @@ pub const BodyWriter = struct { |
| 758 | /// As a debugging utility, counts down to zero as bytes are written. | 758 | /// As a debugging utility, counts down to zero as bytes are written. |
| 759 | content_length: u64, | 759 | content_length: u64, |
| 760 | /// Each chunk is wrapped in a header and trailer. | 760 | /// Each chunk is wrapped in a header and trailer. |
| 761 | chunked: Chunked, | 761 | /// This length is the the number of bytes to be written before the |
| | 762 | /// next header. This includes +2 for the `\r\n` trailer and is zero |
| | 763 | /// for the beginning of the stream. |
| | 764 | chunk_len: usize, |
| 762 | /// Cleanly finished stream; connection can be reused. | 765 | /// Cleanly finished stream; connection can be reused. |
| 763 | end, | 766 | end, |
| 764 | | 767 | |
| 765 | pub const Chunked = union(enum) { | 768 | pub const init_chunked: State = .{ .chunk_len = 0 }; |
| 766 | /// Index to the start of the hex-encoded chunk length in the chunk | | |
| 767 | /// header within the buffer of `BodyWriter.http_protocol_output`. | | |
| 768 | /// Buffered chunk data starts here plus length of `chunk_header_template`. | | |
| 769 | offset: usize, | | |
| 770 | /// We are in the middle of a chunk and this is how many bytes are | | |
| 771 | /// left until the next header. This includes +2 for "\r"\n", and | | |
| 772 | /// is zero for the beginning of the stream. | | |
| 773 | chunk_len: usize, | | |
| 774 | | | |
| 775 | pub const init: Chunked = .{ .chunk_len = 0 }; | | |
| 776 | }; | | |
| 777 | }; | 769 | }; |
| 778 | | 770 | |
| 779 | pub fn isEliding(w: *const BodyWriter) bool { | 771 | pub fn isEliding(w: *const BodyWriter) bool { |
| ... | @@ -784,21 +776,7 @@ pub const BodyWriter = struct { | ... | @@ -784,21 +776,7 @@ pub const BodyWriter = struct { |
| 784 | pub fn flush(w: *BodyWriter) Error!void { | 776 | pub fn flush(w: *BodyWriter) Error!void { |
| 785 | const out = w.http_protocol_output; | 777 | const out = w.http_protocol_output; |
| 786 | switch (w.state) { | 778 | switch (w.state) { |
| 787 | .end, .none, .content_length => return out.flush(), | 779 | .end, .none, .content_length, .chunk_len => return out.flush(), |
| 788 | .chunked => |*chunked| switch (chunked.*) { | | |
| 789 | .offset => |offset| { | | |
| 790 | const chunk_len = out.end - offset - chunk_header_template.len; | | |
| 791 | if (chunk_len > 0) { | | |
| 792 | writeHex(out.buffer[offset..][0..chunk_len_digits], chunk_len); | | |
| 793 | chunked.* = .{ .chunk_len = 2 }; | | |
| 794 | } else { | | |
| 795 | out.end = offset; | | |
| 796 | chunked.* = .{ .chunk_len = 0 }; | | |
| 797 | } | | |
| 798 | try out.flush(); | | |
| 799 | }, | | |
| 800 | .chunk_len => return out.flush(), | | |
| 801 | }, | | |
| 802 | } | 780 | } |
| 803 | } | 781 | } |
| 804 | | 782 | |
| ... | @@ -841,7 +819,7 @@ pub const BodyWriter = struct { | ... | @@ -841,7 +819,7 @@ pub const BodyWriter = struct { |
| 841 | w.state = .end; | 819 | w.state = .end; |
| 842 | }, | 820 | }, |
| 843 | .none => {}, | 821 | .none => {}, |
| 844 | .chunked => return endChunkedUnflushed(w, .{}), | 822 | .chunk_len => return endChunkedUnflushed(w, .{}), |
| 845 | } | 823 | } |
| 846 | } | 824 | } |
| 847 | | 825 | |
| ... | @@ -877,24 +855,16 @@ pub const BodyWriter = struct { | ... | @@ -877,24 +855,16 @@ pub const BodyWriter = struct { |
| 877 | /// * `endUnflushed` | 855 | /// * `endUnflushed` |
| 878 | /// * `end` | 856 | /// * `end` |
| 879 | pub fn endChunkedUnflushed(w: *BodyWriter, options: EndChunkedOptions) Error!void { | 857 | pub fn endChunkedUnflushed(w: *BodyWriter, options: EndChunkedOptions) Error!void { |
| 880 | const chunked = &w.state.chunked; | | |
| 881 | if (w.isEliding()) { | 858 | if (w.isEliding()) { |
| 882 | w.state = .end; | 859 | w.state = .end; |
| 883 | return; | 860 | return; |
| 884 | } | 861 | } |
| 885 | const bw = w.http_protocol_output; | 862 | const bw = w.http_protocol_output; |
| 886 | switch (chunked.*) { | 863 | switch (w.state.chunk_len) { |
| 887 | .offset => |offset| { | 864 | 0 => {}, |
| 888 | const chunk_len = bw.end - offset - chunk_header_template.len; | 865 | 1 => try bw.writeByte('\n'), |
| 889 | writeHex(bw.buffer[offset..][0..chunk_len_digits], chunk_len); | 866 | 2 => try bw.writeAll("\r\n"), |
| 890 | try bw.writeAll("\r\n"); | 867 | else => unreachable, // An earlier write call indicated more data would follow. |
| 891 | }, | | |
| 892 | .chunk_len => |chunk_len| switch (chunk_len) { | | |
| 893 | 0 => {}, | | |
| 894 | 1 => try bw.writeByte('\n'), | | |
| 895 | 2 => try bw.writeAll("\r\n"), | | |
| 896 | else => unreachable, // An earlier write call indicated more data would follow. | | |
| 897 | }, | | |
| 898 | } | 868 | } |
| 899 | try bw.writeAll("0\r\n"); | 869 | try bw.writeAll("0\r\n"); |
| 900 | for (options.trailers) |trailer| { | 870 | for (options.trailers) |trailer| { |
| ... | @@ -991,44 +961,32 @@ pub const BodyWriter = struct { | ... | @@ -991,44 +961,32 @@ pub const BodyWriter = struct { |
| 991 | return error.Unimplemented; | 961 | return error.Unimplemented; |
| 992 | }; | 962 | }; |
| 993 | const out = bw.http_protocol_output; | 963 | const out = bw.http_protocol_output; |
| 994 | const chunked = &bw.state.chunked; | 964 | switch (bw.state.chunk_len) { |
| 995 | state: switch (chunked.*) { | 965 | 0 => { |
| 996 | .offset => |off| { | 966 | const header_buf = try out.writableArray(chunk_header_template.len); |
| 997 | // TODO: is it better perf to read small files into the buffer? | 967 | @memcpy(header_buf, chunk_header_template); |
| 998 | const buffered_len = out.end - off - chunk_header_template.len; | 968 | writeHex(header_buf[0..chunk_len_digits], data_len); |
| 999 | const chunk_len = data_len + buffered_len; | | |
| 1000 | writeHex(out.buffer[off..][0..chunk_len_digits], chunk_len); | | |
| 1001 | const n = try out.sendFileHeader(w.buffered(), file_reader, limit); | 969 | const n = try out.sendFileHeader(w.buffered(), file_reader, limit); |
| 1002 | chunked.* = .{ .chunk_len = data_len + 2 - n }; | 970 | bw.state.chunk_len = data_len + 2 - n; |
| 1003 | return w.consume(n); | 971 | const ret = w.consume(n); |
| | 972 | return ret; |
| 1004 | }, | 973 | }, |
| 1005 | .chunk_len => |chunk_len| l: switch (chunk_len) { | 974 | 1 => unreachable, |
| 1006 | 0 => { | 975 | 2 => { |
| 1007 | const off = out.end; | 976 | try out.writeAll("\r\n"); |
| 1008 | const header_buf = try out.writableArray(chunk_header_template.len); | 977 | bw.state.chunk_len = 0; |
| 1009 | @memcpy(header_buf, chunk_header_template); | 978 | assert(file_reader.atEnd()); |
| 1010 | chunked.* = .{ .offset = off }; | 979 | return error.EndOfStream; |
| 1011 | continue :state .{ .offset = off }; | 980 | }, |
| 1012 | }, | 981 | else => { |
| 1013 | 1 => { | 982 | const chunk_limit: std.Io.Limit = .limited(bw.state.chunk_len - 2); |
| 1014 | try out.writeByte('\n'); | 983 | const n = if (chunk_limit.subtract(w.buffered().len)) |sendfile_limit| |
| 1015 | chunked.chunk_len = 0; | 984 | try out.sendFileHeader(w.buffered(), file_reader, sendfile_limit.min(limit)) |
| 1016 | continue :l 0; | 985 | else |
| 1017 | }, | 986 | try out.write(chunk_limit.slice(w.buffered())); |
| 1018 | 2 => { | 987 | bw.state.chunk_len -= n; |
| 1019 | try out.writeByte('\r'); | 988 | const ret = w.consume(n); |
| 1020 | chunked.chunk_len = 1; | 989 | return ret; |
| 1021 | continue :l 1; | | |
| 1022 | }, | | |
| 1023 | else => { | | |
| 1024 | const chunk_limit: std.Io.Limit = .limited(chunk_len - 2); | | |
| 1025 | const n = if (chunk_limit.subtract(w.buffered().len)) |sendfile_limit| | | |
| 1026 | try out.sendFileHeader(w.buffered(), file_reader, sendfile_limit.min(limit)) | | |
| 1027 | else | | |
| 1028 | try out.write(chunk_limit.slice(w.buffered())); | | |
| 1029 | chunked.chunk_len = chunk_len - n; | | |
| 1030 | return w.consume(n); | | |
| 1031 | }, | | |
| 1032 | }, | 990 | }, |
| 1033 | } | 991 | } |
| 1034 | } | 992 | } |
| ... | @@ -1038,42 +996,25 @@ pub const BodyWriter = struct { | ... | @@ -1038,42 +996,25 @@ pub const BodyWriter = struct { |
| 1038 | assert(!bw.isEliding()); | 996 | assert(!bw.isEliding()); |
| 1039 | const out = bw.http_protocol_output; | 997 | const out = bw.http_protocol_output; |
| 1040 | const data_len = w.end + Writer.countSplat(data, splat); | 998 | const data_len = w.end + Writer.countSplat(data, splat); |
| 1041 | const chunked = &bw.state.chunked; | 999 | l: switch (bw.state.chunk_len) { |
| 1042 | state: switch (chunked.*) { | 1000 | 0 => { |
| 1043 | .offset => |offset| { | 1001 | const header_buf = try out.writableArray(chunk_header_template.len); |
| 1044 | if (out.unusedCapacityLen() >= data_len) { | 1002 | @memcpy(header_buf, chunk_header_template); |
| 1045 | return w.consume(out.writeSplatHeader(w.buffered(), data, splat) catch unreachable); | 1003 | writeHex(header_buf[0..chunk_len_digits], data_len); |
| 1046 | } | | |
| 1047 | const buffered_len = out.end - offset - chunk_header_template.len; | | |
| 1048 | const chunk_len = data_len + buffered_len; | | |
| 1049 | writeHex(out.buffer[offset..][0..chunk_len_digits], chunk_len); | | |
| 1050 | const n = try out.writeSplatHeader(w.buffered(), data, splat); | 1004 | const n = try out.writeSplatHeader(w.buffered(), data, splat); |
| 1051 | chunked.* = .{ .chunk_len = data_len + 2 - n }; | 1005 | bw.state.chunk_len = data_len + 2 - n; |
| 1052 | return w.consume(n); | 1006 | return w.consume(n); |
| 1053 | }, | 1007 | }, |
| 1054 | .chunk_len => |chunk_len| l: switch (chunk_len) { | 1008 | 1 => unreachable, |
| 1055 | 0 => { | 1009 | 2 => { |
| 1056 | const offset = out.end; | 1010 | try out.writeAll("\r\n"); |
| 1057 | const header_buf = try out.writableArray(chunk_header_template.len); | 1011 | bw.state.chunk_len = 0; |
| 1058 | @memcpy(header_buf, chunk_header_template); | 1012 | continue :l 0; |
| 1059 | chunked.* = .{ .offset = offset }; | 1013 | }, |
| 1060 | continue :state .{ .offset = offset }; | 1014 | else => { |
| 1061 | }, | 1015 | const n = try out.writeSplatHeaderLimit(w.buffered(), data, splat, .limited(bw.state.chunk_len - 2)); |
| 1062 | 1 => { | 1016 | bw.state.chunk_len -= n; |
| 1063 | try out.writeByte('\n'); | 1017 | return w.consume(n); |
| 1064 | chunked.chunk_len = 0; | | |
| 1065 | continue :l 0; | | |
| 1066 | }, | | |
| 1067 | 2 => { | | |
| 1068 | try out.writeByte('\r'); | | |
| 1069 | chunked.chunk_len = 1; | | |
| 1070 | continue :l 1; | | |
| 1071 | }, | | |
| 1072 | else => { | | |
| 1073 | const n = try out.writeSplatHeaderLimit(w.buffered(), data, splat, .limited(chunk_len - 2)); | | |
| 1074 | chunked.chunk_len = chunk_len - n; | | |
| 1075 | return w.consume(n); | | |
| 1076 | }, | | |
| 1077 | }, | 1018 | }, |
| 1078 | } | 1019 | } |
| 1079 | } | 1020 | } |