xhr: yield each fetch steps

This commit is contained in:
Pierre Tachoire
2024-02-06 16:01:48 +01:00
parent f3a1920d8f
commit 8a61f0f454

View File

@@ -97,11 +97,16 @@ pub const XMLHttpRequest = struct {
JSON, JSON,
}; };
const PrivState = enum { new, open, send, finish, wait, done };
proto: XMLHttpRequestEventTarget, proto: XMLHttpRequestEventTarget,
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
cli: Client, cli: Client,
impl: YieldImpl, impl: YieldImpl,
priv_state: PrivState = .new,
req: ?Client.Request = null,
method: std.http.Method, method: std.http.Method,
state: u16, state: u16,
url: ?[]const u8, url: ?[]const u8,
@@ -143,6 +148,8 @@ pub const XMLHttpRequest = struct {
if (self.url) |v| alloc.free(v); if (self.url) |v| alloc.free(v);
if (self.response_bytes) |v| alloc.free(v); if (self.response_bytes) |v| alloc.free(v);
if (self.response_headers) |v| alloc.free(v); if (self.response_headers) |v| alloc.free(v);
if (self.req) |*r| r.deinit();
// TODO the client must be shared between requests. // TODO the client must be shared between requests.
self.cli.deinit(); self.cli.deinit();
} }
@@ -206,6 +213,11 @@ pub const XMLHttpRequest = struct {
if (self.response_bytes) |v| alloc.free(v); if (self.response_bytes) |v| alloc.free(v);
self.state = OPENED; self.state = OPENED;
self.priv_state = .new;
if (self.req) |*r| {
r.deinit();
self.req = null;
}
} }
const methods = [_]struct { const methods = [_]struct {
@@ -259,54 +271,89 @@ pub const XMLHttpRequest = struct {
self.impl.yield(self); self.impl.yield(self);
} }
// onYield is a callback called between each request's steps.
// Between each step, the code is blocking.
// Yielding allows pseudo-async and gives a chance to other async process
// to be called.
pub fn onYield(self: *XMLHttpRequest, err: ?anyerror) void { pub fn onYield(self: *XMLHttpRequest, err: ?anyerror) void {
if (err) |e| return self.onerr(e); if (err) |e| return self.onerr(e);
var req = self.cli.open(self.method, self.uri, self.headers, .{}) catch |e| return self.onerr(e);
defer req.deinit();
req.send(.{}) catch |e| return self.onerr(e); switch (self.priv_state) {
req.finish() catch |e| return self.onerr(e); .new => {
req.wait() catch |e| return self.onerr(e); self.priv_state = .open;
self.req = self.cli.open(self.method, self.uri, self.headers, .{}) catch |e| return self.onerr(e);
},
.open => {
self.priv_state = .send;
self.req.?.send(.{}) catch |e| return self.onerr(e);
},
.send => {
self.priv_state = .finish;
self.req.?.finish() catch |e| return self.onerr(e);
},
.finish => {
self.priv_state = .wait;
self.req.?.wait() catch |e| return self.onerr(e);
},
.wait => {
self.priv_state = .done;
self.response_headers = self.req.?.response.headers.clone(self.response_headers.allocator) catch |e| return self.onerr(e);
self.response_headers = req.response.headers.clone(self.response_headers.allocator) catch |e| return self.onerr(e); self.state = HEADERS_RECEIVED;
self.state = HEADERS_RECEIVED; self.response_status = @intFromEnum(self.req.?.response.status);
self.response_status = @intFromEnum(req.response.status); self.state = LOADING;
self.state = LOADING; var buf: std.ArrayListUnmanaged(u8) = .{};
var buf: std.ArrayListUnmanaged(u8) = .{}; const reader = self.req.?.reader();
var buffer: [1024]u8 = undefined;
var ln = buffer.len;
while (ln > 0) {
ln = reader.read(&buffer) catch |e| {
buf.deinit(self.alloc);
return self.onerr(e);
};
buf.appendSlice(self.alloc, buffer[0..ln]) catch |e| {
buf.deinit(self.alloc);
return self.onerr(e);
};
}
self.response_bytes = buf.items;
const reader = req.reader(); self.state = DONE;
var buffer: [1024]u8 = undefined; },
var ln = buffer.len; .done => {
while (ln > 0) { if (self.req) |*r| {
ln = reader.read(&buffer) catch |e| { r.deinit();
buf.deinit(self.alloc); self.req = null;
return self.onerr(e); }
};
buf.appendSlice(self.alloc, buffer[0..ln]) catch |e| { // TODO use events instead
buf.deinit(self.alloc); if (self.proto.onload_cbk) |cbk| {
return self.onerr(e); // TODO pass an EventProgress
}; cbk.call(null) catch |e| {
std.debug.print("--- CALLBACK ERROR: {any}\n", .{e});
}; // TODO handle error
}
// finalize fetch process.
return;
},
} }
self.response_bytes = buf.items;
self.state = DONE; self.impl.yield(self);
// TODO use events instead
if (self.proto.onload_cbk) |cbk| {
// TODO pass an EventProgress
cbk.call(null) catch |e| {
std.debug.print("--- CALLBACK ERROR: {any}\n", .{e});
}; // TODO handle error
}
} }
fn onerr(self: *XMLHttpRequest, err: anyerror) void { fn onerr(self: *XMLHttpRequest, err: anyerror) void {
self.err = err; self.err = err;
self.state = DONE; self.state = DONE;
self.priv_state = .done;
if (self.req) |*r| {
r.deinit();
self.req = null;
}
} }
pub fn get_responseText(self: *XMLHttpRequest) ![]const u8 { pub fn get_responseText(self: *XMLHttpRequest) ![]const u8 {