| author | |
| committer | |
| log | 4689d93cb204a4143770105200eb65dcdca5d7a0 |
| tree | d02590b41be334c650d01dfb1c6436bc1cb064b6 |
| parent | ddef683fcb321882cc912090a25c12bc8705ff55 |
| signature |
4 files changed, 49 insertions(+), 22 deletions(-)
lib/std/http.zig+37-12| ... | @@ -1,3 +1,5 @@ | ... | @@ -1,3 +1,5 @@ |
| 1 | const std = @import("std.zig"); | ||
| 2 | |||
| 1 | pub const Client = @import("http/Client.zig"); | 3 | pub const Client = @import("http/Client.zig"); |
| 2 | pub const Server = @import("http/Server.zig"); | 4 | pub const Server = @import("http/Server.zig"); |
| 3 | pub const protocol = @import("http/protocol.zig"); | 5 | pub const protocol = @import("http/protocol.zig"); |
| ... | @@ -14,16 +16,36 @@ pub const Version = enum { | ... | @@ -14,16 +16,36 @@ pub const Version = enum { |
| 14 | /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods | 16 | /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods |
| 15 | /// https://datatracker.ietf.org/doc/html/rfc7231#section-4 Initial definition | 17 | /// https://datatracker.ietf.org/doc/html/rfc7231#section-4 Initial definition |
| 16 | /// https://datatracker.ietf.org/doc/html/rfc5789#section-2 PATCH | 18 | /// https://datatracker.ietf.org/doc/html/rfc5789#section-2 PATCH |
| 17 | pub const Method = enum { | 19 | pub const Method = enum(u64) { // TODO: should be u192 or u256, but neither is supported by the C backend, and therefore cannot pass CI |
| 18 | GET, | 20 | GET = parse("GET"), |
| 19 | HEAD, | 21 | HEAD = parse("HEAD"), |
| 20 | POST, | 22 | POST = parse("POST"), |
| 21 | PUT, | 23 | PUT = parse("PUT"), |
| 22 | DELETE, | 24 | DELETE = parse("DELETE"), |
| 23 | CONNECT, | 25 | CONNECT = parse("CONNECT"), |
| 24 | OPTIONS, | 26 | OPTIONS = parse("OPTIONS"), |
| 25 | TRACE, | 27 | TRACE = parse("TRACE"), |
| 26 | PATCH, | 28 | PATCH = parse("PATCH"), |
| 29 | |||
| 30 | _, | ||
| 31 | |||
| 32 | /// Converts `s` into a type that may be used as a `Method` field. | ||
| 33 | /// Asserts that `s` is 24 or fewer bytes. | ||
| 34 | pub fn parse(s: []const u8) u64 { | ||
| 35 | var x: u64 = 0; | ||
| 36 | @memcpy(std.mem.asBytes(&x)[0..s.len], s); | ||
| 37 | return x; | ||
| 38 | } | ||
| 39 | |||
| 40 | pub fn write(self: Method, w: anytype) !void { | ||
| 41 | const bytes = std.mem.asBytes(&@intFromEnum(self)); | ||
| 42 | const str = std.mem.sliceTo(bytes, 0); | ||
| 43 | try w.writeAll(str); | ||
| 44 | } | ||
| 45 | |||
| 46 | pub fn format(value: Method, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { | ||
| 47 | return try value.write(writer); | ||
| 48 | } | ||
| 27 | 49 | ||
| 28 | /// Returns true if a request of this method is allowed to have a body | 50 | /// Returns true if a request of this method is allowed to have a body |
| 29 | /// Actual behavior from servers may vary and should still be checked | 51 | /// Actual behavior from servers may vary and should still be checked |
| ... | @@ -31,6 +53,7 @@ pub const Method = enum { | ... | @@ -31,6 +53,7 @@ pub const Method = enum { |
| 31 | return switch (self) { | 53 | return switch (self) { |
| 32 | .POST, .PUT, .PATCH => true, | 54 | .POST, .PUT, .PATCH => true, |
| 33 | .GET, .HEAD, .DELETE, .CONNECT, .OPTIONS, .TRACE => false, | 55 | .GET, .HEAD, .DELETE, .CONNECT, .OPTIONS, .TRACE => false, |
| 56 | else => true, | ||
| 34 | }; | 57 | }; |
| 35 | } | 58 | } |
| 36 | 59 | ||
| ... | @@ -40,6 +63,7 @@ pub const Method = enum { | ... | @@ -40,6 +63,7 @@ pub const Method = enum { |
| 40 | return switch (self) { | 63 | return switch (self) { |
| 41 | .GET, .POST, .DELETE, .CONNECT, .OPTIONS, .PATCH => true, | 64 | .GET, .POST, .DELETE, .CONNECT, .OPTIONS, .PATCH => true, |
| 42 | .HEAD, .PUT, .TRACE => false, | 65 | .HEAD, .PUT, .TRACE => false, |
| 66 | else => true, | ||
| 43 | }; | 67 | }; |
| 44 | } | 68 | } |
| 45 | 69 | ||
| ... | @@ -50,6 +74,7 @@ pub const Method = enum { | ... | @@ -50,6 +74,7 @@ pub const Method = enum { |
| 50 | return switch (self) { | 74 | return switch (self) { |
| 51 | .GET, .HEAD, .OPTIONS, .TRACE => true, | 75 | .GET, .HEAD, .OPTIONS, .TRACE => true, |
| 52 | .POST, .PUT, .DELETE, .CONNECT, .PATCH => false, | 76 | .POST, .PUT, .DELETE, .CONNECT, .PATCH => false, |
| 77 | else => false, | ||
| 53 | }; | 78 | }; |
| 54 | } | 79 | } |
| 55 | 80 | ||
| ... | @@ -60,6 +85,7 @@ pub const Method = enum { | ... | @@ -60,6 +85,7 @@ pub const Method = enum { |
| 60 | return switch (self) { | 85 | return switch (self) { |
| 61 | .GET, .HEAD, .PUT, .DELETE, .OPTIONS, .TRACE => true, | 86 | .GET, .HEAD, .PUT, .DELETE, .OPTIONS, .TRACE => true, |
| 62 | .CONNECT, .POST, .PATCH => false, | 87 | .CONNECT, .POST, .PATCH => false, |
| 88 | else => false, | ||
| 63 | }; | 89 | }; |
| 64 | } | 90 | } |
| 65 | 91 | ||
| ... | @@ -70,6 +96,7 @@ pub const Method = enum { | ... | @@ -70,6 +96,7 @@ pub const Method = enum { |
| 70 | return switch (self) { | 96 | return switch (self) { |
| 71 | .GET, .HEAD => true, | 97 | .GET, .HEAD => true, |
| 72 | .POST, .PUT, .DELETE, .CONNECT, .OPTIONS, .TRACE, .PATCH => false, | 98 | .POST, .PUT, .DELETE, .CONNECT, .OPTIONS, .TRACE, .PATCH => false, |
| 99 | else => false, | ||
| 73 | }; | 100 | }; |
| 74 | } | 101 | } |
| 75 | }; | 102 | }; |
| ... | @@ -269,8 +296,6 @@ pub const Connection = enum { | ... | @@ -269,8 +296,6 @@ pub const Connection = enum { |
| 269 | close, | 296 | close, |
| 270 | }; | 297 | }; |
| 271 | 298 | ||
| 272 | const std = @import("std.zig"); | ||
| 273 | |||
| 274 | test { | 299 | test { |
| 275 | _ = Client; | 300 | _ = Client; |
| 276 | _ = Method; | 301 | _ = Method; |
lib/std/http/Client.zig+5-5| ... | @@ -545,7 +545,7 @@ pub const Request = struct { | ... | @@ -545,7 +545,7 @@ pub const Request = struct { |
| 545 | var buffered = std.io.bufferedWriter(req.connection.?.data.writer()); | 545 | var buffered = std.io.bufferedWriter(req.connection.?.data.writer()); |
| 546 | const w = buffered.writer(); | 546 | const w = buffered.writer(); |
| 547 | 547 | ||
| 548 | try w.writeAll(@tagName(req.method)); | 548 | try req.method.write(w); |
| 549 | try w.writeByte(' '); | 549 | try w.writeByte(' '); |
| 550 | 550 | ||
| 551 | if (req.method == .CONNECT) { | 551 | if (req.method == .CONNECT) { |
| ... | @@ -627,15 +627,15 @@ pub const Request = struct { | ... | @@ -627,15 +627,15 @@ pub const Request = struct { |
| 627 | try buffered.flush(); | 627 | try buffered.flush(); |
| 628 | } | 628 | } |
| 629 | 629 | ||
| 630 | pub const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError; | 630 | const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError; |
| 631 | 631 | ||
| 632 | pub const TransferReader = std.io.Reader(*Request, TransferReadError, transferRead); | 632 | const TransferReader = std.io.Reader(*Request, TransferReadError, transferRead); |
| 633 | 633 | ||
| 634 | pub fn transferReader(req: *Request) TransferReader { | 634 | fn transferReader(req: *Request) TransferReader { |
| 635 | return .{ .context = req }; | 635 | return .{ .context = req }; |
| 636 | } | 636 | } |
| 637 | 637 | ||
| 638 | pub fn transferRead(req: *Request, buf: []u8) TransferReadError!usize { | 638 | fn transferRead(req: *Request, buf: []u8) TransferReadError!usize { |
| 639 | if (req.response.parser.done) return 0; | 639 | if (req.response.parser.done) return 0; |
| 640 | 640 | ||
| 641 | var index: usize = 0; | 641 | var index: usize = 0; |
lib/std/http/Server.zig+6-4| ... | @@ -185,8 +185,10 @@ pub const Request = struct { | ... | @@ -185,8 +185,10 @@ pub const Request = struct { |
| 185 | return error.HttpHeadersInvalid; | 185 | return error.HttpHeadersInvalid; |
| 186 | 186 | ||
| 187 | const method_end = mem.indexOfScalar(u8, first_line, ' ') orelse return error.HttpHeadersInvalid; | 187 | const method_end = mem.indexOfScalar(u8, first_line, ' ') orelse return error.HttpHeadersInvalid; |
| 188 | if (method_end > 24) return error.HttpHeadersInvalid; | ||
| 189 | |||
| 188 | const method_str = first_line[0..method_end]; | 190 | const method_str = first_line[0..method_end]; |
| 189 | const method = std.meta.stringToEnum(http.Method, method_str) orelse return error.UnknownHttpMethod; | 191 | const method: http.Method = @enumFromInt(http.Method.parse(method_str)); |
| 190 | 192 | ||
| 191 | const version_start = mem.lastIndexOfScalar(u8, first_line, ' ') orelse return error.HttpHeadersInvalid; | 193 | const version_start = mem.lastIndexOfScalar(u8, first_line, ' ') orelse return error.HttpHeadersInvalid; |
| 192 | if (version_start == method_end) return error.HttpHeadersInvalid; | 194 | if (version_start == method_end) return error.HttpHeadersInvalid; |
| ... | @@ -467,11 +469,11 @@ pub const Response = struct { | ... | @@ -467,11 +469,11 @@ pub const Response = struct { |
| 467 | try buffered.flush(); | 469 | try buffered.flush(); |
| 468 | } | 470 | } |
| 469 | 471 | ||
| 470 | pub const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError; | 472 | const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError; |
| 471 | 473 | ||
| 472 | pub const TransferReader = std.io.Reader(*Response, TransferReadError, transferRead); | 474 | const TransferReader = std.io.Reader(*Response, TransferReadError, transferRead); |
| 473 | 475 | ||
| 474 | pub fn transferReader(res: *Response) TransferReader { | 476 | fn transferReader(res: *Response) TransferReader { |
| 475 | return .{ .context = res }; | 477 | return .{ .context = res }; |
| 476 | } | 478 | } |
| 477 | 479 |
test/standalone/http.zig+1-1| ... | @@ -20,7 +20,7 @@ var server: Server = undefined; | ... | @@ -20,7 +20,7 @@ var server: Server = undefined; |
| 20 | fn handleRequest(res: *Server.Response) !void { | 20 | fn handleRequest(res: *Server.Response) !void { |
| 21 | const log = std.log.scoped(.server); | 21 | const log = std.log.scoped(.server); |
| 22 | 22 | ||
| 23 | log.info("{s} {s} {s}", .{ @tagName(res.request.method), @tagName(res.request.version), res.request.target }); | 23 | log.info("{} {s} {s}", .{ res.request.method, @tagName(res.request.version), res.request.target }); |
| 24 | 24 | ||
| 25 | if (res.request.headers.contains("expect")) { | 25 | if (res.request.headers.contains("expect")) { |
| 26 | if (mem.eql(u8, res.request.headers.getFirstValue("expect").?, "100-continue")) { | 26 | if (mem.eql(u8, res.request.headers.getFirstValue("expect").?, "100-continue")) { |