Skip to content

Commit 12d05b9

Browse files
committed
chore(all): reduce allocations & general perf
1 parent 35df0ff commit 12d05b9

File tree

7 files changed

+136
-91
lines changed

7 files changed

+136
-91
lines changed

examples/middleware/main.zig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ fn pre_middleware(next: *Next, _: void) !void {
4141
return try next.run();
4242
}
4343

44+
fn pre_fail_middleware(next: *Next, _: void) !void {
45+
log.info("pre fail request middleware: {s}", .{next.ctx.request.uri.?});
46+
return error.ExpectedFailure;
47+
}
48+
4449
fn post_middleware(next: *Next, _: void) !void {
4550
log.info("post request middleware: {s}", .{next.ctx.request.uri.?});
4651
return try next.run();
@@ -67,6 +72,8 @@ pub fn main() !void {
6772
var router = try Router.init(allocator, &.{
6873
Middleware.init().before({}, pre_middleware).after({}, post_middleware).layer(),
6974
Route.init("/").get(num, root_handler).layer(),
75+
Middleware.init().before({}, pre_fail_middleware).layer(),
76+
Route.init("/fail").get(num, root_handler).layer(),
7077
}, .{});
7178
defer router.deinit(allocator);
7279
router.print_route_tree();

src/http/context.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub const Context = struct {
3636
captures: []Capture,
3737
queries: *QueryMap,
3838
provision: *Provision,
39-
next: *Next,
39+
next: Next,
4040
triggered: bool = false,
4141

4242
pub fn to_sse(self: *Self, then: TaskFn(bool, *SSE)) !void {

src/http/provision.zig

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ const Capture = @import("router/routing_trie.zig").Capture;
66
const QueryMap = @import("router/routing_trie.zig").QueryMap;
77
const Request = @import("request.zig").Request;
88
const Response = @import("response.zig").Response;
9+
const Context = @import("context.zig").Context;
910
const ServerConfig = @import("server.zig").ServerConfig;
1011

12+
const Runtime = @import("tardy").Runtime;
13+
1114
pub const Stage = union(enum) {
1215
header,
1316
body: usize,
@@ -25,9 +28,11 @@ pub const Provision = struct {
2528
request: Request,
2629
response: Response,
2730
stage: Stage,
31+
context: Context,
2832

2933
pub const InitContext = struct {
3034
allocator: std.mem.Allocator,
35+
runtime: *Runtime,
3136
config: ServerConfig,
3237
};
3338

@@ -58,6 +63,26 @@ pub const Provision = struct {
5863
provision.response = Response.init(ctx.allocator, config.header_count_max) catch {
5964
@panic("attempting to statically allocate more memory than available. (Response)");
6065
};
66+
67+
// Anything undefined within here is sourced at Handler time.
68+
provision.context = .{
69+
.provision = provision,
70+
.allocator = provision.arena.allocator(),
71+
.request = &provision.request,
72+
.response = &provision.response,
73+
.runtime = ctx.runtime,
74+
.next = .{
75+
.ctx = &provision.context,
76+
.stage = .pre,
77+
// Handler Time.
78+
.pre_chain = undefined,
79+
.post_chain = undefined,
80+
},
81+
82+
// Handler Time.
83+
.captures = undefined,
84+
.queries = undefined,
85+
};
6186
}
6287
}
6388

src/http/router.zig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ pub const Router = struct {
6565
captures: []Capture,
6666
queries: *QueryMap,
6767
) !FoundBundle {
68-
return try self.routes.get_route(path, captures, queries) orelse {
69-
queries.clear();
68+
queries.clear();
69+
70+
return try self.routes.get_bundle(path, captures, queries) orelse {
7071
const not_found_bundle: Bundle = .{
7172
.pre = &.{},
7273
.route = Route.init("").all({}, self.configuration.not_found),

src/http/router/middleware.zig

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const std = @import("std");
2+
const log = std.log.scoped(.@"zzz/router/middleware");
23
const assert = std.debug.assert;
34

45
const Runtime = @import("tardy").Runtime;
@@ -9,6 +10,7 @@ const Task = @import("tardy").TaskFn;
910
const Pseudoslice = @import("../../core/pseudoslice.zig").Pseudoslice;
1011
const Server = @import("../server.zig").Server;
1112

13+
const Mime = @import("../mime.zig").Mime;
1214
const Route = @import("route.zig").Route;
1315
const HandlerWithData = @import("route.zig").HandlerWithData;
1416
const Layer = @import("layer.zig").Layer;
@@ -34,13 +36,31 @@ pub const Next = struct {
3436
assert(n.pre_chain.chain.len > 0);
3537
const next_middleware = n.pre_chain.chain[0];
3638
n.pre_chain.chain = n.pre_chain.chain[1..];
37-
try @call(.auto, next_middleware.middleware, .{ n, next_middleware.data });
39+
@call(.auto, next_middleware.middleware, .{ n, next_middleware.data }) catch |e| {
40+
log.err("\"{s}\" [pre] middleware failed with error: {}", .{ n.ctx.provision.request.uri.?, e });
41+
n.ctx.provision.response.set(.{
42+
.status = .@"Internal Server Error",
43+
.mime = Mime.HTML,
44+
.body = "",
45+
});
46+
47+
return try n.ctx.respond_without_middleware();
48+
};
3849
},
3950
.post => {
4051
assert(n.post_chain.len > 0);
4152
const next_middleware = n.post_chain[0];
4253
n.post_chain = n.post_chain[1..];
43-
try @call(.auto, next_middleware.middleware, .{ n, next_middleware.data });
54+
@call(.auto, next_middleware.middleware, .{ n, next_middleware.data }) catch |e| {
55+
log.err("\"{s}\" [post] middleware failed with error: {}", .{ n.ctx.provision.request.uri.?, e });
56+
n.ctx.provision.response.set(.{
57+
.status = .@"Internal Server Error",
58+
.mime = Mime.HTML,
59+
.body = "",
60+
});
61+
62+
return try n.ctx.respond_without_middleware();
63+
};
4464
},
4565
}
4666
}

src/http/router/routing_trie.zig

Lines changed: 52 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -233,70 +233,62 @@ pub const RoutingTrie = struct {
233233
print_node(&self.root, 0);
234234
}
235235

236-
pub fn get_route(
236+
pub fn get_bundle(
237237
self: Self,
238238
path: []const u8,
239239
captures: []Capture,
240240
queries: *QueryMap,
241241
) !?FoundBundle {
242242
var capture_idx: usize = 0;
243-
244-
queries.clear();
245243
const query_pos = std.mem.indexOfScalar(u8, path, '?');
246244
var iter = std.mem.tokenizeScalar(u8, path[0..(query_pos orelse path.len)], '/');
247245

248246
var current = self.root;
249247

250248
slash_loop: while (iter.next()) |chunk| {
251-
const fragment = Token{ .fragment = chunk };
252-
253-
// If it is the fragment, match it here.
254-
if (current.children.get(fragment)) |child| {
255-
current = child;
256-
continue;
257-
}
258-
259-
var matched = false;
260-
for (std.meta.tags(TokenMatch)) |token_type| {
261-
const token = Token{ .match = token_type };
262-
if (current.children.get(token)) |child| {
263-
matched = true;
264-
switch (token_type) {
265-
.signed => if (std.fmt.parseInt(i64, chunk, 10)) |value| {
266-
captures[capture_idx] = Capture{ .signed = value };
267-
} else |_| continue,
268-
.unsigned => if (std.fmt.parseInt(u64, chunk, 10)) |value| {
269-
captures[capture_idx] = Capture{ .unsigned = value };
270-
} else |_| continue,
271-
.float => if (std.fmt.parseFloat(f64, chunk)) |value| {
272-
captures[capture_idx] = Capture{ .float = value };
273-
} else |_| continue,
274-
.string => captures[capture_idx] = Capture{ .string = chunk },
275-
// This ends the matching sequence and claims everything.
276-
// Does not claim the query values.
277-
.remaining => {
278-
const rest = iter.buffer[(iter.index - chunk.len)..];
279-
captures[capture_idx] = Capture{ .remaining = rest };
280-
281-
current = child;
282-
capture_idx += 1;
283-
284-
break :slash_loop;
285-
},
286-
}
287-
288-
current = child;
289-
capture_idx += 1;
249+
var child_iter = current.children.iterator();
250+
child_loop: while (child_iter.next()) |entry| {
251+
const token = entry.key_ptr.*;
252+
const child = entry.value_ptr.*;
253+
254+
switch (token) {
255+
.fragment => |inner| if (std.mem.eql(u8, inner, chunk)) {
256+
current = child;
257+
continue :slash_loop;
258+
},
259+
.match => |kind| {
260+
switch (kind) {
261+
.signed => if (std.fmt.parseInt(i64, chunk, 10)) |value| {
262+
captures[capture_idx] = Capture{ .signed = value };
263+
} else |_| continue :child_loop,
264+
.unsigned => if (std.fmt.parseInt(u64, chunk, 10)) |value| {
265+
captures[capture_idx] = Capture{ .unsigned = value };
266+
} else |_| continue :child_loop,
267+
.float => if (std.fmt.parseFloat(f64, chunk)) |value| {
268+
captures[capture_idx] = Capture{ .float = value };
269+
} else |_| continue :child_loop,
270+
.string => captures[capture_idx] = Capture{ .string = chunk },
271+
.remaining => {
272+
const rest = iter.buffer[(iter.index - chunk.len)..];
273+
captures[capture_idx] = Capture{ .remaining = rest };
274+
275+
current = child;
276+
capture_idx += 1;
277+
278+
break :slash_loop;
279+
},
280+
}
290281

291-
if (capture_idx > captures.len) return error.TooManyCaptures;
292-
break;
282+
current = child;
283+
capture_idx += 1;
284+
if (capture_idx > captures.len) return error.TooManyCaptures;
285+
continue :slash_loop;
286+
},
293287
}
294288
}
295289

296290
// If we failed to match, this is an invalid route.
297-
if (!matched) {
298-
return null;
299-
}
291+
return null;
300292
}
301293

302294
if (query_pos) |pos| {
@@ -416,17 +408,17 @@ test "Routing with Paths" {
416408

417409
var captures: [8]Capture = [_]Capture{undefined} ** 8;
418410

419-
try testing.expectEqual(null, try s.get_route("/item/name", captures[0..], &q));
411+
try testing.expectEqual(null, try s.get_bundle("/item/name", captures[0..], &q));
420412

421413
{
422-
const captured = (try s.get_route("/item/name/HELLO", captures[0..], &q)).?;
414+
const captured = (try s.get_bundle("/item/name/HELLO", captures[0..], &q)).?;
423415

424416
try testing.expectEqual(Route.init("/item/name/%s"), captured.bundle.route);
425417
try testing.expectEqualStrings("HELLO", captured.captures[0].string);
426418
}
427419

428420
{
429-
const captured = (try s.get_route("/item/2112.22121/price_float", captures[0..], &q)).?;
421+
const captured = (try s.get_bundle("/item/2112.22121/price_float", captures[0..], &q)).?;
430422

431423
try testing.expectEqual(Route.init("/item/%f/price_float"), captured.bundle.route);
432424
try testing.expectEqual(2112.22121, captured.captures[0].float);
@@ -447,27 +439,27 @@ test "Routing with Remaining" {
447439

448440
var captures: [8]Capture = [_]Capture{undefined} ** 8;
449441

450-
try testing.expectEqual(null, try s.get_route("/item/name", captures[0..], &q));
442+
try testing.expectEqual(null, try s.get_bundle("/item/name", captures[0..], &q));
451443

452444
{
453-
const captured = (try s.get_route("/item/name/HELLO", captures[0..], &q)).?;
445+
const captured = (try s.get_bundle("/item/name/HELLO", captures[0..], &q)).?;
454446
try testing.expectEqual(Route.init("/item/name/%r"), captured.bundle.route);
455447
try testing.expectEqualStrings("HELLO", captured.captures[0].remaining);
456448
}
457449
{
458-
const captured = (try s.get_route("/item/name/THIS/IS/A/FILE/SYSTEM/PATH.html", captures[0..], &q)).?;
450+
const captured = (try s.get_bundle("/item/name/THIS/IS/A/FILE/SYSTEM/PATH.html", captures[0..], &q)).?;
459451
try testing.expectEqual(Route.init("/item/name/%r"), captured.bundle.route);
460452
try testing.expectEqualStrings("THIS/IS/A/FILE/SYSTEM/PATH.html", captured.captures[0].remaining);
461453
}
462454

463455
{
464-
const captured = (try s.get_route("/item/2112.22121/price_float", captures[0..], &q)).?;
456+
const captured = (try s.get_bundle("/item/2112.22121/price_float", captures[0..], &q)).?;
465457
try testing.expectEqual(Route.init("/item/%f/price_float"), captured.bundle.route);
466458
try testing.expectEqual(2112.22121, captured.captures[0].float);
467459
}
468460

469461
{
470-
const captured = (try s.get_route("/item/100/price/283.21", captures[0..], &q)).?;
462+
const captured = (try s.get_bundle("/item/100/price/283.21", captures[0..], &q)).?;
471463
try testing.expectEqual(Route.init("/item/%i/price/%f"), captured.bundle.route);
472464
try testing.expectEqual(100, captured.captures[0].signed);
473465
try testing.expectEqual(283.21, captured.captures[1].float);
@@ -488,10 +480,10 @@ test "Routing with Queries" {
488480

489481
var captures: [8]Capture = [_]Capture{undefined} ** 8;
490482

491-
try testing.expectEqual(null, try s.get_route("/item/name", captures[0..], &q));
483+
try testing.expectEqual(null, try s.get_bundle("/item/name", captures[0..], &q));
492484

493485
{
494-
const captured = (try s.get_route("/item/name/HELLO?name=muki&food=waffle", captures[0..], &q)).?;
486+
const captured = (try s.get_bundle("/item/name/HELLO?name=muki&food=waffle", captures[0..], &q)).?;
495487
try testing.expectEqual(Route.init("/item/name/%r"), captured.bundle.route);
496488
try testing.expectEqualStrings("HELLO", captured.captures[0].remaining);
497489
try testing.expectEqual(2, q.dirty());
@@ -501,15 +493,15 @@ test "Routing with Queries" {
501493

502494
{
503495
// Purposefully bad format with no keys or values.
504-
const captured = (try s.get_route("/item/2112.22121/price_float?", captures[0..], &q)).?;
496+
const captured = (try s.get_bundle("/item/2112.22121/price_float?", captures[0..], &q)).?;
505497
try testing.expectEqual(Route.init("/item/%f/price_float"), captured.bundle.route);
506498
try testing.expectEqual(2112.22121, captured.captures[0].float);
507499
try testing.expectEqual(0, q.dirty());
508500
}
509501

510502
{
511503
// Purposefully bad format with incomplete key/value pair.
512-
const captured = (try s.get_route("/item/100/price/283.21?help", captures[0..], &q)).?;
504+
const captured = (try s.get_bundle("/item/100/price/283.21?help", captures[0..], &q)).?;
513505
try testing.expectEqual(Route.init("/item/%i/price/%f"), captured.bundle.route);
514506
try testing.expectEqual(100, captured.captures[0].signed);
515507
try testing.expectEqual(283.21, captured.captures[1].float);
@@ -518,7 +510,7 @@ test "Routing with Queries" {
518510

519511
{
520512
// Purposefully have too many queries.
521-
const captured = try s.get_route(
513+
const captured = try s.get_bundle(
522514
"/item/100/price/283.21?a=1&b=2&c=3&d=4&e=5&f=6&g=7&h=8&i=9&j=10&k=11",
523515
captures[0..],
524516
&q,

0 commit comments

Comments
 (0)