authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-18 22:13:06-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-23 02:37:11-07:00
log68d3e103b707f8d55ba29ed8c5ccfdcf791076ca
tree46a2a87f5651bbc2782f6685fc3e351d90c7bece
parent968d08af6db476942c60d17d55e3086ec0736fac

full send


1 files changed, 113 insertions(+), 31 deletions(-)

lib/std/http/Server.zig+113-31
...@@ -1,13 +1,7 @@...@@ -1,13 +1,7 @@
1version: http.Version,
2status: http.Status,
3reason: ?[]const u8,
4transfer_encoding: ResponseTransfer,
5keep_alive: bool,
6connection: Connection,1connection: Connection,
7connection_closing: bool,2/// This value is determined by Server when sending headers to the client, and
83/// then used to determine the return value of `reset`.
9/// Externally-owned; must outlive the Server.4connection_keep_alive: bool,
10extra_headers: []const http.Header,
115
12/// The HTTP request that this response is responding to.6/// The HTTP request that this response is responding to.
13///7///
...@@ -21,20 +15,14 @@ state: State = .first,...@@ -21,20 +15,14 @@ state: State = .first,
21/// The returned `Server` is ready for `reset` or `wait` to be called.15/// The returned `Server` is ready for `reset` or `wait` to be called.
22pub fn init(connection: std.net.Server.Connection, options: Server.Request.InitOptions) Server {16pub fn init(connection: std.net.Server.Connection, options: Server.Request.InitOptions) Server {
23 return .{17 return .{
24 .transfer_encoding = .none,
25 .keep_alive = true,
26 .connection = .{18 .connection = .{
27 .stream = connection.stream,19 .stream = connection.stream,
28 .read_buf = undefined,20 .read_buf = undefined,
29 .read_start = 0,21 .read_start = 0,
30 .read_end = 0,22 .read_end = 0,
31 },23 },
32 .connection_closing = true,24 .connection_keep_alive = false,
33 .request = Server.Request.init(options),25 .request = Server.Request.init(options),
34 .version = .@"HTTP/1.1",
35 .status = .ok,
36 .reason = null,
37 .extra_headers = &.{},
38 };26 };
39}27}
4028
...@@ -232,30 +220,123 @@ pub fn reset(res: *Server) ResetState {...@@ -232,30 +220,123 @@ pub fn reset(res: *Server) ResetState {
232220
233 if (!res.request.parser.done) {221 if (!res.request.parser.done) {
234 // If the response wasn't fully read, then we need to close the connection.222 // If the response wasn't fully read, then we need to close the connection.
235 res.connection_closing = true;223 res.connection_keep_alive = false;
236 return .closing;224 return .closing;
237 }225 }
238226
239 // A connection is only keep-alive if the Connection header is present
240 // and its value is not "close". The server and client must both agree.
241 //
242 // send() defaults to using keep-alive if the client requests it.
243 res.connection_closing = !res.keep_alive or !res.request.keep_alive;
244
245 res.state = .start;227 res.state = .start;
246 res.version = .@"HTTP/1.1";
247 res.status = .ok;
248 res.reason = null;
249
250 res.transfer_encoding = .none;
251
252 res.request = Request.init(.{228 res.request = Request.init(.{
253 .client_header_buffer = res.request.parser.header_bytes_buffer,229 .client_header_buffer = res.request.parser.header_bytes_buffer,
254 });230 });
255231
256 return if (res.connection_closing) .closing else .reset;232 return if (res.connection_keep_alive) .reset else .closing;
233}
234
235pub const SendAllError = std.net.Stream.WriteError;
236
237pub const SendOptions = struct {
238 version: http.Version = .@"HTTP/1.1",
239 status: http.Status = .ok,
240 reason: ?[]const u8 = null,
241 keep_alive: bool = true,
242 extra_headers: []const http.Header = &.{},
243 content: []const u8,
244};
245
246/// Send an entire HTTP response to the client, including headers and body.
247/// Automatically handles HEAD requests by omitting the body.
248/// Uses the "content-length" header.
249/// Asserts status is not `continue`.
250/// Asserts there are at most 25 extra_headers.
251pub fn sendAll(s: *Server, options: SendOptions) SendAllError!void {
252 const max_extra_headers = 25;
253 assert(options.status != .@"continue");
254 assert(options.extra_headers.len <= max_extra_headers);
255
256 switch (s.state) {
257 .waited => s.state = .finished,
258 .first => unreachable, // Call reset() first.
259 .start => unreachable, // Call wait() first.
260 .responded => unreachable, // Cannot mix sendAll() with send().
261 .finished => unreachable, // Call reset() first.
262 }
263
264 s.connection_keep_alive = options.keep_alive and s.request.keep_alive;
265 const keep_alive_line = if (s.connection_keep_alive)
266 "connection: keep-alive\r\n"
267 else
268 "";
269 const phrase = options.reason orelse options.status.phrase() orelse "";
270
271 var first_buffer: [500]u8 = undefined;
272 const first_bytes = std.fmt.bufPrint(
273 &first_buffer,
274 "{s} {d} {s}\r\n{s}content-length: {d}\r\n",
275 .{
276 @tagName(options.version),
277 @intFromEnum(options.status),
278 phrase,
279 keep_alive_line,
280 options.content.len,
281 },
282 ) catch unreachable;
283
284 var iovecs: [max_extra_headers * 4 + 3]std.posix.iovec_const = undefined;
285 var iovecs_len: usize = 0;
286
287 iovecs[iovecs_len] = .{
288 .iov_base = first_bytes.ptr,
289 .iov_len = first_bytes.len,
290 };
291 iovecs_len += 1;
292
293 for (options.extra_headers) |header| {
294 iovecs[iovecs_len] = .{
295 .iov_base = header.name.ptr,
296 .iov_len = header.name.len,
297 };
298 iovecs_len += 1;
299
300 iovecs[iovecs_len] = .{
301 .iov_base = ": ",
302 .iov_len = 2,
303 };
304 iovecs_len += 1;
305
306 iovecs[iovecs_len] = .{
307 .iov_base = header.value.ptr,
308 .iov_len = header.value.len,
309 };
310 iovecs_len += 1;
311
312 iovecs[iovecs_len] = .{
313 .iov_base = "\r\n",
314 .iov_len = 2,
315 };
316 iovecs_len += 1;
317 }
318
319 iovecs[iovecs_len] = .{
320 .iov_base = "\r\n",
321 .iov_len = 2,
322 };
323 iovecs_len += 1;
324
325 if (s.request.method != .HEAD) {
326 iovecs[iovecs_len] = .{
327 .iov_base = options.content.ptr,
328 .iov_len = options.content.len,
329 };
330 iovecs_len += 1;
331 }
332
333 return s.connection.stream.writevAll(iovecs[0..iovecs_len]);
257}334}
258335
336pub const Response = struct {
337 transfer_encoding: ResponseTransfer,
338};
339
259pub const SendError = Connection.WriteError || error{340pub const SendError = Connection.WriteError || error{
260 UnsupportedTransferEncoding,341 UnsupportedTransferEncoding,
261 InvalidContentLength,342 InvalidContentLength,
...@@ -285,7 +366,8 @@ pub fn send(res: *Server) SendError!void {...@@ -285,7 +366,8 @@ pub fn send(res: *Server) SendError!void {
285 if (res.status == .@"continue") {366 if (res.status == .@"continue") {
286 res.state = .waited; // we still need to send another request after this367 res.state = .waited; // we still need to send another request after this
287 } else {368 } else {
288 if (res.keep_alive and res.request.keep_alive) {369 res.connection_keep_alive = res.keep_alive and res.request.keep_alive;
370 if (res.connection_keep_alive) {
289 try w.writeAll("connection: keep-alive\r\n");371 try w.writeAll("connection: keep-alive\r\n");
290 } else {372 } else {
291 try w.writeAll("connection: close\r\n");373 try w.writeAll("connection: close\r\n");