Skip to content

Commit b3438fd

Browse files
committed
feat: added TypedStorage to Context
This allows for you to have values that persist throughout the lifetime of the Context.
1 parent a6b9764 commit b3438fd

File tree

5 files changed

+99
-15
lines changed

5 files changed

+99
-15
lines changed

examples/middleware/main.zig

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,15 @@ fn root_handler(ctx: *const Context, id: i8) !Respond {
2424
\\ <body>
2525
\\ <h1>Hello, World!</h1>
2626
\\ <p>id: {d}</p>
27+
\\ <p>stored: {d}</p>
2728
\\ </body>
2829
\\ </html>
2930
;
30-
const body = try std.fmt.allocPrint(ctx.allocator, body_fmt, .{id});
31+
const body = try std.fmt.allocPrint(
32+
ctx.allocator,
33+
body_fmt,
34+
.{ id, ctx.storage.get(usize).? },
35+
);
3136

3237
// This is the standard response and what you
3338
// will usually be using. This will send to the
@@ -41,6 +46,7 @@ fn root_handler(ctx: *const Context, id: i8) !Respond {
4146

4247
fn passing_middleware(next: *Next, _: void) !Respond {
4348
log.info("pass middleware: {s}", .{next.context.request.uri.?});
49+
try next.context.storage.put(usize, 100);
4450
return try next.run();
4551
}
4652

src/core/typed_storage.zig

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
const std = @import("std");
2+
3+
pub const TypedStorage = struct {
4+
arena: std.heap.ArenaAllocator,
5+
storage: std.AutoHashMapUnmanaged(u64, *anyopaque),
6+
7+
pub fn init(allocator: std.mem.Allocator) TypedStorage {
8+
return .{
9+
.arena = std.heap.ArenaAllocator.init(allocator),
10+
.storage = std.AutoHashMapUnmanaged(u64, *anyopaque){},
11+
};
12+
}
13+
14+
pub fn deinit(self: *TypedStorage) void {
15+
self.arena.deinit();
16+
}
17+
18+
/// Clears the Storage.
19+
pub fn clear(self: *TypedStorage) void {
20+
self.storage.clearAndFree(self.arena.allocator());
21+
_ = self.arena.reset(.retain_capacity);
22+
}
23+
24+
/// Inserts a value into the Storage.
25+
/// It uses the given type as the K.
26+
pub fn put(self: *TypedStorage, comptime T: type, value: T) !void {
27+
const allocator = self.arena.allocator();
28+
const ptr = try allocator.create(T);
29+
ptr.* = value;
30+
const type_id = comptime std.hash.Wyhash.hash(0, @typeName(T));
31+
try self.storage.put(allocator, type_id, @ptrCast(ptr));
32+
}
33+
34+
/// Extracts a value out of the Storage.
35+
/// It uses the given type as the K.
36+
pub fn get(self: *TypedStorage, comptime T: type) ?T {
37+
const type_id = comptime std.hash.Wyhash.hash(0, @typeName(T));
38+
const ptr = self.storage.get(type_id) orelse return null;
39+
return @as(*T, @ptrCast(@alignCast(ptr))).*;
40+
}
41+
};
42+
43+
const testing = std.testing;
44+
45+
test "TypedStorage: Basic" {
46+
var storage = TypedStorage.init(testing.allocator);
47+
defer storage.deinit();
48+
49+
// Test inserting and getting different types
50+
try storage.put(u32, 42);
51+
try storage.put([]const u8, "hello");
52+
try storage.put(f32, 3.14);
53+
54+
try testing.expectEqual(@as(u32, 42), storage.get(u32).?);
55+
try testing.expectEqualStrings("hello", storage.get([]const u8).?);
56+
try testing.expectEqual(@as(f32, 3.14), storage.get(f32).?);
57+
58+
// Test overwriting a value
59+
try storage.put(u32, 100);
60+
try testing.expectEqual(@as(u32, 100), storage.get(u32).?);
61+
62+
// Test getting non-existent type
63+
try testing.expectEqual(@as(?bool, null), storage.get(bool));
64+
65+
// Test clearing
66+
storage.clear();
67+
try testing.expectEqual(@as(?u32, null), storage.get(u32));
68+
try testing.expectEqual(@as(?[]const u8, null), storage.get([]const u8));
69+
try testing.expectEqual(@as(?f32, null), storage.get(f32));
70+
71+
// Test inserting after clear
72+
try storage.put(u32, 200);
73+
try testing.expectEqual(@as(u32, 200), storage.get(u32).?);
74+
}

src/http/context.zig

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ const Runtime = @import("tardy").Runtime;
55
const SecureSocket = @import("../core/secure_socket.zig").SecureSocket;
66

77
const Capture = @import("router/routing_trie.zig").Capture;
8+
9+
const TypedStorage = @import("../core/typed_storage.zig").TypedStorage;
810
const AnyCaseStringMap = @import("../core/any_case_string_map.zig").AnyCaseStringMap;
911

10-
// Context is dependent on the server that gets created.
12+
/// HTTP Context. Contains all of the various information
13+
/// that will persist throughout the lifetime of this Request/Response.
1114
pub const Context = struct {
12-
const Self = @This();
1315
allocator: std.mem.Allocator,
1416
/// Not safe to access unless you are manually sending the headers
1517
/// and returning the .responded variant of Respond.
@@ -18,6 +20,8 @@ pub const Context = struct {
1820
/// The Request that triggered this handler.
1921
request: *const Request,
2022
response: *Response,
23+
/// Storage
24+
storage: *TypedStorage,
2125
/// Socket for this Connection.
2226
socket: SecureSocket,
2327
/// Slice of the URL Slug Captures

src/http/server.zig

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const tag = builtin.os.tag;
44
const assert = std.debug.assert;
55
const log = std.log.scoped(.@"zzz/http/server");
66

7+
const TypedStorage = @import("../core/typed_storage.zig").TypedStorage;
78
const Pseudoslice = @import("../core/pseudoslice.zig").Pseudoslice;
89
const AnyCaseStringMap = @import("../core/any_case_string_map.zig").AnyCaseStringMap;
910

@@ -139,6 +140,7 @@ pub const Provision = struct {
139140
zc_recv_buffer: ZeroCopy(u8),
140141
header_buffer: std.ArrayList(u8),
141142
arena: std.heap.ArenaAllocator,
143+
storage: TypedStorage,
142144
captures: []Capture,
143145
queries: AnyCaseStringMap,
144146
request: Request,
@@ -187,14 +189,17 @@ pub const Server = struct {
187189
respond,
188190
};
189191

190-
fn prepare_new_request(state: *State, provision: *Provision, config: ServerConfig) !void {
192+
fn prepare_new_request(state: ?*State, provision: *Provision, config: ServerConfig) !void {
193+
assert(provision.initalized);
191194
provision.request.clear();
192195
provision.response.clear();
196+
provision.storage.clear();
193197
provision.zc_recv_buffer.clear_retaining_capacity();
194198
provision.header_buffer.clearRetainingCapacity();
195199
_ = provision.arena.reset(.{ .retain_with_limit = config.connection_arena_bytes_retain });
196-
state.* = .{ .request = .header };
197200
provision.recv_slice = try provision.zc_recv_buffer.get_write_area(config.socket_buffer_bytes);
201+
202+
if (state) |s| s.* = .{ .request = .header };
198203
}
199204

200205
pub fn main_frame(
@@ -256,20 +261,12 @@ pub const Server = struct {
256261
@panic("attempting to allocate more memory than available. (Captures)");
257262
};
258263
provision.queries = AnyCaseStringMap.init(rt.allocator);
264+
provision.storage = TypedStorage.init(rt.allocator);
259265
provision.request = Request.init(rt.allocator);
260266
provision.response = Response.init(rt.allocator);
261267
provision.initalized = true;
262268
}
263-
264-
defer if (provision.zc_recv_buffer.len > config.list_recv_bytes_retain)
265-
provision.zc_recv_buffer.shrink_clear_and_free(config.list_recv_bytes_retain) catch unreachable
266-
else
267-
provision.zc_recv_buffer.clear_retaining_capacity();
268-
defer provision.header_buffer.clearRetainingCapacity();
269-
defer _ = provision.arena.reset(.{ .retain_with_limit = config.connection_arena_bytes_retain });
270-
defer provision.queries.clearRetainingCapacity();
271-
defer provision.request.clear();
272-
defer provision.response.clear();
269+
defer prepare_new_request(null, provision, config) catch unreachable;
273270

274271
var state: State = .{ .request = .header };
275272
const buffer = try provision.zc_recv_buffer.get_write_area(config.socket_buffer_bytes);
@@ -387,6 +384,7 @@ pub const Server = struct {
387384
.header_buffer = &provision.header_buffer,
388385
.request = &provision.request,
389386
.response = &provision.response,
387+
.storage = &provision.storage,
390388
.socket = secure,
391389
.captures = found.captures,
392390
.queries = found.queries,
@@ -523,6 +521,7 @@ pub const Server = struct {
523521
@panic("attempting to allocate more memory than available. (Captures)");
524522
};
525523
provision.queries = AnyCaseStringMap.init(rt.allocator);
524+
provision.storage = TypedStorage.init(rt.allocator);
526525
provision.request = Request.init(rt.allocator);
527526
provision.response = Response.init(rt.allocator);
528527
}

src/unit_test.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ test "zzz unit tests" {
88
// Core
99
testing.refAllDecls(@import("./core/any_case_string_map.zig"));
1010
testing.refAllDecls(@import("./core/pseudoslice.zig"));
11+
testing.refAllDecls(@import("./core/typed_storage.zig"));
1112

1213
// HTTP
1314
testing.refAllDecls(@import("./http/context.zig"));

0 commit comments

Comments
 (0)