web: some stuff
This commit is contained in:
@@ -1,24 +1,23 @@
|
||||
const std = @import("std");
|
||||
const Parser = @This();
|
||||
|
||||
const Callbacks = struct {
|
||||
self: ?*anyopaque = null,
|
||||
|
||||
route: ?*const fn (self: ?*anyopaque, route: Route) void = null,
|
||||
header: ?*const fn (self: ?*anyopaque, name: []const u8, value: []const u8) void = null,
|
||||
body: ?*const fn (self: ?*anyopaque, body: []const u8) void = null,
|
||||
|
||||
pub const null_callbacks: Callbacks = .{};
|
||||
};
|
||||
const Header = @import("Header.zig");
|
||||
const Method = @import("Method.zig").Method;
|
||||
const Response = @import("../Response.zig");
|
||||
const RequestHandler = @import("../RequestHandler.zig");
|
||||
const RequestRouter = @import("../RequestRouter.zig");
|
||||
|
||||
const Error = error{
|
||||
MethodNotSupported,
|
||||
HttpVersionNotSupported,
|
||||
MissingLineFeed,
|
||||
InvalidContentLength,
|
||||
RouterError,
|
||||
HandlerError,
|
||||
};
|
||||
|
||||
const Vec = @Vector(32, u8);
|
||||
const Vec: type = @Vector(std.simd.suggestVectorLength(u8).?, u8);
|
||||
const vec_len = @typeInfo(Vec).vector.len;
|
||||
|
||||
const Pattern = struct {
|
||||
value: Vec,
|
||||
@@ -26,13 +25,13 @@ const Pattern = struct {
|
||||
len: u6,
|
||||
|
||||
pub fn init(comptime prefix: []const u8) Pattern {
|
||||
if (prefix.len > 32) {
|
||||
if (prefix.len > vec_len) {
|
||||
@compileError("Prefix length is too high");
|
||||
}
|
||||
|
||||
var value: [32]u8 = undefined;
|
||||
var mask: [32]u8 = undefined;
|
||||
for (0..32) |i| {
|
||||
var value: [vec_len]u8 = undefined;
|
||||
var mask: [vec_len]u8 = undefined;
|
||||
for (0..vec_len) |i| {
|
||||
if (i < prefix.len) {
|
||||
value[i] = prefix[i];
|
||||
mask[i] = 0xFF;
|
||||
@@ -49,14 +48,24 @@ const Pattern = struct {
|
||||
};
|
||||
|
||||
const patterns = struct {
|
||||
method_delete: Pattern.init("DELETE "),
|
||||
method_get: Pattern.init("GET "),
|
||||
method_head: Pattern.init("HEAD "),
|
||||
method_patch: Pattern.init("PATCH "),
|
||||
method_post: Pattern.init("POST "),
|
||||
method_put: Pattern.init("PUT "),
|
||||
pub const methods = struct {
|
||||
// NOTE These patterns are arranged in a specific order, such that the
|
||||
// first ones are the most common (based on vibes only).
|
||||
|
||||
@"version_http/1.1": Pattern.init("HTTP/1.1\r\n"),
|
||||
pub const GET = Pattern.init("GET ");
|
||||
pub const POST = Pattern.init("POST ");
|
||||
pub const HEAD = Pattern.init("HEAD ");
|
||||
|
||||
pub const PUT = Pattern.init("PUT ");
|
||||
pub const DELETE = Pattern.init("DELETE ");
|
||||
pub const PATCH = Pattern.init("PATCH ");
|
||||
|
||||
pub const OPTIONS = Pattern.init("OPTIONS ");
|
||||
pub const CONNECT = Pattern.init("CONNECT ");
|
||||
pub const TRACE = Pattern.init("TRACE ");
|
||||
};
|
||||
|
||||
pub const @"version_http/1.1" = Pattern.init("HTTP/1.1\r\n");
|
||||
};
|
||||
|
||||
inline fn hasSpace(vec: Vec) bool {
|
||||
@@ -98,6 +107,8 @@ const State = union(enum) {
|
||||
}
|
||||
|
||||
init: void,
|
||||
// TODO Add all methods here and in `consumeChar` (they are covered by
|
||||
// `consumeVec`, though)
|
||||
method_d: void,
|
||||
method_g: void,
|
||||
method_h: void,
|
||||
@@ -145,30 +156,20 @@ const ConsumeCharResult = enum {
|
||||
done,
|
||||
};
|
||||
|
||||
pub const Method = enum {
|
||||
DELETE,
|
||||
GET,
|
||||
HEAD,
|
||||
PATCH,
|
||||
POST,
|
||||
PUT,
|
||||
};
|
||||
|
||||
pub const Route = struct {
|
||||
method: Method,
|
||||
pathname: []const u8,
|
||||
};
|
||||
|
||||
callbacks: Callbacks,
|
||||
request_router: RequestRouter,
|
||||
response: *Response,
|
||||
state: State,
|
||||
current_header_is_content_length: bool,
|
||||
content_length: usize,
|
||||
|
||||
pub fn init(callbacks: Callbacks) Parser {
|
||||
request_handler: ?RequestHandler = null,
|
||||
last_router_error: anyerror = undefined,
|
||||
last_handler_error: anyerror = undefined,
|
||||
|
||||
pub fn init(request_router: RequestRouter, response: *Response) Parser {
|
||||
return .{
|
||||
.callbacks = callbacks,
|
||||
.request_router = request_router,
|
||||
.response = response,
|
||||
.state = .init,
|
||||
.current_header_is_content_length = false,
|
||||
.content_length = 0,
|
||||
};
|
||||
}
|
||||
@@ -186,9 +187,10 @@ pub fn consume(self: *Parser, chars: []const u8) Error!ConsumeResult {
|
||||
const done = new_body.len >= self.content_length;
|
||||
|
||||
if (done) {
|
||||
if (self.callbacks.body) |bodyCallback| {
|
||||
bodyCallback(self.callbacks.self, new_body);
|
||||
}
|
||||
self.request_handler.?.rawBody(self.response, new_body) catch |err| {
|
||||
self.last_handler_error = err;
|
||||
return error.HandlerError;
|
||||
};
|
||||
}
|
||||
|
||||
return .{
|
||||
@@ -197,9 +199,25 @@ pub fn consume(self: *Parser, chars: []const u8) Error!ConsumeResult {
|
||||
};
|
||||
},
|
||||
else => {
|
||||
const res = try self.consumeChar(&chars[i]);
|
||||
if (chars.len - i >= vec_len) {
|
||||
const vec_res = try self.consumeVec(chars[i..][0..vec_len]);
|
||||
i += vec_res.consumed;
|
||||
|
||||
if (vec_res.done) {
|
||||
return .{
|
||||
.consumed = i,
|
||||
.done = true,
|
||||
};
|
||||
}
|
||||
|
||||
if (vec_res.consumed > 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const char_res = try self.consumeChar(&chars[i]);
|
||||
i += 1;
|
||||
if (res == .done) return .{
|
||||
if (char_res == .done) return .{
|
||||
.consumed = i,
|
||||
.done = true,
|
||||
};
|
||||
@@ -213,6 +231,65 @@ pub fn consume(self: *Parser, chars: []const u8) Error!ConsumeResult {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn consumeVec(self: *Parser, vec: *const [vec_len]u8) Error!ConsumeResult {
|
||||
switch (self.state) {
|
||||
.init => {
|
||||
inline for (@typeInfo(patterns.methods).@"struct".decls) |decl| {
|
||||
const pattern: Pattern = @field(patterns.methods, decl.name);
|
||||
if (pattern.check(vec)) {
|
||||
self.state = .methodComplete(@field(Method, decl.name));
|
||||
return .{
|
||||
.consumed = pattern.len,
|
||||
.done = false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return error.MethodNotSupported;
|
||||
},
|
||||
.pathname_state => |s| {
|
||||
if (hasSpace(vec)) {
|
||||
// Delegate to `consumeChar`.
|
||||
return .{
|
||||
.consumed = 0,
|
||||
.done = false,
|
||||
};
|
||||
}
|
||||
|
||||
self.state = .pathname(s.method, s.pathname.ptr[0 .. s.pathname.len + vec_len]);
|
||||
},
|
||||
.pathname_complete => {
|
||||
if (patterns.@"version_http/1.1".check(vec)) {
|
||||
self.state = .header_name_start;
|
||||
return .{
|
||||
.consumed = patterns.@"version_http/1.1".len,
|
||||
.done = false,
|
||||
};
|
||||
} else {
|
||||
return error.HttpVersionNotSupported;
|
||||
}
|
||||
},
|
||||
.header_value => |s| {
|
||||
if (hasCRLF(vec)) {
|
||||
// Delegate to `consumeChar`.
|
||||
return .{
|
||||
.consumed = 0,
|
||||
.done = false,
|
||||
};
|
||||
}
|
||||
|
||||
self.state = .headerValue(s.name, s.value.ptr[0 .. s.value.len + vec_len]);
|
||||
},
|
||||
else => {
|
||||
// Delegate to `consumeChar`.
|
||||
return .{
|
||||
.consumed = 0,
|
||||
.done = false,
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
||||
const c = c_ptr.*;
|
||||
const c_slice = @as([*]const u8, @ptrCast(c_ptr))[0..1];
|
||||
@@ -301,12 +378,10 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
||||
.pathname_state => |s| switch (c) {
|
||||
' ' => {
|
||||
self.state = .pathname_complete;
|
||||
if (self.callbacks.route) |routeCallback| {
|
||||
routeCallback(self.callbacks.self, .{
|
||||
.method = s.method,
|
||||
.pathname = s.pathname,
|
||||
});
|
||||
}
|
||||
self.request_handler = self.request_router.rawRoute(.init(s.method, s.pathname)) catch |err| {
|
||||
self.last_router_error = err;
|
||||
return error.RouterError;
|
||||
};
|
||||
},
|
||||
else => self.state = .pathname(s.method, s.pathname.ptr[0 .. s.pathname.len + 1]),
|
||||
},
|
||||
@@ -357,21 +432,22 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
||||
.header_name => |name| switch (c) {
|
||||
':' => {
|
||||
self.state = .headerValue(name, @as([*]const u8, @ptrCast(c_ptr))[1..1]);
|
||||
self.current_header_is_content_length = std.ascii.eqlIgnoreCase(name, "Content-Length");
|
||||
},
|
||||
else => self.state = .{ .header_name = name.ptr[0 .. name.len + 1] },
|
||||
},
|
||||
.header_value => |s| switch (c) {
|
||||
'\r' => {
|
||||
self.state = .header_line_end;
|
||||
const value_trimmed = std.mem.trim(u8, s.value, " \t");
|
||||
if (self.current_header_is_content_length) {
|
||||
self.content_length = std.fmt.parseInt(usize, value_trimmed, 10) catch return error.InvalidContentLength;
|
||||
self.current_header_is_content_length = false;
|
||||
}
|
||||
if (self.callbacks.header) |headerCallback| {
|
||||
headerCallback(self.callbacks.self, s.name, value_trimmed);
|
||||
const header: Header = .init(s.name, std.mem.trim(u8, s.value, " \t"));
|
||||
|
||||
if (header.isKnown(.@"Content-Length")) {
|
||||
self.content_length = std.fmt.parseInt(usize, header.value, 10) catch return error.InvalidContentLength;
|
||||
}
|
||||
|
||||
self.request_handler.rawHeader(header) catch |err| {
|
||||
self.last_handler_error = err;
|
||||
return error.HandlerError;
|
||||
};
|
||||
},
|
||||
else => self.state = .headerValue(s.name, s.value.ptr[0 .. s.value.len + 1]),
|
||||
},
|
||||
@@ -382,9 +458,7 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
||||
.headers_end => switch (c) {
|
||||
'\n' => {
|
||||
if (self.content_length == 0) {
|
||||
if (self.callbacks.body) |bodyCallback| {
|
||||
bodyCallback(self.callbacks.self, &.{});
|
||||
}
|
||||
self.handler.rawBody(self.request, &.{});
|
||||
return .done;
|
||||
}
|
||||
self.state = .{ .body = @as([*]const u8, @ptrCast(c_ptr))[1..1] };
|
||||
@@ -395,9 +469,7 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
||||
const new_body = body.ptr[0 .. body.len + 1];
|
||||
self.state = .{ .body = new_body };
|
||||
if (new_body.len >= self.content_length) {
|
||||
if (self.callbacks.body) |bodyCallback| {
|
||||
bodyCallback(self.callbacks.self, new_body);
|
||||
}
|
||||
self.handler.rawBody(self.request, new_body);
|
||||
return .done;
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user