From f288d2418e5e08d54365c293c4d9a94f4265d897 Mon Sep 17 00:00:00 2001 From: Rene Schallner Date: Thu, 12 Jan 2023 15:24:33 +0100 Subject: [PATCH] progress: SimpleRequest, SimpleHttpListener, etc --- examples/hello/hello.zig | 25 ++++-- src/deps/zap.zig | 177 +++++++++++++++++++++++++++++++++------ 2 files changed, 167 insertions(+), 35 deletions(-) diff --git a/examples/hello/hello.zig b/examples/hello/hello.zig index fd5dbc2..ee0f295 100644 --- a/examples/hello/hello.zig +++ b/examples/hello/hello.zig @@ -1,21 +1,30 @@ const std = @import("std"); const zap = @import("zap"); -fn on_request(r: [*c]zap.C.http_s) callconv(.C) void { - _ = zap.sendBody(r, "

Hello from ZAP!!!

"); +fn on_request(r: zap.SimpleRequest) void { + if (r.path) |the_path| { + std.debug.print("PATH: {s}\n", .{the_path}); + } + + if (r.query) |the_query| { + std.debug.print("QUERY: {s}\n", .{the_query}); + } + _ = r.sendBody("

Hello from ZAP!!!

"); } pub fn main() !void { - // listen - try zap.listen("3000", null, .{ + var listener: zap.SimpleHttpListener = zap.SimpleHttpListener.init(.{ + .port = 3000, .on_request = on_request, - .log = true, + .log = false, }); + try listener.listen(); + std.debug.print("Listening on 0.0.0.0:3000\n", .{}); - // start working + // start worker threads zap.start(.{ - .threads = 4, - .workers = 4, + .threads = 2, + .workers = 2, }); } diff --git a/src/deps/zap.zig b/src/deps/zap.zig index 4464f98..d6d38dd 100644 --- a/src/deps/zap.zig +++ b/src/deps/zap.zig @@ -7,22 +7,156 @@ pub const C = @cImport({ @cInclude("fio.h"); }); -pub fn sendBody(request: [*c]C.http_s, body: []const u8) void { - _ = C.http_send_body(request, @intToPtr( - *anyopaque, - @ptrToInt(body.ptr), - ), body.len); -} - pub fn start(args: C.fio_start_args) void { C.fio_start(args); } +pub fn fio2str(o: C.FIOBJ) ?[]const u8 { + if (o == 0) return null; + const x: C.fio_str_info_s = C.fiobj_obj2cstr(o); + return std.mem.span(x.data); +} + const ListenError = error{ ValueNotZeroTerminated, + AlreadyListening, ListenError, }; +pub const SimpleRequest = struct { + path: ?[]const u8, + query: ?[]const u8, + h: [*c]C.http_s, + + const Self = @This(); + + pub fn sendBody(self: *const Self, body: []const u8) c_int { + return C.http_send_body(self.h, @intToPtr( + *anyopaque, + @ptrToInt(body.ptr), + ), body.len); + } +}; + +pub const HttpRequestFn = *const fn (r: [*c]C.http_s) callconv(.C) void; +pub const SimpleHttpRequestFn = *const fn (SimpleRequest) void; + +pub const SimpleHttpListenerSettings = struct { + port: usize, + interface: [*c]const u8 = null, + on_request: SimpleHttpRequestFn, + public_folder: ?[]u8 = null, + max_clients: ?u8 = null, + timeout: ?u8 = null, + log: bool = false, +}; + +pub const SimpleHttpListener = struct { + settings: SimpleHttpListenerSettings, + + const Self = @This(); + var the_one_and_only_listener: ?*SimpleHttpListener = null; + + pub fn init(settings: SimpleHttpListenerSettings) Self { + return .{ + .settings = settings, + }; + } + + // we could make it dynamic by passing a SimpleHttpListener via udata + pub fn theOneAndOnlyRequestCallBack(r: [*c]C.http_s) callconv(.C) void { + if (the_one_and_only_listener) |l| { + var req: SimpleRequest = .{ + .path = fio2str(r.*.path), + .query = fio2str(r.*.query), + .h = r, + }; + l.settings.on_request(req); + } + } + + pub fn listen(self: *Self) !void { + var pfolder: [*c]const u8 = null; + var pfolder_len: usize = 0; + + if (self.settings.public_folder) |pf| { + pfolder_len = pf.len; + // TODO: make sure it's 0-terminated!!! + if (pf[pf.len - 1] != 0) { + return error.ValueNotZeroTerminated; + } + pfolder = pf.ptr; + } + + var x: C.http_settings_s = .{ + .on_request = Self.theOneAndOnlyRequestCallBack, + .on_upgrade = null, + .on_response = null, + .on_finish = null, + .udata = null, + .public_folder = pfolder, + .public_folder_length = pfolder_len, + .max_header_size = 32 * 1024, + .max_body_size = 50 * 1024 * 1024, + .max_clients = self.settings.max_clients orelse 100, + .tls = null, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + .ws_max_msg_size = 0, + .timeout = self.settings.timeout orelse 5, + .ws_timeout = 0, + .log = if (self.settings.log) 1 else 0, + .is_client = 0, + }; + // TODO: BUG: without this print/sleep statement, -Drelease* loop forever + // in debug2 and debug3 of hello example + // std.debug.print("X\n", .{}); + std.time.sleep(500 * 1000 * 1000); + + var portbuf: [100]u8 = undefined; + const printed_port = try std.fmt.bufPrintZ(&portbuf, "{d}", .{self.settings.port}); + + // pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintError![:0]u8 { + // const result = try bufPrint(buf, fmt ++ "\x00", args); + // return result[0 .. result.len - 1 :0]; + // } + if (C.http_listen(printed_port.ptr, self.settings.interface, x) == -1) { + return error.ListenError; + } + + // set ourselves up to handle requests: + // TODO: do we mind the race condition? + // the SimpleHttpRequestFn will check if this is null and not process + // the request if it isn't set. hence, if started under full load, the + // first request(s) might not be serviced, as long as it takes from + // C.http_listen() to here + Self.the_one_and_only_listener = self; + } +}; + +// +// lower level listening +// +pub const ListenSettings = struct { + on_request: ?*const fn ([*c]C.http_s) callconv(.C) void = null, + on_upgrade: ?*const fn ([*c]C.http_s, [*c]u8, usize) callconv(.C) void = null, + on_response: ?*const fn ([*c]C.http_s) callconv(.C) void = null, + on_finish: ?*const fn ([*c]C.struct_http_settings_s) callconv(.C) void = null, + public_folder: ?[]const u8 = null, + max_header_size: usize = 32 * 1024, + max_body_size: usize = 50 * 1024 * 1024, + max_clients: isize = 100, + keepalive_timeout_s: u8 = 5, + log: bool = false, + + const Self = @This(); + + pub fn init() Self { + return .{}; + } +}; + pub fn listen(port: [*c]const u8, interface: [*c]const u8, settings: ListenSettings) ListenError!void { var pfolder: [*c]const u8 = null; var pfolder_len: usize = 0; @@ -56,8 +190,8 @@ pub fn listen(port: [*c]const u8, interface: [*c]const u8, settings: ListenSetti .log = if (settings.log) 1 else 0, .is_client = 0, }; - // TODO: BUG: without this print statement, -Drelease* loop forever - // in debug2 and debug3 + // TODO: BUG: without this print/sleep statement, -Drelease* loop forever + // in debug2 and debug3 of hello example // std.debug.print("X\n", .{}); std.time.sleep(500 * 1000 * 1000); @@ -66,21 +200,10 @@ pub fn listen(port: [*c]const u8, interface: [*c]const u8, settings: ListenSetti } } -pub const ListenSettings = struct { - on_request: ?*const fn ([*c]C.http_s) callconv(.C) void = null, - on_upgrade: ?*const fn ([*c]C.http_s, [*c]u8, usize) callconv(.C) void = null, - on_response: ?*const fn ([*c]C.http_s) callconv(.C) void = null, - on_finish: ?*const fn ([*c]C.struct_http_settings_s) callconv(.C) void = null, - public_folder: ?[]const u8 = null, - max_header_size: usize = 32 * 1024, - max_body_size: usize = 50 * 1024 * 1024, - max_clients: isize = 100, - keepalive_timeout_s: u8 = 5, - log: bool = false, - - const Self = @This(); - - pub fn init() Self { - return .{}; - } -}; +// lower level sendBody +pub fn sendBody(request: [*c]C.http_s, body: []const u8) void { + _ = C.http_send_body(request, @intToPtr( + *anyopaque, + @ptrToInt(body.ptr), + ), body.len); +}