Parser callbacks, measure and log request time
This commit is contained in:
@@ -119,7 +119,14 @@ pub fn process(conn: std.net.Server.Connection) !void {
|
|||||||
var leftover_bytes: usize = 0;
|
var leftover_bytes: usize = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var parser = Parser.init();
|
const start = try std.time.Instant.now();
|
||||||
|
|
||||||
|
var route: Parser.Route = undefined;
|
||||||
|
|
||||||
|
var parser = Parser.init(.{
|
||||||
|
.self = &route,
|
||||||
|
.route = routeCallback,
|
||||||
|
});
|
||||||
var total_bytes_read: usize = 0;
|
var total_bytes_read: usize = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -185,5 +192,15 @@ pub fn process(conn: std.net.Server.Connection) !void {
|
|||||||
if (leftover_bytes > 0) {
|
if (leftover_bytes > 0) {
|
||||||
@memmove(&read_buffer, read_buffer[total_bytes_read - leftover_bytes .. total_bytes_read]);
|
@memmove(&read_buffer, read_buffer[total_bytes_read - leftover_bytes .. total_bytes_read]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const end = try std.time.Instant.now();
|
||||||
|
const time_ns = end.since(start);
|
||||||
|
const time_us = @divFloor(time_ns, std.time.ns_per_us);
|
||||||
|
|
||||||
|
log.info("{s} {s} ({} μs)", .{ @tagName(route.method), route.pathname, time_us });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn routeCallback(self: ?*anyopaque, route: Parser.Route) void {
|
||||||
|
@as(*Parser.Route, @alignCast(@ptrCast(self))).* = route;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,15 @@ const std = @import("std");
|
|||||||
|
|
||||||
const Parser = @This();
|
const Parser = @This();
|
||||||
|
|
||||||
const log = std.log.scoped(.http_parser);
|
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 init: Callbacks = .{};
|
||||||
|
};
|
||||||
|
|
||||||
const Error = error{
|
const Error = error{
|
||||||
MethodNotSupported,
|
MethodNotSupported,
|
||||||
@@ -12,6 +20,32 @@ const Error = error{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const State = union(enum) {
|
const State = union(enum) {
|
||||||
|
pub fn methodComplete(method: Method) State {
|
||||||
|
return .{
|
||||||
|
.method_complete = .{
|
||||||
|
.method = method,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pathname(method: Method, p: []const u8) State {
|
||||||
|
return .{
|
||||||
|
.pathname_state = .{
|
||||||
|
.method = method,
|
||||||
|
.pathname = p,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn headerValue(name: []const u8, value: []const u8) State {
|
||||||
|
return .{
|
||||||
|
.header_value = .{
|
||||||
|
.name = name,
|
||||||
|
.value = value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
init: void,
|
init: void,
|
||||||
method_d: void,
|
method_d: void,
|
||||||
method_g: void,
|
method_g: void,
|
||||||
@@ -30,8 +64,8 @@ const State = union(enum) {
|
|||||||
method_dele: void,
|
method_dele: void,
|
||||||
method_patc: void,
|
method_patc: void,
|
||||||
method_delet: void,
|
method_delet: void,
|
||||||
method_complete: void,
|
method_complete: struct { method: Method },
|
||||||
pathname: []const u8,
|
pathname_state: struct { method: Method, pathname: []const u8 },
|
||||||
pathname_complete: void,
|
pathname_complete: void,
|
||||||
version_h: void,
|
version_h: void,
|
||||||
version_ht: void,
|
version_ht: void,
|
||||||
@@ -44,23 +78,23 @@ const State = union(enum) {
|
|||||||
start_line_end: void,
|
start_line_end: void,
|
||||||
header_name_start: void,
|
header_name_start: void,
|
||||||
header_name: []const u8,
|
header_name: []const u8,
|
||||||
header_value: []const u8,
|
header_value: struct { name: []const u8, value: []const u8 },
|
||||||
header_line_end: void,
|
header_line_end: void,
|
||||||
headers_end: void,
|
headers_end: void,
|
||||||
body: []const u8,
|
body: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ConsumeResult = struct {
|
const ConsumeResult = struct {
|
||||||
consumed: usize,
|
consumed: usize,
|
||||||
done: bool,
|
done: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ConsumeCharResult = enum {
|
const ConsumeCharResult = enum {
|
||||||
not_done,
|
not_done,
|
||||||
done,
|
done,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Method = enum {
|
pub const Method = enum {
|
||||||
DELETE,
|
DELETE,
|
||||||
GET,
|
GET,
|
||||||
HEAD,
|
HEAD,
|
||||||
@@ -69,12 +103,23 @@ const Method = enum {
|
|||||||
PUT,
|
PUT,
|
||||||
};
|
};
|
||||||
|
|
||||||
state: State = .init,
|
pub const Route = struct {
|
||||||
current_header_is_content_length: bool = false,
|
method: Method,
|
||||||
content_length: usize = 0,
|
pathname: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn init() Parser {
|
callbacks: Callbacks,
|
||||||
return .{};
|
state: State,
|
||||||
|
current_header_is_content_length: bool,
|
||||||
|
content_length: usize,
|
||||||
|
|
||||||
|
pub fn init(callbacks: Callbacks) Parser {
|
||||||
|
return .{
|
||||||
|
.callbacks = callbacks,
|
||||||
|
.state = .init,
|
||||||
|
.current_header_is_content_length = false,
|
||||||
|
.content_length = 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn consume(self: *Parser, chars: []const u8) Error!ConsumeResult {
|
pub fn consume(self: *Parser, chars: []const u8) Error!ConsumeResult {
|
||||||
@@ -87,9 +132,17 @@ pub fn consume(self: *Parser, chars: []const u8) Error!ConsumeResult {
|
|||||||
self.state = .{ .body = new_body };
|
self.state = .{ .body = new_body };
|
||||||
i += to_consume;
|
i += to_consume;
|
||||||
|
|
||||||
|
const done = new_body.len >= self.content_length;
|
||||||
|
|
||||||
|
if (done) {
|
||||||
|
if (self.callbacks.body) |bodyCallback| {
|
||||||
|
bodyCallback(self.callbacks.self, new_body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.consumed = i,
|
.consumed = i,
|
||||||
.done = new_body.len >= self.content_length,
|
.done = done,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
@@ -143,10 +196,7 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
|||||||
else => return error.MethodNotSupported,
|
else => return error.MethodNotSupported,
|
||||||
},
|
},
|
||||||
.method_ge => switch (c) {
|
.method_ge => switch (c) {
|
||||||
'T' => {
|
'T' => self.state = .methodComplete(.GET),
|
||||||
self.state = .method_complete;
|
|
||||||
log.debug("Method: GET", .{});
|
|
||||||
},
|
|
||||||
else => return error.MethodNotSupported,
|
else => return error.MethodNotSupported,
|
||||||
},
|
},
|
||||||
.method_he => switch (c) {
|
.method_he => switch (c) {
|
||||||
@@ -162,10 +212,7 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
|||||||
else => return error.MethodNotSupported,
|
else => return error.MethodNotSupported,
|
||||||
},
|
},
|
||||||
.method_pu => switch (c) {
|
.method_pu => switch (c) {
|
||||||
'T' => {
|
'T' => self.state = .methodComplete(.PUT),
|
||||||
self.state = .method_complete;
|
|
||||||
log.debug("Method: PUT", .{});
|
|
||||||
},
|
|
||||||
else => return error.MethodNotSupported,
|
else => return error.MethodNotSupported,
|
||||||
},
|
},
|
||||||
.method_del => switch (c) {
|
.method_del => switch (c) {
|
||||||
@@ -173,10 +220,7 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
|||||||
else => return error.MethodNotSupported,
|
else => return error.MethodNotSupported,
|
||||||
},
|
},
|
||||||
.method_hea => switch (c) {
|
.method_hea => switch (c) {
|
||||||
'D' => {
|
'D' => self.state = .methodComplete(.HEAD),
|
||||||
self.state = .method_complete;
|
|
||||||
log.debug("Method: HEAD", .{});
|
|
||||||
},
|
|
||||||
else => return error.MethodNotSupported,
|
else => return error.MethodNotSupported,
|
||||||
},
|
},
|
||||||
.method_pat => switch (c) {
|
.method_pat => switch (c) {
|
||||||
@@ -184,10 +228,7 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
|||||||
else => return error.MethodNotSupported,
|
else => return error.MethodNotSupported,
|
||||||
},
|
},
|
||||||
.method_pos => switch (c) {
|
.method_pos => switch (c) {
|
||||||
'T' => {
|
'T' => self.state = .methodComplete(.POST),
|
||||||
self.state = .method_complete;
|
|
||||||
log.debug("Method: POST", .{});
|
|
||||||
},
|
|
||||||
else => return error.MethodNotSupported,
|
else => return error.MethodNotSupported,
|
||||||
},
|
},
|
||||||
.method_dele => switch (c) {
|
.method_dele => switch (c) {
|
||||||
@@ -195,29 +236,28 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
|||||||
else => return error.MethodNotSupported,
|
else => return error.MethodNotSupported,
|
||||||
},
|
},
|
||||||
.method_patc => switch (c) {
|
.method_patc => switch (c) {
|
||||||
'H' => {
|
'H' => self.state = .methodComplete(.PATCH),
|
||||||
self.state = .method_complete;
|
|
||||||
log.debug("Method: PATCH", .{});
|
|
||||||
},
|
|
||||||
else => return error.MethodNotSupported,
|
else => return error.MethodNotSupported,
|
||||||
},
|
},
|
||||||
.method_delet => switch (c) {
|
.method_delet => switch (c) {
|
||||||
'E' => {
|
'E' => self.state = .methodComplete(.DELETE),
|
||||||
self.state = .method_complete;
|
|
||||||
log.debug("Method: DELETE", .{});
|
|
||||||
},
|
|
||||||
else => return error.MethodNotSupported,
|
else => return error.MethodNotSupported,
|
||||||
},
|
},
|
||||||
.method_complete => switch (c) {
|
.method_complete => |s| switch (c) {
|
||||||
' ' => self.state = .{ .pathname = @as([*]const u8, @ptrCast(c_ptr))[1..1] },
|
' ' => self.state = .pathname(s.method, @as([*]const u8, @ptrCast(c_ptr))[1..1]),
|
||||||
else => return error.MethodNotSupported,
|
else => return error.MethodNotSupported,
|
||||||
},
|
},
|
||||||
.pathname => |pathname| switch (c) {
|
.pathname_state => |s| switch (c) {
|
||||||
' ' => {
|
' ' => {
|
||||||
self.state = .pathname_complete;
|
self.state = .pathname_complete;
|
||||||
log.debug("Pathname [{}]: {s}", .{ pathname.len, pathname });
|
if (self.callbacks.route) |routeCallback| {
|
||||||
|
routeCallback(self.callbacks.self, .{
|
||||||
|
.method = s.method,
|
||||||
|
.pathname = s.pathname,
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
else => self.state = .{ .pathname = pathname.ptr[0 .. pathname.len + 1] },
|
else => self.state = .pathname(s.method, s.pathname.ptr[0 .. s.pathname.len + 1]),
|
||||||
},
|
},
|
||||||
.pathname_complete => switch (c) {
|
.pathname_complete => switch (c) {
|
||||||
'H' => self.state = .version_h,
|
'H' => self.state = .version_h,
|
||||||
@@ -248,10 +288,7 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
|||||||
else => return error.HttpVersionNotSupported,
|
else => return error.HttpVersionNotSupported,
|
||||||
},
|
},
|
||||||
.@"version_http/1.@" => switch (c) {
|
.@"version_http/1.@" => switch (c) {
|
||||||
'1' => {
|
'1' => self.state = .version_complete,
|
||||||
self.state = .version_complete;
|
|
||||||
log.debug("Version: HTTP/1.1", .{});
|
|
||||||
},
|
|
||||||
else => return error.HttpVersionNotSupported,
|
else => return error.HttpVersionNotSupported,
|
||||||
},
|
},
|
||||||
.version_complete => switch (c) {
|
.version_complete => switch (c) {
|
||||||
@@ -268,23 +305,24 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
|||||||
},
|
},
|
||||||
.header_name => |name| switch (c) {
|
.header_name => |name| switch (c) {
|
||||||
':' => {
|
':' => {
|
||||||
self.state = .{ .header_value = @as([*]const u8, @ptrCast(c_ptr))[1..1] };
|
self.state = .headerValue(name, @as([*]const u8, @ptrCast(c_ptr))[1..1]);
|
||||||
self.current_header_is_content_length = std.ascii.eqlIgnoreCase(name, "Content-Length");
|
self.current_header_is_content_length = std.ascii.eqlIgnoreCase(name, "Content-Length");
|
||||||
log.debug("Header name [{}]: {s}", .{ name.len, name });
|
|
||||||
},
|
},
|
||||||
else => self.state = .{ .header_name = name.ptr[0 .. name.len + 1] },
|
else => self.state = .{ .header_name = name.ptr[0 .. name.len + 1] },
|
||||||
},
|
},
|
||||||
.header_value => |value| switch (c) {
|
.header_value => |s| switch (c) {
|
||||||
'\r' => {
|
'\r' => {
|
||||||
self.state = .header_line_end;
|
self.state = .header_line_end;
|
||||||
const value_trimmed = std.mem.trim(u8, value, " \t");
|
const value_trimmed = std.mem.trim(u8, s.value, " \t");
|
||||||
log.debug("Header value [{}]: {s}", .{ value_trimmed.len, value_trimmed });
|
|
||||||
if (self.current_header_is_content_length) {
|
if (self.current_header_is_content_length) {
|
||||||
self.content_length = std.fmt.parseInt(usize, value_trimmed, 10) catch return error.InvalidContentLength;
|
self.content_length = std.fmt.parseInt(usize, value_trimmed, 10) catch return error.InvalidContentLength;
|
||||||
self.current_header_is_content_length = false;
|
self.current_header_is_content_length = false;
|
||||||
}
|
}
|
||||||
|
if (self.callbacks.header) |headerCallback| {
|
||||||
|
headerCallback(self.callbacks.self, s.name, value_trimmed);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
else => self.state = .{ .header_value = value.ptr[0 .. value.len + 1] },
|
else => self.state = .headerValue(s.name, s.value.ptr[0 .. s.value.len + 1]),
|
||||||
},
|
},
|
||||||
.header_line_end => switch (c) {
|
.header_line_end => switch (c) {
|
||||||
'\n' => self.state = .header_name_start,
|
'\n' => self.state = .header_name_start,
|
||||||
@@ -293,7 +331,9 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
|||||||
.headers_end => switch (c) {
|
.headers_end => switch (c) {
|
||||||
'\n' => {
|
'\n' => {
|
||||||
if (self.content_length == 0) {
|
if (self.content_length == 0) {
|
||||||
log.debug("End of request (no body)", .{});
|
if (self.callbacks.body) |bodyCallback| {
|
||||||
|
bodyCallback(self.callbacks.self, &.{});
|
||||||
|
}
|
||||||
return .done;
|
return .done;
|
||||||
}
|
}
|
||||||
self.state = .{ .body = @as([*]const u8, @ptrCast(c_ptr))[1..1] };
|
self.state = .{ .body = @as([*]const u8, @ptrCast(c_ptr))[1..1] };
|
||||||
@@ -304,6 +344,9 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
|||||||
const new_body = body.ptr[0 .. body.len + 1];
|
const new_body = body.ptr[0 .. body.len + 1];
|
||||||
self.state = .{ .body = new_body };
|
self.state = .{ .body = new_body };
|
||||||
if (new_body.len >= self.content_length) {
|
if (new_body.len >= self.content_length) {
|
||||||
|
if (self.callbacks.body) |bodyCallback| {
|
||||||
|
bodyCallback(self.callbacks.self, new_body);
|
||||||
|
}
|
||||||
return .done;
|
return .done;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user