From 3a2d58d1dee1d88d812bef76a52457a96bf32e56 Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Thu, 26 Mar 2026 18:50:46 -0700 Subject: [PATCH] require timestamp passed in with cache request --- src/browser/HttpClient.zig | 2 +- src/network/cache/Cache.zig | 1 + src/network/cache/FsCache.zig | 58 ++++++++++++++++++++++++++++++++--- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/browser/HttpClient.zig b/src/browser/HttpClient.zig index 74140814..fb4f1a60 100644 --- a/src/browser/HttpClient.zig +++ b/src/browser/HttpClient.zig @@ -341,7 +341,7 @@ fn processRequest(self: *Client, req: Request) !void { const arena = try self.network.app.arena_pool.acquire(); defer self.network.app.arena_pool.release(arena); - if (cache.get(arena, .{ .url = req.url })) |cached| { + if (cache.get(arena, .{ .url = req.url, .timestamp = std.time.timestamp() })) |cached| { log.debug(.browser, "http.cache.get", .{ .url = req.url, .found = true, diff --git a/src/network/cache/Cache.zig b/src/network/cache/Cache.zig index 98bea107..54603172 100644 --- a/src/network/cache/Cache.zig +++ b/src/network/cache/Cache.zig @@ -126,6 +126,7 @@ pub const CachedMetadata = struct { pub const CacheRequest = struct { url: []const u8, + timestamp: i64, }; pub const CachedData = union(enum) { diff --git a/src/network/cache/FsCache.zig b/src/network/cache/FsCache.zig index 29afd9b0..4fd53e2f 100644 --- a/src/network/cache/FsCache.zig +++ b/src/network/cache/FsCache.zig @@ -127,7 +127,7 @@ pub fn get(self: *FsCache, arena: std.mem.Allocator, req: CacheRequest) ?Cache.C return null; } - const now = std.time.timestamp(); + const now = req.timestamp; const age = (now - metadata.stored_at) + @as(i64, @intCast(metadata.age_at_store)); if (age < 0 or @as(u64, @intCast(age)) >= metadata.cache_control.max_age) { self.dir.deleteFile(&meta_p) catch {}; @@ -208,7 +208,7 @@ test "FsCache: basic put and get" { var fs_cache = try FsCache.init(path); defer fs_cache.deinit(); - var c = Cache{ .kind = .{ .fs = fs_cache } }; + var cache = Cache{ .kind = .{ .fs = fs_cache } }; var arena = std.heap.ArenaAllocator.init(testing.allocator); defer arena.deinit(); @@ -228,9 +228,9 @@ test "FsCache: basic put and get" { }; const body = "hello world"; - try c.put(meta, body); + try cache.put(meta, body); - const result = c.get(arena.allocator(), .{ .url = "https://example.com" }) orelse return error.CacheMiss; + const result = cache.get(arena.allocator(), .{ .url = "https://example.com", .timestamp = now }) orelse return error.CacheMiss; defer result.data.file.close(); var buf: [64]u8 = undefined; @@ -241,3 +241,53 @@ test "FsCache: basic put and get" { try testing.expectEqualStrings(body, read_buf); } + +test "FsCache: get expiration" { + var tmp = testing.tmpDir(.{}); + defer tmp.cleanup(); + + const path = try tmp.dir.realpathAlloc(testing.allocator, "."); + defer testing.allocator.free(path); + + var fs_cache = try FsCache.init(path); + defer fs_cache.deinit(); + var cache = Cache{ .kind = .{ .fs = fs_cache } }; + + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + + const now = 5000; + const max_age = 1000; + + const meta = CachedMetadata{ + .url = "https://example.com", + .content_type = "text/html", + .status = 200, + .stored_at = now, + .age_at_store = 900, + .etag = null, + .last_modified = null, + .cache_control = .{ .max_age = max_age }, + .vary = null, + .headers = &.{}, + }; + + const body = "hello world"; + try cache.put(meta, body); + + const result = cache.get( + arena.allocator(), + .{ .url = "https://example.com", .timestamp = now + 50 }, + ) orelse return error.CacheMiss; + result.data.file.close(); + + try testing.expectEqual(null, cache.get( + arena.allocator(), + .{ .url = "https://example.com", .timestamp = now + 200 }, + )); + + try testing.expectEqual(null, cache.get( + arena.allocator(), + .{ .url = "https://example.com", .timestamp = now }, + )); +}