Merge pull request #765 from lightpanda-io/http_client_optimization

Optimize the lifecycle of async requests
This commit is contained in:
Karl Seguin
2025-06-06 13:21:54 +08:00
committed by GitHub
2 changed files with 35 additions and 8 deletions

View File

@@ -468,7 +468,6 @@ pub const XMLHttpRequest = struct {
if (progress.first) { if (progress.first) {
const header = progress.header; const header = progress.header;
log.debug(.http, "request header", .{ log.debug(.http, "request header", .{
.source = "xhr", .source = "xhr",
.url = self.url, .url = self.url,
@@ -522,7 +521,7 @@ pub const XMLHttpRequest = struct {
log.info(.http, "request complete", .{ log.info(.http, "request complete", .{
.source = "xhr", .source = "xhr",
.url = self.url, .url = self.url,
.status = progress.header.status, .status = self.response_status,
}); });
// Not that the request is done, the http/client will free the request // Not that the request is done, the http/client will free the request

View File

@@ -828,6 +828,7 @@ fn AsyncHandler(comptime H: type, comptime L: type) type {
wait, wait,
done, done,
need_more, need_more,
handler_error,
}; };
fn deinit(self: *Self) void { fn deinit(self: *Self) void {
@@ -966,12 +967,35 @@ fn AsyncHandler(comptime H: type, comptime L: type) type {
switch (status) { switch (status) {
.wait => {}, .wait => {},
.need_more => self.receive(), .need_more => self.receive(),
.handler_error => {
// handler should never have been called if we're redirecting
std.debug.assert(self.redirect == null);
self.request.requestCompleted(self.reader.response);
self.deinit();
return;
},
.done => { .done => {
const redirect = self.redirect orelse { const redirect = self.redirect orelse {
var handler = self.handler;
self.request.requestCompleted(self.reader.response); self.request.requestCompleted(self.reader.response);
self.deinit(); self.deinit();
// Emit the done chunk. We expect the caller to do
// processing once the full request is completed. By
// emiting this AFTER we've relreased the connection,
// we free the connection and its state for re-use.
// If we don't do this this way, we can end up with
// _a lot_ of pending request/states.
// DO NOT USE `self` here, it's no longer valid.
handler.onHttpResponse(.{
.data = null,
.done = true,
.first = false,
.header = .{},
}) catch {};
return; return;
}; };
self.request.redirectAsync(redirect, self.loop, self.handler) catch |err| { self.request.redirectAsync(redirect, self.loop, self.handler) catch |err| {
self.handleError("Setup async redirect", err); self.handleError("Setup async redirect", err);
return; return;
@@ -1116,22 +1140,22 @@ fn AsyncHandler(comptime H: type, comptime L: type) type {
self.handler.onHttpResponse(.{ self.handler.onHttpResponse(.{
.data = chunk, .data = chunk,
.first = first, .first = first,
.done = next == null, .done = false,
.header = reader.response, .header = reader.response,
}) catch return .done; }) catch return .handler_error;
first = false; first = false;
} }
} }
} else if (result.data != null or done or would_be_first) { } else if (result.data != null or would_be_first) {
// If we have data. Or if the request is done. Or if this is the // If we have data. Or if the request is done. Or if this is the
// first time we have a complete header. Emit the chunk. // first time we have a complete header. Emit the chunk.
self.handler.onHttpResponse(.{ self.handler.onHttpResponse(.{
.done = done, .done = false,
.data = result.data, .data = result.data,
.first = would_be_first, .first = would_be_first,
.header = reader.response, .header = reader.response,
}) catch return .done; }) catch return .handler_error;
} }
if (done == true) { if (done == true) {
@@ -3135,7 +3159,8 @@ const CaptureHandler = struct {
const progress = try progress_; const progress = try progress_;
const allocator = self.response.arena.allocator(); const allocator = self.response.arena.allocator();
try self.response.body.appendSlice(allocator, progress.data orelse ""); try self.response.body.appendSlice(allocator, progress.data orelse "");
if (progress.done) { if (progress.first) {
std.debug.assert(!progress.done);
self.response.status = progress.header.status; self.response.status = progress.header.status;
try self.response.headers.ensureTotalCapacity(allocator, progress.header.headers.items.len); try self.response.headers.ensureTotalCapacity(allocator, progress.header.headers.items.len);
for (progress.header.headers.items) |header| { for (progress.header.headers.items) |header| {
@@ -3144,6 +3169,9 @@ const CaptureHandler = struct {
.value = try allocator.dupe(u8, header.value), .value = try allocator.dupe(u8, header.value),
}); });
} }
}
if (progress.done) {
self.reset.set(); self.reset.set();
} }
} }