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