mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 22:53:28 +00:00
upgrade to zig 0.12
This commit is contained in:
1282
src/async/Client.zig
1282
src/async/Client.zig
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,7 @@ pub const Stream = struct {
|
||||
handle: posix.socket_t,
|
||||
|
||||
pub fn close(self: Stream) void {
|
||||
posix.closeSocket(self.handle);
|
||||
posix.close(self.handle);
|
||||
self.alloc.destroy(self.conn);
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ pub fn tcpConnectToHost(alloc: std.mem.Allocator, loop: *Loop, name: []const u8,
|
||||
|
||||
pub fn tcpConnectToAddress(alloc: std.mem.Allocator, loop: *Loop, addr: net.Address) !Stream {
|
||||
const sockfd = try std.posix.socket(addr.any.family, std.posix.SOCK.STREAM, std.posix.IPPROTO.TCP);
|
||||
errdefer std.posix.closeSocket(sockfd);
|
||||
errdefer std.posix.close(sockfd);
|
||||
|
||||
var conn = try alloc.create(Conn);
|
||||
conn.* = Conn{ .loop = loop };
|
||||
|
||||
@@ -40,11 +40,9 @@ test "blocking mode fetch API" {
|
||||
// force client's CA cert scan from system.
|
||||
try client.ca_bundle.rescan(client.allocator);
|
||||
|
||||
var res = try client.fetch(alloc, .{
|
||||
const res = try client.fetch(.{
|
||||
.location = .{ .uri = try std.Uri.parse(url) },
|
||||
.payload = .none,
|
||||
});
|
||||
defer res.deinit();
|
||||
|
||||
try std.testing.expect(res.status == .ok);
|
||||
}
|
||||
@@ -64,10 +62,10 @@ test "blocking mode open/send/wait API" {
|
||||
// force client's CA cert scan from system.
|
||||
try client.ca_bundle.rescan(client.allocator);
|
||||
|
||||
var headers = try std.http.Headers.initList(alloc, &[_]std.http.Field{});
|
||||
defer headers.deinit();
|
||||
|
||||
var req = try client.open(.GET, try std.Uri.parse(url), headers, .{});
|
||||
var buf: [2014]u8 = undefined;
|
||||
var req = try client.open(.GET, try std.Uri.parse(url), .{
|
||||
.server_header_buffer = &buf,
|
||||
});
|
||||
defer req.deinit();
|
||||
|
||||
try req.send(.{});
|
||||
@@ -87,7 +85,6 @@ const AsyncClient = struct {
|
||||
|
||||
cli: *Client,
|
||||
uri: std.Uri,
|
||||
headers: std.http.Headers,
|
||||
|
||||
req: ?Request = undefined,
|
||||
state: State = .new,
|
||||
@@ -95,9 +92,10 @@ const AsyncClient = struct {
|
||||
impl: YieldImpl,
|
||||
err: ?anyerror = null,
|
||||
|
||||
buf: [2014]u8 = undefined,
|
||||
|
||||
pub fn deinit(self: *AsyncRequest) void {
|
||||
if (self.req) |*r| r.deinit();
|
||||
self.headers.deinit();
|
||||
}
|
||||
|
||||
pub fn fetch(self: *AsyncRequest) void {
|
||||
@@ -116,7 +114,9 @@ const AsyncClient = struct {
|
||||
switch (self.state) {
|
||||
.new => {
|
||||
self.state = .open;
|
||||
self.req = self.cli.open(.GET, self.uri, self.headers, .{}) catch |e| return self.onerr(e);
|
||||
self.req = self.cli.open(.GET, self.uri, .{
|
||||
.server_header_buffer = &self.buf,
|
||||
}) catch |e| return self.onerr(e);
|
||||
},
|
||||
.open => {
|
||||
self.state = .send;
|
||||
@@ -164,7 +164,6 @@ const AsyncClient = struct {
|
||||
.impl = YieldImpl.init(self.cli.loop),
|
||||
.cli = &self.cli,
|
||||
.uri = uri,
|
||||
.headers = .{ .allocator = self.cli.allocator, .owned = false },
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -247,29 +247,39 @@ pub const Page = struct {
|
||||
|
||||
// TODO handle redirection
|
||||
if (req.response.status != .ok) {
|
||||
log.debug("{?} {d} {s}\n{any}", .{
|
||||
log.debug("{?} {d} {s}", .{
|
||||
req.response.version,
|
||||
req.response.status,
|
||||
req.response.reason,
|
||||
req.response.headers,
|
||||
// TODO log headers
|
||||
});
|
||||
return error.BadStatusCode;
|
||||
}
|
||||
|
||||
// TODO handle charset
|
||||
// https://html.spec.whatwg.org/#content-type
|
||||
const ct = req.response.headers.getFirstValue("Content-Type") orelse {
|
||||
var it = req.response.iterateHeaders();
|
||||
var ct: ?[]const u8 = null;
|
||||
while (true) {
|
||||
const h = it.next() orelse break;
|
||||
if (std.ascii.eqlIgnoreCase(h.name, "Content-Type")) {
|
||||
ct = try alloc.dupe(u8, h.value);
|
||||
}
|
||||
}
|
||||
if (ct == null) {
|
||||
// no content type in HTTP headers.
|
||||
// TODO try to sniff mime type from the body.
|
||||
log.info("no content-type HTTP header", .{});
|
||||
return;
|
||||
};
|
||||
log.debug("header content-type: {s}", .{ct});
|
||||
const mime = try Mime.parse(ct);
|
||||
}
|
||||
defer alloc.free(ct.?);
|
||||
|
||||
log.debug("header content-type: {s}", .{ct.?});
|
||||
const mime = try Mime.parse(ct.?);
|
||||
if (mime.eql(Mime.HTML)) {
|
||||
try self.loadHTMLDoc(req.reader(), mime.charset orelse "utf-8");
|
||||
} else {
|
||||
log.info("non-HTML document: {s}", .{ct});
|
||||
log.info("non-HTML document: {s}", .{ct.?});
|
||||
|
||||
// save the body into the page.
|
||||
self.raw_data = try req.reader().readAllAlloc(alloc, 16 * 1024 * 1024);
|
||||
@@ -500,20 +510,24 @@ pub const Page = struct {
|
||||
|
||||
log.debug("starting fetch script {s}", .{src});
|
||||
|
||||
const u = std.Uri.parse(src) catch try std.Uri.parseWithoutScheme(src);
|
||||
const ru = try std.Uri.resolve(self.uri, u, false, alloc);
|
||||
var buffer: [1024]u8 = undefined;
|
||||
const u = try std.Uri.resolve_inplace(self.uri, src, &buffer);
|
||||
|
||||
var fetchres = try self.session.loader.fetch(alloc, ru);
|
||||
var fetchres = try self.session.loader.get(alloc, u);
|
||||
defer fetchres.deinit();
|
||||
|
||||
log.info("fech script {any}: {d}", .{ ru, fetchres.status });
|
||||
const resp = fetchres.req.response;
|
||||
|
||||
if (fetchres.status != .ok) return FetchError.BadStatusCode;
|
||||
log.info("fech script {any}: {d}", .{ u, resp.status });
|
||||
|
||||
if (resp.status != .ok) return FetchError.BadStatusCode;
|
||||
|
||||
// TODO check content-type
|
||||
const body = try fetchres.req.reader().readAllAlloc(alloc, 16 * 1024 * 1024);
|
||||
defer alloc.free(body);
|
||||
|
||||
// check no body
|
||||
if (fetchres.body == null) return FetchError.NoBody;
|
||||
if (body.len == 0) return FetchError.NoBody;
|
||||
|
||||
var res = try self.session.env.execTryCatch(alloc, fetchres.body.?, src);
|
||||
defer res.deinit(alloc);
|
||||
|
||||
@@ -22,6 +22,7 @@ const user_agent = "Lightpanda.io/1.0";
|
||||
|
||||
pub const Loader = struct {
|
||||
client: std.http.Client,
|
||||
server_header_buffer: [1024]u8 = undefined,
|
||||
|
||||
pub const Response = struct {
|
||||
alloc: std.mem.Allocator,
|
||||
@@ -45,42 +46,26 @@ pub const Loader = struct {
|
||||
self.client.deinit();
|
||||
}
|
||||
|
||||
// the caller must deinit the FetchResult.
|
||||
pub fn fetch(self: *Loader, alloc: std.mem.Allocator, uri: std.Uri) !std.http.Client.FetchResult {
|
||||
var headers = try std.http.Headers.initList(alloc, &[_]std.http.Field{
|
||||
.{ .name = "User-Agent", .value = user_agent },
|
||||
.{ .name = "Accept", .value = "*/*" },
|
||||
.{ .name = "Accept-Language", .value = "en-US,en;q=0.5" },
|
||||
});
|
||||
defer headers.deinit();
|
||||
|
||||
return try self.client.fetch(alloc, .{
|
||||
.location = .{ .uri = uri },
|
||||
.headers = headers,
|
||||
.payload = .none,
|
||||
});
|
||||
}
|
||||
|
||||
// see
|
||||
// https://ziglang.org/documentation/master/std/#A;std:http.Client.fetch
|
||||
// for reference.
|
||||
// The caller is responsible for calling `deinit()` on the `Response`.
|
||||
pub fn get(self: *Loader, alloc: std.mem.Allocator, uri: std.Uri) !Response {
|
||||
var headers = try std.http.Headers.initList(alloc, &[_]std.http.Field{
|
||||
.{ .name = "User-Agent", .value = user_agent },
|
||||
.{ .name = "Accept", .value = "*/*" },
|
||||
.{ .name = "Accept-Language", .value = "en-US,en;q=0.5" },
|
||||
});
|
||||
defer headers.deinit();
|
||||
|
||||
var resp = Response{
|
||||
.alloc = alloc,
|
||||
.req = try alloc.create(std.http.Client.Request),
|
||||
};
|
||||
errdefer alloc.destroy(resp.req);
|
||||
|
||||
resp.req.* = try self.client.open(.GET, uri, headers, .{
|
||||
.handle_redirects = true, // TODO handle redirects manually
|
||||
resp.req.* = try self.client.open(.GET, uri, .{
|
||||
.headers = .{
|
||||
.user_agent = .{ .override = user_agent },
|
||||
},
|
||||
.extra_headers = &.{
|
||||
.{ .name = "Accept", .value = "*/*" },
|
||||
.{ .name = "Accept-Language", .value = "en-US,en;q=0.5" },
|
||||
},
|
||||
.server_header_buffer = &self.server_header_buffer,
|
||||
});
|
||||
errdefer resp.req.deinit();
|
||||
|
||||
@@ -92,13 +77,13 @@ pub const Loader = struct {
|
||||
}
|
||||
};
|
||||
|
||||
test "basic url fetch" {
|
||||
test "basic url get" {
|
||||
const alloc = std.testing.allocator;
|
||||
var loader = Loader.init(alloc);
|
||||
defer loader.deinit();
|
||||
|
||||
var result = try loader.fetch(alloc, "https://en.wikipedia.org/wiki/Main_Page");
|
||||
var result = try loader.get(alloc, "https://en.wikipedia.org/wiki/Main_Page");
|
||||
defer result.deinit();
|
||||
|
||||
try std.testing.expect(result.status == std.http.Status.ok);
|
||||
try std.testing.expect(result.req.response.status == std.http.Status.ok);
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ const apiweb = @import("apiweb.zig");
|
||||
pub const Types = jsruntime.reflect(apiweb.Interfaces);
|
||||
pub const UserContext = apiweb.UserContext;
|
||||
|
||||
pub const std_options = struct {
|
||||
pub const log_level = .debug;
|
||||
pub const std_options = std.Options{
|
||||
.log_level = .debug,
|
||||
};
|
||||
|
||||
const usage =
|
||||
|
||||
174
src/xhr/xhr.zig
174
src/xhr/xhr.zig
@@ -95,6 +95,45 @@ pub const XMLHttpRequestBodyInit = union(XMLHttpRequestBodyInitTag) {
|
||||
};
|
||||
|
||||
pub const XMLHttpRequest = struct {
|
||||
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
|
||||
alloc: std.mem.Allocator,
|
||||
cli: Client,
|
||||
impl: YieldImpl,
|
||||
|
||||
priv_state: PrivState = .new,
|
||||
req: ?Client.Request = null,
|
||||
|
||||
method: std.http.Method,
|
||||
state: u16,
|
||||
url: ?[]const u8,
|
||||
uri: std.Uri,
|
||||
// request headers
|
||||
headers: Headers,
|
||||
sync: bool = true,
|
||||
err: ?anyerror = null,
|
||||
|
||||
// TODO uncomment this field causes casting issue with
|
||||
// XMLHttpRequestEventTarget. I think it's dueto an alignement issue, but
|
||||
// not sure. see
|
||||
// https://lightpanda.slack.com/archives/C05TRU6RBM1/p1707819010681019
|
||||
// upload: ?XMLHttpRequestUpload = null,
|
||||
|
||||
timeout: u32 = 0,
|
||||
withCredentials: bool = false,
|
||||
// TODO: response readonly attribute any response;
|
||||
response_bytes: ?[]const u8 = null,
|
||||
response_type: ResponseType = .Empty,
|
||||
response_headers: Headers,
|
||||
// used by zig client to parse reponse headers.
|
||||
response_header_buffer: [1024]u8 = undefined,
|
||||
response_status: u10 = 0,
|
||||
response_override_mime_type: ?[]const u8 = null,
|
||||
response_mime: Mime = undefined,
|
||||
response_obj: ?ResponseObj = null,
|
||||
send_flag: bool = false,
|
||||
|
||||
payload: ?[]const u8 = null,
|
||||
|
||||
pub const prototype = *XMLHttpRequestEventTarget;
|
||||
pub const mem_guarantied = true;
|
||||
|
||||
@@ -116,6 +155,91 @@ pub const XMLHttpRequest = struct {
|
||||
|
||||
const JSONValue = std.json.Value;
|
||||
|
||||
const Headers = struct {
|
||||
alloc: std.mem.Allocator,
|
||||
list: List,
|
||||
|
||||
const List = std.ArrayListUnmanaged(std.http.Header);
|
||||
|
||||
fn init(alloc: std.mem.Allocator) Headers {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.list = List{},
|
||||
};
|
||||
}
|
||||
|
||||
fn deinit(self: *Headers) void {
|
||||
self.free();
|
||||
self.list.deinit(self.alloc);
|
||||
}
|
||||
|
||||
fn append(self: *Headers, k: []const u8, v: []const u8) !void {
|
||||
// duplicate strings
|
||||
const kk = try self.alloc.dupe(u8, k);
|
||||
const vv = try self.alloc.dupe(u8, v);
|
||||
try self.list.append(self.alloc, .{ .name = kk, .value = vv });
|
||||
}
|
||||
|
||||
// free all strings allocated.
|
||||
fn free(self: *Headers) void {
|
||||
for (self.list.items) |h| {
|
||||
self.alloc.free(h.name);
|
||||
self.alloc.free(h.value);
|
||||
}
|
||||
}
|
||||
|
||||
fn clearAndFree(self: *Headers) void {
|
||||
self.free();
|
||||
self.list.clearAndFree(self.alloc);
|
||||
}
|
||||
|
||||
fn has(self: Headers, k: []const u8) bool {
|
||||
for (self.list.items) |h| {
|
||||
if (std.ascii.eqlIgnoreCase(k, h.value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fn getFirstValue(self: Headers, k: []const u8) ?[]const u8 {
|
||||
for (self.list.items) |h| {
|
||||
if (std.ascii.eqlIgnoreCase(k, h.value)) {
|
||||
return h.value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// replace any existing header with the same key
|
||||
fn set(self: *Headers, k: []const u8, v: []const u8) !void {
|
||||
for (self.list.items, 0..) |h, i| {
|
||||
if (std.ascii.eqlIgnoreCase(k, h.value)) {
|
||||
const hh = self.list.swapRemove(i);
|
||||
self.alloc.free(hh.name);
|
||||
self.alloc.free(hh.value);
|
||||
}
|
||||
}
|
||||
self.append(k, v);
|
||||
}
|
||||
|
||||
// TODO
|
||||
fn sort(_: *Headers) void {}
|
||||
|
||||
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) {
|
||||
Empty: void,
|
||||
Text: []const u8,
|
||||
@@ -149,49 +273,13 @@ pub const XMLHttpRequest = struct {
|
||||
|
||||
const PrivState = enum { new, open, send, write, finish, wait, done };
|
||||
|
||||
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
|
||||
alloc: std.mem.Allocator,
|
||||
cli: *Client,
|
||||
impl: YieldImpl,
|
||||
|
||||
priv_state: PrivState = .new,
|
||||
req: ?Client.Request = null,
|
||||
|
||||
method: std.http.Method,
|
||||
state: u16,
|
||||
url: ?[]const u8,
|
||||
uri: std.Uri,
|
||||
headers: std.http.Headers,
|
||||
sync: bool = true,
|
||||
err: ?anyerror = null,
|
||||
|
||||
// TODO uncomment this field causes casting issue with
|
||||
// XMLHttpRequestEventTarget. I think it's dueto an alignement issue, but
|
||||
// not sure. see
|
||||
// https://lightpanda.slack.com/archives/C05TRU6RBM1/p1707819010681019
|
||||
// upload: ?XMLHttpRequestUpload = null,
|
||||
|
||||
timeout: u32 = 0,
|
||||
withCredentials: bool = false,
|
||||
// TODO: response readonly attribute any response;
|
||||
response_bytes: ?[]const u8 = null,
|
||||
response_type: ResponseType = .Empty,
|
||||
response_headers: std.http.Headers,
|
||||
response_status: u10 = 0,
|
||||
response_override_mime_type: ?[]const u8 = null,
|
||||
response_mime: Mime = undefined,
|
||||
response_obj: ?ResponseObj = null,
|
||||
send_flag: bool = false,
|
||||
|
||||
payload: ?[]const u8 = null,
|
||||
|
||||
const min_delay: u64 = 50000000; // 50ms
|
||||
|
||||
pub fn constructor(alloc: std.mem.Allocator, loop: *Loop, userctx: UserContext) !XMLHttpRequest {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.headers = .{ .allocator = alloc, .owned = true },
|
||||
.response_headers = .{ .allocator = alloc, .owned = true },
|
||||
.headers = Headers.init(alloc),
|
||||
.response_headers = Headers.init(alloc),
|
||||
.impl = YieldImpl.init(loop),
|
||||
.method = undefined,
|
||||
.url = null,
|
||||
@@ -385,7 +473,7 @@ pub const XMLHttpRequest = struct {
|
||||
const body_init = XMLHttpRequestBodyInit{ .String = body.? };
|
||||
|
||||
// keep the user content type from request headers.
|
||||
if (self.headers.getFirstEntry("Content-Type") == null) {
|
||||
if (self.headers.has("Content-Type")) {
|
||||
// https://fetch.spec.whatwg.org/#bodyinit-safely-extract
|
||||
try self.headers.append("Content-Type", try body_init.contentType());
|
||||
}
|
||||
@@ -411,7 +499,10 @@ pub const XMLHttpRequest = struct {
|
||||
switch (self.priv_state) {
|
||||
.new => {
|
||||
self.priv_state = .open;
|
||||
self.req = self.cli.open(self.method, self.uri, self.headers, .{}) catch |e| return self.onErr(e);
|
||||
self.req = self.cli.open(self.method, self.uri, .{
|
||||
.server_header_buffer = &self.response_header_buffer,
|
||||
.extra_headers = self.headers.all(),
|
||||
}) catch |e| return self.onErr(e);
|
||||
},
|
||||
.open => {
|
||||
// prepare payload transfert.
|
||||
@@ -441,7 +532,8 @@ pub const XMLHttpRequest = struct {
|
||||
log.info("{any} {any} {d}", .{ self.method, self.uri, self.req.?.response.status });
|
||||
|
||||
self.priv_state = .done;
|
||||
self.response_headers = self.req.?.response.headers.clone(self.response_headers.allocator) catch |e| return self.onErr(e);
|
||||
var it = self.req.?.response.iterateHeaders();
|
||||
self.response_headers.load(&it) catch |e| return self.onErr(e);
|
||||
|
||||
// extract a mime type from headers.
|
||||
const ct = self.response_headers.getFirstValue("Content-Type") orelse "text/xml";
|
||||
|
||||
Reference in New Issue
Block a user