only store stuff when we know we will cache

This commit is contained in:
Muki Kiboigo
2026-03-26 18:18:26 -07:00
parent 621ea49cb6
commit 39c09b7b5f
2 changed files with 77 additions and 74 deletions

View File

@@ -981,25 +981,17 @@ fn processOneMessage(self: *Client, msg: http.Handles.MultiMessage, transfer: *T
try transfer.req.done_callback(transfer.req.ctx);
cache: {
if (self.network.cache) |*cache| {
const headers = &transfer.response_header.?;
if (transfer._pending_cache_metadata) |metadata| {
const cache = &self.network.cache.?;
const metadata = try CacheMetadata.fromHeaders(
transfer.req.url,
headers.status,
std.time.timestamp(),
header_list.items,
) orelse break :cache;
// TODO: Support Vary Keying
const cache_key = transfer.req.url;
// TODO: Support Vary Keying
const cache_key = transfer.req.url;
log.err(.browser, "http cache", .{ .key = cache_key, .metadata = metadata });
cache.put(metadata, body) catch |err| log.warn(.http, "cache put failed", .{ .err = err });
log.debug(.browser, "http.cache.put", .{ .url = transfer.req.url });
}
log.debug(.browser, "http cache", .{ .key = cache_key, .metadata = metadata });
cache.put(metadata, body) catch |err| {
log.warn(.http, "cache put failed", .{ .err = err });
};
log.debug(.browser, "http.cache.put", .{ .url = transfer.req.url });
}
transfer.req.notification.dispatch(.http_request_done, &.{
@@ -1184,6 +1176,7 @@ pub const Transfer = struct {
// total bytes received in the response, including the response status line,
// the headers, and the [encoded] body.
bytes_received: usize = 0,
_pending_cache_metadata: ?CacheMetadata = null,
aborted: bool = false,
@@ -1554,6 +1547,40 @@ pub const Transfer = struct {
return err;
};
if (transfer.client.network.cache != null and transfer.req.method == .GET) {
const rh = &transfer.response_header.?;
const allocator = transfer.arena.allocator();
const maybe_cm = try Cache.tryCache(
allocator,
std.time.timestamp(),
transfer.url,
rh.status,
rh.contentType(),
if (conn.getResponseHeader("cache-control", 0)) |h| h.value else null,
if (conn.getResponseHeader("vary", 0)) |h| h.value else null,
if (conn.getResponseHeader("etag", 0)) |h| h.value else null,
if (conn.getResponseHeader("last-modified", 0)) |h| h.value else null,
if (conn.getResponseHeader("age", 0)) |h| h.value else null,
conn.getResponseHeader("set-cookie", 0) != null,
conn.getResponseHeader("authorization", 0) != null,
);
if (maybe_cm) |cm| {
var header_list: std.ArrayList(http.Header) = .empty;
var it = transfer.responseHeaderIterator();
while (it.next()) |hdr| {
try header_list.append(allocator, .{
.name = try allocator.dupe(u8, hdr.name),
.value = try allocator.dupe(u8, hdr.value),
});
}
transfer._pending_cache_metadata = cm;
transfer._pending_cache_metadata.?.headers = header_list.items;
}
}
transfer.req.notification.dispatch(.http_response_header_done, &.{
.transfer = transfer,
});

View File

@@ -122,63 +122,6 @@ pub const CachedMetadata = struct {
cache_control: CacheControl,
vary: ?Vary,
headers: []const Http.Header,
pub fn fromHeaders(
url: [:0]const u8,
status: u16,
timestamp: i64,
headers: []const Http.Header,
) !?CachedMetadata {
var cc: ?CacheControl = null;
var vary: ?Vary = null;
var etag: ?[]const u8 = null;
var last_modified: ?[]const u8 = null;
var age_at_store: u64 = 0;
var content_type: []const u8 = "application/octet-stream";
// Only cache 200 for now. Technically, we can cache others.
switch (status) {
200 => {},
else => return null,
}
for (headers) |hdr| {
if (std.ascii.eqlIgnoreCase(hdr.name, "cache-control")) {
cc = CacheControl.parse(hdr.value) orelse return null;
} else if (std.ascii.eqlIgnoreCase(hdr.name, "etag")) {
etag = hdr.value;
} else if (std.ascii.eqlIgnoreCase(hdr.name, "last-modified")) {
last_modified = hdr.value;
} else if (std.ascii.eqlIgnoreCase(hdr.name, "vary")) {
vary = Vary.parse(hdr.value);
// Vary: * means the response cannot be cached
if (vary) |v| if (v == .wildcard) return null;
} else if (std.ascii.eqlIgnoreCase(hdr.name, "age")) {
age_at_store = std.fmt.parseInt(u64, hdr.value, 10) catch 0;
} else if (std.ascii.eqlIgnoreCase(hdr.name, "content-type")) {
content_type = hdr.value;
} else if (std.ascii.eqlIgnoreCase(hdr.name, "set-cookie")) {
// Don't cache if has Set-Cookie.
return null;
} else if (std.ascii.eqlIgnoreCase(hdr.name, "authorization")) {
// Don't cache if has Authorization.
return null;
}
}
return .{
.url = url,
.content_type = content_type,
.status = status,
.stored_at = timestamp,
.age_at_store = age_at_store,
.etag = etag,
.last_modified = last_modified,
.cache_control = cc orelse return null,
.vary = vary,
.headers = headers,
};
}
};
pub const CacheRequest = struct {
@@ -194,3 +137,36 @@ pub const CachedResponse = struct {
metadata: CachedMetadata,
data: CachedData,
};
pub fn tryCache(
arena: std.mem.Allocator,
timestamp: i64,
url: [:0]const u8,
status: u16,
content_type: ?[]const u8,
cache_control: ?[]const u8,
vary: ?[]const u8,
etag: ?[]const u8,
last_modified: ?[]const u8,
age: ?[]const u8,
has_set_cookie: bool,
has_authorization: bool,
) !?CachedMetadata {
if (status != 200) return null;
if (has_set_cookie) return null;
if (has_authorization) return null;
const cc = CacheControl.parse(cache_control orelse return null) orelse return null;
return .{
.url = url,
.content_type = if (content_type) |ct| try arena.dupe(u8, ct) else "application/octet-stream",
.status = status,
.stored_at = timestamp,
.age_at_store = if (age) |a| std.fmt.parseInt(u64, a, 10) catch 0 else 0,
.cache_control = cc,
.vary = if (vary) |v| Vary.parse(v) else null,
.etag = if (etag) |e| try arena.dupe(u8, e) else null,
.last_modified = if (last_modified) |lm| try arena.dupe(u8, lm) else null,
.headers = &.{},
};
}