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