// zig type definitions for facilio lib // or maybe let's just make it zap directly... const std = @import("std"); /// The facilio C API. No need to use this. pub const fio = @import("fio.zig"); /// Server-Side TLS function wrapper pub const Tls = @import("tls.zig"); pub const Endpoint = @import("endpoint.zig"); pub const Router = @import("router.zig"); pub const App = @import("App.zig"); /// A struct to handle Mustache templating. /// /// This is a wrapper around fiobj's mustache template handling. /// See http://facil.io/0.7.x/fiobj_mustache for more information. pub const Mustache = @import("mustache.zig"); /// Authenticators pub const Auth = @import("http_auth.zig"); /// Http request and supporting types. pub const Request = @import("request.zig"); /// Middleware support. /// Contains a special Listener and a Handler struct that support chaining /// requests handlers, with an optional stop once a handler indicates it /// processed the request. Also sports an EndpointHandler for using regular zap /// Endpoints as Handlers. pub const Middleware = @import("middleware.zig"); /// Websocket API pub const WebSockets = @import("websockets.zig"); pub const Logging = @import("Logging.zig"); pub const log = std.log.scoped(.zap); pub const http = @import("http.zig"); pub const util = @import("util.zig"); /// Start the IO reactor /// /// Will start listeners etc. pub fn start(args: fio.fio_start_args) void { fio.fio_start(args); } /// Stop ZAP: /// /// 1. Stop accepting further incoming requests /// 2. Wait for all running request handlers to return /// 3. return from `zap.start(...)` pub fn stop() void { fio.fio_stop(); } /// Extremely simplistic zap debug function, to save a few keystrokes pub fn debug(comptime fmt: []const u8, args: anytype) void { log.debug("[zap] - " ++ fmt, args); } /// Registers a new mimetype to be used for files ending with the given extension. pub fn mimetypeRegister(file_extension: []const u8, mime_type_str: []const u8) void { // NOTE: facil.io is expecting a non-const pointer to u8 values, but it does not // not appear to actually modify the value. Here we do a const cast so // that it is easy to pass static strings to http_mimetype_register without // needing to allocate a buffer on the heap. const extension = @constCast(file_extension); const mimetype = fio.fiobj_str_new(mime_type_str.ptr, mime_type_str.len); fio.http_mimetype_register(extension.ptr, extension.len, mimetype); } /// Clears the Mime-Type registry (it will be empty after this call). pub fn mimetypeClear() void { fio.http_mimetype_clear(); } pub const ListenError = error{ AlreadyListening, ListenError, }; pub const HttpError = error{ HttpSendBody, HttpSetContentType, HttpSetHeader, HttpParseBody, HttpIterParams, SetCookie, SendFile, }; /// Http Content Type enum. /// Needs some love. pub const ContentType = enum { TEXT, HTML, XML, JSON, XHTML, // TODO: more content types pub const string_map = std.StaticStringMap(ContentType).initComptime(.{ .{ "text/plain", .TEXT }, .{ "text/html", .HTML }, .{ "application/xml", .XML }, .{ "application/json", .JSON }, .{ "application/xhtml+xml", .XHTML }, }); }; /// Used internally: facilio Http request callback function type pub const FioHttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.c) void; /// Zap Http request callback function type. pub const HttpRequestFn = *const fn (Request) anyerror!void; /// websocket connection upgrade callback type /// fn(request, targetstring) pub const HttpUpgradeFn = *const fn (r: Request, target_protocol: []const u8) anyerror!void; /// http finish, called when zap finishes. You get your udata back in the /// HttpFinishSetting struct. pub const HttpFinishSettings = [*c]fio.struct_http_settings_s; /// Http finish callback type pub const HttpFinishFn = *const fn (HttpFinishSettings) anyerror!void; /// Listener settings pub const HttpListenerSettings = struct { port: usize, interface: [*c]const u8 = null, on_request: ?HttpRequestFn, on_response: ?HttpRequestFn = null, on_upgrade: ?HttpUpgradeFn = null, on_finish: ?HttpFinishFn = null, // provide any pointer in there for "user data". it will be passed pack in // on_finish()'s copy of the struct_http_settings_s udata: ?*anyopaque = null, public_folder: ?[]const u8 = null, max_clients: ?isize = null, max_body_size: ?usize = null, timeout: ?u8 = null, log: bool = false, ws_timeout: u8 = 40, ws_max_msg_size: usize = 262144, tls: ?Tls = null, }; /// Http listener pub const HttpListener = struct { settings: HttpListenerSettings, var the_one_and_only_listener: ?*HttpListener = null; /// Create a listener pub fn init(settings: HttpListenerSettings) HttpListener { std.debug.assert(settings.on_request != null); return .{ .settings = settings, }; } // we could make it dynamic by passing a HttpListener via udata /// Used internally: the listener's facilio request callback pub fn theOneAndOnlyRequestCallBack(r: [*c]fio.http_s) callconv(.c) void { if (the_one_and_only_listener) |l| { var req: Request = .{ .path = util.fio2str(r.*.path), .query = util.fio2str(r.*.query), .body = util.fio2str(r.*.body), .method = util.fio2str(r.*.method), .h = r, ._is_finished_request_global = false, ._user_context = undefined, }; req._is_finished = &req._is_finished_request_global; var user_context: Request.UserContext = .{}; req._user_context = &user_context; req.markAsFinished(false); std.debug.assert(l.settings.on_request != null); if (l.settings.on_request) |on_request| { on_request(req) catch |err| { Logging.on_uncaught_error("HttpListener on_request", err); }; } } } /// Used internally: the listener's facilio response callback pub fn theOneAndOnlyResponseCallBack(r: [*c]fio.http_s) callconv(.c) void { if (the_one_and_only_listener) |l| { var req: Request = .{ .path = util.fio2str(r.*.path), .query = util.fio2str(r.*.query), .body = util.fio2str(r.*.body), .method = util.fio2str(r.*.method), .h = r, ._is_finished_request_global = false, ._user_context = undefined, }; req._is_finished = &req._is_finished_request_global; var user_context: Request.UserContext = .{}; req._user_context = &user_context; l.settings.on_response.?(req) catch |err| { Logging.on_uncaught_error("HttpListener on_response", err); }; } } /// Used internally: the listener's facilio upgrade callback pub fn theOneAndOnlyUpgradeCallBack(r: [*c]fio.http_s, target: [*c]u8, target_len: usize) callconv(.c) void { if (the_one_and_only_listener) |l| { var req: Request = .{ .path = util.fio2str(r.*.path), .query = util.fio2str(r.*.query), .body = util.fio2str(r.*.body), .method = util.fio2str(r.*.method), .h = r, ._is_finished_request_global = false, ._user_context = undefined, }; const zigtarget: []u8 = target[0..target_len]; req._is_finished = &req._is_finished_request_global; var user_context: Request.UserContext = .{}; req._user_context = &user_context; l.settings.on_upgrade.?(req, zigtarget) catch |err| { Logging.on_uncaught_error("HttpListener on_upgrade", err); }; } } /// Used internally: the listener's facilio finish callback pub fn theOneAndOnlyFinishCallBack(s: [*c]fio.struct_http_settings_s) callconv(.c) void { if (the_one_and_only_listener) |l| { l.settings.on_finish.?(s) catch |err| { Logging.on_uncaught_error("HttpListener on_finish", err); }; } } /// Start listening pub fn listen(self: *HttpListener) !void { var pfolder: [*c]const u8 = null; var pfolder_len: usize = 0; if (self.settings.public_folder) |pf| { debug("HttpListener.listen(): public folder is {s}\n", .{pf}); pfolder_len = pf.len; pfolder = pf.ptr; } const x: fio.http_settings_s = .{ .on_request = if (self.settings.on_request) |_| HttpListener.theOneAndOnlyRequestCallBack else null, .on_upgrade = if (self.settings.on_upgrade) |_| HttpListener.theOneAndOnlyUpgradeCallBack else null, .on_response = if (self.settings.on_response) |_| HttpListener.theOneAndOnlyResponseCallBack else null, .on_finish = if (self.settings.on_finish) |_| HttpListener.theOneAndOnlyFinishCallBack else null, .udata = null, .public_folder = pfolder, .public_folder_length = pfolder_len, .max_header_size = 32 * 1024, .max_body_size = self.settings.max_body_size orelse 50 * 1024 * 1024, // fio provides good default: .max_clients = self.settings.max_clients orelse 0, .tls = if (self.settings.tls) |tls| tls.fio_tls else null, .reserved1 = 0, .reserved2 = 0, .reserved3 = 0, .ws_max_msg_size = 0, .timeout = self.settings.timeout orelse 5, .ws_timeout = self.settings.ws_timeout, .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", .{}); // TODO: still happening? std.Thread.sleep(500 * std.time.ns_per_ms); var portbuf: [100]u8 = undefined; const printed_port = try std.fmt.bufPrintZ(&portbuf, "{d}", .{self.settings.port}); const ret = fio.http_listen(printed_port.ptr, self.settings.interface, x); if (ret == -1) { return error.ListenError; } // set ourselves up to handle requests: // TODO: do we mind the race condition? // the HttpRequestFn 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 // fio.http_listen() to here HttpListener.the_one_and_only_listener = self; } }; /// Low-level API pub const LowLevel = struct { /// lower level listening, if you don't want to use a listener but rather use /// the listen() function. pub const ListenSettings = struct { on_request: ?FioHttpRequestFn = null, on_upgrade: ?FioHttpRequestFn = null, on_response: ?FioHttpRequestFn = null, on_finish: ?FioHttpRequestFn = 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, /// Create settings with defaults pub fn init() ListenSettings { return .{}; } }; /// Low level listen function 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; if (settings.public_folder) |pf| { pfolder_len = pf.len; pfolder = pf.ptr; } const x: fio.http_settings_s = .{ .on_request = settings.on_request, .on_upgrade = settings.on_upgrade, .on_response = settings.on_response, .on_finish = settings.on_finish, .udata = null, .public_folder = pfolder, .public_folder_length = pfolder_len, .max_header_size = settings.max_header_size, .max_body_size = settings.max_body_size, .max_clients = settings.max_clients, .tls = null, .reserved1 = 0, .reserved2 = 0, .reserved3 = 0, .ws_max_msg_size = settings.ws_max_msg_size, .timeout = settings.keepalive_timeout_s, .ws_timeout = 0, .log = if (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", .{}); // TODO: still happening? std.Thread.sleep(500 * std.time.ns_per_ms); if (fio.http_listen(port, interface, x) == -1) { return error.ListenError; } } /// lower level sendBody pub fn sendBody(request: [*c]fio.http_s, body: []const u8) HttpError!void { const ret = fio.http_send_body(request, @as( *anyopaque, @ptrFromInt(@intFromPtr(body.ptr)), ), body.len); if (ret != -1) return error.HttpSendBody; } };