mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-21 20:24:42 +00:00
cache headers along with response
This commit is contained in:
@@ -33,6 +33,7 @@ const RobotStore = Robots.RobotStore;
|
|||||||
const WebBotAuth = @import("../network/WebBotAuth.zig");
|
const WebBotAuth = @import("../network/WebBotAuth.zig");
|
||||||
|
|
||||||
const Cache = @import("../network/cache/Cache.zig");
|
const Cache = @import("../network/cache/Cache.zig");
|
||||||
|
const CacheMetadata = Cache.CachedMetadata;
|
||||||
const CachedResponse = Cache.CachedResponse;
|
const CachedResponse = Cache.CachedResponse;
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
@@ -907,6 +908,23 @@ fn processMessages(self: *Client) !bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allocator = transfer.arena.allocator();
|
||||||
|
var header_list: std.ArrayList(Net.Header) = .empty;
|
||||||
|
|
||||||
|
var it = transfer.responseHeaderIterator();
|
||||||
|
while (it.next()) |hdr| {
|
||||||
|
header_list.append(
|
||||||
|
allocator,
|
||||||
|
.{
|
||||||
|
.name = try allocator.dupe(u8, hdr.name),
|
||||||
|
.value = try allocator.dupe(u8, hdr.value),
|
||||||
|
},
|
||||||
|
) catch |err| {
|
||||||
|
log.warn(.http, "cache header collect failed", .{ .err = err });
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// release it ASAP so that it's available; some done_callbacks
|
// release it ASAP so that it's available; some done_callbacks
|
||||||
// will load more resources.
|
// will load more resources.
|
||||||
self.endTransfer(transfer);
|
self.endTransfer(transfer);
|
||||||
@@ -941,37 +959,36 @@ fn processMessages(self: *Client) !bool {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (self.network.cache) |*cache| {
|
cache: {
|
||||||
var headers = &transfer.response_header.?;
|
if (self.network.cache) |*cache| {
|
||||||
|
const headers = &transfer.response_header.?;
|
||||||
|
|
||||||
if (transfer.req.method == .GET and headers.status == 200) {
|
const metadata = try CacheMetadata.fromHeaders(
|
||||||
cache.put(
|
|
||||||
transfer.req.url,
|
transfer.req.url,
|
||||||
.{
|
headers.status,
|
||||||
.url = transfer.req.url,
|
std.time.timestamp(),
|
||||||
.content_type = headers.contentType() orelse "application/octet-stream",
|
header_list.items,
|
||||||
.status = headers.status,
|
) orelse break :cache;
|
||||||
.stored_at = std.time.timestamp(),
|
|
||||||
.age_at_store = 0,
|
// TODO: Support Vary Keying
|
||||||
.max_age = 3600,
|
const cache_key = transfer.req.url;
|
||||||
.etag = null,
|
|
||||||
.last_modified = null,
|
log.err(.browser, "http cache", .{ .key = cache_key, .metadata = metadata });
|
||||||
.must_revalidate = false,
|
|
||||||
.no_cache = false,
|
cache.put(
|
||||||
.immutable = false,
|
cache_key,
|
||||||
.vary = null,
|
metadata,
|
||||||
},
|
|
||||||
transfer.body.items,
|
transfer.body.items,
|
||||||
) catch |err| log.warn(.http, "cache put failed", .{ .err = err });
|
) 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.put", .{ .url = transfer.req.url });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transfer.req.notification.dispatch(.http_request_done, &.{
|
|
||||||
.transfer = transfer,
|
|
||||||
});
|
|
||||||
processed = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transfer.req.notification.dispatch(.http_request_done, &.{
|
||||||
|
.transfer = transfer,
|
||||||
|
});
|
||||||
|
processed = true;
|
||||||
}
|
}
|
||||||
return processed;
|
return processed;
|
||||||
}
|
}
|
||||||
@@ -1133,8 +1150,7 @@ pub const Response = struct {
|
|||||||
pub fn headerIterator(self: Response) HeaderIterator {
|
pub fn headerIterator(self: Response) HeaderIterator {
|
||||||
return switch (self.inner) {
|
return switch (self.inner) {
|
||||||
.live => |live| live.responseHeaderIterator(),
|
.live => |live| live.responseHeaderIterator(),
|
||||||
// TODO: Cache HTTP Headers
|
.cached => |c| HeaderIterator{ .list = .{ .list = c.metadata.headers } },
|
||||||
.cached => unreachable,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
111
src/network/cache/Cache.zig
vendored
111
src/network/cache/Cache.zig
vendored
@@ -17,6 +17,7 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Http = @import("../http.zig");
|
||||||
const FsCache = @import("FsCache.zig");
|
const FsCache = @import("FsCache.zig");
|
||||||
|
|
||||||
/// A browser-wide cache for resources across the network.
|
/// A browser-wide cache for resources across the network.
|
||||||
@@ -39,6 +40,55 @@ pub fn put(self: *Cache, key: []const u8, metadata: CachedMetadata, body: []cons
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const CacheControl = struct {
|
||||||
|
max_age: ?u64 = null,
|
||||||
|
must_revalidate: bool = false,
|
||||||
|
no_cache: bool = false,
|
||||||
|
no_store: bool = false,
|
||||||
|
immutable: bool = false,
|
||||||
|
|
||||||
|
pub fn parse(value: []const u8) CacheControl {
|
||||||
|
var cc: CacheControl = .{};
|
||||||
|
|
||||||
|
var iter = std.mem.splitScalar(u8, value, ',');
|
||||||
|
while (iter.next()) |part| {
|
||||||
|
const directive = std.mem.trim(u8, part, &std.ascii.whitespace);
|
||||||
|
if (std.ascii.eqlIgnoreCase(directive, "no-store")) {
|
||||||
|
cc.no_store = true;
|
||||||
|
} else if (std.ascii.eqlIgnoreCase(directive, "no-cache")) {
|
||||||
|
cc.no_cache = true;
|
||||||
|
} else if (std.ascii.eqlIgnoreCase(directive, "must-revalidate")) {
|
||||||
|
cc.must_revalidate = true;
|
||||||
|
} else if (std.ascii.eqlIgnoreCase(directive, "immutable")) {
|
||||||
|
cc.immutable = true;
|
||||||
|
} else if (std.ascii.startsWithIgnoreCase(directive, "max-age=")) {
|
||||||
|
cc.max_age = std.fmt.parseInt(u64, directive[8..], 10) catch null;
|
||||||
|
} else if (std.ascii.startsWithIgnoreCase(directive, "s-maxage=")) {
|
||||||
|
// s-maxage takes precedence over max-age
|
||||||
|
cc.max_age = std.fmt.parseInt(u64, directive[9..], 10) catch cc.max_age;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cc;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Vary = union(enum) {
|
||||||
|
wildcard: void,
|
||||||
|
value: []const u8,
|
||||||
|
|
||||||
|
pub fn parse(value: []const u8) Vary {
|
||||||
|
if (std.mem.eql(u8, value, "*")) return .wildcard;
|
||||||
|
return .{ .value = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toString(self: Vary) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.wildcard => "*",
|
||||||
|
.value => |v| v,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const CachedMetadata = struct {
|
pub const CachedMetadata = struct {
|
||||||
url: [:0]const u8,
|
url: [:0]const u8,
|
||||||
content_type: []const u8,
|
content_type: []const u8,
|
||||||
@@ -52,17 +102,74 @@ pub const CachedMetadata = struct {
|
|||||||
etag: ?[]const u8,
|
etag: ?[]const u8,
|
||||||
// for If-Modified-Since
|
// for If-Modified-Since
|
||||||
last_modified: ?[]const u8,
|
last_modified: ?[]const u8,
|
||||||
|
// If non-null, must be incorporated into cache key.
|
||||||
|
vary: ?[]const u8,
|
||||||
|
|
||||||
must_revalidate: bool,
|
must_revalidate: bool,
|
||||||
no_cache: bool,
|
no_cache: bool,
|
||||||
immutable: bool,
|
immutable: bool,
|
||||||
|
|
||||||
// If non-null, must be incorporated into cache key.
|
headers: []const Http.Header,
|
||||||
vary: ?[]const u8,
|
|
||||||
|
pub fn fromHeaders(
|
||||||
|
url: [:0]const u8,
|
||||||
|
status: u16,
|
||||||
|
timestamp: i64,
|
||||||
|
headers: []const Http.Header,
|
||||||
|
) !?CachedMetadata {
|
||||||
|
var cc: CacheControl = .{};
|
||||||
|
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";
|
||||||
|
|
||||||
|
for (headers) |hdr| {
|
||||||
|
if (std.ascii.eqlIgnoreCase(hdr.name, "cache-control")) {
|
||||||
|
cc = CacheControl.parse(hdr.value);
|
||||||
|
} 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);
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.url = url,
|
||||||
|
.content_type = content_type,
|
||||||
|
.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,
|
||||||
|
.headers = headers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deinit(self: CachedMetadata, allocator: std.mem.Allocator) void {
|
pub fn deinit(self: CachedMetadata, allocator: std.mem.Allocator) void {
|
||||||
allocator.free(self.url);
|
allocator.free(self.url);
|
||||||
allocator.free(self.content_type);
|
allocator.free(self.content_type);
|
||||||
|
for (self.headers) |header| {
|
||||||
|
allocator.free(header.name);
|
||||||
|
allocator.free(header.value);
|
||||||
|
}
|
||||||
|
allocator.free(self.headers);
|
||||||
if (self.etag) |e| allocator.free(e);
|
if (self.etag) |e| allocator.free(e);
|
||||||
if (self.last_modified) |lm| allocator.free(lm);
|
if (self.last_modified) |lm| allocator.free(lm);
|
||||||
if (self.vary) |v| allocator.free(v);
|
if (self.vary) |v| allocator.free(v);
|
||||||
|
|||||||
34
src/network/cache/FsCache.zig
vendored
34
src/network/cache/FsCache.zig
vendored
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Cache = @import("Cache.zig");
|
const Cache = @import("Cache.zig");
|
||||||
|
const Http = @import("../http.zig");
|
||||||
const CachedMetadata = Cache.CachedMetadata;
|
const CachedMetadata = Cache.CachedMetadata;
|
||||||
const CachedResponse = Cache.CachedResponse;
|
const CachedResponse = Cache.CachedResponse;
|
||||||
|
|
||||||
@@ -73,6 +74,12 @@ fn serializeMeta(writer: *std.Io.Writer, meta: *const CachedMetadata) !void {
|
|||||||
meta.immutable,
|
meta.immutable,
|
||||||
});
|
});
|
||||||
try writer.flush();
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserializeMetaOptionalString(bytes: []const u8) ?[]const u8 {
|
fn deserializeMetaOptionalString(bytes: []const u8) ?[]const u8 {
|
||||||
@@ -145,6 +152,32 @@ fn deserializeMeta(allocator: std.mem.Allocator, file: std.fs.File) !CachedMetad
|
|||||||
break :blk try deserializeMetaBoolean(line);
|
break :blk try deserializeMetaBoolean(line);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const headers = blk: {
|
||||||
|
const line = try reader.takeDelimiter('\n') orelse return error.Malformed;
|
||||||
|
const count = std.fmt.parseInt(usize, line, 10) catch return error.Malformed;
|
||||||
|
|
||||||
|
const hdrs = try allocator.alloc(Http.Header, count);
|
||||||
|
errdefer allocator.free(hdrs);
|
||||||
|
|
||||||
|
for (hdrs) |*hdr| {
|
||||||
|
const name = try reader.takeDelimiter('\n') orelse return error.Malformed;
|
||||||
|
const value = try reader.takeDelimiter('\n') orelse return error.Malformed;
|
||||||
|
hdr.* = .{
|
||||||
|
.name = try allocator.dupe(u8, name),
|
||||||
|
.value = try allocator.dupe(u8, value),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
break :blk hdrs;
|
||||||
|
};
|
||||||
|
errdefer {
|
||||||
|
for (headers) |hdr| {
|
||||||
|
allocator.free(hdr.name);
|
||||||
|
allocator.free(hdr.value);
|
||||||
|
}
|
||||||
|
allocator.free(headers);
|
||||||
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.url = url,
|
.url = url,
|
||||||
.content_type = content_type,
|
.content_type = content_type,
|
||||||
@@ -158,6 +191,7 @@ fn deserializeMeta(allocator: std.mem.Allocator, file: std.fs.File) !CachedMetad
|
|||||||
.no_cache = no_cache,
|
.no_cache = no_cache,
|
||||||
.immutable = immutable,
|
.immutable = immutable,
|
||||||
.vary = vary,
|
.vary = vary,
|
||||||
|
.headers = headers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user