Created
July 10, 2022 16:31
-
-
Save joedavis/7e9fb40b61b5c8a69b8697f79e064204 to your computer and use it in GitHub Desktop.
Barebones HTTP implementation in Zig
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
pub const Headers = std.StringHashMap([]const u8); | |
pub const StatusCode = enum(u32) { | |
@"continue" = 100, | |
switching_protocols = 101, | |
processing = 102, | |
early_hints = 103, | |
ok = 200, | |
created = 201, | |
accepted = 202, | |
non_authorative_information = 203, | |
no_content = 204, | |
reset_content = 205, | |
partial_content = 206, | |
multi_status = 207, | |
already_reported = 208, | |
im_used = 226, | |
multiple_choices = 300, | |
moved_permanently = 301, | |
found = 302, | |
see_other = 303, | |
not_modified = 304, | |
use_proxy = 305, | |
unused = 306, | |
temporary_redirect = 307, | |
permanent_redirect = 308, | |
bad_request = 400, | |
unauthorized = 401, | |
payment_required = 402, | |
forbidden = 403, | |
not_found = 404, | |
method_not_allowed = 405, | |
not_acceptable = 406, | |
proxy_authentication = 407, | |
request_timeout = 408, | |
conflict = 409, | |
gone = 410, | |
length_required = 411, | |
precondition_failed = 412, | |
payload_too_large = 413, | |
uri_too_long = 414, | |
unsupported_media_type = 415, | |
range_not_satisfiable = 416, | |
expectation_failed = 417, | |
im_a_teapot = 418, | |
misdirected_request = 421, | |
unprocessable_entity = 422, | |
locked = 423, | |
failed_dependency = 424, | |
too_early = 425, | |
upgrade_required = 426, | |
precondition_required = 428, | |
too_many_requests = 429, | |
request_header_fields_too_large = 431, | |
unavailable_for_legal_reasons = 451, | |
internal_server_error = 500, | |
not_implemented = 501, | |
bad_gateway = 502, | |
service_unavailable = 503, | |
gateway_timeout = 504, | |
http_version_not_supported = 505, | |
variant_also_negotiates = 506, | |
insufficient_storage = 507, | |
loop_detected = 508, | |
not_extended = 510, | |
network_authenticaiton_required = 511, | |
}; | |
pub const reason_phrases: std.enums.EnumMap(StatusCode, []const u8) = blk: { | |
@setEvalBranchQuota(10000); | |
var map = std.enums.EnumMap(StatusCode, []const u8){}; | |
for (@typeInfo(StatusCode).Enum.fields) |enum_field| { | |
var upcased_name = [_]u8{0} ** (enum_field.name.len); | |
var new_word: bool = true; | |
for (enum_field.name) |c, i| { | |
if (new_word) { | |
upcased_name[i] = std.ascii.toUpper(c); | |
new_word = false; | |
} else if (c == '_') { | |
upcased_name[i] = ' '; | |
new_word = true; | |
} else { | |
upcased_name[i] = c; | |
} | |
} | |
map.put(@intToEnum(StatusCode, enum_field.value), &upcased_name); | |
} | |
break :blk map; | |
}; | |
pub const Method = enum { | |
GET, | |
HEAD, | |
POST, | |
PUT, | |
DELETE, | |
CONNECT, | |
OPTIONS, | |
TRACE, | |
PATCH, | |
}; | |
pub const ParseError = error{FailedParse} || std.mem.Allocator.Error; | |
pub const CRLF = "\r\n"; | |
pub const Request = struct { | |
method: Method, | |
path: []const u8, | |
headers: Headers, | |
body: []const u8, | |
fn parseMethod(r: *Request, request_text: []const u8) ParseError![]const u8 { | |
var first_space = std.mem.indexOfScalar(u8, request_text, ' ') orelse return error.FailedParse; | |
var method = request_text[0..first_space]; | |
r.method = std.meta.stringToEnum(Method, method) orelse return error.FailedParse; | |
if (first_space + 1 > request_text.len) { | |
return error.FailedParse; | |
} | |
return request_text[first_space + 1 ..]; | |
} | |
fn parsePath(r: *Request, request_text: []const u8) ParseError![]const u8 { | |
const end_of_path = std.mem.indexOfScalar(u8, request_text, ' ') orelse std.mem.indexOf(u8, request_text, CRLF) orelse return error.FailedParse; | |
r.path = request_text[0..end_of_path]; | |
if (end_of_path + 1 > request_text.len) { | |
return error.FailedParse; | |
} | |
return request_text[end_of_path + 1 ..]; | |
} | |
fn skipToNextLine(request_text: []const u8) ParseError![]const u8 { | |
const next_line = std.mem.indexOf(u8, request_text, CRLF) orelse return error.FailedParse; | |
if (next_line + 2 > request_text.len) { | |
return error.FailedParse; | |
} | |
return request_text[next_line + 2 ..]; | |
} | |
fn parseHeaders(r: *Request, request_text: []const u8) ParseError![]const u8 { | |
var remaining = request_text; | |
while (remaining.len > 0 and remaining[0] != '\r') { | |
const header_name_end = std.mem.indexOfScalar(u8, remaining, ':') orelse return error.FailedParse; | |
const header_name = remaining[0..header_name_end]; | |
remaining = remaining[header_name_end + 2 ..]; | |
const header_value_end = std.mem.indexOf(u8, remaining, CRLF) orelse return error.FailedParse; | |
const header_value = remaining[0..header_value_end]; | |
try r.headers.put(header_name, header_value); | |
remaining = remaining[header_value_end + 2 ..]; | |
} | |
return remaining; | |
} | |
pub fn parse(allocator: std.mem.Allocator, request_text: []const u8) ParseError!Request { | |
var r = Request{ | |
.method = undefined, | |
.path = undefined, | |
.body = undefined, | |
.headers = Headers.init(allocator), | |
}; | |
errdefer r.headers.deinit(); | |
var remaining = try r.parseMethod(request_text); | |
remaining = try r.parsePath(remaining); | |
remaining = try skipToNextLine(remaining); | |
remaining = try r.parseHeaders(remaining); | |
r.body = try skipToNextLine(remaining); | |
return r; | |
} | |
pub fn deinit(r: *Request) void { | |
r.headers.deinit(); | |
} | |
}; | |
pub const Response = struct { | |
headers: Headers, | |
status_code: StatusCode, | |
pub fn init(allocator: std.mem.Allocator) Response { | |
var self = Response{ | |
.headers = Headers.init(allocator), | |
.status_code = .ok, | |
}; | |
return self; | |
} | |
pub fn writeHeaders(self: *Response, writer: anytype) !void { | |
try std.fmt.format(writer, "HTTP/1.1 {} {s}\r\n", .{ @enumToInt(self.status_code), reason_phrases.get(self.status_code) }); | |
var iter = self.headers.iterator(); | |
while (iter.next()) |header| { | |
try std.fmt.format(writer, "{s}: {s}\r\n", .{ header.key_ptr.*, header.value_ptr.* }); | |
} | |
try writer.writeAll(CRLF); | |
} | |
pub fn deinit(self: *Response) void { | |
self.headers.deinit(); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment