| ... | @@ -1,13 +1,7 @@ | ... | @@ -1,13 +1,7 @@ |
| 1 | version: http.Version, | | |
| 2 | status: http.Status, | | |
| 3 | reason: ?[]const u8, | | |
| 4 | transfer_encoding: ResponseTransfer, | | |
| 5 | keep_alive: bool, | | |
| 6 | connection: Connection, | 1 | connection: Connection, |
| 7 | connection_closing: bool, | 2 | /// This value is determined by Server when sending headers to the client, and |
| 8 | | 3 | /// then used to determine the return value of `reset`. |
| 9 | /// Externally-owned; must outlive the Server. | 4 | connection_keep_alive: bool, |
| 10 | extra_headers: []const http.Header, | | |
| 11 | | 5 | |
| 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. |
| 22 | pub fn init(connection: std.net.Server.Connection, options: Server.Request.InitOptions) Server { | 16 | pub 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 | } |
| 40 | | 28 | |
| ... | @@ -232,30 +220,123 @@ pub fn reset(res: *Server) ResetState { | ... | @@ -232,30 +220,123 @@ pub fn reset(res: *Server) ResetState { |
| 232 | | 220 | |
| 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 | } |
| 238 | | 226 | |
| 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 | }); |
| 255 | | 231 | |
| 256 | return if (res.connection_closing) .closing else .reset; | 232 | return if (res.connection_keep_alive) .reset else .closing; |
| | 233 | } |
| | 234 | |
| | 235 | pub const SendAllError = std.net.Stream.WriteError; |
| | 236 | |
| | 237 | pub 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. |
| | 251 | pub 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 | } |
| 258 | | 335 | |
| | 336 | pub const Response = struct { |
| | 337 | transfer_encoding: ResponseTransfer, |
| | 338 | }; |
| | 339 | |
| 259 | pub const SendError = Connection.WriteError || error{ | 340 | pub 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 this | 367 | 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"); |