const std = @import("std"); const Io = std.Io; const Allocator = std.mem.Allocator; const http = std.http; const Request = http.Server.Request; const net = Io.net; const Server = net.Server; const fern = @import("fern"); pub const Config = @import("config.zig"); pub const data = @import("data.zig"); pub const gen = @import("gen.zig"); pub const handler = @import("handler.zig"); const Connection = handler.Connection; pub const helpers = @import("helpers.zig"); pub const lua = @import("lua/root.zig"); pub const Router = @import("router.zig"); pub var build: bool = false; pub var dev: bool = false; pub var rev: ?[]const u8 = null; // null indicates a dev build pub var config: *const Config = undefined; var socket_io: Io = undefined; var addr: net.IpAddress = undefined; pub var nserver: ?Server = null; var closed = false; pub var router: Router = undefined; // allocator ================================================================== pub var allocator: Allocator = undefined; pub fn init(io: Io, init_allocator: Allocator, cfg: *const Config) !void { allocator = init_allocator; config = cfg; fern.debug().ctx("maivi.init") .str("root", config.root) .str("base_url", config.base_url) .boolean("dev", dev) .log(); router = .init(); data.init(); errdefer data.deinit(); try lua.init(); errdefer lua.deinit(); try data.load(io); } pub fn deinit(io: Io) void { fern.trace().ctx("maivi.deinit").log(); router.deinit(); data.deinit(); lua.deinit(); if (nserver == null) return; nserver.?.deinit(io); } // io handle will be used for all net handling pub fn server(io: Io) !void { socket_io = io; addr = try .resolve(socket_io, "127.0.0.1", config.port); nserver = try addr.listen(socket_io, .{ .reuse_address = true }); } fn shutdown(signo: std.os.linux.SIG) callconv(.c) void { fern.info().ctx("main") .fmt("sig", "{}", .{signo}).log(); if (nserver == null) return; closed = true; socket_io.vtable.netShutdown(socket_io.userdata, nserver.?.socket.handle, .recv) catch |e| fern.err().err(e).log(); } pub fn listen(io: Io) !void { if (nserver == null) return error.ServerNotInitialized; fern.info().ctx("maivi.listen") .msg("listening") .int("port", config.port) .int("pid", std.c.getpid()) .log(); std.posix.sigaction(std.posix.SIG.INT, &.{ .handler = .{ .handler = shutdown }, .mask = std.posix.sigemptyset(), .flags = 0, }, null); std.posix.sigaction(std.posix.SIG.TERM, &.{ .handler = .{ .handler = shutdown }, .mask = std.posix.sigemptyset(), .flags = 0, }, null); var queue: Io.Queue(net.Stream) = .init(&.{}); var task = Io.async(io, consume, .{ io, &queue }); defer task.cancel(io) catch |e| { fern.err().err(e).log(); }; while (!closed) { const stream = nserver.?.accept(io) catch |e| { fern.err().err(e).log(); continue; }; queue.putOne(io, stream) catch |e| { fern.err().err(e).log(); continue; }; } } pub fn consume(io: Io, queue: *Io.Queue(net.Stream)) !void { while (!closed) { const stream = try queue.getOne(io); handler.task(io, stream) catch |e| { fern.err().err(e).log(); continue; }; } } // handlers =================================================================== // try: // with(router) $uri // else: // with(build) $uri $uri/index.html // with(static) $uri pub fn handle(conn: *Connection) !void { const request = conn.request; try lua.trigger(conn.lua, Connection, conn, .handle, .{ .path = conn.path, .pattern = conn.path, }); if (conn.fallen) return; const path = router.paths.get(conn.path) orelse return handle_fs(conn); if (std.mem.findScalar( std.http.Method, path.config.allowed_methods, request.head.method, ) == null) return request.respond("", .{ .status = .method_not_allowed, }); try path.action(conn); // TODO: pre-register all static & build paths // if (dev) // try handle_fs(request); } fn handle_fs(conn: *Connection) !void { const build_dir = try Io.Dir.openDirAbsolute(conn.io, config.buildroot, .{}); defer build_dir.close(conn.io); if (std.mem.eql(u8, conn.path, "/")) { if (try helpers.try_path(conn, build_dir, "/index.html")) return; return error.FileNotFound; } if (try helpers.try_path(conn, build_dir, null)) return; const path = try std.mem.join(allocator, "/", &.{ conn.path, "index.html" }); defer allocator.free(path); if (try helpers.try_path(conn, build_dir, path)) return; const static_dir = try Io.Dir.openDirAbsolute(conn.io, config.staticroot, .{}); defer static_dir.close(conn.io); if (try helpers.try_path(conn, static_dir, null)) return; return error.FileNotFound; }