diff --git a/build.zig.zon b/build.zig.zon index 93b1e2b..a840979 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -4,8 +4,9 @@ .minimum_zig_version = "0.13.0", .dependencies = .{ .tardy = .{ - .url = "git+https://github.com/mookums/tardy?ref=v0.2.0#543e6b01cba3caa691960a4a5a54d3419969f2d8", - .hash = "12202bc544928f0bb67ab4a30d3ff6d54c9d62643296c8d04303762b477b71fd002d", + //.url = "git+https://github.com/mookums/tardy?ref=v0.2.1#d6452871e1eabf802c2f2423b87932edd3645e94", + //.hash = "1220ceafa7dec628f8d4ac02e2f4484b79f3a5bd34ad3c0c7333604a10e5cb807f6d", + .path = "../tardy", }, .bearssl = .{ .url = "git+https://github.com/mookums/bearssl-zig#37a96eee56fe2543579bbc6da148ca886f3dd32b", diff --git a/src/core/case_string_map.zig b/src/core/case_string_map.zig index 63962cc..d091724 100644 --- a/src/core/case_string_map.zig +++ b/src/core/case_string_map.zig @@ -1,35 +1,92 @@ const std = @import("std"); const assert = std.debug.assert; +const Pool = @import("tardy").Pool; + pub fn CaseStringMap(comptime T: type) type { - return std.ArrayHashMapUnmanaged([]const u8, T, struct { - pub fn hash(_: @This(), input: []const u8) u32 { - var h: u32 = 0; - for (input) |byte| { - h = h *% 31 +% std.ascii.toLower(byte); + return struct { + const Self = @This(); + const InnerPool = Pool(Entry); + const PoolIterator = InnerPool.Iterator; + + const Entry = struct { + hash: u32, + key: []const u8, + data: T, + }; + + pool: InnerPool, + + pub fn init(allocator: std.mem.Allocator, size: usize) !Self { + const pool = try Pool(Entry).init(allocator, size, null, null); + return .{ .pool = pool }; + } + + pub fn deinit(self: *Self) void { + self.pool.deinit(null, null); + } + + pub fn put(self: *Self, name: []const u8, data: T) !void { + const name_hash = hash(name); + const entry = try self.pool.borrow_hint(@intCast(name_hash)); + entry.item.* = .{ .key = name, .hash = name_hash, .data = data }; + } + + pub fn put_assume_capacity(self: *Self, name: []const u8, data: T) void { + assert(self.pool.clean() > 0); + const name_hash = hash(name); + const entry = self.pool.borrow_hint(@intCast(name_hash)) catch unreachable; + entry.item.* = .{ .key = name, .hash = name_hash, .data = data }; + } + + pub fn get(self: *Self, name: []const u8) ?T { + const name_hash = hash(name); + + var iter = self.pool.iterator(); + while (iter.next()) |entry| { + if (entry.hash == name_hash) { + return entry.data; + } } - return h; + + return null; + } + + pub fn iterator(self: *Self) PoolIterator { + return self.pool.iterator(); + } + + pub fn num_clean(self: *const Self) usize { + return self.pool.clean(); + } + + pub fn dirty(self: *const Self) usize { + return self.pool.dirty.count(); + } + + pub fn clear(self: *Self) void { + // unset all of the dirty bits, effectively clearing it. + self.pool.dirty.unsetAll(); } - pub fn eql(_: @This(), first: []const u8, second: []const u8, _: usize) bool { - if (first.len != second.len) return false; - for (first, second) |a, b| { - if (std.ascii.toLower(a) != std.ascii.toLower(b)) return false; + fn hash(name: []const u8) u32 { + var h = std.hash.Fnv1a_32.init(); + for (name) |byte| { + h.update(&.{std.ascii.toLower(byte)}); } - return true; + return h.final(); } - }, true); + }; } const testing = std.testing; test "CaseStringMap: Add Stuff" { - var csm = CaseStringMap([]const u8){}; - defer csm.deinit(testing.allocator); - try csm.ensureUnusedCapacity(testing.allocator, 2); + var csm = try CaseStringMap([]const u8).init(testing.allocator, 2); + defer csm.deinit(); - csm.putAssumeCapacity("Content-Length", "100"); - csm.putAssumeCapacity("Host", "localhost:9999"); + try csm.put("Content-Length", "100"); + csm.put_assume_capacity("Host", "localhost:9999"); const content_length = csm.get("content-length"); try testing.expect(content_length != null); diff --git a/src/core/zc_buffer.zig b/src/core/zc_buffer.zig index 74b0c4e..bf16c1b 100644 --- a/src/core/zc_buffer.zig +++ b/src/core/zc_buffer.zig @@ -21,7 +21,7 @@ pub const ZeroCopyBuffer = struct { self.allocator.free(self.ptr[0..self.capacity]); } - pub fn as_slice(self: *ZeroCopyBuffer) []u8 { + pub fn as_slice(self: *const ZeroCopyBuffer) []u8 { return self.ptr[0..self.len]; } @@ -30,7 +30,7 @@ pub const ZeroCopyBuffer = struct { end: ?usize = null, }; - pub fn subslice(self: *ZeroCopyBuffer, options: SubsliceOptions) []u8 { + pub fn subslice(self: *const ZeroCopyBuffer, options: SubsliceOptions) []u8 { const start: usize = options.start orelse 0; const end: usize = options.end orelse self.len; assert(start <= end); @@ -68,7 +68,7 @@ pub const ZeroCopyBuffer = struct { } } - pub fn get_write_area_assume_space(self: *ZeroCopyBuffer, size: usize) []u8 { + pub fn get_write_area_assume_space(self: *const ZeroCopyBuffer, size: usize) []u8 { assert(self.capacity - self.len >= size); return self.ptr[self.len .. self.len + size]; } diff --git a/src/http/provision.zig b/src/http/provision.zig index 49c86fd..9cdde0c 100644 --- a/src/http/provision.zig +++ b/src/http/provision.zig @@ -49,8 +49,7 @@ pub const Provision = struct { provision.captures = ctx.allocator.alloc(Capture, config.capture_count_max) catch { @panic("attempting to statically allocate more memory than available. (Captures)"); }; - provision.queries = QueryMap{}; - provision.queries.ensureUnusedCapacity(ctx.allocator, config.query_count_max) catch { + provision.queries = QueryMap.init(ctx.allocator, config.query_count_max) catch { @panic("attempting to statically allocate more memory than available. (QueryMap)"); }; provision.request = Request.init(ctx.allocator, config.header_count_max) catch { @@ -68,7 +67,7 @@ pub const Provision = struct { provision.arena.deinit(); provision.request.deinit(); provision.response.deinit(); - provision.queries.deinit(allocator); + provision.queries.deinit(); allocator.free(provision.captures); } } diff --git a/src/http/request.zig b/src/http/request.zig index e31e301..357f7e2 100644 --- a/src/http/request.zig +++ b/src/http/request.zig @@ -16,27 +16,23 @@ pub const Request = struct { /// This is for constructing a Request. pub fn init(allocator: std.mem.Allocator, header_count_max: usize) !Request { - var headers = Headers{}; - try headers.ensureUnusedCapacity(allocator, header_count_max); + const headers = try Headers.init(allocator, header_count_max); return Request{ .allocator = allocator, .headers = headers, - .method = null, - .uri = null, - .body = null, }; } pub fn deinit(self: *Request) void { - self.headers.deinit(self.allocator); + self.headers.deinit(); } pub fn clear(self: *Request) void { self.method = null; self.uri = null; self.body = null; - self.headers.clearRetainingCapacity(); + self.headers.clear(); } const RequestParseOptions = struct { @@ -88,8 +84,8 @@ pub const Request = struct { const value = std.mem.trimLeft(u8, header_iter.rest(), &.{' '}); if (value.len == 0) return HTTPError.MalformedRequest; - if (self.headers.count() >= self.headers.capacity() / 2) return HTTPError.TooManyHeaders; - self.headers.putAssumeCapacity(key, value); + if (self.headers.num_clean() == 0) return HTTPError.TooManyHeaders; + self.headers.put_assume_capacity(key, value); } } } diff --git a/src/http/response.zig b/src/http/response.zig index 646b98b..3759ae2 100644 --- a/src/http/response.zig +++ b/src/http/response.zig @@ -14,8 +14,7 @@ pub const Response = struct { headers: Headers, pub fn init(allocator: std.mem.Allocator, header_count_max: usize) !Response { - var headers = Headers{}; - try headers.ensureUnusedCapacity(allocator, header_count_max); + const headers = try Headers.init(allocator, header_count_max); return Response{ .allocator = allocator, @@ -24,7 +23,7 @@ pub const Response = struct { } pub fn deinit(self: *Response) void { - self.headers.deinit(self.allocator); + self.headers.deinit(); } pub fn clear(self: *Response) void { @@ -77,12 +76,12 @@ pub const Response = struct { // Headers var iter = self.headers.iterator(); while (iter.next()) |entry| { - std.mem.copyForwards(u8, buffer[index..], entry.key_ptr.*); - index += entry.key_ptr.len; + std.mem.copyForwards(u8, buffer[index..], entry.key); + index += entry.key.len; std.mem.copyForwards(u8, buffer[index..], ": "); index += 2; - std.mem.copyForwards(u8, buffer[index..], entry.value_ptr.*); - index += entry.value_ptr.len; + std.mem.copyForwards(u8, buffer[index..], entry.data); + index += entry.data.len; std.mem.copyForwards(u8, buffer[index..], "\r\n"); index += 2; } diff --git a/src/http/router.zig b/src/http/router.zig index c914a15..c2e0a3a 100644 --- a/src/http/router.zig +++ b/src/http/router.zig @@ -59,7 +59,7 @@ pub fn Router(comptime Server: type, comptime AppState: type) type { pub fn get_route_from_host(self: Self, path: []const u8, captures: []Capture, queries: *QueryMap) !FoundRoute { return try self.routes.get_route(path, captures, queries) orelse { - queries.clearRetainingCapacity(); + queries.clear(); return FoundRoute{ .route = self.not_found_route, .captures = captures[0..0], .queries = queries }; }; } diff --git a/src/http/router/routing_trie.zig b/src/http/router/routing_trie.zig index f31dc3c..5d30a6a 100644 --- a/src/http/router/routing_trie.zig +++ b/src/http/router/routing_trie.zig @@ -199,7 +199,7 @@ pub fn RoutingTrie(comptime Server: type, comptime AppState: type) type { ) !?FoundRoute { var capture_idx: usize = 0; - queries.clearRetainingCapacity(); + queries.clear(); const query_pos = std.mem.indexOfScalar(u8, path, '?'); var iter = std.mem.tokenizeScalar(u8, path[0..(query_pos orelse path.len)], '/'); @@ -262,7 +262,7 @@ pub fn RoutingTrie(comptime Server: type, comptime AppState: type) type { var query_iter = std.mem.tokenizeScalar(u8, path[pos + 1 ..], '&'); while (query_iter.next()) |chunk| { - if (queries.count() >= queries.capacity() / 2) return null; + if (queries.pool.clean() == 0) return null; const field_idx = std.mem.indexOfScalar(u8, chunk, '=') orelse break; if (chunk.len < field_idx + 1) break; @@ -272,7 +272,7 @@ pub fn RoutingTrie(comptime Server: type, comptime AppState: type) type { assert(std.mem.indexOfScalar(u8, key, '=') == null); assert(std.mem.indexOfScalar(u8, value, '=') == null); - queries.putAssumeCapacity(key, value); + queries.put_assume_capacity(key, value); } } } @@ -372,9 +372,8 @@ test "Routing with Paths" { Route.init("/item/list"), }); - var q = try QueryMap.init(testing.allocator, &[_][]const u8{}, &[_][]const u8{}); - try q.ensureTotalCapacity(testing.allocator, 8); - defer q.deinit(testing.allocator); + var q = try QueryMap.init(testing.allocator, 8); + defer q.deinit(); var captures: [8]Capture = [_]Capture{undefined} ** 8; @@ -405,9 +404,8 @@ test "Routing with Remaining" { Route.init("/item/%i/price/%f"), }); - var q = try QueryMap.init(testing.allocator, &[_][]const u8{}, &[_][]const u8{}); - try q.ensureTotalCapacity(testing.allocator, 8); - defer q.deinit(testing.allocator); + var q = try QueryMap.init(testing.allocator, 8); + defer q.deinit(); var captures: [8]Capture = [_]Capture{undefined} ** 8; @@ -448,9 +446,8 @@ test "Routing with Queries" { Route.init("/item/%i/price/%f"), }); - var q = try QueryMap.init(testing.allocator, &[_][]const u8{}, &[_][]const u8{}); - try q.ensureTotalCapacity(testing.allocator, 8); - defer q.deinit(testing.allocator); + var q = try QueryMap.init(testing.allocator, 8); + defer q.deinit(); var captures: [8]Capture = [_]Capture{undefined} ** 8; @@ -460,7 +457,7 @@ test "Routing with Queries" { const captured = (try s.get_route("/item/name/HELLO?name=muki&food=waffle", captures[0..], &q)).?; try testing.expectEqual(Route.init("/item/name/%r"), captured.route); try testing.expectEqualStrings("HELLO", captured.captures[0].remaining); - try testing.expectEqual(2, q.count()); + try testing.expectEqual(2, q.dirty()); try testing.expectEqualStrings("muki", q.get("name").?); try testing.expectEqualStrings("waffle", q.get("food").?); } @@ -470,7 +467,7 @@ test "Routing with Queries" { const captured = (try s.get_route("/item/2112.22121/price_float?", captures[0..], &q)).?; try testing.expectEqual(Route.init("/item/%f/price_float"), captured.route); try testing.expectEqual(2112.22121, captured.captures[0].float); - try testing.expectEqual(0, q.count()); + try testing.expectEqual(0, q.dirty()); } { @@ -479,7 +476,7 @@ test "Routing with Queries" { try testing.expectEqual(Route.init("/item/%i/price/%f"), captured.route); try testing.expectEqual(100, captured.captures[0].signed); try testing.expectEqual(283.21, captured.captures[1].float); - try testing.expectEqual(0, q.count()); + try testing.expectEqual(0, q.dirty()); } { diff --git a/src/http/server.zig b/src/http/server.zig index 48ec0a3..0afb757 100644 --- a/src/http/server.zig +++ b/src/http/server.zig @@ -71,7 +71,7 @@ pub inline fn raw_respond(p: *Provision) !RecvStatus { const body = p.response.body orelse ""; const header_buffer = try p.response.headers_into_buffer(p.buffer, @intCast(body.len)); - p.response.headers.clearRetainingCapacity(); + p.response.headers.clear(); const pseudo = Pseudoslice.init(header_buffer, body, p.buffer); return .{ .send = pseudo }; } @@ -125,12 +125,8 @@ pub const ServerConfig = struct { /// Size of the buffer (in bytes) used for /// interacting with the socket. /// - /// Default: 4 KB. - socket_buffer_bytes: u32 = 1024 * 4, - /// Maximum length of a Header Key - /// - /// Default: 64 - header_key_length_max: u16 = 64, + /// Default: 1 KB. + socket_buffer_bytes: u32 = 1024, /// Maximum number of Headers in a Request/Response /// /// Default: 32 @@ -740,7 +736,7 @@ pub fn Server(comptime security: Security, comptime AppState: type) type { }.send_then_inner; } - pub inline fn serve(self: *Self, router: *const Router, rt: *Runtime) !void { + pub fn serve(self: *Self, router: *const Router, rt: *Runtime) !void { if (self.addr == null) return error.ServerNotBinded; const addr = self.addr.?; try rt.storage.store_alloc("__zzz_is_unix", addr.any.family == std.posix.AF.UNIX); @@ -786,7 +782,7 @@ pub fn Server(comptime security: Security, comptime AppState: type) type { try rt.net.accept(socket, accept_task, socket); } - pub inline fn clean(rt: *Runtime) !void { + pub fn clean(rt: *Runtime) !void { // clean up socket. const server_socket = rt.storage.get("__zzz_server_socket", std.posix.socket_t); std.posix.close(server_socket); @@ -896,7 +892,7 @@ pub fn Server(comptime security: Security, comptime AppState: type) type { break :route; }; - p.response.headers.putAssumeCapacity("Allow", allowed); + p.response.headers.put_assume_capacity("Allow", allowed); break :route; } }