mirror of
https://github.com/ziglang/zig.git
synced 2025-12-13 01:33:09 +00:00
I originally removed these in 402f967ed5339fa3d828b7fe1d57cdb5bf38dbf2. I allowed them to be added back in #15299 because they were smuggled in alongside a bug fix, however, I wasn't kidding when I said that I wanted to take the design of std.http in a different direction than using this data structure. Instead, some headers are provided via explicit field names populated while parsing the HTTP request/response, and some are provided via new fields that support passing extra, arbitrary headers. This resulted in simplification of logic in many places, as well as elimination of the possibility of failure in many places. There is less deinitialization code happening now. Furthermore, it made it no longer necessary to clone the headers data structure in order to handle redirects. http_proxy and https_proxy fields are now pointers since it is common for them to be unpopulated. loadDefaultProxies is changed into initDefaultProxies to communicate that it does not actually load anything from disk or from the network. The function now is leaky; the API user must pass an already instantiated arena allocator. Removes the need to deinitialize proxies. Before, proxies stored arbitrary sets of headers. Now they only store the authorization value. Removed the duplicated code between https_proxy and http_proxy. Finally, parsing failures of the environment variables result in errors being emitted rather than silently ignoring the proxy. error.CompressionNotSupported is renamed to error.CompressionUnsupported, matching the naming convention from all the other errors in the same set. Removed documentation comments that were redundant with field and type names. Disabling zstd decompression in the server for now; see #18937. I found some apparently dead code in src/Package/Fetch/git.zig. I want to check with Ian about this. I discovered that test/standalone/http.zig is dead code, it is only being compiled but not being run. Furthermore it hangs at the end if you run it manually. The previous commits in this branch were written under the assumption that this test was being run with `zig build test-standalone`.
319 lines
11 KiB
Zig
319 lines
11 KiB
Zig
const std = @import("std.zig");
|
|
|
|
pub const Client = @import("http/Client.zig");
|
|
pub const Server = @import("http/Server.zig");
|
|
pub const protocol = @import("http/protocol.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);
|
|
}
|
|
|
|
pub fn format(value: Method, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void {
|
|
return try value.write(writer);
|
|
}
|
|
|
|
/// 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,
|
|
};
|
|
|
|
test {
|
|
_ = Client;
|
|
_ = Method;
|
|
_ = Server;
|
|
_ = Status;
|
|
}
|