mirror of
https://github.com/zigzap/zap.git
synced 2025-10-20 15:14:08 +00:00
410 lines
13 KiB
Zig
410 lines
13 KiB
Zig
// 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");
|
|
|
|
/// Endpoint and supporting types.
|
|
/// Create one and pass in your callbacks. Then,
|
|
/// pass it to a HttpListener's `register()` function to register with the
|
|
/// listener.
|
|
///
|
|
/// **NOTE**: A common endpoint pattern for zap is to create your own struct
|
|
/// that embeds an Endpoint, provides specific callbacks, and uses
|
|
/// `@fieldParentPtr` to get a reference to itself.
|
|
///
|
|
/// Example:
|
|
/// A simple endpoint listening on the /stop route that shuts down zap.
|
|
/// The main thread usually continues at the instructions after the call to zap.start().
|
|
///
|
|
/// ```zig
|
|
/// const StopEndpoint = struct {
|
|
/// ep: zap.Endpoint = undefined,
|
|
///
|
|
/// pub fn init(
|
|
/// path: []const u8,
|
|
/// ) StopEndpoint {
|
|
/// return .{
|
|
/// .ep = zap.Endpoint.init(.{
|
|
/// .path = path,
|
|
/// .get = get,
|
|
/// }),
|
|
/// };
|
|
/// }
|
|
///
|
|
/// // access the internal Endpoint
|
|
/// pub fn endpoint(self: *StopEndpoint) *zap.Endpoint {
|
|
/// return &self.ep;
|
|
/// }
|
|
///
|
|
/// fn get(e: *zap.Endpoint, r: zap.Request) void {
|
|
/// const self: *StopEndpoint = @fieldParentPtr(StopEndpoint, "ep", e);
|
|
/// _ = self;
|
|
/// _ = r;
|
|
/// zap.stop();
|
|
/// }
|
|
/// };
|
|
/// ```
|
|
pub const Endpoint = @import("endpoint.zig");
|
|
|
|
pub const Router = @import("router.zig");
|
|
|
|
pub usingnamespace @import("util.zig");
|
|
pub usingnamespace @import("http.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 Log = @import("log.zig");
|
|
const http = @import("http.zig");
|
|
|
|
const util = @import("util.zig");
|
|
|
|
// TODO: replace with comptime debug logger like in log.zig
|
|
var _debug: bool = false;
|
|
|
|
/// 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.
|
|
/// TODO: re-wwrite logging or use std.log
|
|
pub fn debug(comptime fmt: []const u8, args: anytype) void {
|
|
if (_debug) {
|
|
std.debug.print("[zap] - " ++ fmt, args);
|
|
}
|
|
}
|
|
|
|
/// Enable zap debug logging
|
|
pub fn enableDebugLog() void {
|
|
_debug = true;
|
|
}
|
|
|
|
/// start Zap with debug logging on
|
|
pub fn startWithLogging(args: fio.fio_start_args) void {
|
|
debug = true;
|
|
fio.fio_start(args);
|
|
}
|
|
|
|
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,
|
|
JSON,
|
|
// TODO: more content types
|
|
};
|
|
|
|
/// 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) void;
|
|
|
|
/// websocket connection upgrade callback type
|
|
/// fn(request, targetstring)
|
|
pub const HttpUpgradeFn = *const fn (r: Request, target_protocol: []const u8) 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) 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,
|
|
|
|
const Self = @This();
|
|
var the_one_and_only_listener: ?*HttpListener = null;
|
|
|
|
/// Create a listener
|
|
pub fn init(settings: HttpListenerSettings) Self {
|
|
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| {
|
|
// l.settings.on_request.?(req);
|
|
on_request(req);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
}
|
|
|
|
/// Start listening
|
|
pub fn listen(self: *Self) !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) |_| Self.theOneAndOnlyRequestCallBack else null,
|
|
.on_upgrade = if (self.settings.on_upgrade) |_| Self.theOneAndOnlyUpgradeCallBack else null,
|
|
.on_response = if (self.settings.on_response) |_| Self.theOneAndOnlyResponseCallBack else null,
|
|
.on_finish = if (self.settings.on_finish) |_| Self.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.time.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
|
|
Self.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,
|
|
|
|
const Self = @This();
|
|
|
|
/// Create settings with defaults
|
|
pub fn init() Self {
|
|
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.time.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);
|
|
debug("sendBody(): ret = {}\n", .{ret});
|
|
if (ret != -1) return error.HttpSendBody;
|
|
}
|
|
};
|