| ... | ... | @@ -875,49 +875,86 @@ pub const Response = struct { |
| 875 | 875 | |
| 876 | 876 | /// When using content-length, asserts that the amount of data sent matches |
| 877 | 877 | /// the value sent in the header, then calls `flush`. |
| 878 | /// |
| 878 | 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 | 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 | 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 | 908 | switch (r.transfer_encoding) { |
| 883 | | .content_length => |len| { |
| 884 | | assert(len == 0); // Trips when end() called before all bytes written. |
| 885 | | try flushContentLength(r); |
| 886 | | }, |
| 887 | | .none => { |
| 888 | | try flushContentLength(r); |
| 889 | | }, |
| 890 | | .chunked => { |
| 891 | | try flushChunked(r, &.{}); |
| 892 | | }, |
| 909 | .content_length => |len| assert(len == 0), // Trips when end() called before all bytes written. |
| 910 | .none => {}, |
| 911 | .chunked => try endChunked(r, .{}), |
| 893 | 912 | } |
| 894 | | r.* = undefined; |
| 895 | 913 | } |
| 896 | 914 | |
| 897 | 915 | pub const EndChunkedOptions = struct { |
| 898 | 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 | 923 | /// Asserts that the Response is using transfer-encoding: chunked. |
| 902 | | /// Writes the end-of-stream message and any optional trailers, then |
| 903 | | /// flushes the stream to the system. |
| 924 | /// |
| 904 | 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 | 930 | pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void { |
| 907 | | assert(r.transfer_encoding == .chunked); |
| 908 | | try flushChunked(r, options.trailers); |
| 909 | | r.* = undefined; |
| 910 | | } |
| 911 | | |
| 912 | | /// If using content-length, asserts that writing these bytes to the client |
| 913 | | /// would not exceed the content-length value sent in the HTTP header. |
| 914 | | /// May return 0, which does not indicate end of stream. The caller decides |
| 915 | | /// when the end of stream occurs by calling `end`. |
| 916 | | pub fn write(r: *Response, bytes: []const u8) WriteError!usize { |
| 917 | | switch (r.transfer_encoding) { |
| 918 | | .content_length, .none => return contentLengthWriteSplat(r, &.{bytes}, 1), |
| 919 | | .chunked => return chunkedWriteSplat(r, &.{bytes}, 1), |
| 931 | const chunked = &r.transfer_encoding.chunked; |
| 932 | if (r.elide_body) return; |
| 933 | const bw = r.server_output; |
| 934 | switch (chunked.*) { |
| 935 | .offset => |offset| { |
| 936 | const chunk_len = bw.end - offset - chunk_header_template.len; |
| 937 | writeHex(bw.buffer[offset..][0..chunk_len_digits], chunk_len); |
| 938 | try bw.writeAll("\r\n"); |
| 939 | }, |
| 940 | .chunk_len => |chunk_len| switch (chunk_len) { |
| 941 | 0 => {}, |
| 942 | 1 => try bw.writeByte('\n'), |
| 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 | 960 | fn contentLengthWriteSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) WriteError!usize { |
| ... | ... | @@ -1017,32 +1054,30 @@ pub const Response = struct { |
| 1017 | 1054 | chunked.* = .{ .chunk_len = data_len + 2 - n }; |
| 1018 | 1055 | return n; |
| 1019 | 1056 | }, |
| 1020 | | .chunk_len => |chunk_len| { |
| 1021 | | l: switch (chunk_len) { |
| 1022 | | 0 => { |
| 1023 | | const header_buf = try bw.writableArray(chunk_header_template.len); |
| 1024 | | const off = bw.end; |
| 1025 | | @memcpy(header_buf, chunk_header_template); |
| 1026 | | chunked.* = .{ .offset = off }; |
| 1027 | | continue :state .{ .offset = off }; |
| 1028 | | }, |
| 1029 | | 1 => { |
| 1030 | | try bw.writeByte('\n'); |
| 1031 | | chunked.chunk_len = 0; |
| 1032 | | continue :l 0; |
| 1033 | | }, |
| 1034 | | 2 => { |
| 1035 | | try bw.writeByte('\r'); |
| 1036 | | chunked.chunk_len = 1; |
| 1037 | | continue :l 1; |
| 1038 | | }, |
| 1039 | | else => { |
| 1040 | | const new_limit = limit.min(.limited(chunk_len - 2)); |
| 1041 | | const n = try bw.writeFile(file, offset, new_limit, headers_and_trailers, headers_len); |
| 1042 | | chunked.chunk_len = chunk_len - n; |
| 1043 | | return n; |
| 1044 | | }, |
| 1045 | | } |
| 1057 | .chunk_len => |chunk_len| l: switch (chunk_len) { |
| 1058 | 0 => { |
| 1059 | const header_buf = try bw.writableArray(chunk_header_template.len); |
| 1060 | const off = bw.end; |
| 1061 | @memcpy(header_buf, chunk_header_template); |
| 1062 | chunked.* = .{ .offset = off }; |
| 1063 | continue :state .{ .offset = off }; |
| 1064 | }, |
| 1065 | 1 => { |
| 1066 | try bw.writeByte('\n'); |
| 1067 | chunked.chunk_len = 0; |
| 1068 | continue :l 0; |
| 1069 | }, |
| 1070 | 2 => { |
| 1071 | try bw.writeByte('\r'); |
| 1072 | chunked.chunk_len = 1; |
| 1073 | continue :l 1; |
| 1074 | }, |
| 1075 | else => { |
| 1076 | const new_limit = limit.min(.limited(chunk_len - 2)); |
| 1077 | const n = try bw.writeFile(file, offset, new_limit, headers_and_trailers, headers_len); |
| 1078 | chunked.chunk_len = chunk_len - n; |
| 1079 | return n; |
| 1080 | }, |
| 1046 | 1081 | }, |
| 1047 | 1082 | } |
| 1048 | 1083 | } |
| ... | ... | @@ -1068,31 +1103,29 @@ pub const Response = struct { |
| 1068 | 1103 | chunked.* = .{ .chunk_len = data_len + 2 - n }; |
| 1069 | 1104 | return n; |
| 1070 | 1105 | }, |
| 1071 | | .chunk_len => |chunk_len| { |
| 1072 | | l: switch (chunk_len) { |
| 1073 | | 0 => { |
| 1074 | | const header_buf = try bw.writableArray(chunk_header_template.len); |
| 1075 | | const offset = bw.end; |
| 1076 | | @memcpy(header_buf, chunk_header_template); |
| 1077 | | chunked.* = .{ .offset = offset }; |
| 1078 | | continue :state .{ .offset = offset }; |
| 1079 | | }, |
| 1080 | | 1 => { |
| 1081 | | try bw.writeByte('\n'); |
| 1082 | | chunked.chunk_len = 0; |
| 1083 | | continue :l 0; |
| 1084 | | }, |
| 1085 | | 2 => { |
| 1086 | | try bw.writeByte('\r'); |
| 1087 | | chunked.chunk_len = 1; |
| 1088 | | continue :l 1; |
| 1089 | | }, |
| 1090 | | else => { |
| 1091 | | const n = try bw.writeSplatLimit(data, splat, .limited(chunk_len - 2)); |
| 1092 | | chunked.chunk_len = chunk_len - n; |
| 1093 | | return n; |
| 1094 | | }, |
| 1095 | | } |
| 1106 | .chunk_len => |chunk_len| l: switch (chunk_len) { |
| 1107 | 0 => { |
| 1108 | const header_buf = try bw.writableArray(chunk_header_template.len); |
| 1109 | const offset = bw.end; |
| 1110 | @memcpy(header_buf, chunk_header_template); |
| 1111 | chunked.* = .{ .offset = offset }; |
| 1112 | continue :state .{ .offset = offset }; |
| 1113 | }, |
| 1114 | 1 => { |
| 1115 | try bw.writeByte('\n'); |
| 1116 | chunked.chunk_len = 0; |
| 1117 | continue :l 0; |
| 1118 | }, |
| 1119 | 2 => { |
| 1120 | try bw.writeByte('\r'); |
| 1121 | chunked.chunk_len = 1; |
| 1122 | continue :l 1; |
| 1123 | }, |
| 1124 | else => { |
| 1125 | const n = try bw.writeSplatLimit(data, splat, .limited(chunk_len - 2)); |
| 1126 | chunked.chunk_len = chunk_len - n; |
| 1127 | return n; |
| 1128 | }, |
| 1096 | 1129 | }, |
| 1097 | 1130 | } |
| 1098 | 1131 | } |
| ... | ... | @@ -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 | 1148 | pub fn writer(r: *Response) std.io.Writer { |
| 1197 | 1149 | return .{ |
| 1198 | 1150 | .context = r, |