| ... | ... | @@ -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 | 1 | connection: Connection, |
| 7 | | connection_closing: bool, |
| 8 | | |
| 9 | | /// Externally-owned; must outlive the Server. |
| 10 | | extra_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`. |
| 4 | connection_keep_alive: bool, |
| 11 | 5 | |
| 12 | 6 | /// The HTTP request that this response is responding to. |
| 13 | 7 | /// |
| ... | ... | @@ -21,20 +15,14 @@ state: State = .first, |
| 21 | 15 | /// The returned `Server` is ready for `reset` or `wait` to be called. |
| 22 | 16 | pub fn init(connection: std.net.Server.Connection, options: Server.Request.InitOptions) Server { |
| 23 | 17 | return .{ |
| 24 | | .transfer_encoding = .none, |
| 25 | | .keep_alive = true, |
| 26 | 18 | .connection = .{ |
| 27 | 19 | .stream = connection.stream, |
| 28 | 20 | .read_buf = undefined, |
| 29 | 21 | .read_start = 0, |
| 30 | 22 | .read_end = 0, |
| 31 | 23 | }, |
| 32 | | .connection_closing = true, |
| 24 | .connection_keep_alive = false, |
| 33 | 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 | 220 | |
| 233 | 221 | if (!res.request.parser.done) { |
| 234 | 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 | 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 | 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 | 228 | res.request = Request.init(.{ |
| 253 | 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 | 340 | pub const SendError = Connection.WriteError || error{ |
| 260 | 341 | UnsupportedTransferEncoding, |
| 261 | 342 | InvalidContentLength, |
| ... | ... | @@ -285,7 +366,8 @@ pub fn send(res: *Server) SendError!void { |
| 285 | 366 | if (res.status == .@"continue") { |
| 286 | 367 | res.state = .waited; // we still need to send another request after this |
| 287 | 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 | 371 | try w.writeAll("connection: keep-alive\r\n"); |
| 290 | 372 | } else { |
| 291 | 373 | try w.writeAll("connection: close\r\n"); |