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 @@
1version: http.Version,
2status: http.Status,
3reason: ?[]const u8,
4transfer_encoding: ResponseTransfer,
5keep_alive: bool,
61connection: Connection,
7connection_closing: bool,
8
9/// Externally-owned; must outlive the Server.
10extra_headers: []const http.Header,
2/// This value is determined by Server when sending headers to the client, and
3/// then used to determine the return value of `reset`.
4connection_keep_alive: bool,
115
126/// The HTTP request that this response is responding to.
137///
......@@ -21,20 +15,14 @@ state: State = .first,
2115/// The returned `Server` is ready for `reset` or `wait` to be called.
2216pub fn init(connection: std.net.Server.Connection, options: Server.Request.InitOptions) Server {
2317 return .{
24 .transfer_encoding = .none,
25 .keep_alive = true,
2618 .connection = .{
2719 .stream = connection.stream,
2820 .read_buf = undefined,
2921 .read_start = 0,
3022 .read_end = 0,
3123 },
32 .connection_closing = true,
24 .connection_keep_alive = false,
3325 .request = Server.Request.init(options),
34 .version = .@"HTTP/1.1",
35 .status = .ok,
36 .reason = null,
37 .extra_headers = &.{},
3826 };
3927}
4028
......@@ -232,30 +220,123 @@ pub fn reset(res: *Server) ResetState {
232220
233221 if (!res.request.parser.done) {
234222 // 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;
236224 return .closing;
237225 }
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
245227 res.state = .start;
246 res.version = .@"HTTP/1.1";
247 res.status = .ok;
248 res.reason = null;
249
250 res.transfer_encoding = .none;
251
252228 res.request = Request.init(.{
253229 .client_header_buffer = res.request.parser.header_bytes_buffer,
254230 });
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]);
257334}
258335
336pub const Response = struct {
337 transfer_encoding: ResponseTransfer,
338};
339
259340pub const SendError = Connection.WriteError || error{
260341 UnsupportedTransferEncoding,
261342 InvalidContentLength,
......@@ -285,7 +366,8 @@ pub fn send(res: *Server) SendError!void {
285366 if (res.status == .@"continue") {
286367 res.state = .waited; // we still need to send another request after this
287368 } 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) {
289371 try w.writeAll("connection: keep-alive\r\n");
290372 } else {
291373 try w.writeAll("connection: close\r\n");