summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2021-06-10 09:18:12 +0200
committerYorhel <git@yorhel.nl>2021-06-10 09:18:12 +0200
commit269af7e83d52cecbe5c345e679538537be31151a (patch)
tree30c10c9453de3ec51c6804c26abbf0efd835bcb4
parent746547338c33515c1d05518fefd85a59147de0e2 (diff)
Committing some old changes. This project is pretty broken right nowzig
-rw-r--r--Config.zig38
-rw-r--r--main.zig112
2 files changed, 108 insertions, 42 deletions
diff --git a/Config.zig b/Config.zig
index b8c990f..f52709d 100644
--- a/Config.zig
+++ b/Config.zig
@@ -18,15 +18,16 @@ pub const Listener = struct {
pub const Backend = struct {
spawn: ?[]const u8 = null,
+ cwd: ?std.fs.Dir = null,
max_processes: u32 = 0,
};
-// Should be marked as noreturn, but https://github.com/ziglang/zig/issues/5728
-pub fn err(comptime fmt: []const u8, arg: anytype) void {
- _ = std.io.getStdErr().writer().print(fmt, arg) catch {};
+pub fn err(comptime fmt: []const u8, arg: anytype) noreturn {
+ _ = nosuspend std.io.getStdErr().writer().print(fmt, arg) catch {};
std.process.exit(1);
}
+// TODO: This really should not have async calling convention.
pub fn read() !Self {
const eq = std.mem.eql;
var args = std.process.args();
@@ -53,10 +54,8 @@ pub fn read() !Self {
// Return the currently-being-configured listener, or exit with an error if we're not configuring a listener.
fn listener(self: *Self) *Listener {
- if (self.listeners.items.len == 0 or self.backend.spawn != null) {
+ if (self.listeners.items.len == 0 or self.backend.spawn != null)
err("Option '{s}' can only be used when configuring a listener.\n", .{self._current_opt});
- unreachable;
- }
return &self.listeners.items[self.listeners.items.len - 1];
}
@@ -65,7 +64,6 @@ fn optArg(self: *Self) [:0]const u8 {
return arg;
}
err("Option '{s}' requires an argument.\n", .{self._current_opt});
- unreachable;
}
fn parseTypedAddr(arg: []const u8, ltype: *[]const u8) !?Address {
@@ -99,30 +97,30 @@ fn readListen(self: *Self) Listener {
const arg = self.optArg();
var ltype: []const u8 = undefined;
if (parseTypedAddr(arg, &ltype)) |a| {
- if (!std.mem.eql(u8, ltype, "fastcgi")) {
+ if (!std.mem.eql(u8, ltype, "fastcgi"))
err("Unknown listener type '{s}', only fastcgi is supported at the moment.\n", .{ltype});
- }
return Listener{ .addr = a };
- } else |e| {
- err("Error parsing address '{s}': {}\n", .{ arg, e });
- unreachable;
- }
+ } else |e| err("Error parsing address '{s}': {}\n", .{ arg, e });
}
fn readInt(self: *Self, comptime T: type) T {
- return std.fmt.parseInt(T, self.optArg(), 10) catch |_| {
- err("Option '{s}' expects an integer argument.\n", .{self._current_opt});
- unreachable;
- };
+ return std.fmt.parseInt(T, self.optArg(), 10)
+ catch |_| err("Option '{s}' expects an integer argument.\n", .{self._current_opt});
}
fn readSpawn(self: *Self) void {
if (self.backend.spawn != null) err("Only a single 'spawn' option is supported.\n", .{});
const arg = self.optArg();
- if (!std.mem.startsWith(u8, arg, "fastcgi:")) err("The 'spawn' option only supports 'fastcgi:' paths at the moment.\n", .{});
- const path = arg[8..];
+ if (!std.mem.startsWith(u8, arg, "cgi:")) err("The 'spawn' option only supports 'cgi:' paths at the moment.\n", .{});
+ const path = arg[4..];
if (path.len == 0) err("No spawn path given.\n", .{});
- self.backend.spawn = path;
+ var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+ const rpath = std.os.realpath(path, buf[0..]) catch |e|
+ err("Unable to obtain full path of '{s}': {}.\n", .{path, e});
+ self.backend.spawn = alloc.dupe(u8, rpath) catch unreachable;
+ // TODO: dirname can return null; may instead want to check if rpath points to an (executable) file to catch config errors early.
+ self.backend.cwd = std.fs.openDirAbsolute(std.fs.path.dirname(rpath).?, .{})
+ catch |e| err("Unable to open parent directory of '{s}': {}.\n", .{rpath, e});
}
test "parse typed address" {
diff --git a/main.zig b/main.zig
index de1cfc5..b3abe72 100644
--- a/main.zig
+++ b/main.zig
@@ -8,32 +8,50 @@ const fastcgi = @import("fastcgi.zig");
const Config = @import("Config.zig");
const Address = std.net.Address;
-pub const io_mode = .evented;
+// Initialize a custom event loop, because the default one spawns far too many threads.
+var _event_loop: std.event.Loop = undefined;
+pub const event_loop: *std.event.Loop = &_event_loop;
+//pub const io_mode = .evented;
+//const event_loop = std.event.Loop.instance.?;
var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
pub const alloc = &general_purpose_allocator.allocator;
-const mainloop = std.event.Loop.instance.?;
+
+var backend: Backend = undefined;
pub fn main() !void {
+ event_loop.initSingleThreaded() catch unreachable;
+ event_loop.beginOneEvent(); // The event loop quits early for some reason without this, no clue why. I mean, there's always at least one event queued.
+ var m = async amain();
+ event_loop.run();
+ nosuspend try await m;
+}
+
+fn amain() !void {
const conf = try Config.read();
std.debug.print("Listener = {}, Front = {}\n", .{@sizeOf(Listener), @sizeOf(Front)});
if (conf.version) {
- std.io.getStdOut().writeAll("fcgy 0.1\n") catch unreachable;
+ std.io.getStdOut().writeAll("fcgy 0.1\n") catch {};
std.process.exit(0);
}
if (conf.help) {
- std.io.getStdOut().writeAll("fcgy <options> <listen-options> <spawn-options>\n") catch unreachable;
+ std.io.getStdOut().writeAll("fcgy <options> <listen-options> <spawn-options>\n") catch {};
std.process.exit(0);
}
- if (conf.listeners.items.len == 0) {
+ if (conf.listeners.items.len == 0)
Config.err("No listeners defined\n", .{});
- }
- if (conf.listeners.items.len == 1 and conf.listeners.items[0].addr == null) {
+ if (conf.listeners.items.len == 1 and conf.listeners.items[0].addr == null)
Config.err("Listening on standard I/O is not supported yet.\n", .{});
- }
+ if (conf.backend.spawn == null)
+ Config.err("No backend configured.\n", .{});
+
+ std.debug.print("Backend CGI exec '{s}'.\n", .{ conf.backend.spawn.? });
+ backend = .{
+ .conf = &conf.backend,
+ };
for (conf.listeners.items) |l| {
const listener = alloc.create(Listener) catch unreachable;
@@ -44,13 +62,11 @@ pub fn main() !void {
if (l.addr) |a| {
try listener.server.listen(l.addr.?);
std.debug.print("Listening on {}\n", .{l.addr});
- } else {
+ } else
Config.err("Cannot mix standard I/O and server-type listeners.\n", .{});
- }
listener.frame_next_tick = .{ .data = &listener.frame };
listener.frame = async listener.listen();
}
- mainloop.run();
}
@@ -79,7 +95,7 @@ const Listener = struct {
const front = alloc.create(Front) catch unreachable;
front.* = Front.init(self, con.address, con.stream);
self.connections.append(&front.listener_node);
- mainloop.runDetached(alloc, Front.run, .{front}) catch unreachable;
+ event_loop.runDetached(alloc, Front.run, .{front}) catch unreachable;
}
}
@@ -90,7 +106,7 @@ const Listener = struct {
if (self.listen_suspended) {
self.listen_suspended = false;
- mainloop.onNextTick(&self.frame_next_tick);
+ event_loop.onNextTick(&self.frame_next_tick);
}
}
};
@@ -103,7 +119,6 @@ const Front = struct {
requests: std.AutoHashMap(u16, *Request) = std.AutoHashMap(u16, *Request).init(alloc),
has_shutdown: bool = false,
rd: std.io.BufferedReader(4096, std.net.Stream.Reader),
- wr: std.io.BufferedWriter(4096, std.net.Stream.Writer),
// TODO: Put the frame of run() inside this struct? Problem: run() can't destroy() the Front struct in that case.
const Self = @This();
@@ -114,22 +129,21 @@ const Front = struct {
.stream = stream,
.listener = listener,
.rd = std.io.bufferedReader(stream.reader()),
- .wr = std.io.bufferedWriter(stream.writer()),
};
}
fn run(self: *Self) void {
std.debug.print("{*} Connection from {}\n", .{ self, self.addr });
- self.loop() catch |e| std.debug.print("{*} Connection closed: {}\n", .{ self, e });
+ self.loop() catch |e| if(!self.has_shutdown) std.debug.print("{*} Connection closed: {}\n", .{ self, e });
if (self.has_shutdown) std.debug.print("{*} Connection has been shutdown\n", .{self});
- self.shutdown();
+ self.stream.close();
self.listener.remove_connection(self);
}
/// Close the socket and stop the loop()
fn shutdown(self: *Self) void {
- if (!self.has_shutdown) self.stream.close();
+ if (!self.has_shutdown) std.os.shutdown(self.stream.handle, .both) catch {};
self.has_shutdown = true;
}
@@ -165,12 +179,63 @@ const Front = struct {
// We don't assign a back-end until we have all params, so this shouldn't happen.
if (req.back != null) return error.invalidState;
if (r.content_length > 0) return r.readArrayList(self.rd.reader(), &req.params);
- // TODO: We received all params now, assign a backend and process the request
try r.skip(self.rd.reader());
+ try backend.init_request(req);
}
};
const Backend = struct {
+ conf: *const Config.Backend,
+ processes: std.TailQueue(void) = .{},
+
+ const Self = @This();
+ const Process = struct {
+ node: std.TailQueue(void).Node = undefined,
+ connections: std.TailQueue(void) = .{},
+ proc: *std.ChildProcess,
+
+ const Self = @This();
+
+ fn deinit(self: *Self) void {
+ }
+ };
+
+ const Connection = struct {
+ node: std.TailQueue(void).Node = undefined,
+ process: *Process,
+ rd: std.io.BufferedReader(4096, std.fs.File.Reader),
+ // TODO: fd's (if not CGI)
+ };
+
+ // Find/spawn a backend connection for the request and pass along the parameters.
+ fn init_request(self: *Self, req: *Request) !void {
+ std.debug.assert(req.back == null);
+ var cmd = try std.ChildProcess.init(&[_]([]const u8){self.conf.spawn.?}, alloc);
+ errdefer cmd.deinit();
+ cmd.stdin_behavior = .Pipe;
+ cmd.stdout_behavior = .Pipe;
+ cmd.stderr_behavior = .Ignore; // TODO: Capture stderr
+
+ try cmd.spawn(); // https://github.com/ziglang/zig/issues/6682
+ errdefer { _ = cmd.kill() catch {}; } // XXX: May block
+
+ var proc = try alloc.create(Process);
+ errdefer alloc.destroy(proc);
+ proc.* = .{ .proc = cmd };
+
+ var con = try alloc.create(Connection);
+ errdefer alloc.destroy(con);
+ con.* = .{
+ .process = proc,
+ .rd = std.io.bufferedReader(cmd.stdout.?.reader()),
+ };
+
+ // TODO: Spawn async reader.
+
+ req.back = con;
+ proc.connections.append(&con.node);
+ self.processes.append(&proc.node);
+ }
};
const Request = struct {
@@ -182,17 +247,20 @@ const Request = struct {
/// Whether we should keep the front-end connection alive after this request has been processed.
front_keep_conn: bool,
- // TODO: backend pointer
- back: ?*void = null,
+ back: ?*Backend.Connection = null,
/// List of parameters in FastCGI format.
params: std.ArrayList(u8) = std.ArrayList(u8).init(alloc),
const Self = @This();
+ fn cgi_env_map(req: *Request) !std.BufMap {
+ ...
+ }
+
/// Free resources associated with this request.
/// Does nothing when 'front' or 'back' are still set, as that means this request is still referenced from either side.
- pub fn deinit(self: *Self) void {
+ fn deinit(self: *Self) void {
if (self.front != null or self.back != null) {
return;
}