proper fetch method and body setting

This commit is contained in:
Muki Kiboigo
2025-08-25 08:03:51 -07:00
parent dc2addb0ed
commit a133a71eb9
4 changed files with 73 additions and 31 deletions

View File

@@ -38,8 +38,8 @@ pub const RequestInput = union(enum) {
// https://developer.mozilla.org/en-US/docs/Web/API/RequestInit // https://developer.mozilla.org/en-US/docs/Web/API/RequestInit
pub const RequestInit = struct { pub const RequestInit = struct {
method: []const u8 = "GET", method: ?[]const u8 = null,
body: []const u8 = "", body: ?[]const u8 = null,
}; };
// https://developer.mozilla.org/en-US/docs/Web/API/Request/Request // https://developer.mozilla.org/en-US/docs/Web/API/Request/Request
@@ -47,7 +47,7 @@ const Request = @This();
method: Http.Method, method: Http.Method,
url: [:0]const u8, url: [:0]const u8,
body: []const u8, body: ?[]const u8,
pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Request { pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Request {
const arena = page.arena; const arena = page.arena;
@@ -62,15 +62,21 @@ pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Re
}, },
}; };
const method: Http.Method = blk: for (std.enums.values(Http.Method)) |method| { const method: Http.Method = blk: {
if (std.ascii.eqlIgnoreCase(options.method, @tagName(method))) { if (options.method) |given_method| {
break :blk method; for (std.enums.values(Http.Method)) |method| {
if (std.ascii.eqlIgnoreCase(given_method, @tagName(method))) {
break :blk method;
}
} else {
return error.TypeError;
}
} else {
break :blk Http.Method.GET;
} }
} else {
return error.InvalidMethod;
}; };
const body = try arena.dupe(u8, options.body); const body = if (options.body) |body| try arena.dupe(u8, body) else null;
return .{ return .{
.method = method, .method = method,
@@ -87,9 +93,9 @@ pub fn get_method(self: *const Request) []const u8 {
return @tagName(self.method); return @tagName(self.method);
} }
pub fn get_body(self: *const Request) []const u8 { // pub fn get_body(self: *const Request) ?[]const u8 {
return self.body; // return self.body;
} // }
const FetchContext = struct { const FetchContext = struct {
arena: std.mem.Allocator, arena: std.mem.Allocator,
@@ -123,13 +129,14 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi
const arena = page.arena; const arena = page.arena;
const req = try Request.constructor(input, options, page); const req = try Request.constructor(input, options, page);
const resolver = Env.PromiseResolver{ const resolver = Env.PromiseResolver{
.js_context = page.main_context, .js_context = page.main_context,
.resolver = v8.PromiseResolver.init(page.main_context.v8_context), .resolver = v8.PromiseResolver.init(page.main_context.v8_context),
}; };
const client = page.http_client; var headers = try Http.Headers.init();
const headers = try HttpClient.Headers.init(); try page.requestCookie(.{}).headersForRequest(arena, req.url, &headers);
const fetch_ctx = try arena.create(FetchContext); const fetch_ctx = try arena.create(FetchContext);
fetch_ctx.* = .{ fetch_ctx.* = .{
@@ -143,47 +150,51 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi
.url = req.url, .url = req.url,
}; };
try client.request(.{ try page.http_client.request(.{
.method = req.method, .ctx = @ptrCast(fetch_ctx),
.url = req.url, .url = req.url,
.method = req.method,
.headers = headers, .headers = headers,
.body = req.body, .body = req.body,
.cookie_jar = page.cookie_jar, .cookie_jar = page.cookie_jar,
.ctx = @ptrCast(fetch_ctx), .resource_type = .fetch,
.start_callback = struct { .start_callback = struct {
fn startCallback(transfer: *HttpClient.Transfer) !void { fn startCallback(transfer: *HttpClient.Transfer) !void {
const self: *FetchContext = @alignCast(@ptrCast(transfer.ctx)); const self: *FetchContext = @alignCast(@ptrCast(transfer.ctx));
log.debug(.http, "request start", .{ .method = self.method, .url = self.url, .source = "fetch" }); log.debug(.http, "request start", .{ .method = self.method, .url = self.url, .source = "fetch" });
self.transfer = transfer; self.transfer = transfer;
} }
}.startCallback, }.startCallback,
.header_callback = struct { .header_callback = struct {
fn headerCallback(transfer: *HttpClient.Transfer, header: []const u8) !void { fn headerCallback(transfer: *HttpClient.Transfer) !void {
const self: *FetchContext = @alignCast(@ptrCast(transfer.ctx));
try self.headers.append(self.arena, try self.arena.dupe(u8, header));
}
}.headerCallback,
.header_done_callback = struct {
fn headerDoneCallback(transfer: *HttpClient.Transfer) !void {
const self: *FetchContext = @alignCast(@ptrCast(transfer.ctx)); const self: *FetchContext = @alignCast(@ptrCast(transfer.ctx));
const header = &transfer.response_header.?; const header = &transfer.response_header.?;
log.debug(.http, "request header", .{ log.debug(.http, "request header", .{
.source = "fetch", .source = "fetch",
.method = self.method,
.url = self.url, .url = self.url,
.status = header.status, .status = header.status,
}); });
if (header.contentType()) |ct| { if (header.contentType()) |ct| {
self.mime = Mime.parse(ct) catch { self.mime = Mime.parse(ct) catch {
return error.Todo; return error.MimeParsing;
}; };
} }
var it = transfer.responseHeaderIterator();
while (it.next()) |hdr| {
const joined = try std.fmt.allocPrint(self.arena, "{s}: {s}", .{ hdr.name, hdr.value });
try self.headers.append(self.arena, joined);
}
self.status = header.status; self.status = header.status;
} }
}.headerDoneCallback, }.headerCallback,
.data_callback = struct { .data_callback = struct {
fn dataCallback(transfer: *HttpClient.Transfer, data: []const u8) !void { fn dataCallback(transfer: *HttpClient.Transfer, data: []const u8) !void {
const self: *FetchContext = @alignCast(@ptrCast(transfer.ctx)); const self: *FetchContext = @alignCast(@ptrCast(transfer.ctx));
@@ -196,6 +207,7 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi
log.info(.http, "request complete", .{ log.info(.http, "request complete", .{
.source = "fetch", .source = "fetch",
.method = self.method,
.url = self.url, .url = self.url,
.status = self.status, .status = self.status,
}); });
@@ -212,6 +224,8 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi
.error_callback = struct { .error_callback = struct {
fn errorCallback(ctx: *anyopaque, err: anyerror) void { fn errorCallback(ctx: *anyopaque, err: anyerror) void {
const self: *FetchContext = @alignCast(@ptrCast(ctx)); const self: *FetchContext = @alignCast(@ptrCast(ctx));
self.transfer = null;
const promise_resolver: Env.PromiseResolver = .{ const promise_resolver: Env.PromiseResolver = .{
.js_context = self.js_ctx, .js_context = self.js_ctx,
.resolver = self.promise_resolver.castToPromiseResolver(), .resolver = self.promise_resolver.castToPromiseResolver(),

View File

@@ -19,6 +19,9 @@
const std = @import("std"); const std = @import("std");
const URL = @import("../../url.zig").URL; const URL = @import("../../url.zig").URL;
const Page = @import("../page.zig").Page; const Page = @import("../page.zig").Page;
const Env = @import("../env.zig").Env;
const v8 = @import("v8");
const Http = @import("../../http/Http.zig"); const Http = @import("../../http/Http.zig");
const HttpClient = @import("../../http/Client.zig"); const HttpClient = @import("../../http/Client.zig");
@@ -36,13 +39,26 @@ const ResponseInput = union(enum) {
string: []const u8, string: []const u8,
}; };
pub fn constructor(input: ResponseInput, page: *Page) !Response { const ResponseOptions = struct {
status: u16 = 200,
statusText: []const u8 = "",
// List of header pairs.
headers: []const []const u8 = &[][].{},
};
pub fn constructor(_input: ?ResponseInput, page: *Page) !Response {
const arena = page.arena; const arena = page.arena;
const body = blk: switch (input) { const body = blk: {
.string => |str| { if (_input) |input| {
break :blk try arena.dupe(u8, str); switch (input) {
}, .string => |str| {
break :blk try arena.dupe(u8, str);
},
}
} else {
break :blk "";
}
}; };
return .{ return .{
@@ -55,6 +71,16 @@ pub fn get_ok(self: *const Response) bool {
return self.status >= 200 and self.status <= 299; return self.status >= 200 and self.status <= 299;
} }
pub fn _text(self: *const Response, page: *Page) !Env.Promise {
const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
};
try resolver.resolve(self.body);
return resolver.promise();
}
const testing = @import("../../testing.zig"); const testing = @import("../../testing.zig");
test "fetch: response" { test "fetch: response" {
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io" }); var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io" });

View File

@@ -200,6 +200,7 @@ pub fn requestIntercept(arena: Allocator, bc: anytype, intercept: *const Notific
.script => "Script", .script => "Script",
.xhr => "XHR", .xhr => "XHR",
.document => "Document", .document => "Document",
.fetch => "Fetch",
}, },
.networkId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}), .networkId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}),
}, .{ .session_id = session_id }); }, .{ .session_id = session_id });

View File

@@ -649,6 +649,7 @@ pub const Request = struct {
document, document,
xhr, xhr,
script, script,
fetch,
}; };
}; };