Commit 68d3e103b7
Changed files (1)
lib
std
http
lib/std/http/Server.zig
@@ -1,13 +1,7 @@
-version: http.Version,
-status: http.Status,
-reason: ?[]const u8,
-transfer_encoding: ResponseTransfer,
-keep_alive: bool,
connection: Connection,
-connection_closing: bool,
-
-/// Externally-owned; must outlive the Server.
-extra_headers: []const http.Header,
+/// This value is determined by Server when sending headers to the client, and
+/// then used to determine the return value of `reset`.
+connection_keep_alive: bool,
/// The HTTP request that this response is responding to.
///
@@ -21,20 +15,14 @@ state: State = .first,
/// The returned `Server` is ready for `reset` or `wait` to be called.
pub fn init(connection: std.net.Server.Connection, options: Server.Request.InitOptions) Server {
return .{
- .transfer_encoding = .none,
- .keep_alive = true,
.connection = .{
.stream = connection.stream,
.read_buf = undefined,
.read_start = 0,
.read_end = 0,
},
- .connection_closing = true,
+ .connection_keep_alive = false,
.request = Server.Request.init(options),
- .version = .@"HTTP/1.1",
- .status = .ok,
- .reason = null,
- .extra_headers = &.{},
};
}
@@ -232,30 +220,123 @@ pub fn reset(res: *Server) ResetState {
if (!res.request.parser.done) {
// If the response wasn't fully read, then we need to close the connection.
- res.connection_closing = true;
+ res.connection_keep_alive = false;
return .closing;
}
- // A connection is only keep-alive if the Connection header is present
- // and its value is not "close". The server and client must both agree.
- //
- // send() defaults to using keep-alive if the client requests it.
- res.connection_closing = !res.keep_alive or !res.request.keep_alive;
-
res.state = .start;
- res.version = .@"HTTP/1.1";
- res.status = .ok;
- res.reason = null;
-
- res.transfer_encoding = .none;
-
res.request = Request.init(.{
.client_header_buffer = res.request.parser.header_bytes_buffer,
});
- return if (res.connection_closing) .closing else .reset;
+ return if (res.connection_keep_alive) .reset else .closing;
+}
+
+pub const SendAllError = std.net.Stream.WriteError;
+
+pub const SendOptions = struct {
+ version: http.Version = .@"HTTP/1.1",
+ status: http.Status = .ok,
+ reason: ?[]const u8 = null,
+ keep_alive: bool = true,
+ extra_headers: []const http.Header = &.{},
+ content: []const u8,
+};
+
+/// Send an entire HTTP response to the client, including headers and body.
+/// Automatically handles HEAD requests by omitting the body.
+/// Uses the "content-length" header.
+/// Asserts status is not `continue`.
+/// Asserts there are at most 25 extra_headers.
+pub fn sendAll(s: *Server, options: SendOptions) SendAllError!void {
+ const max_extra_headers = 25;
+ assert(options.status != .@"continue");
+ assert(options.extra_headers.len <= max_extra_headers);
+
+ switch (s.state) {
+ .waited => s.state = .finished,
+ .first => unreachable, // Call reset() first.
+ .start => unreachable, // Call wait() first.
+ .responded => unreachable, // Cannot mix sendAll() with send().
+ .finished => unreachable, // Call reset() first.
+ }
+
+ s.connection_keep_alive = options.keep_alive and s.request.keep_alive;
+ const keep_alive_line = if (s.connection_keep_alive)
+ "connection: keep-alive\r\n"
+ else
+ "";
+ const phrase = options.reason orelse options.status.phrase() orelse "";
+
+ var first_buffer: [500]u8 = undefined;
+ const first_bytes = std.fmt.bufPrint(
+ &first_buffer,
+ "{s} {d} {s}\r\n{s}content-length: {d}\r\n",
+ .{
+ @tagName(options.version),
+ @intFromEnum(options.status),
+ phrase,
+ keep_alive_line,
+ options.content.len,
+ },
+ ) catch unreachable;
+
+ var iovecs: [max_extra_headers * 4 + 3]std.posix.iovec_const = undefined;
+ var iovecs_len: usize = 0;
+
+ iovecs[iovecs_len] = .{
+ .iov_base = first_bytes.ptr,
+ .iov_len = first_bytes.len,
+ };
+ iovecs_len += 1;
+
+ for (options.extra_headers) |header| {
+ iovecs[iovecs_len] = .{
+ .iov_base = header.name.ptr,
+ .iov_len = header.name.len,
+ };
+ iovecs_len += 1;
+
+ iovecs[iovecs_len] = .{
+ .iov_base = ": ",
+ .iov_len = 2,
+ };
+ iovecs_len += 1;
+
+ iovecs[iovecs_len] = .{
+ .iov_base = header.value.ptr,
+ .iov_len = header.value.len,
+ };
+ iovecs_len += 1;
+
+ iovecs[iovecs_len] = .{
+ .iov_base = "\r\n",
+ .iov_len = 2,
+ };
+ iovecs_len += 1;
+ }
+
+ iovecs[iovecs_len] = .{
+ .iov_base = "\r\n",
+ .iov_len = 2,
+ };
+ iovecs_len += 1;
+
+ if (s.request.method != .HEAD) {
+ iovecs[iovecs_len] = .{
+ .iov_base = options.content.ptr,
+ .iov_len = options.content.len,
+ };
+ iovecs_len += 1;
+ }
+
+ return s.connection.stream.writevAll(iovecs[0..iovecs_len]);
}
+pub const Response = struct {
+ transfer_encoding: ResponseTransfer,
+};
+
pub const SendError = Connection.WriteError || error{
UnsupportedTransferEncoding,
InvalidContentLength,
@@ -285,7 +366,8 @@ pub fn send(res: *Server) SendError!void {
if (res.status == .@"continue") {
res.state = .waited; // we still need to send another request after this
} else {
- if (res.keep_alive and res.request.keep_alive) {
+ res.connection_keep_alive = res.keep_alive and res.request.keep_alive;
+ if (res.connection_keep_alive) {
try w.writeAll("connection: keep-alive\r\n");
} else {
try w.writeAll("connection: close\r\n");