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
pub const RequestInit = struct {
method: []const u8 = "GET",
body: []const u8 = "",
method: ?[]const u8 = null,
body: ?[]const u8 = null,
};
// https://developer.mozilla.org/en-US/docs/Web/API/Request/Request
@@ -47,7 +47,7 @@ const Request = @This();
method: Http.Method,
url: [:0]const u8,
body: []const u8,
body: ?[]const u8,
pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Request {
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| {
if (std.ascii.eqlIgnoreCase(options.method, @tagName(method))) {
break :blk method;
const method: Http.Method = blk: {
if (options.method) |given_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 .{
.method = method,
@@ -87,9 +93,9 @@ pub fn get_method(self: *const Request) []const u8 {
return @tagName(self.method);
}
pub fn get_body(self: *const Request) []const u8 {
return self.body;
}
// pub fn get_body(self: *const Request) ?[]const u8 {
// return self.body;
// }
const FetchContext = struct {
arena: std.mem.Allocator,
@@ -123,13 +129,14 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi
const arena = page.arena;
const req = try Request.constructor(input, options, page);
const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
};
const client = page.http_client;
const headers = try HttpClient.Headers.init();
var headers = try Http.Headers.init();
try page.requestCookie(.{}).headersForRequest(arena, req.url, &headers);
const fetch_ctx = try arena.create(FetchContext);
fetch_ctx.* = .{
@@ -143,47 +150,51 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi
.url = req.url,
};
try client.request(.{
.method = req.method,
try page.http_client.request(.{
.ctx = @ptrCast(fetch_ctx),
.url = req.url,
.method = req.method,
.headers = headers,
.body = req.body,
.cookie_jar = page.cookie_jar,
.ctx = @ptrCast(fetch_ctx),
.resource_type = .fetch,
.start_callback = struct {
fn startCallback(transfer: *HttpClient.Transfer) !void {
const self: *FetchContext = @alignCast(@ptrCast(transfer.ctx));
log.debug(.http, "request start", .{ .method = self.method, .url = self.url, .source = "fetch" });
self.transfer = transfer;
}
}.startCallback,
.header_callback = struct {
fn headerCallback(transfer: *HttpClient.Transfer, header: []const u8) !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 {
fn headerCallback(transfer: *HttpClient.Transfer) !void {
const self: *FetchContext = @alignCast(@ptrCast(transfer.ctx));
const header = &transfer.response_header.?;
log.debug(.http, "request header", .{
.source = "fetch",
.method = self.method,
.url = self.url,
.status = header.status,
});
if (header.contentType()) |ct| {
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;
}
}.headerDoneCallback,
}.headerCallback,
.data_callback = struct {
fn dataCallback(transfer: *HttpClient.Transfer, data: []const u8) !void {
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", .{
.source = "fetch",
.method = self.method,
.url = self.url,
.status = self.status,
});
@@ -212,6 +224,8 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi
.error_callback = struct {
fn errorCallback(ctx: *anyopaque, err: anyerror) void {
const self: *FetchContext = @alignCast(@ptrCast(ctx));
self.transfer = null;
const promise_resolver: Env.PromiseResolver = .{
.js_context = self.js_ctx,
.resolver = self.promise_resolver.castToPromiseResolver(),

View File

@@ -19,6 +19,9 @@
const std = @import("std");
const URL = @import("../../url.zig").URL;
const Page = @import("../page.zig").Page;
const Env = @import("../env.zig").Env;
const v8 = @import("v8");
const Http = @import("../../http/Http.zig");
const HttpClient = @import("../../http/Client.zig");
@@ -36,13 +39,26 @@ const ResponseInput = union(enum) {
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 body = blk: switch (input) {
.string => |str| {
break :blk try arena.dupe(u8, str);
},
const body = blk: {
if (_input) |input| {
switch (input) {
.string => |str| {
break :blk try arena.dupe(u8, str);
},
}
} else {
break :blk "";
}
};
return .{
@@ -55,6 +71,16 @@ pub fn get_ok(self: *const Response) bool {
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");
test "fetch: response" {
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",
.xhr => "XHR",
.document => "Document",
.fetch => "Fetch",
},
.networkId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}),
}, .{ .session_id = session_id });

View File

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