Skip to content

Commit f789c84

Browse files
authored
Merge pull request #767 from lightpanda-io/unblock_async_http_request
Unblock async http request
2 parents 09466a2 + fdd1a77 commit f789c84

File tree

9 files changed

+340
-128
lines changed

9 files changed

+340
-128
lines changed

src/app.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ pub const App = struct {
5252
.telemetry = undefined,
5353
.app_dir_path = app_dir_path,
5454
.notification = notification,
55-
.http_client = try HttpClient.init(allocator, 5, .{
55+
.http_client = try HttpClient.init(allocator, .{
56+
.max_concurrent = 3,
5657
.http_proxy = config.http_proxy,
5758
.tls_verify_host = config.tls_verify_host,
5859
}),

src/browser/html/window.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ pub const Window = struct {
164164
}
165165

166166
pub fn _requestAnimationFrame(self: *Window, cbk: Function, page: *Page) !u32 {
167-
return self.createTimeout(cbk, 5, page, .{.animation_frame = true});
167+
return self.createTimeout(cbk, 5, page, .{ .animation_frame = true });
168168
}
169169

170170
pub fn _cancelAnimationFrame(self: *Window, id: u32, page: *Page) !void {
@@ -179,7 +179,7 @@ pub const Window = struct {
179179

180180
// TODO handle callback arguments.
181181
pub fn _setInterval(self: *Window, cbk: Function, delay: ?u32, page: *Page) !u32 {
182-
return self.createTimeout(cbk, delay, page, .{.repeat = true});
182+
return self.createTimeout(cbk, delay, page, .{ .repeat = true });
183183
}
184184

185185
pub fn _clearTimeout(self: *Window, id: u32, page: *Page) !void {

src/browser/page.zig

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,9 @@ pub const Page = struct {
113113
.cookie_jar = &session.cookie_jar,
114114
.microtask_node = .{ .func = microtaskCallback },
115115
.window_clicked_event_node = .{ .func = windowClicked },
116-
.request_factory = browser.http_client.requestFactory(browser.notification),
116+
.request_factory = browser.http_client.requestFactory(.{
117+
.notification = browser.notification,
118+
}),
117119
.scope = undefined,
118120
.module_map = .empty,
119121
};
@@ -205,58 +207,63 @@ pub const Page = struct {
205207
// redirect)
206208
self.url = request_url;
207209

208-
// load the data
209-
var request = try self.newHTTPRequest(opts.method, &self.url, .{ .navigation = true });
210-
defer request.deinit();
211-
request.body = opts.body;
212-
request.notification = notification;
210+
{
211+
// block exists to limit the lifetime of the request, which holds
212+
// onto a connection
213+
var request = try self.newHTTPRequest(opts.method, &self.url, .{ .navigation = true });
214+
defer request.deinit();
213215

214-
notification.dispatch(.page_navigate, &.{
215-
.opts = opts,
216-
.url = &self.url,
217-
.timestamp = timestamp(),
218-
});
216+
request.body = opts.body;
217+
request.notification = notification;
219218

220-
var response = try request.sendSync(.{});
219+
notification.dispatch(.page_navigate, &.{
220+
.opts = opts,
221+
.url = &self.url,
222+
.timestamp = timestamp(),
223+
});
221224

222-
// would be different than self.url in the case of a redirect
223-
self.url = try URL.fromURI(arena, request.request_uri);
225+
var response = try request.sendSync(.{});
224226

225-
const header = response.header;
226-
try session.cookie_jar.populateFromResponse(&self.url.uri, &header);
227+
// would be different than self.url in the case of a redirect
228+
self.url = try URL.fromURI(arena, request.request_uri);
227229

228-
// TODO handle fragment in url.
229-
try self.window.replaceLocation(.{ .url = try self.url.toWebApi(arena) });
230+
const header = response.header;
231+
try session.cookie_jar.populateFromResponse(&self.url.uri, &header);
230232

231-
const content_type = header.get("content-type");
233+
// TODO handle fragment in url.
234+
try self.window.replaceLocation(.{ .url = try self.url.toWebApi(arena) });
232235

233-
const mime: Mime = blk: {
234-
if (content_type) |ct| {
235-
break :blk try Mime.parse(arena, ct);
236-
}
237-
break :blk Mime.sniff(try response.peek());
238-
} orelse .unknown;
236+
const content_type = header.get("content-type");
239237

240-
log.info(.http, "navigation", .{
241-
.status = header.status,
242-
.content_type = content_type,
243-
.charset = mime.charset,
244-
.url = request_url,
245-
});
238+
const mime: Mime = blk: {
239+
if (content_type) |ct| {
240+
break :blk try Mime.parse(arena, ct);
241+
}
242+
break :blk Mime.sniff(try response.peek());
243+
} orelse .unknown;
244+
245+
log.info(.http, "navigation", .{
246+
.status = header.status,
247+
.content_type = content_type,
248+
.charset = mime.charset,
249+
.url = request_url,
250+
});
251+
252+
if (!mime.isHTML()) {
253+
var arr: std.ArrayListUnmanaged(u8) = .{};
254+
while (try response.next()) |data| {
255+
try arr.appendSlice(arena, try arena.dupe(u8, data));
256+
}
257+
// save the body into the page.
258+
self.raw_data = arr.items;
259+
return;
260+
}
246261

247-
if (mime.isHTML()) {
248-
self.raw_data = null;
249262
try self.loadHTMLDoc(&response, mime.charset orelse "utf-8");
250-
try self.processHTMLDoc();
251-
} else {
252-
var arr: std.ArrayListUnmanaged(u8) = .{};
253-
while (try response.next()) |data| {
254-
try arr.appendSlice(arena, try arena.dupe(u8, data));
255-
}
256-
// save the body into the page.
257-
self.raw_data = arr.items;
258263
}
259264

265+
try self.processHTMLDoc();
266+
260267
notification.dispatch(.page_navigated, &.{
261268
.url = &self.url,
262269
.timestamp = timestamp(),

src/browser/session.zig

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ pub const Session = struct {
7272

7373
pub fn deinit(self: *Session) void {
7474
if (self.page != null) {
75-
self.removePage();
75+
self.removePage() catch {};
7676
}
7777
self.cookie_jar.deinit();
7878
self.storage_shed.deinit();
@@ -104,14 +104,35 @@ pub const Session = struct {
104104
return page;
105105
}
106106

107-
pub fn removePage(self: *Session) void {
107+
pub fn removePage(self: *Session) !void {
108108
// Inform CDP the page is going to be removed, allowing other worlds to remove themselves before the main one
109109
self.browser.notification.dispatch(.page_remove, .{});
110110

111111
std.debug.assert(self.page != null);
112-
// Reset all existing callbacks.
113-
self.browser.app.loop.reset();
112+
113+
// Cleanup is a bit sensitive. We could still have inflight I/O. For
114+
// example, we could have an XHR request which is still in the connect
115+
// phase. It's important that we clean these up, as they're holding onto
116+
// limited resources (like our fixed-sized http state pool).
117+
//
118+
// First thing we do, is endScope() which will execute the destructor
119+
// of any type that registered a destructor (e.g. XMLHttpRequest).
120+
// This will shutdown any pending sockets, which begins our cleaning
121+
// processed
114122
self.executor.endScope();
123+
124+
// Second thing we do is reset the loop. This increments the loop ctx_id
125+
// so that any "stale" timeouts we process will get ignored. We need to
126+
// do this BEFORE running the loop because, at this point, things like
127+
// window.setTimeout and running microtasks should be ignored
128+
self.browser.app.loop.reset();
129+
130+
// Finally, we run the loop. Because of the reset just above, this will
131+
// ignore any timeouts. And, because of the endScope about this, it
132+
// should ensure that the http requests detect the shutdown socket and
133+
// release their resources.
134+
try self.browser.app.loop.run();
135+
115136
self.page = null;
116137

117138
// clear netsurf memory arena.
@@ -143,7 +164,7 @@ pub const Session = struct {
143164
// the final URL, possibly following redirects)
144165
const url = try self.page.?.url.resolve(self.transfer_arena, url_string);
145166

146-
self.removePage();
167+
try self.removePage();
147168
var page = try self.createPage();
148169
return page.navigate(url, opts);
149170
}

src/browser/xhr/xhr.zig

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const Mime = @import("../mime.zig").Mime;
3030
const parser = @import("../netsurf.zig");
3131
const http = @import("../../http/client.zig");
3232
const Page = @import("../page.zig").Page;
33+
const Loop = @import("../../runtime/loop.zig").Loop;
3334
const CookieJar = @import("../storage/storage.zig").CookieJar;
3435

3536
// XHR interfaces
@@ -78,6 +79,7 @@ const XMLHttpRequestBodyInit = union(enum) {
7879

7980
pub const XMLHttpRequest = struct {
8081
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
82+
loop: *Loop,
8183
arena: Allocator,
8284
request: ?*http.Request = null,
8385

@@ -91,6 +93,7 @@ pub const XMLHttpRequest = struct {
9193
sync: bool = true,
9294
err: ?anyerror = null,
9395
last_dispatch: i64 = 0,
96+
request_body: ?[]const u8 = null,
9497

9598
cookie_jar: *CookieJar,
9699
// the URI of the page where this request is originating from
@@ -241,12 +244,13 @@ pub const XMLHttpRequest = struct {
241244
pub fn constructor(page: *Page) !XMLHttpRequest {
242245
const arena = page.arena;
243246
return .{
247+
.url = null,
244248
.arena = arena,
249+
.loop = page.loop,
245250
.headers = Headers.init(arena),
246251
.response_headers = Headers.init(arena),
247252
.method = undefined,
248253
.state = .unsent,
249-
.url = null,
250254
.origin_url = &page.url,
251255
.cookie_jar = page.cookie_jar,
252256
};
@@ -422,18 +426,31 @@ pub const XMLHttpRequest = struct {
422426
log.debug(.http, "request", .{ .method = self.method, .url = self.url, .source = "xhr" });
423427

424428
self.send_flag = true;
429+
if (body) |b| {
430+
self.request_body = try self.arena.dupe(u8, b);
431+
}
425432

426-
self.request = try page.request_factory.create(self.method, &self.url.?.uri);
427-
var request = self.request.?;
428-
errdefer request.deinit();
433+
try page.request_factory.initAsync(
434+
page.arena,
435+
self.method,
436+
&self.url.?.uri,
437+
self,
438+
onHttpRequestReady,
439+
self.loop,
440+
);
441+
}
442+
443+
fn onHttpRequestReady(ctx: *anyopaque, request: *http.Request) !void {
444+
// on error, our caller will cleanup request
445+
const self: *XMLHttpRequest = @alignCast(@ptrCast(ctx));
429446

430447
for (self.headers.list.items) |hdr| {
431448
try request.addHeader(hdr.name, hdr.value, .{});
432449
}
433450

434451
{
435452
var arr: std.ArrayListUnmanaged(u8) = .{};
436-
try self.cookie_jar.forRequest(&self.url.?.uri, arr.writer(page.arena), .{
453+
try self.cookie_jar.forRequest(&self.url.?.uri, arr.writer(self.arena), .{
437454
.navigation = false,
438455
.origin_uri = &self.origin_url.uri,
439456
});
@@ -447,14 +464,15 @@ pub const XMLHttpRequest = struct {
447464
// if the request method is GET or HEAD.
448465
// https://xhr.spec.whatwg.org/#the-send()-method
449466
// var used_body: ?XMLHttpRequestBodyInit = null;
450-
if (body) |b| {
467+
if (self.request_body) |b| {
451468
if (self.method != .GET and self.method != .HEAD) {
452-
request.body = try page.arena.dupe(u8, b);
469+
request.body = b;
453470
try request.addHeader("Content-Type", "text/plain; charset=UTF-8", .{});
454471
}
455472
}
456473

457-
try request.sendAsync(page.loop, self, .{});
474+
try request.sendAsync(self.loop, self, .{});
475+
self.request = request;
458476
}
459477

460478
pub fn onHttpResponse(self: *XMLHttpRequest, progress_: anyerror!http.Progress) !void {

src/cdp/domains/input.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ fn clickNavigate(cmd: anytype, uri: std.Uri) !void {
9090
.disposition = "currentTab",
9191
}, .{ .session_id = bc.session_id.? });
9292

93-
bc.session.removePage();
93+
try bc.session.removePage();
9494
_ = try bc.session.createPage(null);
9595

9696
try @import("page.zig").navigateToUrl(cmd, url, false);

src/cdp/domains/target.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ fn closeTarget(cmd: anytype) !void {
220220
bc.session_id = null;
221221
}
222222

223-
bc.session.removePage();
223+
try bc.session.removePage();
224224
if (bc.isolated_world) |*world| {
225225
world.deinit();
226226
bc.isolated_world = null;

0 commit comments

Comments
 (0)