mirror of
https://github.com/ziglang/zig.git
synced 2024-11-15 16:45:27 +00:00
eae9aa800e
Note that the `_ = Address` statements in tests previously were a nop, and now actually check that the type is valid. However, on WASI, the type is *not* valid.
324 lines
12 KiB
Zig
324 lines
12 KiB
Zig
pub const Client = @import("http/Client.zig");
|
|
pub const Server = @import("http/Server.zig");
|
|
pub const protocol = @import("http/protocol.zig");
|
|
pub const HeadParser = @import("http/HeadParser.zig");
|
|
pub const ChunkParser = @import("http/ChunkParser.zig");
|
|
pub const HeaderIterator = @import("http/HeaderIterator.zig");
|
|
|
|
pub const Version = enum {
|
|
@"HTTP/1.0",
|
|
@"HTTP/1.1",
|
|
};
|
|
|
|
/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
|
|
///
|
|
/// https://datatracker.ietf.org/doc/html/rfc7231#section-4 Initial definition
|
|
///
|
|
/// https://datatracker.ietf.org/doc/html/rfc5789#section-2 PATCH
|
|
pub const Method = enum(u64) {
|
|
GET = parse("GET"),
|
|
HEAD = parse("HEAD"),
|
|
POST = parse("POST"),
|
|
PUT = parse("PUT"),
|
|
DELETE = parse("DELETE"),
|
|
CONNECT = parse("CONNECT"),
|
|
OPTIONS = parse("OPTIONS"),
|
|
TRACE = parse("TRACE"),
|
|
PATCH = parse("PATCH"),
|
|
|
|
_,
|
|
|
|
/// Converts `s` into a type that may be used as a `Method` field.
|
|
/// Asserts that `s` is 24 or fewer bytes.
|
|
pub fn parse(s: []const u8) u64 {
|
|
var x: u64 = 0;
|
|
const len = @min(s.len, @sizeOf(@TypeOf(x)));
|
|
@memcpy(std.mem.asBytes(&x)[0..len], s[0..len]);
|
|
return x;
|
|
}
|
|
|
|
pub fn write(self: Method, w: anytype) !void {
|
|
const bytes = std.mem.asBytes(&@intFromEnum(self));
|
|
const str = std.mem.sliceTo(bytes, 0);
|
|
try w.writeAll(str);
|
|
}
|
|
|
|
/// Returns true if a request of this method is allowed to have a body
|
|
/// Actual behavior from servers may vary and should still be checked
|
|
pub fn requestHasBody(self: Method) bool {
|
|
return switch (self) {
|
|
.POST, .PUT, .PATCH => true,
|
|
.GET, .HEAD, .DELETE, .CONNECT, .OPTIONS, .TRACE => false,
|
|
else => true,
|
|
};
|
|
}
|
|
|
|
/// Returns true if a response to this method is allowed to have a body
|
|
/// Actual behavior from clients may vary and should still be checked
|
|
pub fn responseHasBody(self: Method) bool {
|
|
return switch (self) {
|
|
.GET, .POST, .DELETE, .CONNECT, .OPTIONS, .PATCH => true,
|
|
.HEAD, .PUT, .TRACE => false,
|
|
else => true,
|
|
};
|
|
}
|
|
|
|
/// An HTTP method is safe if it doesn't alter the state of the server.
|
|
///
|
|
/// https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP
|
|
///
|
|
/// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.1
|
|
pub fn safe(self: Method) bool {
|
|
return switch (self) {
|
|
.GET, .HEAD, .OPTIONS, .TRACE => true,
|
|
.POST, .PUT, .DELETE, .CONNECT, .PATCH => false,
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
/// An HTTP method is idempotent if an identical request can be made once or several times in a row with the same effect while leaving the server in the same state.
|
|
///
|
|
/// https://developer.mozilla.org/en-US/docs/Glossary/Idempotent
|
|
///
|
|
/// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.2
|
|
pub fn idempotent(self: Method) bool {
|
|
return switch (self) {
|
|
.GET, .HEAD, .PUT, .DELETE, .OPTIONS, .TRACE => true,
|
|
.CONNECT, .POST, .PATCH => false,
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
/// A cacheable response is an HTTP response that can be cached, that is stored to be retrieved and used later, saving a new request to the server.
|
|
///
|
|
/// https://developer.mozilla.org/en-US/docs/Glossary/cacheable
|
|
///
|
|
/// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.3
|
|
pub fn cacheable(self: Method) bool {
|
|
return switch (self) {
|
|
.GET, .HEAD => true,
|
|
.POST, .PUT, .DELETE, .CONNECT, .OPTIONS, .TRACE, .PATCH => false,
|
|
else => false,
|
|
};
|
|
}
|
|
};
|
|
|
|
/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
|
|
pub const Status = enum(u10) {
|
|
@"continue" = 100, // RFC7231, Section 6.2.1
|
|
switching_protocols = 101, // RFC7231, Section 6.2.2
|
|
processing = 102, // RFC2518
|
|
early_hints = 103, // RFC8297
|
|
|
|
ok = 200, // RFC7231, Section 6.3.1
|
|
created = 201, // RFC7231, Section 6.3.2
|
|
accepted = 202, // RFC7231, Section 6.3.3
|
|
non_authoritative_info = 203, // RFC7231, Section 6.3.4
|
|
no_content = 204, // RFC7231, Section 6.3.5
|
|
reset_content = 205, // RFC7231, Section 6.3.6
|
|
partial_content = 206, // RFC7233, Section 4.1
|
|
multi_status = 207, // RFC4918
|
|
already_reported = 208, // RFC5842
|
|
im_used = 226, // RFC3229
|
|
|
|
multiple_choice = 300, // RFC7231, Section 6.4.1
|
|
moved_permanently = 301, // RFC7231, Section 6.4.2
|
|
found = 302, // RFC7231, Section 6.4.3
|
|
see_other = 303, // RFC7231, Section 6.4.4
|
|
not_modified = 304, // RFC7232, Section 4.1
|
|
use_proxy = 305, // RFC7231, Section 6.4.5
|
|
temporary_redirect = 307, // RFC7231, Section 6.4.7
|
|
permanent_redirect = 308, // RFC7538
|
|
|
|
bad_request = 400, // RFC7231, Section 6.5.1
|
|
unauthorized = 401, // RFC7235, Section 3.1
|
|
payment_required = 402, // RFC7231, Section 6.5.2
|
|
forbidden = 403, // RFC7231, Section 6.5.3
|
|
not_found = 404, // RFC7231, Section 6.5.4
|
|
method_not_allowed = 405, // RFC7231, Section 6.5.5
|
|
not_acceptable = 406, // RFC7231, Section 6.5.6
|
|
proxy_auth_required = 407, // RFC7235, Section 3.2
|
|
request_timeout = 408, // RFC7231, Section 6.5.7
|
|
conflict = 409, // RFC7231, Section 6.5.8
|
|
gone = 410, // RFC7231, Section 6.5.9
|
|
length_required = 411, // RFC7231, Section 6.5.10
|
|
precondition_failed = 412, // RFC7232, Section 4.2][RFC8144, Section 3.2
|
|
payload_too_large = 413, // RFC7231, Section 6.5.11
|
|
uri_too_long = 414, // RFC7231, Section 6.5.12
|
|
unsupported_media_type = 415, // RFC7231, Section 6.5.13][RFC7694, Section 3
|
|
range_not_satisfiable = 416, // RFC7233, Section 4.4
|
|
expectation_failed = 417, // RFC7231, Section 6.5.14
|
|
teapot = 418, // RFC 7168, 2.3.3
|
|
misdirected_request = 421, // RFC7540, Section 9.1.2
|
|
unprocessable_entity = 422, // RFC4918
|
|
locked = 423, // RFC4918
|
|
failed_dependency = 424, // RFC4918
|
|
too_early = 425, // RFC8470
|
|
upgrade_required = 426, // RFC7231, Section 6.5.15
|
|
precondition_required = 428, // RFC6585
|
|
too_many_requests = 429, // RFC6585
|
|
request_header_fields_too_large = 431, // RFC6585
|
|
unavailable_for_legal_reasons = 451, // RFC7725
|
|
|
|
internal_server_error = 500, // RFC7231, Section 6.6.1
|
|
not_implemented = 501, // RFC7231, Section 6.6.2
|
|
bad_gateway = 502, // RFC7231, Section 6.6.3
|
|
service_unavailable = 503, // RFC7231, Section 6.6.4
|
|
gateway_timeout = 504, // RFC7231, Section 6.6.5
|
|
http_version_not_supported = 505, // RFC7231, Section 6.6.6
|
|
variant_also_negotiates = 506, // RFC2295
|
|
insufficient_storage = 507, // RFC4918
|
|
loop_detected = 508, // RFC5842
|
|
not_extended = 510, // RFC2774
|
|
network_authentication_required = 511, // RFC6585
|
|
|
|
_,
|
|
|
|
pub fn phrase(self: Status) ?[]const u8 {
|
|
return switch (self) {
|
|
// 1xx statuses
|
|
.@"continue" => "Continue",
|
|
.switching_protocols => "Switching Protocols",
|
|
.processing => "Processing",
|
|
.early_hints => "Early Hints",
|
|
|
|
// 2xx statuses
|
|
.ok => "OK",
|
|
.created => "Created",
|
|
.accepted => "Accepted",
|
|
.non_authoritative_info => "Non-Authoritative Information",
|
|
.no_content => "No Content",
|
|
.reset_content => "Reset Content",
|
|
.partial_content => "Partial Content",
|
|
.multi_status => "Multi-Status",
|
|
.already_reported => "Already Reported",
|
|
.im_used => "IM Used",
|
|
|
|
// 3xx statuses
|
|
.multiple_choice => "Multiple Choice",
|
|
.moved_permanently => "Moved Permanently",
|
|
.found => "Found",
|
|
.see_other => "See Other",
|
|
.not_modified => "Not Modified",
|
|
.use_proxy => "Use Proxy",
|
|
.temporary_redirect => "Temporary Redirect",
|
|
.permanent_redirect => "Permanent Redirect",
|
|
|
|
// 4xx statuses
|
|
.bad_request => "Bad Request",
|
|
.unauthorized => "Unauthorized",
|
|
.payment_required => "Payment Required",
|
|
.forbidden => "Forbidden",
|
|
.not_found => "Not Found",
|
|
.method_not_allowed => "Method Not Allowed",
|
|
.not_acceptable => "Not Acceptable",
|
|
.proxy_auth_required => "Proxy Authentication Required",
|
|
.request_timeout => "Request Timeout",
|
|
.conflict => "Conflict",
|
|
.gone => "Gone",
|
|
.length_required => "Length Required",
|
|
.precondition_failed => "Precondition Failed",
|
|
.payload_too_large => "Payload Too Large",
|
|
.uri_too_long => "URI Too Long",
|
|
.unsupported_media_type => "Unsupported Media Type",
|
|
.range_not_satisfiable => "Range Not Satisfiable",
|
|
.expectation_failed => "Expectation Failed",
|
|
.teapot => "I'm a teapot",
|
|
.misdirected_request => "Misdirected Request",
|
|
.unprocessable_entity => "Unprocessable Entity",
|
|
.locked => "Locked",
|
|
.failed_dependency => "Failed Dependency",
|
|
.too_early => "Too Early",
|
|
.upgrade_required => "Upgrade Required",
|
|
.precondition_required => "Precondition Required",
|
|
.too_many_requests => "Too Many Requests",
|
|
.request_header_fields_too_large => "Request Header Fields Too Large",
|
|
.unavailable_for_legal_reasons => "Unavailable For Legal Reasons",
|
|
|
|
// 5xx statuses
|
|
.internal_server_error => "Internal Server Error",
|
|
.not_implemented => "Not Implemented",
|
|
.bad_gateway => "Bad Gateway",
|
|
.service_unavailable => "Service Unavailable",
|
|
.gateway_timeout => "Gateway Timeout",
|
|
.http_version_not_supported => "HTTP Version Not Supported",
|
|
.variant_also_negotiates => "Variant Also Negotiates",
|
|
.insufficient_storage => "Insufficient Storage",
|
|
.loop_detected => "Loop Detected",
|
|
.not_extended => "Not Extended",
|
|
.network_authentication_required => "Network Authentication Required",
|
|
|
|
else => return null,
|
|
};
|
|
}
|
|
|
|
pub const Class = enum {
|
|
informational,
|
|
success,
|
|
redirect,
|
|
client_error,
|
|
server_error,
|
|
};
|
|
|
|
pub fn class(self: Status) Class {
|
|
return switch (@intFromEnum(self)) {
|
|
100...199 => .informational,
|
|
200...299 => .success,
|
|
300...399 => .redirect,
|
|
400...499 => .client_error,
|
|
else => .server_error,
|
|
};
|
|
}
|
|
|
|
test {
|
|
try std.testing.expectEqualStrings("OK", Status.ok.phrase().?);
|
|
try std.testing.expectEqualStrings("Not Found", Status.not_found.phrase().?);
|
|
}
|
|
|
|
test {
|
|
try std.testing.expectEqual(Status.Class.success, Status.ok.class());
|
|
try std.testing.expectEqual(Status.Class.client_error, Status.not_found.class());
|
|
}
|
|
};
|
|
|
|
pub const TransferEncoding = enum {
|
|
chunked,
|
|
none,
|
|
// compression is intentionally omitted here, as std.http.Client stores it as content-encoding
|
|
};
|
|
|
|
pub const ContentEncoding = enum {
|
|
identity,
|
|
compress,
|
|
@"x-compress",
|
|
deflate,
|
|
gzip,
|
|
@"x-gzip",
|
|
zstd,
|
|
};
|
|
|
|
pub const Connection = enum {
|
|
keep_alive,
|
|
close,
|
|
};
|
|
|
|
pub const Header = struct {
|
|
name: []const u8,
|
|
value: []const u8,
|
|
};
|
|
|
|
const builtin = @import("builtin");
|
|
const std = @import("std.zig");
|
|
|
|
test {
|
|
if (builtin.os.tag != .wasi) {
|
|
_ = Client;
|
|
_ = Method;
|
|
_ = Server;
|
|
_ = Status;
|
|
_ = HeadParser;
|
|
_ = ChunkParser;
|
|
_ = @import("http/test.zig");
|
|
}
|
|
}
|