From 470f5b5029bccdf77494abb717f1a07ae150607a Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 21 Nov 2025 20:22:24 +0800 Subject: [PATCH] Headers and improved Request --- src/browser/js/bridge.zig | 1 + src/browser/tests/net/headers.html | 31 +++++++++ src/browser/tests/net/request.html | 104 +++++++++++++++++++++++++++++ src/browser/webapi/net/Fetch.zig | 2 +- src/browser/webapi/net/Headers.zig | 63 +++++++++++++++++ src/browser/webapi/net/Request.zig | 78 +++++++++++++++++++++- 6 files changed, 275 insertions(+), 4 deletions(-) create mode 100644 src/browser/tests/net/headers.html create mode 100644 src/browser/tests/net/request.html create mode 100644 src/browser/webapi/net/Headers.zig diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index f9ba64f6..7acb8473 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -550,6 +550,7 @@ pub const JsApis = flattenTypes(&.{ @import("../webapi/Location.zig"), @import("../webapi/Navigator.zig"), @import("../webapi/net/FormData.zig"), + @import("../webapi/net/Headers.zig"), @import("../webapi/net/Request.zig"), @import("../webapi/net/Response.zig"), @import("../webapi/net/URLSearchParams.zig"), diff --git a/src/browser/tests/net/headers.html b/src/browser/tests/net/headers.html new file mode 100644 index 00000000..d0d1c35e --- /dev/null +++ b/src/browser/tests/net/headers.html @@ -0,0 +1,31 @@ + + + diff --git a/src/browser/tests/net/request.html b/src/browser/tests/net/request.html new file mode 100644 index 00000000..c0028cf8 --- /dev/null +++ b/src/browser/tests/net/request.html @@ -0,0 +1,104 @@ + + + + + + + + + diff --git a/src/browser/webapi/net/Fetch.zig b/src/browser/webapi/net/Fetch.zig index 7bbc2da9..547a6ab1 100644 --- a/src/browser/webapi/net/Fetch.zig +++ b/src/browser/webapi/net/Fetch.zig @@ -39,7 +39,7 @@ pub const Input = Request.Input; // @ZIGDOM just enough to get campire demo working pub fn init(input: Input, page: *Page) !js.Promise { - const request = try Request.init(input, page); + const request = try Request.init(input, null, page); const fetch = try page.arena.create(Fetch); fetch.* = .{ diff --git a/src/browser/webapi/net/Headers.zig b/src/browser/webapi/net/Headers.zig new file mode 100644 index 00000000..2f2fa68f --- /dev/null +++ b/src/browser/webapi/net/Headers.zig @@ -0,0 +1,63 @@ +const std = @import("std"); +const js = @import("../../js/js.zig"); + +const Page = @import("../../Page.zig"); +const KeyValueList = @import("../KeyValueList.zig"); + +const Headers = @This(); + +_list: KeyValueList, + +pub fn init(page: *Page) !*Headers { + return page._factory.create(Headers{ + ._list = KeyValueList.init(), + }); +} + + +pub fn append(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void { + try self._list.append(page.arena, name, value); +} + +pub fn delete(self: *Headers, name: []const u8) void { + self._list.delete(name, null); +} + +pub fn get(self: *const Headers, name: []const u8) ?[]const u8 { + return self._list.get(name); +} + +pub fn getAll(self: *const Headers, name: []const u8, page: *Page) ![]const []const u8 { + return self._list.getAll(name, page); +} + +pub fn has(self: *const Headers, name: []const u8) bool { + return self._list.has(name); +} + +pub fn set(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void { + try self._list.set(page.arena, name, value); +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(Headers); + + pub const Meta = struct { + pub const name = "Headers"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; + + pub const constructor = bridge.constructor(Headers.init, .{}); + pub const append = bridge.function(Headers.append, .{}); + pub const delete = bridge.function(Headers.delete, .{}); + pub const get = bridge.function(Headers.get, .{}); + pub const getAll = bridge.function(Headers.getAll, .{}); + pub const has = bridge.function(Headers.has, .{}); + pub const set = bridge.function(Headers.set, .{}); +}; + +const testing = @import("../../../testing.zig"); +test "WebApi: Headers" { + try testing.htmlRunner("net/headers.html", .{}); +} diff --git a/src/browser/webapi/net/Request.zig b/src/browser/webapi/net/Request.zig index d715c53b..d1524afe 100644 --- a/src/browser/webapi/net/Request.zig +++ b/src/browser/webapi/net/Request.zig @@ -22,28 +22,92 @@ const js = @import("../../js/js.zig"); const URL = @import("../URL.zig"); const Page = @import("../../Page.zig"); +const Headers = @import("Headers.zig"); const Allocator = std.mem.Allocator; const Request = @This(); _url: [:0]const u8, +_method: std.http.Method, +_headers: ?*Headers, _arena: Allocator, pub const Input = union(enum) { + request: *Request, url: [:0]const u8, - // request: *Request, TODO }; -pub fn init(input: Input, page: *Page) !*Request { +pub const Options = struct { + method: ?[]const u8 = null, + headers: ?*Headers = null, +}; + +pub fn init(input: Input, opts_: ?Options, page: *Page) !*Request { const arena = page.arena; - const url = try URL.resolve(arena, page.url, input.url, .{ .always_dupe = true }); + const url = switch (input) { + .url => |u| try URL.resolve(arena, page.url, u, .{ .always_dupe = true }), + .request => |r| try arena.dupeZ(u8, r._url), + }; + + const opts = opts_ orelse Options{}; + const method = if (opts.method) |m| + try parseMethod(m, page) + else switch (input) { + .url => .GET, + .request => |r| r._method, + }; + + const headers = if (opts.headers) |h| + h + else switch (input) { + .url => null, + .request => |r| r._headers, + }; return page._factory.create(Request{ ._url = url, ._arena = arena, + ._method = method, + ._headers = headers, }); } +fn parseMethod(method: []const u8, page: *Page) !std.http.Method { + if (method.len > "options".len) { + return error.InvalidMethod; + } + + const lower = std.ascii.lowerString(&page.buf, method); + + if (std.mem.eql(u8, lower, "get")) return .GET; + if (std.mem.eql(u8, lower, "post")) return .POST; + if (std.mem.eql(u8, lower, "delete")) return .DELETE; + if (std.mem.eql(u8, lower, "put")) return .PUT; + if (std.mem.eql(u8, lower, "patch")) return .PATCH; + if (std.mem.eql(u8, lower, "head")) return .HEAD; + if (std.mem.eql(u8, lower, "options")) return .OPTIONS; + + return error.InvalidMethod; +} + +pub fn getUrl(self: *const Request) []const u8 { + return self._url; +} + +pub fn getMethod(self: *const Request) []const u8 { + return @tagName(self._method); +} + +pub fn getHeaders(self: *Request, page: *Page) !*Headers { + if (self._headers) |headers| { + return headers; + } + + const headers = try Headers.init(page); + self._headers = headers; + return headers; +} + pub const JsApi = struct { pub const bridge = js.Bridge(Request); @@ -54,4 +118,12 @@ pub const JsApi = struct { }; pub const constructor = bridge.constructor(Request.init, .{}); + pub const url = bridge.accessor(Request.getUrl, null, .{}); + pub const method = bridge.accessor(Request.getMethod, null, .{}); + pub const headers = bridge.accessor(Request.getHeaders, null, .{}); }; + +const testing = @import("../../../testing.zig"); +test "WebApi: Request" { + try testing.htmlRunner("net/request.html", .{}); +}