diff --git a/src/browser/fetch/Request.zig b/src/browser/fetch/Request.zig index 0401dcc7..95b3eba1 100644 --- a/src/browser/fetch/Request.zig +++ b/src/browser/fetch/Request.zig @@ -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(), diff --git a/src/browser/fetch/Response.zig b/src/browser/fetch/Response.zig index 32da75c6..159dfbf4 100644 --- a/src/browser/fetch/Response.zig +++ b/src/browser/fetch/Response.zig @@ -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" }); diff --git a/src/cdp/domains/fetch.zig b/src/cdp/domains/fetch.zig index ea7d225a..dbe055ae 100644 --- a/src/cdp/domains/fetch.zig +++ b/src/cdp/domains/fetch.zig @@ -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 }); diff --git a/src/http/Client.zig b/src/http/Client.zig index 11e8512d..07a33390 100644 --- a/src/http/Client.zig +++ b/src/http/Client.zig @@ -649,6 +649,7 @@ pub const Request = struct { document, xhr, script, + fetch, }; };