Skip to content

Commit

Permalink
feat(router): Major Rewrite + Middleware
Browse files Browse the repository at this point in the history
This commit changes a lot regarding Routing. We have returned to a
runtime Router as it makes things much easier in terms of maintaining
it. This also adds layers allowing for Middleware to be applied
subjectively.
  • Loading branch information
mookums committed Dec 23, 2024
1 parent eadd35c commit 35df0ff
Show file tree
Hide file tree
Showing 32 changed files with 2,304 additions and 2,176 deletions.
1 change: 1 addition & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub fn build(b: *std.Build) void {
add_example(b, "tls", true, target, optimize, zzz);
add_example(b, "minram", false, target, optimize, zzz);
add_example(b, "fs", false, target, optimize, zzz);
add_example(b, "middleware", false, target, optimize, zzz);
add_example(b, "multithread", false, target, optimize, zzz);
add_example(b, "benchmark", false, target, optimize, zzz);
add_example(b, "valgrind", true, target, optimize, zzz);
Expand Down
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
.minimum_zig_version = "0.13.0",
.dependencies = .{
.tardy = .{
.url = "git+https://github.com/mookums/tardy?ref=v0.2.1#e133b423e455a637a068296d0a3d00a4c71b1143",
.hash = "1220c3c3e6dd2fc6e61645dff2d22d46cb3eca3fb5c4c14a0015dab881d0a5af3976",
.url = "git+https://github.com/mookums/tardy#c2851c1e8ec5c66a16a0fb318d6bacea4ae9cc0b",
.hash = "122054b7c88eca71ab699e7ff530cd56303459356ea9ff3c9c78794943b035cadfea",
},
.bearssl = .{
.url = "git+https://github.com/mookums/bearssl-zig#37a96eee56fe2543579bbc6da148ca886f3dd32b",
Expand Down
105 changes: 44 additions & 61 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
# Getting Started
zzz is a networking framework that allows for modularity and flexibility in design. For most use cases, this flexibility is not a requirement and so various defaults are provided.

For this guide, we will assume that you are running on a modern Linux platform and looking to design a service that utilizes HTTP. We will need both `zzz` and `tardy` for this to work.
You will need to match the version of Tardy that zzz is currently using to the version of Tardy you currently use within your program. This will eventually be standardized.

These are the current latest releases and are compatible.
For this guide, we will assume that you are running on a modern Linux platform and looking to design a service that utilizes HTTP.
This is the current latest release.

`zig fetch --save git+https://github.com/mookums/zzz#v0.2.0`

`zig fetch --save git+https://github.com/mookums/tardy#v0.1.0`

## Hello, World!
We can write a quick example that serves out "Hello, World" responses to any client that connects to the server. This example is the same as the one that is provided within the `examples/basic` directory.

Expand All @@ -24,10 +20,43 @@ const tardy = zzz.tardy;
const Tardy = tardy.Tardy(.auto);
const Runtime = tardy.Runtime;
const Server = http.Server(.plain, *const i8);
const Router = Server.Router;
const Context = Server.Context;
const Route = Server.Route;
const Server = http.Server;
const Router = http.Router;
const Context = http.Context;
const Route = http.Route;
fn root_handler(ctx: *Context, id: i8) !void {
const body_fmt =
\\ <!DOCTYPE html>
\\ <html>
\\ <body>
\\ <h1>Hello, World!</h1>
\\ <p>id: {d}</p>
\\ </body>
\\ </html>
;
const body = try std.fmt.allocPrint(ctx.allocator, body_fmt, .{id});
// This is the standard response and what you
// will usually be using. This will send to the
// client and then continue to await more requests.
return try ctx.respond(.{
.status = .OK,
.mime = http.Mime.HTML,
.body = body[0..],
});
}
fn echo_handler(ctx: *Context, _: void) !void {
const body = if (ctx.request.body) |b|
try ctx.allocator.dupe(u8, b)
else
"";
return try ctx.respond(.{
.status = .OK,
.mime = http.Mime.HTML,
.body = body[0..],
});
}
pub fn main() !void {
const host: []const u8 = "0.0.0.0";
Expand All @@ -47,57 +76,11 @@ pub fn main() !void {
const num: i8 = 12;
var router = Router.init(&num, &[_]Route{
Route.init("/").get(struct {
fn handler_fn(ctx: *Context) !void {
const body_fmt =
\\ <!DOCTYPE html>
\\ <html>
\\ <body>
\\ <h1>Hello, World!</h1>
\\ <p>id: {d}</p>
\\ </body>
\\ </html>
;
const body = try std.fmt.allocPrint(ctx.allocator, body_fmt, .{ctx.state.*});
// This is the standard response and what you
// will usually be using. This will send to the
// client and then continue to await more requests.
try ctx.respond(.{
.status = .OK,
.mime = http.Mime.HTML,
.body = body[0..],
});
}
}.handler_fn),
Route.init("/echo").post(struct {
fn handler_fn(ctx: *Context) !void {
const body = if (ctx.request.body) |b|
try ctx.allocator.dupe(u8, b)
else
"";
try ctx.respond(.{
.status = .OK,
.mime = http.Mime.HTML,
.body = body[0..],
});
}
}.handler_fn),
}, .{
.not_found_handler = struct {
fn handler_fn(ctx: *Context) !void {
try ctx.respond(.{
.status = .@"Not Found",
.mime = http.Mime.HTML,
.body = "Not Found Handler!",
});
}
}.handler_fn,
});
var router = try Router.init(allocator, &.{
Route.init("/").get(num, root_handler).layer(),
Route.init("/echo").post({}, echo_handler).layer(),
}, .{});
defer router.deinit(allocator);
// This provides the entry function into the Tardy runtime. This will run
// exactly once inside of each runtime (each thread gets a single runtime).
Expand Down
89 changes: 47 additions & 42 deletions docs/https.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,33 @@ const log = std.log.scoped(.@"examples/tls");
const zzz = @import("zzz");
const http = zzz.HTTP;
const tardy = @import("tardy");
const tardy = zzz.tardy;
const Tardy = tardy.Tardy(.auto);
const Runtime = tardy.Runtime;
const Server = http.Server(.{ .tls = .{
.cert = .{ .file = .{ .path = "./examples/tls/certs/cert.pem" } },
.key = .{ .file = .{ .path = "./examples/tls/certs/key.pem" } },
.cert_name = "CERTIFICATE",
.key_name = "EC PRIVATE KEY",
} }, void);
const Server = http.Server;
const Context = http.Context;
const Route = http.Route;
const Router = http.Router;
const Context = Server.Context;
const Route = Server.Route;
const Router = Server.Router;
fn root_handler(ctx: *Context, _: void) !void {
const body =
\\ <!DOCTYPE html>
\\ <html>
\\ <head>
\\ <link rel="stylesheet" href="/embed/pico.min.css"/>
\\ </head>
\\ <body>
\\ <h1>Hello, World!</h1>
\\ </body>
\\ </html>
;
return try ctx.respond(.{
.status = .OK,
.mime = http.Mime.HTML,
.body = body[0..],
});
}
pub fn main() !void {
const host: []const u8 = "0.0.0.0";
Expand All @@ -37,49 +50,41 @@ pub fn main() !void {
const allocator = gpa.allocator();
defer _ = gpa.deinit();
var router = Router.init({}, &[_]Route{
Route.init("/embed/pico.min.css").serve_embedded_file(http.Mime.CSS, @embedFile("embed/pico.min.css")),
var t = try Tardy.init(.{
.allocator = allocator,
.threading = .single,
});
defer t.deinit();
Route.init("/").get(struct {
pub fn handler_fn(ctx: *Context) !void {
const body =
\\ <!DOCTYPE html>
\\ <html>
\\ <head>
\\ <link rel="stylesheet" href="/embed/pico.min.css"/>
\\ </head>
\\ <body>
\\ <h1>Hello, World!</h1>
\\ </body>
\\ </html>
;
var router = try Router.init(allocator, &.{
Route.init("/embed/pico.min.css").serve_embedded_file(
http.Mime.CSS,
@embedFile("embed/pico.min.css"),
).layer(),
try ctx.respond(.{
.status = .OK,
.mime = http.Mime.HTML,
.body = body[0..],
});
}
}.handler_fn),
Route.init("/").get({}, root_handler).layer(),
Route.init("/kill").get(struct {
pub fn handler_fn(ctx: *Context) !void {
Route.init("/kill").get({}, struct {
pub fn handler_fn(ctx: *Context, _: void) !void {
ctx.runtime.stop();
}
}.handler_fn),
}.handler_fn).layer(),
}, .{});
var t = try Tardy.init(.{
.allocator = allocator,
.threading = .single,
});
defer t.deinit();
defer router.deinit(allocator);
router.print_route_tree();
try t.entry(
&router,
struct {
fn entry(rt: *Runtime, r: *const Router) !void {
var server = Server.init(rt.allocator, .{});
var server = Server.init(rt.allocator, .{
.security = .{ .tls = .{
.cert = .{ .file = .{ .path = "./examples/tls/certs/cert.pem" } },
.key = .{ .file = .{ .path = "./examples/tls/certs/key.pem" } },
.cert_name = "CERTIFICATE",
.key_name = "EC PRIVATE KEY",
} },
});
try server.bind(.{ .ip = .{ .host = host, .port = port } });
try server.serve(r, rt);
}
Expand Down
95 changes: 43 additions & 52 deletions examples/basic/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,43 @@ const tardy = zzz.tardy;
const Tardy = tardy.Tardy(.auto);
const Runtime = tardy.Runtime;

const Server = http.Server(.plain, *const i8);
const Router = Server.Router;
const Context = Server.Context;
const Route = Server.Route;
const Server = http.Server;
const Router = http.Router;
const Context = http.Context;
const Route = http.Route;

fn root_handler(ctx: *Context, id: i8) !void {
const body_fmt =
\\ <!DOCTYPE html>
\\ <html>
\\ <body>
\\ <h1>Hello, World!</h1>
\\ <p>id: {d}</p>
\\ </body>
\\ </html>
;
const body = try std.fmt.allocPrint(ctx.allocator, body_fmt, .{id});
// This is the standard response and what you
// will usually be using. This will send to the
// client and then continue to await more requests.
return try ctx.respond(.{
.status = .OK,
.mime = http.Mime.HTML,
.body = body[0..],
});
}

fn echo_handler(ctx: *Context, _: void) !void {
const body = if (ctx.request.body) |b|
try ctx.allocator.dupe(u8, b)
else
"";
return try ctx.respond(.{
.status = .OK,
.mime = http.Mime.HTML,
.body = body[0..],
});
}

pub fn main() !void {
const host: []const u8 = "0.0.0.0";
Expand All @@ -31,54 +64,12 @@ pub fn main() !void {

const num: i8 = 12;

var router = Router.init(&num, &[_]Route{
Route.init("/").get(struct {
fn handler_fn(ctx: *Context) !void {
const body_fmt =
\\ <!DOCTYPE html>
\\ <html>
\\ <body>
\\ <h1>Hello, World!</h1>
\\ <p>id: {d}</p>
\\ </body>
\\ </html>
;
const body = try std.fmt.allocPrint(ctx.allocator, body_fmt, .{ctx.state.*});
// This is the standard response and what you
// will usually be using. This will send to the
// client and then continue to await more requests.
try ctx.respond(.{
.status = .OK,
.mime = http.Mime.HTML,
.body = body[0..],
});
}
}.handler_fn),

Route.init("/echo").post(struct {
fn handler_fn(ctx: *Context) !void {
const body = if (ctx.request.body) |b|
try ctx.allocator.dupe(u8, b)
else
"";
try ctx.respond(.{
.status = .OK,
.mime = http.Mime.HTML,
.body = body[0..],
});
}
}.handler_fn),
}, .{
.not_found_handler = struct {
fn handler_fn(ctx: *Context) !void {
try ctx.respond(.{
.status = .@"Not Found",
.mime = http.Mime.HTML,
.body = "Not Found Handler!",
});
}
}.handler_fn,
});
var router = try Router.init(allocator, &.{
Route.init("/").get(num, root_handler).layer(),
Route.init("/echo").post({}, echo_handler).layer(),
}, .{});
defer router.deinit(allocator);
router.print_route_tree();

// This provides the entry function into the Tardy runtime. This will run
// exactly once inside of each runtime (each thread gets a single runtime).
Expand Down
Loading

0 comments on commit 35df0ff

Please sign in to comment.