| ... | @@ -875,49 +875,86 @@ pub const Response = struct { | ... | @@ -875,49 +875,86 @@ pub const Response = struct { |
| 875 | | 875 | |
| 876 | /// When using content-length, asserts that the amount of data sent matches | 876 | /// When using content-length, asserts that the amount of data sent matches |
| 877 | /// the value sent in the header, then calls `flush`. | 877 | /// the value sent in the header, then calls `flush`. |
| | 878 | /// |
| 878 | /// Otherwise, transfer-encoding: chunked is being used, and it writes the | 879 | /// Otherwise, transfer-encoding: chunked is being used, and it writes the |
| 879 | /// end-of-stream message, then flushes the stream to the system. | 880 | /// end-of-stream message with empty trailers, then flushes the stream to |
| | 881 | /// the system. |
| | 882 | /// |
| 880 | /// Respects the value of `elide_body` to omit all data after the headers. | 883 | /// Respects the value of `elide_body` to omit all data after the headers. |
| | 884 | /// |
| | 885 | /// Sets `r` to undefined. |
| | 886 | /// |
| | 887 | /// See also: |
| | 888 | /// * `endUnflushed` |
| | 889 | /// * `endChunked` |
| 881 | pub fn end(r: *Response) WriteError!void { | 890 | pub fn end(r: *Response) WriteError!void { |
| | 891 | try endUnflushed(r); |
| | 892 | try r.server_output.flush(); |
| | 893 | r.* = undefined; |
| | 894 | } |
| | 895 | |
| | 896 | /// When using content-length, asserts that the amount of data sent matches |
| | 897 | /// the value sent in the header. |
| | 898 | /// |
| | 899 | /// Otherwise, transfer-encoding: chunked is being used, and it writes the |
| | 900 | /// end-of-stream message with empty trailers. |
| | 901 | /// |
| | 902 | /// Respects the value of `elide_body` to omit all data after the headers. |
| | 903 | /// |
| | 904 | /// See also: |
| | 905 | /// * `end` |
| | 906 | /// * `endChunked` |
| | 907 | pub fn endUnflushed(r: *Response) WriteError!void { |
| 882 | switch (r.transfer_encoding) { | 908 | switch (r.transfer_encoding) { |
| 883 | .content_length => |len| { | 909 | .content_length => |len| assert(len == 0), // Trips when end() called before all bytes written. |
| 884 | assert(len == 0); // Trips when end() called before all bytes written. | 910 | .none => {}, |
| 885 | try flushContentLength(r); | 911 | .chunked => try endChunked(r, .{}), |
| 886 | }, | | |
| 887 | .none => { | | |
| 888 | try flushContentLength(r); | | |
| 889 | }, | | |
| 890 | .chunked => { | | |
| 891 | try flushChunked(r, &.{}); | | |
| 892 | }, | | |
| 893 | } | 912 | } |
| 894 | r.* = undefined; | | |
| 895 | } | 913 | } |
| 896 | | 914 | |
| 897 | pub const EndChunkedOptions = struct { | 915 | pub const EndChunkedOptions = struct { |
| 898 | trailers: []const http.Header = &.{}, | 916 | trailers: []const http.Header = &.{}, |
| 899 | }; | 917 | }; |
| 900 | | 918 | |
| | 919 | /// Writes the end-of-stream message and any optional trailers. |
| | 920 | /// |
| | 921 | /// Does not flush. |
| | 922 | /// |
| 901 | /// Asserts that the Response is using transfer-encoding: chunked. | 923 | /// Asserts that the Response is using transfer-encoding: chunked. |
| 902 | /// Writes the end-of-stream message and any optional trailers, then | 924 | /// |
| 903 | /// flushes the stream to the system. | | |
| 904 | /// Respects the value of `elide_body` to omit all data after the headers. | 925 | /// Respects the value of `elide_body` to omit all data after the headers. |
| 905 | /// Asserts there are at most 25 trailers. | 926 | /// |
| | 927 | /// See also: |
| | 928 | /// * `end` |
| | 929 | /// * `endUnflushed` |
| 906 | pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void { | 930 | pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void { |
| 907 | assert(r.transfer_encoding == .chunked); | 931 | const chunked = &r.transfer_encoding.chunked; |
| 908 | try flushChunked(r, options.trailers); | 932 | if (r.elide_body) return; |
| 909 | r.* = undefined; | 933 | const bw = r.server_output; |
| 910 | } | 934 | switch (chunked.*) { |
| 911 | | 935 | .offset => |offset| { |
| 912 | /// If using content-length, asserts that writing these bytes to the client | 936 | const chunk_len = bw.end - offset - chunk_header_template.len; |
| 913 | /// would not exceed the content-length value sent in the HTTP header. | 937 | writeHex(bw.buffer[offset..][0..chunk_len_digits], chunk_len); |
| 914 | /// May return 0, which does not indicate end of stream. The caller decides | 938 | try bw.writeAll("\r\n"); |
| 915 | /// when the end of stream occurs by calling `end`. | 939 | }, |
| 916 | pub fn write(r: *Response, bytes: []const u8) WriteError!usize { | 940 | .chunk_len => |chunk_len| switch (chunk_len) { |
| 917 | switch (r.transfer_encoding) { | 941 | 0 => {}, |
| 918 | .content_length, .none => return contentLengthWriteSplat(r, &.{bytes}, 1), | 942 | 1 => try bw.writeByte('\n'), |
| 919 | .chunked => return chunkedWriteSplat(r, &.{bytes}, 1), | 943 | 2 => try bw.writeAll("\r\n"), |
| | 944 | else => unreachable, // An earlier write call indicated more data would follow. |
| | 945 | }, |
| | 946 | } |
| | 947 | if (options.trailers.len > 0) { |
| | 948 | try bw.writeAll("0\r\n"); |
| | 949 | for (options.trailers) |trailer| { |
| | 950 | try bw.writeAll(trailer.name); |
| | 951 | try bw.writeAll(": "); |
| | 952 | try bw.writeAll(trailer.value); |
| | 953 | try bw.writeAll("\r\n"); |
| | 954 | } |
| | 955 | try bw.writeAll("\r\n"); |
| 920 | } | 956 | } |
| | 957 | r.* = undefined; |
| 921 | } | 958 | } |
| 922 | | 959 | |
| 923 | fn contentLengthWriteSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) WriteError!usize { | 960 | fn contentLengthWriteSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) WriteError!usize { |
| ... | @@ -1017,32 +1054,30 @@ pub const Response = struct { | ... | @@ -1017,32 +1054,30 @@ pub const Response = struct { |
| 1017 | chunked.* = .{ .chunk_len = data_len + 2 - n }; | 1054 | chunked.* = .{ .chunk_len = data_len + 2 - n }; |
| 1018 | return n; | 1055 | return n; |
| 1019 | }, | 1056 | }, |
| 1020 | .chunk_len => |chunk_len| { | 1057 | .chunk_len => |chunk_len| l: switch (chunk_len) { |
| 1021 | l: switch (chunk_len) { | 1058 | 0 => { |
| 1022 | 0 => { | 1059 | const header_buf = try bw.writableArray(chunk_header_template.len); |
| 1023 | const header_buf = try bw.writableArray(chunk_header_template.len); | 1060 | const off = bw.end; |
| 1024 | const off = bw.end; | 1061 | @memcpy(header_buf, chunk_header_template); |
| 1025 | @memcpy(header_buf, chunk_header_template); | 1062 | chunked.* = .{ .offset = off }; |
| 1026 | chunked.* = .{ .offset = off }; | 1063 | continue :state .{ .offset = off }; |
| 1027 | continue :state .{ .offset = off }; | 1064 | }, |
| 1028 | }, | 1065 | 1 => { |
| 1029 | 1 => { | 1066 | try bw.writeByte('\n'); |
| 1030 | try bw.writeByte('\n'); | 1067 | chunked.chunk_len = 0; |
| 1031 | chunked.chunk_len = 0; | 1068 | continue :l 0; |
| 1032 | continue :l 0; | 1069 | }, |
| 1033 | }, | 1070 | 2 => { |
| 1034 | 2 => { | 1071 | try bw.writeByte('\r'); |
| 1035 | try bw.writeByte('\r'); | 1072 | chunked.chunk_len = 1; |
| 1036 | chunked.chunk_len = 1; | 1073 | continue :l 1; |
| 1037 | continue :l 1; | 1074 | }, |
| 1038 | }, | 1075 | else => { |
| 1039 | else => { | 1076 | const new_limit = limit.min(.limited(chunk_len - 2)); |
| 1040 | const new_limit = limit.min(.limited(chunk_len - 2)); | 1077 | const n = try bw.writeFile(file, offset, new_limit, headers_and_trailers, headers_len); |
| 1041 | const n = try bw.writeFile(file, offset, new_limit, headers_and_trailers, headers_len); | 1078 | chunked.chunk_len = chunk_len - n; |
| 1042 | chunked.chunk_len = chunk_len - n; | 1079 | return n; |
| 1043 | return n; | 1080 | }, |
| 1044 | }, | | |
| 1045 | } | | |
| 1046 | }, | 1081 | }, |
| 1047 | } | 1082 | } |
| 1048 | } | 1083 | } |
| ... | @@ -1068,31 +1103,29 @@ pub const Response = struct { | ... | @@ -1068,31 +1103,29 @@ pub const Response = struct { |
| 1068 | chunked.* = .{ .chunk_len = data_len + 2 - n }; | 1103 | chunked.* = .{ .chunk_len = data_len + 2 - n }; |
| 1069 | return n; | 1104 | return n; |
| 1070 | }, | 1105 | }, |
| 1071 | .chunk_len => |chunk_len| { | 1106 | .chunk_len => |chunk_len| l: switch (chunk_len) { |
| 1072 | l: switch (chunk_len) { | 1107 | 0 => { |
| 1073 | 0 => { | 1108 | const header_buf = try bw.writableArray(chunk_header_template.len); |
| 1074 | const header_buf = try bw.writableArray(chunk_header_template.len); | 1109 | const offset = bw.end; |
| 1075 | const offset = bw.end; | 1110 | @memcpy(header_buf, chunk_header_template); |
| 1076 | @memcpy(header_buf, chunk_header_template); | 1111 | chunked.* = .{ .offset = offset }; |
| 1077 | chunked.* = .{ .offset = offset }; | 1112 | continue :state .{ .offset = offset }; |
| 1078 | continue :state .{ .offset = offset }; | 1113 | }, |
| 1079 | }, | 1114 | 1 => { |
| 1080 | 1 => { | 1115 | try bw.writeByte('\n'); |
| 1081 | try bw.writeByte('\n'); | 1116 | chunked.chunk_len = 0; |
| 1082 | chunked.chunk_len = 0; | 1117 | continue :l 0; |
| 1083 | continue :l 0; | 1118 | }, |
| 1084 | }, | 1119 | 2 => { |
| 1085 | 2 => { | 1120 | try bw.writeByte('\r'); |
| 1086 | try bw.writeByte('\r'); | 1121 | chunked.chunk_len = 1; |
| 1087 | chunked.chunk_len = 1; | 1122 | continue :l 1; |
| 1088 | continue :l 1; | 1123 | }, |
| 1089 | }, | 1124 | else => { |
| 1090 | else => { | 1125 | const n = try bw.writeSplatLimit(data, splat, .limited(chunk_len - 2)); |
| 1091 | const n = try bw.writeSplatLimit(data, splat, .limited(chunk_len - 2)); | 1126 | chunked.chunk_len = chunk_len - n; |
| 1092 | chunked.chunk_len = chunk_len - n; | 1127 | return n; |
| 1093 | return n; | 1128 | }, |
| 1094 | }, | | |
| 1095 | } | | |
| 1096 | }, | 1129 | }, |
| 1097 | } | 1130 | } |
| 1098 | } | 1131 | } |
| ... | @@ -1112,87 +1145,6 @@ pub const Response = struct { | ... | @@ -1112,87 +1145,6 @@ pub const Response = struct { |
| 1112 | } | 1145 | } |
| 1113 | } | 1146 | } |
| 1114 | | 1147 | |
| 1115 | /// Sends all buffered data to the client. | | |
| 1116 | /// This is redundant after calling `end`. | | |
| 1117 | /// Respects the value of `elide_body` to omit all data after the headers. | | |
| 1118 | pub fn flush(r: *Response) Error!void { | | |
| 1119 | switch (r.transfer_encoding) { | | |
| 1120 | .none, .content_length => return flushContentLength(r), | | |
| 1121 | .chunked => return flushChunked(r, null), | | |
| 1122 | } | | |
| 1123 | } | | |
| 1124 | | | |
| 1125 | fn flushContentLength(r: *Response) Error!void { | | |
| 1126 | try r.out.writeAll(r.send_buffer[r.send_buffer_start..r.send_buffer_end]); | | |
| 1127 | r.send_buffer_start = 0; | | |
| 1128 | r.send_buffer_end = 0; | | |
| 1129 | } | | |
| 1130 | | | |
| 1131 | fn flushChunked(r: *Response, end_trailers: ?[]const http.Header) Error!void { | | |
| 1132 | const max_trailers = 25; | | |
| 1133 | if (end_trailers) |trailers| assert(trailers.len <= max_trailers); | | |
| 1134 | assert(r.transfer_encoding == .chunked); | | |
| 1135 | | | |
| 1136 | const http_headers = r.send_buffer[r.send_buffer_start .. r.send_buffer_end - r.chunk_len]; | | |
| 1137 | | | |
| 1138 | if (r.elide_body) { | | |
| 1139 | try r.out.writeAll(http_headers); | | |
| 1140 | r.send_buffer_start = 0; | | |
| 1141 | r.send_buffer_end = 0; | | |
| 1142 | r.chunk_len = 0; | | |
| 1143 | return; | | |
| 1144 | } | | |
| 1145 | | | |
| 1146 | var header_buf: [18]u8 = undefined; | | |
| 1147 | const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{r.chunk_len}) catch unreachable; | | |
| 1148 | | | |
| 1149 | var iovecs: [max_trailers * 4 + 5][]const u8 = undefined; | | |
| 1150 | var iovecs_len: usize = 0; | | |
| 1151 | | | |
| 1152 | iovecs[iovecs_len] = http_headers; | | |
| 1153 | iovecs_len += 1; | | |
| 1154 | | | |
| 1155 | if (r.chunk_len > 0) { | | |
| 1156 | iovecs[iovecs_len] = chunk_header; | | |
| 1157 | iovecs_len += 1; | | |
| 1158 | | | |
| 1159 | iovecs[iovecs_len] = r.send_buffer[r.send_buffer_end - r.chunk_len ..][0..r.chunk_len]; | | |
| 1160 | iovecs_len += 1; | | |
| 1161 | | | |
| 1162 | iovecs[iovecs_len] = "\r\n"; | | |
| 1163 | iovecs_len += 1; | | |
| 1164 | } | | |
| 1165 | | | |
| 1166 | if (end_trailers) |trailers| { | | |
| 1167 | iovecs[iovecs_len] = "0\r\n"; | | |
| 1168 | iovecs_len += 1; | | |
| 1169 | | | |
| 1170 | for (trailers) |trailer| { | | |
| 1171 | iovecs[iovecs_len] = trailer.name; | | |
| 1172 | iovecs_len += 1; | | |
| 1173 | | | |
| 1174 | iovecs[iovecs_len] = ": "; | | |
| 1175 | iovecs_len += 1; | | |
| 1176 | | | |
| 1177 | if (trailer.value.len != 0) { | | |
| 1178 | iovecs[iovecs_len] = trailer.value; | | |
| 1179 | iovecs_len += 1; | | |
| 1180 | } | | |
| 1181 | | | |
| 1182 | iovecs[iovecs_len] = "\r\n"; | | |
| 1183 | iovecs_len += 1; | | |
| 1184 | } | | |
| 1185 | | | |
| 1186 | iovecs[iovecs_len] = "\r\n"; | | |
| 1187 | iovecs_len += 1; | | |
| 1188 | } | | |
| 1189 | | | |
| 1190 | try r.out.writeVecAll(iovecs[0..iovecs_len]); | | |
| 1191 | r.send_buffer_start = 0; | | |
| 1192 | r.send_buffer_end = 0; | | |
| 1193 | r.chunk_len = 0; | | |
| 1194 | } | | |
| 1195 | | | |
| 1196 | pub fn writer(r: *Response) std.io.Writer { | 1148 | pub fn writer(r: *Response) std.io.Writer { |
| 1197 | return .{ | 1149 | return .{ |
| 1198 | .context = r, | 1150 | .context = r, |