From 8285cbcaa9bd7e01290753c6391e3e41b55f3108 Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Tue, 2 Sep 2025 23:23:14 -0700 Subject: [PATCH] expand Request/Response interfaces --- src/browser/fetch/Request.zig | 73 ++++++++++++++++--- src/browser/fetch/Response.zig | 59 ++++++++++++--- src/browser/mime.zig | 16 ++++ src/browser/streams/ReadableStream.zig | 20 +++-- .../streams/ReadableStreamDefaultReader.zig | 4 +- 5 files changed, 143 insertions(+), 29 deletions(-) diff --git a/src/browser/fetch/Request.zig b/src/browser/fetch/Request.zig index 8ed64add..6f4e3751 100644 --- a/src/browser/fetch/Request.zig +++ b/src/browser/fetch/Request.zig @@ -23,8 +23,8 @@ const URL = @import("../../url.zig").URL; const Page = @import("../page.zig").Page; const Response = @import("./Response.zig"); - const Http = @import("../../http/Http.zig"); +const ReadableStream = @import("../streams/ReadableStream.zig"); const v8 = @import("v8"); const Env = @import("../env.zig").Env; @@ -37,12 +37,49 @@ pub const RequestInput = union(enum) { request: *Request, }; +pub const RequestCache = enum { + default, + @"no-store", + reload, + @"no-cache", + @"force-cache", + @"only-if-cached", + + pub fn fromString(str: []const u8) ?RequestCache { + for (std.enums.values(RequestCache)) |cache| { + if (std.ascii.eqlIgnoreCase(str, @tagName(cache))) { + return cache; + } + } else { + return null; + } + } +}; + +pub const RequestCredentials = enum { + omit, + @"same-origin", + include, + + pub fn fromString(str: []const u8) ?RequestCredentials { + for (std.enums.values(RequestCredentials)) |cache| { + if (std.ascii.eqlIgnoreCase(str, @tagName(cache))) { + return cache; + } + } else { + return null; + } + } +}; + // https://developer.mozilla.org/en-US/docs/Web/API/RequestInit pub const RequestInit = struct { - method: ?[]const u8 = null, body: ?[]const u8 = null, - integrity: ?[]const u8 = null, + cache: ?[]const u8 = null, + credentials: ?[]const u8 = null, headers: ?HeadersInit = null, + integrity: ?[]const u8 = null, + method: ?[]const u8 = null, }; // https://developer.mozilla.org/en-US/docs/Web/API/Request/Request @@ -50,6 +87,8 @@ const Request = @This(); method: Http.Method, url: [:0]const u8, +cache: RequestCache, +credentials: RequestCredentials, headers: Headers, body: ?[]const u8, body_used: bool = false, @@ -68,6 +107,12 @@ pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Re }, }; + const body = if (options.body) |body| try arena.dupe(u8, body) else null; + const cache = (if (options.cache) |cache| RequestCache.fromString(cache) else null) orelse RequestCache.default; + const credentials = (if (options.credentials) |creds| RequestCredentials.fromString(creds) else null) orelse RequestCredentials.@"same-origin"; + const integrity = if (options.integrity) |integ| try arena.dupe(u8, integ) else ""; + const headers = if (options.headers) |hdrs| try Headers.constructor(hdrs, page) else Headers{}; + const method: Http.Method = blk: { if (options.method) |given_method| { for (std.enums.values(Http.Method)) |method| { @@ -82,27 +127,33 @@ pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Re } }; - const body = if (options.body) |body| try arena.dupe(u8, body) else null; - const integrity = if (options.integrity) |integ| try arena.dupe(u8, integ) else ""; - const headers = if (options.headers) |hdrs| try Headers.constructor(hdrs, page) else Headers{}; - return .{ .method = method, .url = url, + .cache = cache, + .credentials = credentials, .headers = headers, .body = body, .integrity = integrity, }; } -// pub fn get_body(self: *const Request) ?[]const u8 { -// return self.body; -// } +pub fn get_body(self: *const Request, page: *Page) !?*ReadableStream { + if (self.body) |body| { + const stream = try ReadableStream.constructor(null, null, page); + try stream.queue.append(page.arena, body); + return stream; + } else return null; +} pub fn get_bodyUsed(self: *const Request) bool { return self.body_used; } +pub fn get_cache(self: *const Request) RequestCache { + return self.cache; +} + pub fn get_headers(self: *Request) *Headers { return &self.headers; } @@ -133,6 +184,8 @@ pub fn _clone(self: *Request, page: *Page) !Request { return Request{ .body = if (self.body) |body| try arena.dupe(u8, body) else null, .body_used = self.body_used, + .cache = self.cache, + .credentials = self.credentials, .headers = try self.headers.clone(arena), .method = self.method, .integrity = try arena.dupe(u8, self.integrity), diff --git a/src/browser/fetch/Response.zig b/src/browser/fetch/Response.zig index 14ae2e17..21a950de 100644 --- a/src/browser/fetch/Response.zig +++ b/src/browser/fetch/Response.zig @@ -24,6 +24,11 @@ const v8 = @import("v8"); const HttpClient = @import("../../http/Client.zig"); const Http = @import("../../http/Http.zig"); const URL = @import("../../url.zig").URL; + +const ReadableStream = @import("../streams/ReadableStream.zig"); +const Headers = @import("Headers.zig"); +const HeadersInit = @import("Headers.zig").HeadersInit; + const Env = @import("../env.zig").Env; const Mime = @import("../mime.zig").Mime; const Page = @import("../page.zig").Page; @@ -32,26 +37,28 @@ const Page = @import("../page.zig").Page; const Response = @This(); status: u16 = 0, -headers: []const []const u8, +headers: Headers = .{}, mime: ?Mime = null, -body: []const u8, +url: []const u8 = "", +body: []const u8 = "", body_used: bool = false, redirected: bool = false, -const ResponseInput = union(enum) { +const ResponseBody = union(enum) { string: []const u8, }; const ResponseOptions = struct { status: u16 = 200, statusText: []const u8 = "", - // List of header pairs. - headers: []const []const u8 = &[][].{}, + headers: ?HeadersInit = null, }; -pub fn constructor(_input: ?ResponseInput, page: *Page) !Response { +pub fn constructor(_input: ?ResponseBody, _options: ?ResponseOptions, page: *Page) !Response { const arena = page.arena; + const options: ResponseOptions = _options orelse .{}; + const body = blk: { if (_input) |input| { switch (input) { @@ -64,20 +71,32 @@ pub fn constructor(_input: ?ResponseInput, page: *Page) !Response { } }; + const headers: Headers = if (options.headers) |hdrs| try Headers.constructor(hdrs, page) else .{}; + return .{ .body = body, - .headers = &[_][]const u8{}, + .headers = headers, }; } -pub fn get_ok(self: *const Response) bool { - return self.status >= 200 and self.status <= 299; +pub fn get_body(self: *const Response, page: *Page) !*ReadableStream { + const stream = try ReadableStream.constructor(null, null, page); + try stream.queue.append(page.arena, self.body); + return stream; } pub fn get_bodyUsed(self: *const Response) bool { return self.body_used; } +pub fn get_headers(self: *Response) *Headers { + return &self.headers; +} + +pub fn get_ok(self: *const Response) bool { + return self.status >= 200 and self.status <= 299; +} + pub fn get_redirected(self: *const Response) bool { return self.redirected; } @@ -86,6 +105,28 @@ pub fn get_status(self: *const Response) u16 { return self.status; } +pub fn get_url(self: *const Response) []const u8 { + return self.url; +} + +pub fn _clone(self: *const Response, page: *Page) !Response { + if (self.body_used) { + return error.TypeError; + } + + const arena = page.arena; + + return Response{ + .body = try arena.dupe(u8, self.body), + .body_used = self.body_used, + .mime = if (self.mime) |mime| try mime.clone(arena) else null, + .headers = try self.headers.clone(arena), + .redirected = self.redirected, + .status = self.status, + .url = try arena.dupe(u8, self.url), + }; +} + pub fn _bytes(self: *Response, page: *Page) !Env.Promise { if (self.body_used) { return error.TypeError; diff --git a/src/browser/mime.zig b/src/browser/mime.zig index 33ab9958..468ceb36 100644 --- a/src/browser/mime.zig +++ b/src/browser/mime.zig @@ -290,6 +290,22 @@ pub const Mime = struct { fn trimRight(s: []const u8) []const u8 { return std.mem.trimRight(u8, s, &std.ascii.whitespace); } + + pub fn clone(self: *const Mime, allocator: Allocator) !Mime { + return Mime{ + .content_type = blk: { + switch (self.content_type) { + .other => |data| break :blk ContentType{ .other = .{ + .type = try allocator.dupe(u8, data.type), + .sub_type = try allocator.dupe(u8, data.sub_type), + } }, + else => break :blk self.content_type, + } + }, + .params = try allocator.dupe(u8, self.params), + .charset = if (self.charset) |charset| try allocator.dupeZ(u8, charset) else null, + }; + } }; const testing = @import("../testing.zig"); diff --git a/src/browser/streams/ReadableStream.zig b/src/browser/streams/ReadableStream.zig index bcb42952..a25fd635 100644 --- a/src/browser/streams/ReadableStream.zig +++ b/src/browser/streams/ReadableStream.zig @@ -34,11 +34,10 @@ const State = union(enum) { }; // This promise resolves when a stream is canceled. -cancel_resolver: Env.PromiseResolver, +cancel_resolver: v8.Persistent(v8.PromiseResolver), locked: bool = false, state: State = .readable, -// A queue would be ideal here but I don't want to pay the cost of the priority operation. queue: std.ArrayListUnmanaged([]const u8) = .empty, const UnderlyingSource = struct { @@ -56,10 +55,10 @@ const QueueingStrategy = struct { pub fn constructor(underlying: ?UnderlyingSource, strategy: ?QueueingStrategy, page: *Page) !*ReadableStream { _ = strategy; - const cancel_resolver = Env.PromiseResolver{ - .js_context = page.main_context, - .resolver = v8.PromiseResolver.init(page.main_context.v8_context), - }; + const cancel_resolver = v8.Persistent(v8.PromiseResolver).init( + page.main_context.isolate, + v8.PromiseResolver.init(page.main_context.v8_context), + ); const stream = try page.arena.create(ReadableStream); stream.* = ReadableStream{ .cancel_resolver = cancel_resolver }; @@ -76,8 +75,13 @@ pub fn constructor(underlying: ?UnderlyingSource, strategy: ?QueueingStrategy, p return stream; } -pub fn _cancel(self: *const ReadableStream) Env.Promise { - return self.cancel_resolver.promise(); +pub fn _cancel(self: *const ReadableStream, page: *Page) Env.Promise { + const resolver = Env.PromiseResolver{ + .js_context = page.main_context, + .resolver = self.cancel_resolver.castToPromiseResolver(), + }; + + return resolver.promise(); } pub fn get_locked(self: *const ReadableStream) bool { diff --git a/src/browser/streams/ReadableStreamDefaultReader.zig b/src/browser/streams/ReadableStreamDefaultReader.zig index a824cdd6..b3fd560f 100644 --- a/src/browser/streams/ReadableStreamDefaultReader.zig +++ b/src/browser/streams/ReadableStreamDefaultReader.zig @@ -47,8 +47,8 @@ pub fn get_closed(self: *const ReadableStreamDefaultReader) Env.Promise { return self.closed_resolver.promise(); } -pub fn _cancel(self: *ReadableStreamDefaultReader) Env.Promise { - return self.stream._cancel(); +pub fn _cancel(self: *ReadableStreamDefaultReader, page: *Page) Env.Promise { + return self.stream._cancel(page); } pub const ReadableStreamReadResult = struct {