| author | |
| committer | |
| log | b723296e1fa65b73a43b0790bdddcbfcea7d656d |
| tree | 33173696d644f655374e134b73df3d694e4a1a28 |
| parent | 832f6d8f7f7f7b10b86b109a8b26bb5eacc8d13e |
4 files changed, 141 insertions(+), 13 deletions(-)
lib/std/http/Client.zig+50-3| ... | @@ -1,4 +1,8 @@ | ... | @@ -1,4 +1,8 @@ |
| 1 | //! Connecting and opening requests are threadsafe. Individual requests are not. | 1 | //! HTTP(S) Client implementation. |
| 2 | //! | ||
| 3 | //! Connections are opened in a thread-safe manner, but individual Requests are not. | ||
| 4 | //! | ||
| 5 | //! TLS support may be disabled via `std.options.http_disable_tls`. | ||
| 2 | 6 | ||
| 3 | const std = @import("../std.zig"); | 7 | const std = @import("../std.zig"); |
| 4 | const builtin = @import("builtin"); | 8 | const builtin = @import("builtin"); |
| ... | @@ -157,6 +161,9 @@ pub const ConnectionPool = struct { | ... | @@ -157,6 +161,9 @@ pub const ConnectionPool = struct { |
| 157 | pool.free_size = new_size; | 161 | pool.free_size = new_size; |
| 158 | } | 162 | } |
| 159 | 163 | ||
| 164 | /// Frees the connection pool and closes all connections within. This function is threadsafe. | ||
| 165 | /// | ||
| 166 | /// All future operations on the connection pool will deadlock. | ||
| 160 | pub fn deinit(pool: *ConnectionPool, allocator: Allocator) void { | 167 | pub fn deinit(pool: *ConnectionPool, allocator: Allocator) void { |
| 161 | pool.mutex.lock(); | 168 | pool.mutex.lock(); |
| 162 | 169 | ||
| ... | @@ -191,11 +198,19 @@ pub const Connection = struct { | ... | @@ -191,11 +198,19 @@ pub const Connection = struct { |
| 191 | /// undefined unless protocol is tls. | 198 | /// undefined unless protocol is tls. |
| 192 | tls_client: if (!disable_tls) *std.crypto.tls.Client else void, | 199 | tls_client: if (!disable_tls) *std.crypto.tls.Client else void, |
| 193 | 200 | ||
| 201 | /// The protocol that this connection is using. | ||
| 194 | protocol: Protocol, | 202 | protocol: Protocol, |
| 203 | |||
| 204 | /// The host that this connection is connected to. | ||
| 195 | host: []u8, | 205 | host: []u8, |
| 206 | |||
| 207 | /// The port that this connection is connected to. | ||
| 196 | port: u16, | 208 | port: u16, |
| 197 | 209 | ||
| 210 | /// Whether this connection is proxied and is not directly connected. | ||
| 198 | proxied: bool = false, | 211 | proxied: bool = false, |
| 212 | |||
| 213 | /// Whether this connection is closing when we're done with it. | ||
| 199 | closing: bool = false, | 214 | closing: bool = false, |
| 200 | 215 | ||
| 201 | read_start: BufferSize = 0, | 216 | read_start: BufferSize = 0, |
| ... | @@ -232,6 +247,7 @@ pub const Connection = struct { | ... | @@ -232,6 +247,7 @@ pub const Connection = struct { |
| 232 | }; | 247 | }; |
| 233 | } | 248 | } |
| 234 | 249 | ||
| 250 | /// Refills the read buffer with data from the connection. | ||
| 235 | pub fn fill(conn: *Connection) ReadError!void { | 251 | pub fn fill(conn: *Connection) ReadError!void { |
| 236 | if (conn.read_end != conn.read_start) return; | 252 | if (conn.read_end != conn.read_start) return; |
| 237 | 253 | ||
| ... | @@ -244,14 +260,17 @@ pub const Connection = struct { | ... | @@ -244,14 +260,17 @@ pub const Connection = struct { |
| 244 | conn.read_end = @intCast(nread); | 260 | conn.read_end = @intCast(nread); |
| 245 | } | 261 | } |
| 246 | 262 | ||
| 263 | /// Returns the current slice of buffered data. | ||
| 247 | pub fn peek(conn: *Connection) []const u8 { | 264 | pub fn peek(conn: *Connection) []const u8 { |
| 248 | return conn.read_buf[conn.read_start..conn.read_end]; | 265 | return conn.read_buf[conn.read_start..conn.read_end]; |
| 249 | } | 266 | } |
| 250 | 267 | ||
| 268 | /// Discards the given number of bytes from the read buffer. | ||
| 251 | pub fn drop(conn: *Connection, num: BufferSize) void { | 269 | pub fn drop(conn: *Connection, num: BufferSize) void { |
| 252 | conn.read_start += num; | 270 | conn.read_start += num; |
| 253 | } | 271 | } |
| 254 | 272 | ||
| 273 | /// Reads data from the connection into the given buffer. | ||
| 255 | pub fn read(conn: *Connection, buffer: []u8) ReadError!usize { | 274 | pub fn read(conn: *Connection, buffer: []u8) ReadError!usize { |
| 256 | const available_read = conn.read_end - conn.read_start; | 275 | const available_read = conn.read_end - conn.read_start; |
| 257 | const available_buffer = buffer.len; | 276 | const available_buffer = buffer.len; |
| ... | @@ -318,6 +337,7 @@ pub const Connection = struct { | ... | @@ -318,6 +337,7 @@ pub const Connection = struct { |
| 318 | }; | 337 | }; |
| 319 | } | 338 | } |
| 320 | 339 | ||
| 340 | /// Writes the given buffer to the connection. | ||
| 321 | pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize { | 341 | pub fn write(conn: *Connection, buffer: []const u8) WriteError!usize { |
| 322 | if (conn.write_end + buffer.len > conn.write_buf.len) { | 342 | if (conn.write_end + buffer.len > conn.write_buf.len) { |
| 323 | try conn.flush(); | 343 | try conn.flush(); |
| ... | @@ -334,6 +354,7 @@ pub const Connection = struct { | ... | @@ -334,6 +354,7 @@ pub const Connection = struct { |
| 334 | return buffer.len; | 354 | return buffer.len; |
| 335 | } | 355 | } |
| 336 | 356 | ||
| 357 | /// Flushes the write buffer to the connection. | ||
| 337 | pub fn flush(conn: *Connection) WriteError!void { | 358 | pub fn flush(conn: *Connection) WriteError!void { |
| 338 | if (conn.write_end == 0) return; | 359 | if (conn.write_end == 0) return; |
| 339 | 360 | ||
| ... | @@ -352,6 +373,7 @@ pub const Connection = struct { | ... | @@ -352,6 +373,7 @@ pub const Connection = struct { |
| 352 | return Writer{ .context = conn }; | 373 | return Writer{ .context = conn }; |
| 353 | } | 374 | } |
| 354 | 375 | ||
| 376 | /// Closes the connection. | ||
| 355 | pub fn close(conn: *Connection, allocator: Allocator) void { | 377 | pub fn close(conn: *Connection, allocator: Allocator) void { |
| 356 | if (conn.protocol == .tls) { | 378 | if (conn.protocol == .tls) { |
| 357 | if (disable_tls) unreachable; | 379 | if (disable_tls) unreachable; |
| ... | @@ -502,8 +524,13 @@ pub const Response = struct { | ... | @@ -502,8 +524,13 @@ pub const Response = struct { |
| 502 | try expectEqual(@as(u10, 999), parseInt3("999")); | 524 | try expectEqual(@as(u10, 999), parseInt3("999")); |
| 503 | } | 525 | } |
| 504 | 526 | ||
| 527 | /// The HTTP version this response is using. | ||
| 505 | version: http.Version, | 528 | version: http.Version, |
| 529 | |||
| 530 | /// The status code of the response. | ||
| 506 | status: http.Status, | 531 | status: http.Status, |
| 532 | |||
| 533 | /// The reason phrase of the response. | ||
| 507 | reason: []const u8, | 534 | reason: []const u8, |
| 508 | 535 | ||
| 509 | /// If present, the number of bytes in the response body. | 536 | /// If present, the number of bytes in the response body. |
| ... | @@ -528,22 +555,36 @@ pub const Response = struct { | ... | @@ -528,22 +555,36 @@ pub const Response = struct { |
| 528 | /// | 555 | /// |
| 529 | /// Order of operations: open -> send[ -> write -> finish] -> wait -> read | 556 | /// Order of operations: open -> send[ -> write -> finish] -> wait -> read |
| 530 | pub const Request = struct { | 557 | pub const Request = struct { |
| 558 | /// The uri that this request is being sent to. | ||
| 531 | uri: Uri, | 559 | uri: Uri, |
| 560 | |||
| 561 | /// The client that this request was created from. | ||
| 532 | client: *Client, | 562 | client: *Client, |
| 533 | /// is null when this connection is released | 563 | |
| 564 | /// Underlying connection to the server. This is null when the connection is released. | ||
| 534 | connection: ?*Connection, | 565 | connection: ?*Connection, |
| 535 | 566 | ||
| 536 | method: http.Method, | 567 | method: http.Method, |
| 537 | version: http.Version = .@"HTTP/1.1", | 568 | version: http.Version = .@"HTTP/1.1", |
| 569 | |||
| 570 | /// The list of HTTP request headers. | ||
| 538 | headers: http.Headers, | 571 | headers: http.Headers, |
| 539 | 572 | ||
| 540 | /// The transfer encoding of the request body. | 573 | /// The transfer encoding of the request body. |
| 541 | transfer_encoding: RequestTransfer = .none, | 574 | transfer_encoding: RequestTransfer = .none, |
| 542 | 575 | ||
| 576 | /// The redirect quota left for this request. | ||
| 543 | redirects_left: u32, | 577 | redirects_left: u32, |
| 578 | |||
| 579 | /// Whether the request should follow redirects. | ||
| 544 | handle_redirects: bool, | 580 | handle_redirects: bool, |
| 581 | |||
| 582 | /// Whether the request should handle a 100-continue response before sending the request body. | ||
| 545 | handle_continue: bool, | 583 | handle_continue: bool, |
| 546 | 584 | ||
| 585 | /// The response associated with this request. | ||
| 586 | /// | ||
| 587 | /// This field is undefined until `wait` is called. | ||
| 547 | response: Response, | 588 | response: Response, |
| 548 | 589 | ||
| 549 | /// Used as a allocator for resolving redirects locations. | 590 | /// Used as a allocator for resolving redirects locations. |
| ... | @@ -993,6 +1034,7 @@ pub const Request = struct { | ... | @@ -993,6 +1034,7 @@ pub const Request = struct { |
| 993 | } | 1034 | } |
| 994 | }; | 1035 | }; |
| 995 | 1036 | ||
| 1037 | /// A HTTP proxy server. | ||
| 996 | pub const Proxy = struct { | 1038 | pub const Proxy = struct { |
| 997 | allocator: Allocator, | 1039 | allocator: Allocator, |
| 998 | headers: http.Headers, | 1040 | headers: http.Headers, |
| ... | @@ -1144,6 +1186,7 @@ pub fn loadDefaultProxies(client: *Client) !void { | ... | @@ -1144,6 +1186,7 @@ pub fn loadDefaultProxies(client: *Client) !void { |
| 1144 | pub const ConnectTcpError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed }; | 1186 | pub const ConnectTcpError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed }; |
| 1145 | 1187 | ||
| 1146 | /// Connect to `host:port` using the specified protocol. This will reuse a connection if one is already open. | 1188 | /// Connect to `host:port` using the specified protocol. This will reuse a connection if one is already open. |
| 1189 | /// | ||
| 1147 | /// This function is threadsafe. | 1190 | /// This function is threadsafe. |
| 1148 | pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectTcpError!*Connection { | 1191 | pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectTcpError!*Connection { |
| 1149 | if (client.connection_pool.findConnection(.{ | 1192 | if (client.connection_pool.findConnection(.{ |
| ... | @@ -1203,6 +1246,7 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec | ... | @@ -1203,6 +1246,7 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec |
| 1203 | pub const ConnectUnixError = Allocator.Error || std.os.SocketError || error{ NameTooLong, Unsupported } || std.os.ConnectError; | 1246 | pub const ConnectUnixError = Allocator.Error || std.os.SocketError || error{ NameTooLong, Unsupported } || std.os.ConnectError; |
| 1204 | 1247 | ||
| 1205 | /// Connect to `path` as a unix domain socket. This will reuse a connection if one is already open. | 1248 | /// Connect to `path` as a unix domain socket. This will reuse a connection if one is already open. |
| 1249 | /// | ||
| 1206 | /// This function is threadsafe. | 1250 | /// This function is threadsafe. |
| 1207 | pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connection { | 1251 | pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connection { |
| 1208 | if (!net.has_unix_sockets) return error.Unsupported; | 1252 | if (!net.has_unix_sockets) return error.Unsupported; |
| ... | @@ -1237,6 +1281,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti | ... | @@ -1237,6 +1281,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti |
| 1237 | } | 1281 | } |
| 1238 | 1282 | ||
| 1239 | /// Connect to `tunnel_host:tunnel_port` using the specified proxy with HTTP CONNECT. This will reuse a connection if one is already open. | 1283 | /// Connect to `tunnel_host:tunnel_port` using the specified proxy with HTTP CONNECT. This will reuse a connection if one is already open. |
| 1284 | /// | ||
| 1240 | /// This function is threadsafe. | 1285 | /// This function is threadsafe. |
| 1241 | pub fn connectTunnel( | 1286 | pub fn connectTunnel( |
| 1242 | client: *Client, | 1287 | client: *Client, |
| ... | @@ -1318,7 +1363,6 @@ const ConnectErrorPartial = ConnectTcpError || error{ UnsupportedUrlScheme, Conn | ... | @@ -1318,7 +1363,6 @@ const ConnectErrorPartial = ConnectTcpError || error{ UnsupportedUrlScheme, Conn |
| 1318 | pub const ConnectError = ConnectErrorPartial || RequestError; | 1363 | pub const ConnectError = ConnectErrorPartial || RequestError; |
| 1319 | 1364 | ||
| 1320 | /// Connect to `host:port` using the specified protocol. This will reuse a connection if one is already open. | 1365 | /// Connect to `host:port` using the specified protocol. This will reuse a connection if one is already open. |
| 1321 | /// | ||
| 1322 | /// If a proxy is configured for the client, then the proxy will be used to connect to the host. | 1366 | /// If a proxy is configured for the client, then the proxy will be used to connect to the host. |
| 1323 | /// | 1367 | /// |
| 1324 | /// This function is threadsafe. | 1368 | /// This function is threadsafe. |
| ... | @@ -1375,7 +1419,10 @@ pub const RequestOptions = struct { | ... | @@ -1375,7 +1419,10 @@ pub const RequestOptions = struct { |
| 1375 | /// request, then the request *will* deadlock. | 1419 | /// request, then the request *will* deadlock. |
| 1376 | handle_continue: bool = true, | 1420 | handle_continue: bool = true, |
| 1377 | 1421 | ||
| 1422 | /// Automatically follow redirects. This will only follow redirects for repeatable requests (ie. with no payload or the server has acknowledged the payload) | ||
| 1378 | handle_redirects: bool = true, | 1423 | handle_redirects: bool = true, |
| 1424 | |||
| 1425 | /// How many redirects to follow before returning an error. | ||
| 1379 | max_redirects: u32 = 3, | 1426 | max_redirects: u32 = 3, |
| 1380 | header_strategy: StorageStrategy = .{ .dynamic = 16 * 1024 }, | 1427 | header_strategy: StorageStrategy = .{ .dynamic = 16 * 1024 }, |
| 1381 | 1428 |
lib/std/http/Headers.zig+12-1| ... | @@ -35,6 +35,7 @@ pub const CaseInsensitiveStringContext = struct { | ... | @@ -35,6 +35,7 @@ pub const CaseInsensitiveStringContext = struct { |
| 35 | } | 35 | } |
| 36 | }; | 36 | }; |
| 37 | 37 | ||
| 38 | /// A single HTTP header field. | ||
| 38 | pub const Field = struct { | 39 | pub const Field = struct { |
| 39 | name: []const u8, | 40 | name: []const u8, |
| 40 | value: []const u8, | 41 | value: []const u8, |
| ... | @@ -47,6 +48,7 @@ pub const Field = struct { | ... | @@ -47,6 +48,7 @@ pub const Field = struct { |
| 47 | } | 48 | } |
| 48 | }; | 49 | }; |
| 49 | 50 | ||
| 51 | /// A list of HTTP header fields. | ||
| 50 | pub const Headers = struct { | 52 | pub const Headers = struct { |
| 51 | allocator: Allocator, | 53 | allocator: Allocator, |
| 52 | list: HeaderList = .{}, | 54 | list: HeaderList = .{}, |
| ... | @@ -56,10 +58,12 @@ pub const Headers = struct { | ... | @@ -56,10 +58,12 @@ pub const Headers = struct { |
| 56 | /// Use with caution. | 58 | /// Use with caution. |
| 57 | owned: bool = true, | 59 | owned: bool = true, |
| 58 | 60 | ||
| 61 | /// Initialize an empty list of headers. | ||
| 59 | pub fn init(allocator: Allocator) Headers { | 62 | pub fn init(allocator: Allocator) Headers { |
| 60 | return .{ .allocator = allocator }; | 63 | return .{ .allocator = allocator }; |
| 61 | } | 64 | } |
| 62 | 65 | ||
| 66 | /// Initialize a pre-populated list of headers from a list of fields. | ||
| 63 | pub fn initList(allocator: Allocator, list: []const Field) !Headers { | 67 | pub fn initList(allocator: Allocator, list: []const Field) !Headers { |
| 64 | var new = Headers.init(allocator); | 68 | var new = Headers.init(allocator); |
| 65 | 69 | ||
| ... | @@ -72,6 +76,9 @@ pub const Headers = struct { | ... | @@ -72,6 +76,9 @@ pub const Headers = struct { |
| 72 | return new; | 76 | return new; |
| 73 | } | 77 | } |
| 74 | 78 | ||
| 79 | /// Deallocate all memory associated with the headers. | ||
| 80 | /// | ||
| 81 | /// If the `owned` field is false, this will not free the names and values of the headers. | ||
| 75 | pub fn deinit(headers: *Headers) void { | 82 | pub fn deinit(headers: *Headers) void { |
| 76 | headers.deallocateIndexListsAndFields(); | 83 | headers.deallocateIndexListsAndFields(); |
| 77 | headers.index.deinit(headers.allocator); | 84 | headers.index.deinit(headers.allocator); |
| ... | @@ -80,7 +87,9 @@ pub const Headers = struct { | ... | @@ -80,7 +87,9 @@ pub const Headers = struct { |
| 80 | headers.* = undefined; | 87 | headers.* = undefined; |
| 81 | } | 88 | } |
| 82 | 89 | ||
| 83 | /// Appends a header to the list. Both name and value are copied. | 90 | /// Appends a header to the list. |
| 91 | /// | ||
| 92 | /// If the `owned` field is true, both name and value will be copied. | ||
| 84 | pub fn append(headers: *Headers, name: []const u8, value: []const u8) !void { | 93 | pub fn append(headers: *Headers, name: []const u8, value: []const u8) !void { |
| 85 | const n = headers.list.items.len; | 94 | const n = headers.list.items.len; |
| 86 | 95 | ||
| ... | @@ -108,6 +117,7 @@ pub const Headers = struct { | ... | @@ -108,6 +117,7 @@ pub const Headers = struct { |
| 108 | try headers.list.append(headers.allocator, entry); | 117 | try headers.list.append(headers.allocator, entry); |
| 109 | } | 118 | } |
| 110 | 119 | ||
| 120 | /// Returns true if this list of headers contains the given name. | ||
| 111 | pub fn contains(headers: Headers, name: []const u8) bool { | 121 | pub fn contains(headers: Headers, name: []const u8) bool { |
| 112 | return headers.index.contains(name); | 122 | return headers.index.contains(name); |
| 113 | } | 123 | } |
| ... | @@ -285,6 +295,7 @@ pub const Headers = struct { | ... | @@ -285,6 +295,7 @@ pub const Headers = struct { |
| 285 | headers.list.clearRetainingCapacity(); | 295 | headers.list.clearRetainingCapacity(); |
| 286 | } | 296 | } |
| 287 | 297 | ||
| 298 | /// Creates a copy of the headers using the provided allocator. | ||
| 288 | pub fn clone(headers: Headers, allocator: Allocator) !Headers { | 299 | pub fn clone(headers: Headers, allocator: Allocator) !Headers { |
| 289 | var new = Headers.init(allocator); | 300 | var new = Headers.init(allocator); |
| 290 | 301 |
lib/std/http/Server.zig+78-8| ... | @@ -1,3 +1,44 @@ | ... | @@ -1,3 +1,44 @@ |
| 1 | //! HTTP Server implementation. | ||
| 2 | //! | ||
| 3 | //! This server assumes *all* clients are well behaved and standard compliant; it can and will deadlock if a client holds a connection open without sending a request. | ||
| 4 | //! | ||
| 5 | //! Example usage: | ||
| 6 | //! | ||
| 7 | //! ```zig | ||
| 8 | //! var server = Server.init(.{ .reuse_address = true }); | ||
| 9 | //! defer server.deinit(); | ||
| 10 | //! | ||
| 11 | //! try server.listen(bind_addr); | ||
| 12 | //! | ||
| 13 | //! while (true) { | ||
| 14 | //! var res = try server.accept(.{ .allocator = gpa }); | ||
| 15 | //! defer res.deinit(); | ||
| 16 | //! | ||
| 17 | //! while (res.reset() != .closing) { | ||
| 18 | //! res.wait() catch |err| switch (err) { | ||
| 19 | //! error.HttpHeadersInvalid => break, | ||
| 20 | //! error.HttpHeadersExceededSizeLimit => { | ||
| 21 | //! res.status = .request_header_fields_too_large; | ||
| 22 | //! res.send() catch break; | ||
| 23 | //! break; | ||
| 24 | //! }, | ||
| 25 | //! else => { | ||
| 26 | //! res.status = .bad_request; | ||
| 27 | //! res.send() catch break; | ||
| 28 | //! break; | ||
| 29 | //! }, | ||
| 30 | //! } | ||
| 31 | //! | ||
| 32 | //! res.status = .ok; | ||
| 33 | //! res.transfer_encoding = .chunked; | ||
| 34 | //! | ||
| 35 | //! try res.send(); | ||
| 36 | //! try res.writeAll("Hello, World!\n"); | ||
| 37 | //! try res.finish(); | ||
| 38 | //! } | ||
| 39 | //! } | ||
| 40 | //! ``` | ||
| 41 | |||
| 1 | const std = @import("../std.zig"); | 42 | const std = @import("../std.zig"); |
| 2 | const testing = std.testing; | 43 | const testing = std.testing; |
| 3 | const http = std.http; | 44 | const http = std.http; |
| ... | @@ -10,8 +51,7 @@ const assert = std.debug.assert; | ... | @@ -10,8 +51,7 @@ const assert = std.debug.assert; |
| 10 | const Server = @This(); | 51 | const Server = @This(); |
| 11 | const proto = @import("protocol.zig"); | 52 | const proto = @import("protocol.zig"); |
| 12 | 53 | ||
| 13 | allocator: Allocator, | 54 | /// The underlying server socket. |
| 14 | |||
| 15 | socket: net.StreamServer, | 55 | socket: net.StreamServer, |
| 16 | 56 | ||
| 17 | /// An interface to a plain connection. | 57 | /// An interface to a plain connection. |
| ... | @@ -269,8 +309,13 @@ pub const Request = struct { | ... | @@ -269,8 +309,13 @@ pub const Request = struct { |
| 269 | return @as(u64, @bitCast(array.*)); | 309 | return @as(u64, @bitCast(array.*)); |
| 270 | } | 310 | } |
| 271 | 311 | ||
| 312 | /// The HTTP request method. | ||
| 272 | method: http.Method, | 313 | method: http.Method, |
| 314 | |||
| 315 | /// The HTTP request target. | ||
| 273 | target: []const u8, | 316 | target: []const u8, |
| 317 | |||
| 318 | /// The HTTP version of this request. | ||
| 274 | version: http.Version, | 319 | version: http.Version, |
| 275 | 320 | ||
| 276 | /// The length of the request body, if known. | 321 | /// The length of the request body, if known. |
| ... | @@ -282,16 +327,21 @@ pub const Request = struct { | ... | @@ -282,16 +327,21 @@ pub const Request = struct { |
| 282 | /// The compression of the request body, or .identity (no compression) if not present. | 327 | /// The compression of the request body, or .identity (no compression) if not present. |
| 283 | transfer_compression: http.ContentEncoding = .identity, | 328 | transfer_compression: http.ContentEncoding = .identity, |
| 284 | 329 | ||
| 330 | /// The list of HTTP request headers | ||
| 285 | headers: http.Headers, | 331 | headers: http.Headers, |
| 332 | |||
| 286 | parser: proto.HeadersParser, | 333 | parser: proto.HeadersParser, |
| 287 | compression: Compression = .none, | 334 | compression: Compression = .none, |
| 288 | }; | 335 | }; |
| 289 | 336 | ||
| 290 | /// A HTTP response waiting to be sent. | 337 | /// A HTTP response waiting to be sent. |
| 291 | /// | 338 | /// |
| 292 | /// [/ <----------------------------------- \] | 339 | /// Order of operations: |
| 293 | /// Order of operations: accept -> wait -> send [ -> write -> finish][ -> reset /] | 340 | /// ``` |
| 294 | /// \ -> read / | 341 | /// [/ <--------------------------------------- \] |
| 342 | /// accept -> wait -> send [ -> write -> finish][ -> reset /] | ||
| 343 | /// \ -> read / | ||
| 344 | /// ``` | ||
| 295 | pub const Response = struct { | 345 | pub const Response = struct { |
| 296 | version: http.Version = .@"HTTP/1.1", | 346 | version: http.Version = .@"HTTP/1.1", |
| 297 | status: http.Status = .ok, | 347 | status: http.Status = .ok, |
| ... | @@ -299,11 +349,21 @@ pub const Response = struct { | ... | @@ -299,11 +349,21 @@ pub const Response = struct { |
| 299 | 349 | ||
| 300 | transfer_encoding: ResponseTransfer = .none, | 350 | transfer_encoding: ResponseTransfer = .none, |
| 301 | 351 | ||
| 352 | /// The allocator responsible for allocating memory for this response. | ||
| 302 | allocator: Allocator, | 353 | allocator: Allocator, |
| 354 | |||
| 355 | /// The peer's address | ||
| 303 | address: net.Address, | 356 | address: net.Address, |
| 357 | |||
| 358 | /// The underlying connection for this response. | ||
| 304 | connection: Connection, | 359 | connection: Connection, |
| 305 | 360 | ||
| 361 | /// The HTTP response headers | ||
| 306 | headers: http.Headers, | 362 | headers: http.Headers, |
| 363 | |||
| 364 | /// The HTTP request that this response is responding to. | ||
| 365 | /// | ||
| 366 | /// This field is only valid after calling `wait`. | ||
| 307 | request: Request, | 367 | request: Request, |
| 308 | 368 | ||
| 309 | state: State = .first, | 369 | state: State = .first, |
| ... | @@ -495,6 +555,17 @@ pub const Response = struct { | ... | @@ -495,6 +555,17 @@ pub const Response = struct { |
| 495 | pub const WaitError = Connection.ReadError || proto.HeadersParser.CheckCompleteHeadError || Request.ParseError || error{ CompressionInitializationFailed, CompressionNotSupported }; | 555 | pub const WaitError = Connection.ReadError || proto.HeadersParser.CheckCompleteHeadError || Request.ParseError || error{ CompressionInitializationFailed, CompressionNotSupported }; |
| 496 | 556 | ||
| 497 | /// Wait for the client to send a complete request head. | 557 | /// Wait for the client to send a complete request head. |
| 558 | /// | ||
| 559 | /// For correct behavior, the following rules must be followed: | ||
| 560 | /// | ||
| 561 | /// * If this returns any error in `Connection.ReadError`, you MUST immediately close the connection by calling `deinit`. | ||
| 562 | /// * If this returns `error.HttpHeadersInvalid`, you MAY immediately close the connection by calling `deinit`. | ||
| 563 | /// * If this returns `error.HttpHeadersExceededSizeLimit`, you MUST respond with a 431 status code and then call `deinit`. | ||
| 564 | /// * If this returns any error in `Request.ParseError`, you MUST respond with a 400 status code and then call `deinit`. | ||
| 565 | /// * If this returns any other error, you MUST respond with a 400 status code and then call `deinit`. | ||
| 566 | /// * If the request has an Expect header containing 100-continue, you MUST either: | ||
| 567 | /// * Respond with a 100 status code, then call `wait` again. | ||
| 568 | /// * Respond with a 417 status code. | ||
| 498 | pub fn wait(res: *Response) WaitError!void { | 569 | pub fn wait(res: *Response) WaitError!void { |
| 499 | switch (res.state) { | 570 | switch (res.state) { |
| 500 | .first, .start => res.state = .waited, | 571 | .first, .start => res.state = .waited, |
| ... | @@ -664,9 +735,8 @@ pub const Response = struct { | ... | @@ -664,9 +735,8 @@ pub const Response = struct { |
| 664 | }; | 735 | }; |
| 665 | 736 | ||
| 666 | /// Create a new HTTP server. | 737 | /// Create a new HTTP server. |
| 667 | pub fn init(allocator: Allocator, options: net.StreamServer.Options) Server { | 738 | pub fn init(options: net.StreamServer.Options) Server { |
| 668 | return .{ | 739 | return .{ |
| 669 | .allocator = allocator, | ||
| 670 | .socket = net.StreamServer.init(options), | 740 | .socket = net.StreamServer.init(options), |
| 671 | }; | 741 | }; |
| 672 | } | 742 | } |
| ... | @@ -748,7 +818,7 @@ test "HTTP server handles a chunked transfer coding request" { | ... | @@ -748,7 +818,7 @@ test "HTTP server handles a chunked transfer coding request" { |
| 748 | const expect = std.testing.expect; | 818 | const expect = std.testing.expect; |
| 749 | 819 | ||
| 750 | const max_header_size = 8192; | 820 | const max_header_size = 8192; |
| 751 | var server = std.http.Server.init(allocator, .{ .reuse_address = true }); | 821 | var server = std.http.Server.init(.{ .reuse_address = true }); |
| 752 | defer server.deinit(); | 822 | defer server.deinit(); |
| 753 | 823 | ||
| 754 | const address = try std.net.Address.parseIp("127.0.0.1", 0); | 824 | const address = try std.net.Address.parseIp("127.0.0.1", 0); |
test/standalone/http.zig+1-1| ... | @@ -220,7 +220,7 @@ pub fn main() !void { | ... | @@ -220,7 +220,7 @@ pub fn main() !void { |
| 220 | 220 | ||
| 221 | defer _ = gpa_client.deinit(); | 221 | defer _ = gpa_client.deinit(); |
| 222 | 222 | ||
| 223 | server = Server.init(salloc, .{ .reuse_address = true }); | 223 | server = Server.init(.{ .reuse_address = true }); |
| 224 | 224 | ||
| 225 | const addr = std.net.Address.parseIp("127.0.0.1", 0) catch unreachable; | 225 | const addr = std.net.Address.parseIp("127.0.0.1", 0) catch unreachable; |
| 226 | try server.listen(addr); | 226 | try server.listen(addr); |