Skip to content

Commit

Permalink
http/router: asynchronous fs serving
Browse files Browse the repository at this point in the history
This utilizes the new functionality of `Context` to serve files
asynchronously instead of blocking for the entire file send.
  • Loading branch information
mookums committed Oct 22, 2024
1 parent fb57b4e commit 5ddf1ac
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 27 deletions.
28 changes: 24 additions & 4 deletions examples/http/fs/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ pub fn main() !void {
const host: []const u8 = "0.0.0.0";
const port: u16 = 9862;

const allocator = std.heap.page_allocator;
var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true }){ .backing_allocator = std.heap.c_allocator };
const allocator = gpa.allocator();
defer _ = gpa.deinit();

var router = http.Router.init(allocator);
defer router.deinit();

try router.serve_route("/", http.Route.init().get(struct {
pub fn handler_fn(_: http.Request, response: *http.Response, _: http.Context) void {
pub fn handler_fn(ctx: *http.Context) void {
const body =
\\ <!DOCTYPE html>
\\ <html>
Expand All @@ -23,17 +25,35 @@ pub fn main() !void {
\\ </html>
;

response.set(.{
ctx.respond(.{
.status = .OK,
.mime = http.Mime.HTML,
.body = body[0..],
});
}
}.handler_fn));

try router.serve_route("/kill", http.Route.init().get(struct {
pub fn handler_fn(ctx: *http.Context) void {
ctx.runtime.stop();

ctx.respond(.{
.status = .OK,
.mime = http.Mime.HTML,
.body = "",
});
}
}.handler_fn));

try router.serve_fs_dir("/static", "./examples/http/fs/static");

var server = http.Server(.plain, .auto).init(.{ .allocator = allocator });
var server = http.Server(.plain, .auto).init(.{
.allocator = allocator,
.threading = .auto,
.size_connections_max = 256,
});
defer server.deinit();

try server.bind(host, port);
try server.listen(.{ .router = &router });
}
11 changes: 9 additions & 2 deletions src/core/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub const RecvStatus = union(enum) {
spawned,
};

/// Security Model to use.chinp acas
/// Security Model to use.
///
/// Default: .plain (plaintext)
pub const Security = union(enum) {
Expand Down Expand Up @@ -239,7 +239,14 @@ pub fn Server(
provision.job = .empty;
_ = provision.arena.reset(.{ .retain_with_limit = z_config.size_connection_arena_retain });
provision.data.clean();
provision.recv_buffer.clearRetainingCapacity();

// TODO: new z_config setting here!
if (provision.recv_buffer.items.len > 1024) {
provision.recv_buffer.shrinkRetainingCapacity(1024);
} else {
provision.recv_buffer.clearRetainingCapacity();
}

pool.release(provision.index);

const accept_queued = rt.storage.get_ptr("accept_queued", bool);
Expand Down
152 changes: 131 additions & 21 deletions src/http/router.zig
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const Context = @import("context.zig").Context;
const RoutingTrie = @import("routing_trie.zig").RoutingTrie;
const QueryMap = @import("routing_trie.zig").QueryMap;

const Runtime = @import("tardy").Runtime;
const Task = @import("tardy").Task;
const Cross = @import("tardy").Cross;

pub const Router = struct {
allocator: std.mem.Allocator,
routes: RoutingTrie,
Expand All @@ -29,56 +33,162 @@ pub const Router = struct {
self.routes.deinit();
}

const FileProvision = struct {
mime: Mime,
context: *Context,
fd: std.posix.fd_t,
offset: usize,
list: std.ArrayList(u8),
buffer: []u8,
};

fn open_file_task(rt: *Runtime, t: *const Task, ctx: ?*anyopaque) !void {
const provision: *FileProvision = @ptrCast(@alignCast(ctx.?));
errdefer {
provision.context.respond(.{
.status = .@"Internal Server Error",
.mime = Mime.HTML,
.body = "",
});
}

const fd = t.result.?.fd;
if (!Cross.fd.is_valid(fd)) {
provision.context.respond(.{
.status = .@"Not Found",
.mime = Mime.HTML,
.body = "File Not Found",
});
return;
}
provision.fd = fd;

try rt.fs.read(.{
.fd = fd,
.buffer = provision.buffer,
.offset = 0,
.func = read_file_task,
.ctx = provision,
});
}

fn read_file_task(rt: *Runtime, t: *const Task, ctx: ?*anyopaque) !void {
const provision: *FileProvision = @ptrCast(@alignCast(ctx.?));
errdefer {
provision.context.respond(.{
.status = .@"Internal Server Error",
.mime = Mime.HTML,
.body = "",
});
}

const result: i32 = t.result.?.value;
if (result <= 0) {
// If we are done reading...
try rt.fs.close(.{
.fd = provision.fd,
.func = close_file_task,
.ctx = provision,
});
return;
}

const length: usize = @intCast(result);

try provision.list.appendSlice(provision.buffer[0..length]);

// TODO: This needs to be a setting you pass in to the router.
//
//if (provision.list.items.len > 1024 * 1024 * 4) {
// provision.context.respond(.{
// .status = .@"Content Too Large",
// .mime = Mime.HTML,
// .body = "File Too Large",
// });
// return;
//}

provision.offset += length;

try rt.fs.read(.{
.fd = provision.fd,
.buffer = provision.buffer,
.offset = provision.offset,
.func = read_file_task,
.ctx = provision,
});
}

fn close_file_task(_: *Runtime, _: *const Task, ctx: ?*anyopaque) !void {
const provision: *FileProvision = @ptrCast(@alignCast(ctx.?));

provision.context.respond(.{
.status = .OK,
.mime = provision.mime,
.body = provision.list.items[0..],
});
}

pub fn serve_fs_dir(self: *Router, comptime url_path: []const u8, comptime dir_path: []const u8) !void {
assert(!self.locked);

const route = Route.init().get(struct {
pub fn handler_fn(request: Request, response: *Response, context: Context) void {
_ = request;
pub fn handler_fn(ctx: *Context) void {
const search_path = ctx.captures[0].remaining;

const search_path = context.captures[0].remaining;
const file_path = std.fmt.allocPrint(context.allocator, "{s}/{s}", .{ dir_path, search_path }) catch {
response.set(.{
const file_path = std.fmt.allocPrintZ(ctx.allocator, "{s}/{s}", .{ dir_path, search_path }) catch {
ctx.respond(.{
.status = .@"Internal Server Error",
.mime = Mime.HTML,
.body = "",
});
return;
};

// TODO: Ensure that paths cannot go out of scope and reference data that they shouldn't be allowed to.
// Very important.

const extension_start = std.mem.lastIndexOfScalar(u8, search_path, '.');
const mime: Mime = blk: {
if (extension_start) |start| {
break :blk Mime.from_extension(search_path[start..]);
} else {
break :blk Mime.HTML;
break :blk Mime.BIN;
}
};

const file: std.fs.File = std.fs.cwd().openFile(file_path, .{}) catch {
response.set(.{
.status = .@"Not Found",
const provision = ctx.allocator.create(FileProvision) catch {
ctx.respond(.{
.status = .@"Internal Server Error",
.mime = Mime.HTML,
.body = "File Not Found",
.body = "",
});
return;
};
defer file.close();

const file_bytes = file.readToEndAlloc(context.allocator, 1024 * 1024 * 4) catch {
response.set(.{
.status = .@"Content Too Large",
provision.* = .{
.mime = mime,
.context = ctx,
.fd = -1,
.offset = 0,
.list = std.ArrayList(u8).init(ctx.allocator),
.buffer = ctx.provision.buffer,
};

// We also need to support chunked encoding.
// It makes a lot more sense for files atleast.
ctx.runtime.fs.open(.{
.path = file_path,
.func = open_file_task,
.ctx = provision,
}) catch {
ctx.respond(.{
.status = .@"Internal Server Error",
.mime = Mime.HTML,
.body = "File Too Large",
.body = "",
});
return;
};

response.set(.{
.status = .OK,
.mime = mime,
.body = file_bytes,
});
}
}.handler_fn);

Expand Down

0 comments on commit 5ddf1ac

Please sign in to comment.