From 030c4909cc337e47fbc86662b75f127b578e3ea4 Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Fri, 20 Mar 2026 01:54:41 -0700 Subject: [PATCH] use CacheControl and Vary --- src/network/cache/Cache.zig | 33 ++++++-------- src/network/cache/FsCache.zig | 84 +++++++++++++++++++++++------------ 2 files changed, 69 insertions(+), 48 deletions(-) diff --git a/src/network/cache/Cache.zig b/src/network/cache/Cache.zig index 79374e66..a6a711bb 100644 --- a/src/network/cache/Cache.zig +++ b/src/network/cache/Cache.zig @@ -81,6 +81,13 @@ pub const Vary = union(enum) { return .{ .value = value }; } + pub fn deinit(self: Vary, allocator: std.mem.Allocator) void { + switch (self) { + .wildcard => {}, + .value => |v| allocator.free(v), + } + } + pub fn toString(self: Vary) []const u8 { return switch (self) { .wildcard => "*", @@ -96,19 +103,14 @@ pub const CachedMetadata = struct { status: u16, stored_at: i64, age_at_store: u64, - max_age: u64, // for If-None-Match etag: ?[]const u8, // for If-Modified-Since last_modified: ?[]const u8, - // If non-null, must be incorporated into cache key. - vary: ?[]const u8, - - must_revalidate: bool, - no_cache: bool, - immutable: bool, + cache_control: CacheControl, + vary: ?Vary, headers: []const Http.Header, pub fn fromHeaders( @@ -143,7 +145,7 @@ pub const CachedMetadata = struct { // return null for uncacheable responses if (cc.no_store) return null; if (vary) |v| if (v == .wildcard) return null; - const resolved_max_age = cc.max_age orelse return null; + if (cc.max_age == null) return null; return .{ .url = url, @@ -151,13 +153,10 @@ pub const CachedMetadata = struct { .status = status, .stored_at = timestamp, .age_at_store = age_at_store, - .max_age = resolved_max_age, .etag = etag, .last_modified = last_modified, - .must_revalidate = cc.must_revalidate, - .no_cache = cc.no_cache, - .immutable = cc.immutable, - .vary = if (vary) |v| v.toString() else null, + .cache_control = cc, + .vary = vary, .headers = headers, }; } @@ -170,15 +169,9 @@ pub const CachedMetadata = struct { allocator.free(header.value); } allocator.free(self.headers); + if (self.vary) |v| v.deinit(allocator); if (self.etag) |e| allocator.free(e); if (self.last_modified) |lm| allocator.free(lm); - if (self.vary) |v| allocator.free(v); - } - - pub fn isAgeStale(self: *const CachedMetadata) bool { - const now = std.time.timestamp(); - const age = now - self.stored_at + @as(i64, @intCast(self.age_at_store)); - return age < @as(i64, @intCast(self.max_age)); } }; diff --git a/src/network/cache/FsCache.zig b/src/network/cache/FsCache.zig index c931e4a2..de62cd30 100644 --- a/src/network/cache/FsCache.zig +++ b/src/network/cache/FsCache.zig @@ -59,25 +59,35 @@ fn hashKey(key: []const u8) [HASHED_KEY_LEN]u8 { fn serializeMeta(writer: *std.Io.Writer, meta: *const CachedMetadata) !void { try writer.print("{s}\n{s}\n", .{ meta.url, meta.content_type }); - try writer.print("{d}\n{d}\n{d}\n{d}\n", .{ + try writer.print("{d}\n{d}\n{d}\n", .{ meta.status, meta.stored_at, meta.age_at_store, - meta.max_age, }); try writer.print("{s}\n", .{meta.etag orelse "null"}); try writer.print("{s}\n", .{meta.last_modified orelse "null"}); - try writer.print("{s}\n", .{meta.vary orelse "null"}); - try writer.print("{}\n{}\n{}\n", .{ - meta.must_revalidate, - meta.no_cache, - meta.immutable, + + // cache-control + try writer.print("{d}\n", .{meta.cache_control.max_age orelse 0}); + try writer.print("{}\n{}\n{}\n{}\n", .{ + meta.cache_control.max_age != null, + meta.cache_control.must_revalidate, + meta.cache_control.no_cache, + meta.cache_control.immutable, }); + + // vary + if (meta.vary) |v| { + try writer.print("{s}\n", .{v.toString()}); + } else { + try writer.print("null\n", .{}); + } try writer.flush(); try writer.print("{d}\n", .{meta.headers.len}); for (meta.headers) |hdr| { try writer.print("{s}\n{s}\n", .{ hdr.name, hdr.value }); + try writer.flush(); } try writer.flush(); } @@ -121,36 +131,57 @@ fn deserializeMeta(allocator: std.mem.Allocator, file: std.fs.File) !CachedMetad const line = try reader.takeDelimiter('\n') orelse return error.Malformed; break :blk std.fmt.parseInt(u64, line, 10) catch return error.Malformed; }; - const max_age = blk: { - const line = try reader.takeDelimiter('\n') orelse return error.Malformed; - break :blk std.fmt.parseInt(u64, line, 10) catch return error.Malformed; - }; const etag = blk: { const line = try reader.takeDelimiter('\n') orelse return error.Malformed; break :blk if (std.mem.eql(u8, line, "null")) null else try allocator.dupe(u8, line); }; + errdefer if (etag) |e| allocator.free(e); + const last_modified = blk: { const line = try reader.takeDelimiter('\n') orelse return error.Malformed; break :blk if (std.mem.eql(u8, line, "null")) null else try allocator.dupe(u8, line); }; - const vary = blk: { - const line = try reader.takeDelimiter('\n') orelse return error.Malformed; - break :blk if (std.mem.eql(u8, line, "null")) null else try allocator.dupe(u8, line); + errdefer if (last_modified) |lm| allocator.free(lm); + + // cache-control + const cc = cache_control: { + const max_age_val = blk: { + const line = try reader.takeDelimiter('\n') orelse return error.Malformed; + break :blk std.fmt.parseInt(u64, line, 10) catch return error.Malformed; + }; + const max_age_present = blk: { + const line = try reader.takeDelimiter('\n') orelse return error.Malformed; + break :blk try deserializeMetaBoolean(line); + }; + const must_revalidate = blk: { + const line = try reader.takeDelimiter('\n') orelse return error.Malformed; + break :blk try deserializeMetaBoolean(line); + }; + const no_cache = blk: { + const line = try reader.takeDelimiter('\n') orelse return error.Malformed; + break :blk try deserializeMetaBoolean(line); + }; + const immutable = blk: { + const line = try reader.takeDelimiter('\n') orelse return error.Malformed; + break :blk try deserializeMetaBoolean(line); + }; + break :cache_control Cache.CacheControl{ + .max_age = if (max_age_present) max_age_val else null, + .must_revalidate = must_revalidate, + .no_cache = no_cache, + .immutable = immutable, + }; }; - const must_revalidate = blk: { + // vary + const vary = blk: { const line = try reader.takeDelimiter('\n') orelse return error.Malformed; - break :blk try deserializeMetaBoolean(line); - }; - const no_cache = blk: { - const line = try reader.takeDelimiter('\n') orelse return error.Malformed; - break :blk try deserializeMetaBoolean(line); - }; - const immutable = blk: { - const line = try reader.takeDelimiter('\n') orelse return error.Malformed; - break :blk try deserializeMetaBoolean(line); + if (std.mem.eql(u8, line, "null")) break :blk null; + const duped = try allocator.dupe(u8, line); + break :blk Cache.Vary.parse(duped); }; + errdefer if (vary) |v| if (v == .value) allocator.free(v.value); const headers = blk: { const line = try reader.takeDelimiter('\n') orelse return error.Malformed; @@ -184,12 +215,9 @@ fn deserializeMeta(allocator: std.mem.Allocator, file: std.fs.File) !CachedMetad .status = status, .stored_at = stored_at, .age_at_store = age_at_store, - .max_age = max_age, + .cache_control = cc, .etag = etag, .last_modified = last_modified, - .must_revalidate = must_revalidate, - .no_cache = no_cache, - .immutable = immutable, .vary = vary, .headers = headers, };