Skip to content

Commit c0106a2

Browse files
committed
http_headers_done_receiving
1 parent f6c68e4 commit c0106a2

File tree

5 files changed

+101
-27
lines changed

5 files changed

+101
-27
lines changed

src/cdp/cdp.zig

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const Page = @import("../browser/page.zig").Page;
2929
const Inspector = @import("../browser/env.zig").Env.Inspector;
3030
const Incrementing = @import("../id.zig").Incrementing;
3131
const Notification = @import("../notification.zig").Notification;
32+
const NetworkState = @import("domains/network.zig").NetworkState;
3233
const InterceptState = @import("domains/fetch.zig").InterceptState;
3334

3435
const polyfill = @import("../browser/polyfill/polyfill.zig");
@@ -76,6 +77,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
7677
// Extra headers to add to all requests. TBD under which conditions this should be reset.
7778
extra_headers: std.ArrayListUnmanaged(std.http.Header) = .empty,
7879

80+
network_state: NetworkState,
7981
intercept_state: InterceptState,
8082

8183
const Self = @This();
@@ -92,6 +94,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
9294
.browser_context = null,
9395
.message_arena = std.heap.ArenaAllocator.init(allocator),
9496
.notification_arena = std.heap.ArenaAllocator.init(allocator),
97+
.network_state = try NetworkState.init(allocator),
9598
.intercept_state = try InterceptState.init(allocator), // TBD or browser session arena?
9699
};
97100
}
@@ -101,6 +104,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
101104
bc.deinit();
102105
}
103106
self.intercept_state.deinit(); // TBD Should this live in BC?
107+
self.network_state.deinit();
104108
self.browser.deinit();
105109
self.message_arena.deinit();
106110
self.notification_arena.deinit();
@@ -447,13 +451,15 @@ pub fn BrowserContext(comptime CDP_T: type) type {
447451
pub fn networkEnable(self: *Self) !void {
448452
try self.cdp.browser.notification.register(.http_request_fail, self, onHttpRequestFail);
449453
try self.cdp.browser.notification.register(.http_request_start, self, onHttpRequestStart);
450-
try self.cdp.browser.notification.register(.http_request_complete, self, onHttpRequestComplete);
454+
try self.cdp.browser.notification.register(.http_header_received, self, onHttpHeaderReceived);
455+
try self.cdp.browser.notification.register(.http_headers_done_receiving, self, onHttpHeadersDoneReceiving);
451456
}
452457

453458
pub fn networkDisable(self: *Self) void {
454459
self.cdp.browser.notification.unregister(.http_request_fail, self);
455460
self.cdp.browser.notification.unregister(.http_request_start, self);
456-
self.cdp.browser.notification.unregister(.http_request_complete, self);
461+
self.cdp.browser.notification.unregister(.http_header_received, self);
462+
self.cdp.browser.notification.unregister(.http_headers_done_receiving, self);
457463
}
458464

459465
pub fn fetchEnable(self: *Self) !void {
@@ -466,7 +472,8 @@ pub fn BrowserContext(comptime CDP_T: type) type {
466472

467473
pub fn onPageRemove(ctx: *anyopaque, _: Notification.PageRemove) !void {
468474
const self: *Self = @alignCast(@ptrCast(ctx));
469-
return @import("domains/page.zig").pageRemove(self);
475+
try @import("domains/page.zig").pageRemove(self);
476+
try @import("domains/network.zig").pageRemove(self);
470477
}
471478

472479
pub fn onPageCreated(ctx: *anyopaque, page: *Page) !void {
@@ -497,16 +504,22 @@ pub fn BrowserContext(comptime CDP_T: type) type {
497504
try @import("domains/fetch.zig").requestPaused(self.notification_arena, self, data);
498505
}
499506

507+
pub fn onHttpHeaderReceived(ctx: *anyopaque, data: *const Notification.ResponseHeader) !void {
508+
const self: *Self = @alignCast(@ptrCast(ctx));
509+
defer self.resetNotificationArena();
510+
try self.cdp.network_state.putOrAppendReceivedHeader(data.request_id, data.status, data.header);
511+
}
512+
500513
pub fn onHttpRequestFail(ctx: *anyopaque, data: *const Notification.RequestFail) !void {
501514
const self: *Self = @alignCast(@ptrCast(ctx));
502515
defer self.resetNotificationArena();
503516
return @import("domains/network.zig").httpRequestFail(self.notification_arena, self, data);
504517
}
505518

506-
pub fn onHttpRequestComplete(ctx: *anyopaque, data: *const Notification.RequestComplete) !void {
519+
pub fn onHttpHeadersDoneReceiving(ctx: *anyopaque, data: *const Notification.ResponseHeadersDone) !void {
507520
const self: *Self = @alignCast(@ptrCast(ctx));
508521
defer self.resetNotificationArena();
509-
return @import("domains/network.zig").httpRequestComplete(self.notification_arena, self, data);
522+
return @import("domains/network.zig").httpHeadersDoneReceiving(self.notification_arena, self, data);
510523
}
511524

512525
fn resetNotificationArena(self: *Self) void {

src/cdp/domains/network.zig

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,49 @@ pub fn processMessage(cmd: anytype) !void {
5151
}
5252
}
5353

54+
const Response = struct {
55+
status: u16,
56+
headers: std.StringArrayHashMapUnmanaged([]const u8) = .empty, // These may not be complete yet, but we only tell the client Network.responseReceived when all the headers are in
57+
// Later should store body as well to support getResponseBody which should only work once Network.loadingFinished is sent
58+
// but the body itself would be loaded with each chunks as Network.dataReceiveds are coming in.
59+
};
60+
61+
// Stored in CDP
62+
pub const NetworkState = struct {
63+
const Self = @This();
64+
received: std.AutoArrayHashMap(u64, Response),
65+
arena: std.heap.ArenaAllocator,
66+
67+
pub fn init(allocator: Allocator) !NetworkState {
68+
return .{
69+
.received = std.AutoArrayHashMap(u64, Response).init(allocator),
70+
.arena = std.heap.ArenaAllocator.init(allocator),
71+
};
72+
}
73+
74+
pub fn deinit(self: *Self) void {
75+
self.received.deinit();
76+
self.arena.deinit();
77+
}
78+
79+
pub fn putOrAppendReceivedHeader(self: *NetworkState, request_id: u64, status: u16, header: std.http.Header) !void {
80+
const kv = try self.received.getOrPut(request_id);
81+
if (!kv.found_existing) kv.value_ptr.* = .{ .status = status };
82+
83+
const a = self.arena.allocator();
84+
const name = try a.dupe(u8, header.name);
85+
const value = try a.dupe(u8, header.value);
86+
try kv.value_ptr.headers.put(a, name, value);
87+
}
88+
};
89+
90+
pub fn pageRemove(bc: anytype) !void {
91+
// The main page is going to be removed
92+
const state = &bc.cdp.network_state;
93+
state.received.clearRetainingCapacity(); // May need to be in pageRemoved
94+
_ = state.arena.reset(.{ .retain_with_limit = 1024 });
95+
}
96+
5497
fn enable(cmd: anytype) !void {
5598
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
5699
try bc.networkEnable();
@@ -282,7 +325,7 @@ pub fn httpRequestStart(arena: Allocator, bc: anytype, data: *const Notification
282325
}, .{ .session_id = session_id });
283326
}
284327

285-
pub fn httpRequestComplete(arena: Allocator, bc: anytype, request: *const Notification.RequestComplete) !void {
328+
pub fn httpHeadersDoneReceiving(arena: Allocator, bc: anytype, request: *const Notification.ResponseHeadersDone) !void {
286329
// Isn't possible to do a network request within a Browser (which our
287330
// notification is tied to), without a page.
288331
std.debug.assert(bc.session.page != null);
@@ -293,30 +336,25 @@ pub fn httpRequestComplete(arena: Allocator, bc: anytype, request: *const Notifi
293336
const session_id = bc.session_id orelse unreachable;
294337
const target_id = bc.target_id orelse unreachable;
295338

296-
const url = try urlToString(arena, request.url, .{
339+
const url = try urlToString(arena, &request.transfer.uri, .{
297340
.scheme = true,
298341
.authentication = true,
299342
.authority = true,
300343
.path = true,
301344
.query = true,
302345
});
303346

304-
// @newhttp
305-
const headers: std.StringArrayHashMapUnmanaged([]const u8) = .empty;
306-
// try headers.ensureTotalCapacity(arena, request.headers.len);
307-
// for (request.headers) |header| {
308-
// headers.putAssumeCapacity(header.name, header.value);
309-
// }
347+
const response = bc.cdp.network_state.received.get(request.transfer.id) orelse return error.ResponseNotFound;
310348

311349
// We're missing a bunch of fields, but, for now, this seems like enough
312350
try cdp.sendEvent("Network.responseReceived", .{
313-
.requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{request.id}),
351+
.requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{request.transfer.id}),
314352
.loaderId = bc.loader_id,
315353
.response = .{
316354
.url = url,
317-
.status = request.status,
318-
.statusText = @as(std.http.Status, @enumFromInt(request.status)).phrase() orelse "Unknown",
319-
.headers = std.json.ArrayHashMap([]const u8){ .map = headers },
355+
.status = response.status,
356+
.statusText = @as(std.http.Status, @enumFromInt(response.status)).phrase() orelse "Unknown",
357+
.headers = std.json.ArrayHashMap([]const u8){ .map = response.headers },
320358
},
321359
.frameId = target_id,
322360
}, .{ .session_id = session_id });

src/http/Client.zig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ fn makeTransfer(self: *Client, req: Request) !*Transfer {
247247
.req = req,
248248
.ctx = req.ctx,
249249
.client = self,
250+
.notification = &self.notification,
250251
};
251252
return transfer;
252253
}
@@ -549,6 +550,8 @@ pub const Transfer = struct {
549550

550551
_redirecting: bool = false,
551552

553+
notification: *?*Notification, // Points to the Client's notification. TBD if a Browser can remove the notification before all Transfers are gone.
554+
552555
fn deinit(self: *Transfer) void {
553556
self.req.headers.deinit();
554557
if (self._handle) |handle| {
@@ -673,12 +676,26 @@ pub const Transfer = struct {
673676
// returning < buf_len terminates the request
674677
return 0;
675678
};
679+
if (transfer.notification.*) |notification| { // TBD before or after callback?
680+
notification.dispatch(.http_headers_done_receiving, &.{
681+
.transfer = transfer,
682+
});
683+
}
676684
} else {
677685
if (transfer.req.header_callback) |cb| {
678686
cb(transfer, header) catch |err| {
679687
log.err(.http, "header_callback", .{ .err = err, .req = transfer });
680688
return 0;
681689
};
690+
if (transfer.notification.*) |notification| { // TBD before or after callback?
691+
if (Http.Headers.parseHeader(header)) |hdr_name_value| {
692+
notification.dispatch(.http_header_received, &.{
693+
.request_id = transfer.id,
694+
.status = hdr.status,
695+
.header = hdr_name_value,
696+
});
697+
} else log.err(.http, "invalid header", .{ .line = header });
698+
}
682699
}
683700
}
684701
return buf_len;

src/http/Http.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ pub const Headers = struct {
244244
return list;
245245
}
246246

247-
fn parseHeader(header_str: []const u8) ?struct { name: []const u8, value: []const u8 } {
248-
const colon_pos = std.mem.indexOf(u8, header_str, ":") orelse return null;
247+
pub fn parseHeader(header_str: []const u8) ?std.http.Header {
248+
const colon_pos = std.mem.indexOfScalar(u8, header_str, ':') orelse return null;
249249

250250
const name = std.mem.trim(u8, header_str[0..colon_pos], " \t");
251251
const value = std.mem.trim(u8, header_str[colon_pos + 1 ..], " \t");

src/notification.zig

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ pub const Notification = struct {
6363
http_request_fail: List = .{},
6464
http_request_start: List = .{},
6565
http_request_intercept: List = .{},
66-
http_request_complete: List = .{},
66+
http_header_received: List = .{},
67+
http_headers_done_receiving: List = .{},
6768
notification_created: List = .{},
6869
};
6970

@@ -75,7 +76,8 @@ pub const Notification = struct {
7576
http_request_fail: *const RequestFail,
7677
http_request_start: *const RequestStart,
7778
http_request_intercept: *const RequestIntercept,
78-
http_request_complete: *const RequestComplete,
79+
http_header_received: *const ResponseHeader,
80+
http_headers_done_receiving: *const ResponseHeadersDone,
7981
notification_created: *Notification,
8082
};
8183
const EventType = std.meta.FieldEnum(Events);
@@ -102,15 +104,19 @@ pub const Notification = struct {
102104
wait_for_interception: *bool,
103105
};
104106

105-
pub const RequestFail = struct {
107+
pub const ResponseHeader = struct {
108+
request_id: u64,
109+
status: u16,
110+
header: std.http.Header,
111+
};
112+
113+
pub const ResponseHeadersDone = struct {
106114
transfer: *Transfer,
107-
err: anyerror,
108115
};
109116

110-
pub const RequestComplete = struct {
111-
id: usize,
112-
url: *const std.Uri,
113-
status: u16,
117+
pub const RequestFail = struct {
118+
transfer: *Transfer,
119+
err: anyerror,
114120
};
115121

116122
pub fn init(allocator: Allocator, parent: ?*Notification) !*Notification {

0 commit comments

Comments
 (0)