| ... | ... | @@ -758,22 +758,14 @@ pub const BodyWriter = struct { |
| 758 | 758 | /// As a debugging utility, counts down to zero as bytes are written. |
| 759 | 759 | content_length: u64, |
| 760 | 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 | 765 | /// Cleanly finished stream; connection can be reused. |
| 763 | 766 | end, |
| 764 | 767 | |
| 765 | | pub const Chunked = union(enum) { |
| 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 | | }; |
| 768 | pub const init_chunked: State = .{ .chunk_len = 0 }; |
| 777 | 769 | }; |
| 778 | 770 | |
| 779 | 771 | pub fn isEliding(w: *const BodyWriter) bool { |
| ... | ... | @@ -784,21 +776,7 @@ pub const BodyWriter = struct { |
| 784 | 776 | pub fn flush(w: *BodyWriter) Error!void { |
| 785 | 777 | const out = w.http_protocol_output; |
| 786 | 778 | switch (w.state) { |
| 787 | | .end, .none, .content_length => 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 | | }, |
| 779 | .end, .none, .content_length, .chunk_len => return out.flush(), |
| 802 | 780 | } |
| 803 | 781 | } |
| 804 | 782 | |
| ... | ... | @@ -841,7 +819,7 @@ pub const BodyWriter = struct { |
| 841 | 819 | w.state = .end; |
| 842 | 820 | }, |
| 843 | 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 | 855 | /// * `endUnflushed` |
| 878 | 856 | /// * `end` |
| 879 | 857 | pub fn endChunkedUnflushed(w: *BodyWriter, options: EndChunkedOptions) Error!void { |
| 880 | | const chunked = &w.state.chunked; |
| 881 | 858 | if (w.isEliding()) { |
| 882 | 859 | w.state = .end; |
| 883 | 860 | return; |
| 884 | 861 | } |
| 885 | 862 | const bw = w.http_protocol_output; |
| 886 | | switch (chunked.*) { |
| 887 | | .offset => |offset| { |
| 888 | | const chunk_len = bw.end - offset - chunk_header_template.len; |
| 889 | | writeHex(bw.buffer[offset..][0..chunk_len_digits], chunk_len); |
| 890 | | try bw.writeAll("\r\n"); |
| 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 | | }, |
| 863 | switch (w.state.chunk_len) { |
| 864 | 0 => {}, |
| 865 | 1 => try bw.writeByte('\n'), |
| 866 | 2 => try bw.writeAll("\r\n"), |
| 867 | else => unreachable, // An earlier write call indicated more data would follow. |
| 898 | 868 | } |
| 899 | 869 | try bw.writeAll("0\r\n"); |
| 900 | 870 | for (options.trailers) |trailer| { |
| ... | ... | @@ -991,44 +961,32 @@ pub const BodyWriter = struct { |
| 991 | 961 | return error.Unimplemented; |
| 992 | 962 | }; |
| 993 | 963 | const out = bw.http_protocol_output; |
| 994 | | const chunked = &bw.state.chunked; |
| 995 | | state: switch (chunked.*) { |
| 996 | | .offset => |off| { |
| 997 | | // TODO: is it better perf to read small files into the buffer? |
| 998 | | const buffered_len = out.end - off - chunk_header_template.len; |
| 999 | | const chunk_len = data_len + buffered_len; |
| 1000 | | writeHex(out.buffer[off..][0..chunk_len_digits], chunk_len); |
| 964 | switch (bw.state.chunk_len) { |
| 965 | 0 => { |
| 966 | const header_buf = try out.writableArray(chunk_header_template.len); |
| 967 | @memcpy(header_buf, chunk_header_template); |
| 968 | writeHex(header_buf[0..chunk_len_digits], data_len); |
| 1001 | 969 | const n = try out.sendFileHeader(w.buffered(), file_reader, limit); |
| 1002 | | chunked.* = .{ .chunk_len = data_len + 2 - n }; |
| 1003 | | return w.consume(n); |
| 970 | bw.state.chunk_len = data_len + 2 - n; |
| 971 | const ret = w.consume(n); |
| 972 | return ret; |
| 1004 | 973 | }, |
| 1005 | | .chunk_len => |chunk_len| l: switch (chunk_len) { |
| 1006 | | 0 => { |
| 1007 | | const off = out.end; |
| 1008 | | const header_buf = try out.writableArray(chunk_header_template.len); |
| 1009 | | @memcpy(header_buf, chunk_header_template); |
| 1010 | | chunked.* = .{ .offset = off }; |
| 1011 | | continue :state .{ .offset = off }; |
| 1012 | | }, |
| 1013 | | 1 => { |
| 1014 | | try out.writeByte('\n'); |
| 1015 | | chunked.chunk_len = 0; |
| 1016 | | continue :l 0; |
| 1017 | | }, |
| 1018 | | 2 => { |
| 1019 | | try out.writeByte('\r'); |
| 1020 | | chunked.chunk_len = 1; |
| 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 | | }, |
| 974 | 1 => unreachable, |
| 975 | 2 => { |
| 976 | try out.writeAll("\r\n"); |
| 977 | bw.state.chunk_len = 0; |
| 978 | assert(file_reader.atEnd()); |
| 979 | return error.EndOfStream; |
| 980 | }, |
| 981 | else => { |
| 982 | const chunk_limit: std.Io.Limit = .limited(bw.state.chunk_len - 2); |
| 983 | const n = if (chunk_limit.subtract(w.buffered().len)) |sendfile_limit| |
| 984 | try out.sendFileHeader(w.buffered(), file_reader, sendfile_limit.min(limit)) |
| 985 | else |
| 986 | try out.write(chunk_limit.slice(w.buffered())); |
| 987 | bw.state.chunk_len -= n; |
| 988 | const ret = w.consume(n); |
| 989 | return ret; |
| 1032 | 990 | }, |
| 1033 | 991 | } |
| 1034 | 992 | } |
| ... | ... | @@ -1038,42 +996,25 @@ pub const BodyWriter = struct { |
| 1038 | 996 | assert(!bw.isEliding()); |
| 1039 | 997 | const out = bw.http_protocol_output; |
| 1040 | 998 | const data_len = w.end + Writer.countSplat(data, splat); |
| 1041 | | const chunked = &bw.state.chunked; |
| 1042 | | state: switch (chunked.*) { |
| 1043 | | .offset => |offset| { |
| 1044 | | if (out.unusedCapacityLen() >= data_len) { |
| 1045 | | return w.consume(out.writeSplatHeader(w.buffered(), data, splat) catch unreachable); |
| 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); |
| 999 | l: switch (bw.state.chunk_len) { |
| 1000 | 0 => { |
| 1001 | const header_buf = try out.writableArray(chunk_header_template.len); |
| 1002 | @memcpy(header_buf, chunk_header_template); |
| 1003 | writeHex(header_buf[0..chunk_len_digits], data_len); |
| 1050 | 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 | 1006 | return w.consume(n); |
| 1053 | 1007 | }, |
| 1054 | | .chunk_len => |chunk_len| l: switch (chunk_len) { |
| 1055 | | 0 => { |
| 1056 | | const offset = out.end; |
| 1057 | | const header_buf = try out.writableArray(chunk_header_template.len); |
| 1058 | | @memcpy(header_buf, chunk_header_template); |
| 1059 | | chunked.* = .{ .offset = offset }; |
| 1060 | | continue :state .{ .offset = offset }; |
| 1061 | | }, |
| 1062 | | 1 => { |
| 1063 | | try out.writeByte('\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 | | }, |
| 1008 | 1 => unreachable, |
| 1009 | 2 => { |
| 1010 | try out.writeAll("\r\n"); |
| 1011 | bw.state.chunk_len = 0; |
| 1012 | continue :l 0; |
| 1013 | }, |
| 1014 | else => { |
| 1015 | const n = try out.writeSplatHeaderLimit(w.buffered(), data, splat, .limited(bw.state.chunk_len - 2)); |
| 1016 | bw.state.chunk_len -= n; |
| 1017 | return w.consume(n); |
| 1077 | 1018 | }, |
| 1078 | 1019 | } |
| 1079 | 1020 | } |