mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-30 15:41:48 +00:00
replace zig-async-io and std.http.Client with a custom HTTP client
This commit is contained in:
238
src/xhr/xhr.zig
238
src/xhr/xhr.zig
@@ -31,7 +31,7 @@ const XMLHttpRequestEventTarget = @import("event_target.zig").XMLHttpRequestEven
|
||||
const Mime = @import("../browser/mime.zig").Mime;
|
||||
|
||||
const Loop = jsruntime.Loop;
|
||||
const Client = @import("asyncio").Client;
|
||||
const http = @import("../http/client.zig");
|
||||
|
||||
const parser = @import("netsurf");
|
||||
|
||||
@@ -95,14 +95,12 @@ pub const XMLHttpRequestBodyInit = union(XMLHttpRequestBodyInitTag) {
|
||||
pub const XMLHttpRequest = struct {
|
||||
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
|
||||
alloc: std.mem.Allocator,
|
||||
cli: *Client,
|
||||
io: Client.IO,
|
||||
client: *http.Client,
|
||||
request: ?http.Request = null,
|
||||
|
||||
priv_state: PrivState = .new,
|
||||
req: ?Client.Request = null,
|
||||
ctx: ?Client.Ctx = null,
|
||||
|
||||
method: std.http.Method,
|
||||
method: http.Request.Method,
|
||||
state: State,
|
||||
url: ?[]const u8,
|
||||
uri: std.Uri,
|
||||
@@ -125,7 +123,7 @@ pub const XMLHttpRequest = struct {
|
||||
|
||||
withCredentials: bool = false,
|
||||
// TODO: response readonly attribute any response;
|
||||
response_bytes: ?[]const u8 = null,
|
||||
response_bytes: std.ArrayListUnmanaged(u8) = .{},
|
||||
response_type: ResponseType = .Empty,
|
||||
response_headers: Headers,
|
||||
|
||||
@@ -133,7 +131,7 @@ pub const XMLHttpRequest = struct {
|
||||
// use 16KB for headers buffer size.
|
||||
response_header_buffer: [1024 * 16]u8 = undefined,
|
||||
|
||||
response_status: u10 = 0,
|
||||
response_status: u16 = 0,
|
||||
|
||||
// TODO uncomment this field causes casting issue with
|
||||
// XMLHttpRequestEventTarget. I think it's dueto an alignement issue, but
|
||||
@@ -246,13 +244,6 @@ pub const XMLHttpRequest = struct {
|
||||
fn all(self: Headers) []std.http.Header {
|
||||
return self.list.items;
|
||||
}
|
||||
|
||||
fn load(self: *Headers, it: *std.http.HeaderIterator) !void {
|
||||
while (true) {
|
||||
const h = it.next() orelse break;
|
||||
_ = try self.append(h.name, h.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Response = union(ResponseType) {
|
||||
@@ -290,17 +281,16 @@ pub const XMLHttpRequest = struct {
|
||||
|
||||
const min_delay: u64 = 50000000; // 50ms
|
||||
|
||||
pub fn constructor(alloc: std.mem.Allocator, loop: *Loop, userctx: UserContext) !XMLHttpRequest {
|
||||
pub fn constructor(alloc: std.mem.Allocator, userctx: UserContext) !XMLHttpRequest {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.headers = Headers.init(alloc),
|
||||
.response_headers = Headers.init(alloc),
|
||||
.io = Client.IO.init(loop),
|
||||
.method = undefined,
|
||||
.url = null,
|
||||
.uri = undefined,
|
||||
.state = .unsent,
|
||||
.cli = userctx.httpClient,
|
||||
.client = userctx.http_client,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -311,7 +301,6 @@ pub const XMLHttpRequest = struct {
|
||||
if (self.payload) |v| alloc.free(v);
|
||||
self.payload = null;
|
||||
|
||||
if (self.response_bytes) |v| alloc.free(v);
|
||||
if (self.response_obj) |v| v.deinit();
|
||||
|
||||
self.response_obj = null;
|
||||
@@ -329,12 +318,6 @@ pub const XMLHttpRequest = struct {
|
||||
self.send_flag = false;
|
||||
|
||||
self.priv_state = .new;
|
||||
|
||||
if (self.ctx) |*c| c.deinit();
|
||||
self.ctx = null;
|
||||
|
||||
if (self.req) |*r| r.deinit();
|
||||
self.req = null;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *XMLHttpRequest, alloc: std.mem.Allocator) void {
|
||||
@@ -449,7 +432,7 @@ pub const XMLHttpRequest = struct {
|
||||
}
|
||||
|
||||
const methods = [_]struct {
|
||||
tag: std.http.Method,
|
||||
tag: http.Request.Method,
|
||||
name: []const u8,
|
||||
}{
|
||||
.{ .tag = .DELETE, .name = "DELETE" },
|
||||
@@ -461,7 +444,7 @@ pub const XMLHttpRequest = struct {
|
||||
};
|
||||
const methods_forbidden = [_][]const u8{ "CONNECT", "TRACE", "TRACK" };
|
||||
|
||||
pub fn validMethod(m: []const u8) DOMError!std.http.Method {
|
||||
pub fn validMethod(m: []const u8) DOMError!http.Request.Method {
|
||||
for (methods) |method| {
|
||||
if (std.ascii.eqlIgnoreCase(method.name, m)) {
|
||||
return method.tag;
|
||||
@@ -485,7 +468,7 @@ pub const XMLHttpRequest = struct {
|
||||
}
|
||||
|
||||
// TODO body can be either a XMLHttpRequestBodyInit or a document
|
||||
pub fn _send(self: *XMLHttpRequest, alloc: std.mem.Allocator, body: ?[]const u8) !void {
|
||||
pub fn _send(self: *XMLHttpRequest, loop: *Loop, alloc: std.mem.Allocator, body: ?[]const u8) !void {
|
||||
if (self.state != .opened) return DOMError.InvalidState;
|
||||
if (self.send_flag) return DOMError.InvalidState;
|
||||
|
||||
@@ -515,153 +498,77 @@ pub const XMLHttpRequest = struct {
|
||||
|
||||
self.priv_state = .open;
|
||||
|
||||
self.req = try self.cli.create(self.method, self.uri, .{
|
||||
.server_header_buffer = &self.response_header_buffer,
|
||||
.extra_headers = self.headers.all(),
|
||||
});
|
||||
errdefer {
|
||||
self.req.?.deinit();
|
||||
self.req = null;
|
||||
}
|
||||
self.request = try self.client.request(self.method, self.uri);
|
||||
|
||||
self.ctx = try Client.Ctx.init(&self.io, &self.req.?);
|
||||
errdefer {
|
||||
self.ctx.?.deinit();
|
||||
self.ctx = null;
|
||||
}
|
||||
self.ctx.?.userData = self;
|
||||
var request = &self.request.?;
|
||||
errdefer request.deinit();
|
||||
|
||||
try self.cli.async_open(
|
||||
self.method,
|
||||
self.uri,
|
||||
.{ .server_header_buffer = &self.response_header_buffer },
|
||||
&self.ctx.?,
|
||||
onRequestConnect,
|
||||
);
|
||||
for (self.headers.list.items) |hdr| {
|
||||
try request.addHeader(hdr.name, hdr.value, .{});
|
||||
}
|
||||
request.body = self.payload;
|
||||
try request.sendAsync(loop, self, .{});
|
||||
}
|
||||
|
||||
fn onRequestWait(ctx: *Client.Ctx, res: anyerror!void) !void {
|
||||
var self = selfCtx(ctx);
|
||||
res catch |err| return self.onErr(err);
|
||||
pub fn onHttpResponse(self: *XMLHttpRequest, progress_: http.Error!http.Progress) !void {
|
||||
const progress = progress_ catch |err| {
|
||||
self.onErr(err);
|
||||
return err;
|
||||
};
|
||||
|
||||
log.info("{any} {any} {d}", .{ self.method, self.uri, self.req.?.response.status });
|
||||
if (progress.first) {
|
||||
const header = progress.header;
|
||||
log.info("{any} {any} {d}", .{ self.method, self.uri, header.status });
|
||||
|
||||
self.priv_state = .done;
|
||||
var it = self.req.?.response.iterateHeaders();
|
||||
self.response_headers.load(&it) catch |e| return self.onErr(e);
|
||||
self.priv_state = .done;
|
||||
|
||||
// extract a mime type from headers.
|
||||
const ct = self.response_headers.getFirstValue("Content-Type") orelse "text/xml";
|
||||
self.response_mime = Mime.parse(self.alloc, ct) catch |e| return self.onErr(e);
|
||||
for (header.headers.items) |hdr| {
|
||||
try self.response_headers.append(hdr.name, hdr.value);
|
||||
}
|
||||
|
||||
// TODO handle override mime type
|
||||
// extract a mime type from headers.
|
||||
const ct = header.get("Content-Type") orelse "text/xml";
|
||||
self.response_mime = Mime.parse(self.alloc, ct) catch |e| return self.onErr(e);
|
||||
|
||||
self.state = .headers_received;
|
||||
self.dispatchEvt("readystatechange");
|
||||
|
||||
self.response_status = @intFromEnum(self.req.?.response.status);
|
||||
|
||||
var buf: std.ArrayListUnmanaged(u8) = .{};
|
||||
|
||||
// TODO set correct length
|
||||
const total = 0;
|
||||
var loaded: u64 = 0;
|
||||
|
||||
// dispatch a progress event loadstart.
|
||||
self.dispatchProgressEvent("loadstart", .{ .loaded = loaded, .total = total });
|
||||
|
||||
// TODO read async
|
||||
const reader = self.req.?.reader();
|
||||
var buffer: [1024]u8 = undefined;
|
||||
var ln = buffer.len;
|
||||
var prev_dispatch: ?std.time.Instant = null;
|
||||
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);
|
||||
};
|
||||
loaded = loaded + ln;
|
||||
|
||||
// Dispatch only if 50ms have passed.
|
||||
const now = std.time.Instant.now() catch |e| {
|
||||
buf.deinit(self.alloc);
|
||||
return self.onErr(e);
|
||||
};
|
||||
if (prev_dispatch != null and now.since(prev_dispatch.?) < min_delay) continue;
|
||||
defer prev_dispatch = now;
|
||||
|
||||
self.state = .loading;
|
||||
// TODO handle override mime type
|
||||
self.state = .headers_received;
|
||||
self.dispatchEvt("readystatechange");
|
||||
|
||||
// dispatch a progress event progress.
|
||||
self.dispatchProgressEvent("progress", .{
|
||||
.loaded = loaded,
|
||||
.total = total,
|
||||
});
|
||||
}
|
||||
self.response_bytes = buf.items;
|
||||
self.send_flag = false;
|
||||
self.response_status = header.status;
|
||||
|
||||
// TODO correct total
|
||||
self.dispatchProgressEvent("loadstart", .{ .loaded = 0, .total = 0 });
|
||||
|
||||
self.state = .loading;
|
||||
}
|
||||
|
||||
const data = progress.data orelse return;
|
||||
const buf = &self.response_bytes;
|
||||
|
||||
try buf.appendSlice(self.alloc, data);
|
||||
const total_len = buf.items.len;
|
||||
|
||||
// TODO: don't dispatch this more than once every 50ms
|
||||
// dispatch a progress event progress.
|
||||
self.dispatchEvt("readystatechange");
|
||||
|
||||
self.dispatchProgressEvent("progress", .{
|
||||
.total = buf.items.len,
|
||||
.loaded = buf.items.len,
|
||||
});
|
||||
|
||||
if (progress.done == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.send_flag = false;
|
||||
self.state = .done;
|
||||
self.dispatchEvt("readystatechange");
|
||||
|
||||
// dispatch a progress event load.
|
||||
self.dispatchProgressEvent("load", .{ .loaded = loaded, .total = total });
|
||||
self.dispatchProgressEvent("load", .{ .loaded = total_len, .total = total_len });
|
||||
// dispatch a progress event loadend.
|
||||
self.dispatchProgressEvent("loadend", .{ .loaded = loaded, .total = total });
|
||||
|
||||
if (self.ctx) |*c| c.deinit();
|
||||
self.ctx = null;
|
||||
|
||||
if (self.req) |*r| r.deinit();
|
||||
self.req = null;
|
||||
}
|
||||
|
||||
fn onRequestFinish(ctx: *Client.Ctx, res: anyerror!void) !void {
|
||||
var self = selfCtx(ctx);
|
||||
res catch |err| return self.onErr(err);
|
||||
|
||||
self.priv_state = .wait;
|
||||
return ctx.req.async_wait(ctx, onRequestWait) catch |e| return self.onErr(e);
|
||||
}
|
||||
|
||||
fn onRequestSend(ctx: *Client.Ctx, res: anyerror!void) !void {
|
||||
var self = selfCtx(ctx);
|
||||
res catch |err| return self.onErr(err);
|
||||
|
||||
if (self.payload) |payload| {
|
||||
self.priv_state = .write;
|
||||
return ctx.req.async_writeAll(payload, ctx, onRequestWrite) catch |e| return self.onErr(e);
|
||||
}
|
||||
|
||||
self.priv_state = .finish;
|
||||
return ctx.req.async_finish(ctx, onRequestFinish) catch |e| return self.onErr(e);
|
||||
}
|
||||
|
||||
fn onRequestWrite(ctx: *Client.Ctx, res: anyerror!void) !void {
|
||||
var self = selfCtx(ctx);
|
||||
res catch |err| return self.onErr(err);
|
||||
self.priv_state = .finish;
|
||||
return ctx.req.async_finish(ctx, onRequestFinish) catch |e| return self.onErr(e);
|
||||
}
|
||||
|
||||
fn onRequestConnect(ctx: *Client.Ctx, res: anyerror!void) anyerror!void {
|
||||
var self = selfCtx(ctx);
|
||||
res catch |err| return self.onErr(err);
|
||||
|
||||
// prepare payload transfert.
|
||||
if (self.payload) |v| self.req.?.transfer_encoding = .{ .content_length = v.len };
|
||||
|
||||
self.priv_state = .send;
|
||||
return ctx.req.async_send(ctx, onRequestSend) catch |err| return self.onErr(err);
|
||||
}
|
||||
|
||||
fn selfCtx(ctx: *Client.Ctx) *XMLHttpRequest {
|
||||
return @ptrCast(@alignCast(ctx.userData));
|
||||
self.dispatchProgressEvent("loadend", .{ .loaded = total_len, .total = total_len });
|
||||
}
|
||||
|
||||
fn onErr(self: *XMLHttpRequest, err: anyerror) void {
|
||||
@@ -675,12 +582,6 @@ pub const XMLHttpRequest = struct {
|
||||
self.dispatchProgressEvent("loadend", .{});
|
||||
|
||||
log.debug("{any} {any} {any}", .{ self.method, self.uri, self.err });
|
||||
|
||||
if (self.ctx) |*c| c.deinit();
|
||||
self.ctx = null;
|
||||
|
||||
if (self.req) |*r| r.deinit();
|
||||
self.req = null;
|
||||
}
|
||||
|
||||
pub fn _abort(self: *XMLHttpRequest) void {
|
||||
@@ -803,7 +704,7 @@ pub const XMLHttpRequest = struct {
|
||||
}
|
||||
|
||||
if (self.response_type == .JSON) {
|
||||
if (self.response_bytes == null) return null;
|
||||
if (self.response_bytes.items.len == 0) return null;
|
||||
|
||||
// TODO Let jsonObject be the result of running parse JSON from bytes
|
||||
// on this’s received bytes. If that threw an exception, then return
|
||||
@@ -841,7 +742,7 @@ pub const XMLHttpRequest = struct {
|
||||
};
|
||||
defer alloc.free(ccharset);
|
||||
|
||||
var fbs = std.io.fixedBufferStream(self.response_bytes.?);
|
||||
var fbs = std.io.fixedBufferStream(self.response_bytes.items);
|
||||
const doc = parser.documentHTMLParse(fbs.reader(), ccharset) catch {
|
||||
self.response_obj = .{ .Failure = true };
|
||||
return;
|
||||
@@ -862,7 +763,7 @@ pub const XMLHttpRequest = struct {
|
||||
const p = std.json.parseFromSlice(
|
||||
JSONValue,
|
||||
alloc,
|
||||
self.response_bytes.?,
|
||||
self.response_bytes.items,
|
||||
.{},
|
||||
) catch |e| {
|
||||
log.err("parse JSON: {}", .{e});
|
||||
@@ -875,8 +776,7 @@ pub const XMLHttpRequest = struct {
|
||||
|
||||
pub fn get_responseText(self: *XMLHttpRequest) ![]const u8 {
|
||||
if (self.response_type != .Empty and self.response_type != .Text) return DOMError.InvalidState;
|
||||
|
||||
return if (self.response_bytes) |v| v else "";
|
||||
return self.response_bytes.items;
|
||||
}
|
||||
|
||||
pub fn _getResponseHeader(self: *XMLHttpRequest, name: []const u8) ?[]const u8 {
|
||||
|
||||
Reference in New Issue
Block a user