From 6b001c50a426ad1a07c55fca5220c25d6394e62e Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 20 Aug 2025 19:32:19 +0800 Subject: [PATCH] Emits a http_request_done internal notification. With networking enabled, CDP listens to this event and emits a `Network.loadingFinished` event. This is event is used by puppeteer to know that details about the response (i.e. the body) can be queries. Added dummy handling for the Network.getResponseBody message. Returns an empty body. Needed because we emit the loadingFinished event which signals to drivers that they can ask for the body. --- src/cdp/cdp.zig | 8 ++++++++ src/cdp/domains/network.zig | 31 +++++++++++++++++++++++++++++++ src/http/Client.zig | 17 +++++++++++++++-- src/notification.zig | 6 ++++++ 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 1d7f19be..f0d3b682 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -455,12 +455,14 @@ pub fn BrowserContext(comptime CDP_T: type) type { try self.cdp.browser.notification.register(.http_request_fail, self, onHttpRequestFail); try self.cdp.browser.notification.register(.http_request_start, self, onHttpRequestStart); try self.cdp.browser.notification.register(.http_headers_done, self, onHttpHeadersDone); + try self.cdp.browser.notification.register(.http_request_done, self, onHttpRequestDone); } pub fn networkDisable(self: *Self) void { self.cdp.browser.notification.unregister(.http_request_fail, self); self.cdp.browser.notification.unregister(.http_request_start, self); self.cdp.browser.notification.unregister(.http_headers_done, self); + self.cdp.browser.notification.unregister(.http_request_done, self); } pub fn fetchEnable(self: *Self) !void { @@ -516,6 +518,12 @@ pub fn BrowserContext(comptime CDP_T: type) type { return @import("domains/network.zig").httpHeadersDone(self.notification_arena, self, data); } + pub fn onHttpRequestDone(ctx: *anyopaque, data: *const Notification.RequestDone) !void { + const self: *Self = @alignCast(@ptrCast(ctx)); + defer self.resetNotificationArena(); + return @import("domains/network.zig").httpRequestDone(self.notification_arena, self, data); + } + fn resetNotificationArena(self: *Self) void { defer _ = self.cdp.notification_arena.reset(.{ .retain_with_limit = 1024 * 64 }); } diff --git a/src/cdp/domains/network.zig b/src/cdp/domains/network.zig index 81329ff0..a9c39b60 100644 --- a/src/cdp/domains/network.zig +++ b/src/cdp/domains/network.zig @@ -36,6 +36,7 @@ pub fn processMessage(cmd: anytype) !void { setCookie, setCookies, getCookies, + getResponseBody, }, cmd.input.action) orelse return error.UnknownMethod; switch (action) { @@ -49,6 +50,7 @@ pub fn processMessage(cmd: anytype) !void { .setCookie => return setCookie(cmd), .setCookies => return setCookies(cmd), .getCookies => return getCookies(cmd), + .getResponseBody => return getResponseBody(cmd), } } @@ -202,6 +204,19 @@ fn getCookies(cmd: anytype) !void { try cmd.sendResult(.{ .cookies = writer }, .{}); } +fn getResponseBody(cmd: anytype) !void { + const params = (try cmd.params(struct { + requestId: []const u8, // "REQ-{d}" + })) orelse return error.InvalidParams; + + _ = params; + + try cmd.sendResult(.{ + .body = "TODO", + .base64Encoded = false, + }, .{}); +} + pub fn httpRequestFail(arena: Allocator, bc: anytype, data: *const Notification.RequestFail) !void { // It's possible that the request failed because we aborted when the client // sent Target.closeTarget. In that case, bc.session_id will be cleared @@ -264,6 +279,22 @@ pub fn httpHeadersDone(arena: Allocator, bc: anytype, data: *const Notification. }, .{ .session_id = session_id }); } +pub fn httpRequestDone(arena: Allocator, bc: anytype, data: *const Notification.RequestDone) !void { + // Isn't possible to do a network request within a Browser (which our + // notification is tied to), without a page. + std.debug.assert(bc.session.page != null); + + var cdp = bc.cdp; + + // all unreachable because we _have_ to have a page. + const session_id = bc.session_id orelse unreachable; + + try cdp.sendEvent("Network.loadingFinished", .{ + .requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{data.transfer.id}), + .encodedDataLength = data.transfer.bytes_received, + }, .{ .session_id = session_id }); +} + pub const TransferAsRequestWriter = struct { transfer: *Transfer, diff --git a/src/http/Client.zig b/src/http/Client.zig index b2182222..883eb774 100644 --- a/src/http/Client.zig +++ b/src/http/Client.zig @@ -376,8 +376,14 @@ fn perform(self: *Client, timeout_ms: c_int) !void { // transfer isn't valid at this point, don't use it. log.err(.http, "done_callback", .{ .err = err }); self.requestFailed(transfer, err); + continue; }; - // self.requestComplete(transfer); + + if (transfer.client.notification) |notification| { + notification.dispatch(.http_request_done, &.{ + .transfer = transfer, + }); + } } else |err| { self.requestFailed(transfer, err); } @@ -552,11 +558,15 @@ pub const Transfer = struct { uri: std.Uri, // used for setting/getting the cookie ctx: *anyopaque, // copied from req.ctx to make it easier for callback handlers client: *Client, - _notified_fail: bool = false, + // total bytes received in the response, including the response status line, + // the headers, and the [encoded] body. + bytes_received: usize = 0, // We'll store the response header here response_header: ?ResponseHeader = null, + _notified_fail: bool = false, + _handle: ?*Handle = null, _redirecting: bool = false, @@ -716,9 +726,11 @@ pub const Transfer = struct { .url = url, .status = status, }; + transfer.bytes_received = buf_len; return buf_len; } + transfer.bytes_received += buf_len; if (buf_len == 2) { if (getResponseHeader(easy, "content-type", 0)) |ct| { var hdr = &transfer.response_header.?; @@ -777,6 +789,7 @@ pub const Transfer = struct { return chunk_len; } + transfer.bytes_received += chunk_len; transfer.req.data_callback(transfer, buffer[0..chunk_len]) catch |err| { log.err(.http, "data_callback", .{ .err = err, .req = transfer }); return c.CURL_WRITEFUNC_ERROR; diff --git a/src/notification.zig b/src/notification.zig index c499c584..0ed650dc 100644 --- a/src/notification.zig +++ b/src/notification.zig @@ -64,6 +64,7 @@ pub const Notification = struct { http_request_start: List = .{}, http_request_intercept: List = .{}, http_headers_done: List = .{}, + http_request_done: List = .{}, notification_created: List = .{}, }; @@ -76,6 +77,7 @@ pub const Notification = struct { http_request_start: *const RequestStart, http_request_intercept: *const RequestIntercept, http_headers_done: *const ResponseHeadersDone, + http_request_done: *const RequestDone, notification_created: *Notification, }; const EventType = std.meta.FieldEnum(Events); @@ -106,6 +108,10 @@ pub const Notification = struct { transfer: *Transfer, }; + pub const RequestDone = struct { + transfer: *Transfer, + }; + pub const RequestFail = struct { transfer: *Transfer, err: anyerror,