mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 06:33:29 +00:00
replace zig-async-io and std.http.Client with a custom HTTP client
This commit is contained in:
@@ -10,7 +10,6 @@ The default license for this project is [AGPL-3.0-only](LICENSE).
|
|||||||
The following files are licensed under MIT:
|
The following files are licensed under MIT:
|
||||||
|
|
||||||
```
|
```
|
||||||
src/http/Client.zig
|
|
||||||
src/polyfill/fetch.js
|
src/polyfill/fetch.js
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
13
build.zig
13
build.zig
@@ -177,6 +177,9 @@ fn common(
|
|||||||
options: jsruntime.Options,
|
options: jsruntime.Options,
|
||||||
) !void {
|
) !void {
|
||||||
const target = step.root_module.resolved_target.?;
|
const target = step.root_module.resolved_target.?;
|
||||||
|
const optimize = step.root_module.optimize.?;
|
||||||
|
const dep_opts = .{ .target = target, .optimize = optimize };
|
||||||
|
|
||||||
const jsruntimemod = try jsruntime_pkgs.module(
|
const jsruntimemod = try jsruntime_pkgs.module(
|
||||||
b,
|
b,
|
||||||
options,
|
options,
|
||||||
@@ -189,15 +192,7 @@ fn common(
|
|||||||
netsurf.addImport("jsruntime", jsruntimemod);
|
netsurf.addImport("jsruntime", jsruntimemod);
|
||||||
step.root_module.addImport("netsurf", netsurf);
|
step.root_module.addImport("netsurf", netsurf);
|
||||||
|
|
||||||
const asyncio = b.addModule("asyncio", .{
|
step.root_module.addImport("tls", b.dependency("tls", dep_opts).module("tls"));
|
||||||
.root_source_file = b.path("vendor/zig-async-io/src/lib.zig"),
|
|
||||||
});
|
|
||||||
step.root_module.addImport("asyncio", asyncio);
|
|
||||||
|
|
||||||
const tlsmod = b.addModule("tls", .{
|
|
||||||
.root_source_file = b.path("vendor/tls.zig/src/root.zig"),
|
|
||||||
});
|
|
||||||
step.root_module.addImport("tls", tlsmod);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn moduleNetSurf(b: *std.Build, target: std.Build.ResolvedTarget) !*std.Build.Module {
|
fn moduleNetSurf(b: *std.Build, target: std.Build.ResolvedTarget) !*std.Build.Module {
|
||||||
|
|||||||
12
build.zig.zon
Normal file
12
build.zig.zon
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.{
|
||||||
|
.name = .browser,
|
||||||
|
.paths = .{""},
|
||||||
|
.version = "0.0.0",
|
||||||
|
.fingerprint = 0xda130f3af836cea0,
|
||||||
|
.dependencies = .{
|
||||||
|
.tls = .{
|
||||||
|
.url = "https://github.com/karlseguin/tls.zig/archive/e39d40150f10464992da11352fb3955b3345272f.tar.gz",
|
||||||
|
.hash = "122039cd3abe387b69d23930bf12154c2c84fc894874e10129a1fc5e8ac75ca0ddc0"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -24,7 +24,6 @@ const Allocator = std.mem.Allocator;
|
|||||||
const Types = @import("root").Types;
|
const Types = @import("root").Types;
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("netsurf");
|
||||||
const Loader = @import("loader.zig").Loader;
|
|
||||||
const Dump = @import("dump.zig");
|
const Dump = @import("dump.zig");
|
||||||
const Mime = @import("mime.zig").Mime;
|
const Mime = @import("mime.zig").Mime;
|
||||||
|
|
||||||
@@ -44,10 +43,8 @@ const Location = @import("../html/location.zig").Location;
|
|||||||
|
|
||||||
const storage = @import("../storage/storage.zig");
|
const storage = @import("../storage/storage.zig");
|
||||||
|
|
||||||
const FetchResult = @import("../http/Client.zig").Client.FetchResult;
|
const http = @import("../http/client.zig");
|
||||||
|
|
||||||
const UserContext = @import("../user_context.zig").UserContext;
|
const UserContext = @import("../user_context.zig").UserContext;
|
||||||
const HttpClient = @import("asyncio").Client;
|
|
||||||
|
|
||||||
const polyfill = @import("../polyfill/polyfill.zig");
|
const polyfill = @import("../polyfill/polyfill.zig");
|
||||||
|
|
||||||
@@ -63,20 +60,21 @@ pub const Browser = struct {
|
|||||||
app: *App,
|
app: *App,
|
||||||
session: ?*Session,
|
session: ?*Session,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
http_client: *HttpClient,
|
http_client: *http.Client,
|
||||||
session_pool: SessionPool,
|
session_pool: SessionPool,
|
||||||
page_arena: std.heap.ArenaAllocator,
|
page_arena: std.heap.ArenaAllocator,
|
||||||
|
|
||||||
const SessionPool = std.heap.MemoryPool(Session);
|
const SessionPool = std.heap.MemoryPool(Session);
|
||||||
|
|
||||||
pub fn init(app: *App) Browser {
|
pub fn init(app: *App) !Browser {
|
||||||
const allocator = app.allocator;
|
const allocator = app.allocator;
|
||||||
return .{
|
return .{
|
||||||
.app = app,
|
.app = app,
|
||||||
.session = null,
|
.session = null,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.http_client = @ptrCast(&app.http_client),
|
.http_client = &app.http_client,
|
||||||
.session_pool = SessionPool.init(allocator),
|
.session_pool = SessionPool.init(allocator),
|
||||||
|
.http_client = try http.Client.init(allocator, 5),
|
||||||
.page_arena = std.heap.ArenaAllocator.init(allocator),
|
.page_arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -121,9 +119,6 @@ pub const Session = struct {
|
|||||||
// all others Session deps use directly self.alloc and not the arena.
|
// all others Session deps use directly self.alloc and not the arena.
|
||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
|
|
||||||
// TODO handle proxy
|
|
||||||
loader: Loader,
|
|
||||||
|
|
||||||
env: Env,
|
env: Env,
|
||||||
inspector: jsruntime.Inspector,
|
inspector: jsruntime.Inspector,
|
||||||
|
|
||||||
@@ -132,6 +127,7 @@ pub const Session = struct {
|
|||||||
// TODO move the shed to the browser?
|
// TODO move the shed to the browser?
|
||||||
storage_shed: storage.Shed,
|
storage_shed: storage.Shed,
|
||||||
page: ?Page = null,
|
page: ?Page = null,
|
||||||
|
http_client: *http.Client,
|
||||||
|
|
||||||
jstypes: [Types.len]usize = undefined,
|
jstypes: [Types.len]usize = undefined,
|
||||||
|
|
||||||
@@ -143,7 +139,7 @@ pub const Session = struct {
|
|||||||
.env = undefined,
|
.env = undefined,
|
||||||
.browser = browser,
|
.browser = browser,
|
||||||
.inspector = undefined,
|
.inspector = undefined,
|
||||||
.loader = Loader.init(allocator),
|
.http_client = &browser.http_client,
|
||||||
.storage_shed = storage.Shed.init(allocator),
|
.storage_shed = storage.Shed.init(allocator),
|
||||||
.arena = std.heap.ArenaAllocator.init(allocator),
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
.window = Window.create(null, .{ .agent = user_agent }),
|
.window = Window.create(null, .{ .agent = user_agent }),
|
||||||
@@ -181,7 +177,6 @@ pub const Session = struct {
|
|||||||
}
|
}
|
||||||
self.env.deinit();
|
self.env.deinit();
|
||||||
self.arena.deinit();
|
self.arena.deinit();
|
||||||
self.loader.deinit();
|
|
||||||
self.storage_shed.deinit();
|
self.storage_shed.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,32 +365,14 @@ pub const Page = struct {
|
|||||||
} });
|
} });
|
||||||
|
|
||||||
// load the data
|
// load the data
|
||||||
var resp = try self.session.loader.get(arena, self.uri);
|
var request = try self.session.http_client.request(.GET, self.uri);
|
||||||
defer resp.deinit();
|
defer request.deinit();
|
||||||
|
var response = try request.sendSync(.{});
|
||||||
|
|
||||||
const req = resp.req;
|
const header = response.header;
|
||||||
|
log.info("GET {any} {d}", .{ self.uri, header.status });
|
||||||
|
|
||||||
log.info("GET {any} {d}", .{ self.uri, @intFromEnum(req.response.status) });
|
const ct = response.header.get("content-type") orelse {
|
||||||
|
|
||||||
// TODO handle redirection
|
|
||||||
log.debug("{?} {d} {s}", .{
|
|
||||||
req.response.version,
|
|
||||||
@intFromEnum(req.response.status),
|
|
||||||
req.response.reason,
|
|
||||||
// TODO log headers
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO handle charset
|
|
||||||
// https://html.spec.whatwg.org/#content-type
|
|
||||||
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 arena.dupe(u8, h.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const ct = ct_ orelse {
|
|
||||||
// no content type in HTTP headers.
|
// no content type in HTTP headers.
|
||||||
// TODO try to sniff mime type from the body.
|
// TODO try to sniff mime type from the body.
|
||||||
log.info("no content-type HTTP header", .{});
|
log.info("no content-type HTTP header", .{});
|
||||||
@@ -404,14 +381,18 @@ pub const Page = struct {
|
|||||||
|
|
||||||
log.debug("header content-type: {s}", .{ct});
|
log.debug("header content-type: {s}", .{ct});
|
||||||
var mime = try Mime.parse(arena, ct);
|
var mime = try Mime.parse(arena, ct);
|
||||||
|
defer mime.deinit();
|
||||||
|
|
||||||
if (mime.isHTML()) {
|
if (mime.isHTML()) {
|
||||||
try self.loadHTMLDoc(req.reader(), mime.charset orelse "utf-8", aux_data);
|
try self.loadHTMLDoc(&response, mime.charset orelse "utf-8", aux_data);
|
||||||
} else {
|
} else {
|
||||||
log.info("non-HTML document: {s}", .{ct});
|
log.info("non-HTML document: {s}", .{ct});
|
||||||
|
var arr: std.ArrayListUnmanaged(u8) = .{};
|
||||||
|
while (try response.next()) |data| {
|
||||||
|
try arr.appendSlice(arena, try arena.dupe(u8, data));
|
||||||
|
}
|
||||||
// save the body into the page.
|
// save the body into the page.
|
||||||
self.raw_data = try req.reader().readAllAlloc(arena, 16 * 1024 * 1024);
|
self.raw_data = arr.items;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,7 +434,7 @@ pub const Page = struct {
|
|||||||
// replace the user context document with the new one.
|
// replace the user context document with the new one.
|
||||||
try session.env.setUserContext(.{
|
try session.env.setUserContext(.{
|
||||||
.document = html_doc,
|
.document = html_doc,
|
||||||
.httpClient = self.session.browser.http_client,
|
.http_client = self.session.browser.http_client,
|
||||||
});
|
});
|
||||||
|
|
||||||
// browse the DOM tree to retrieve scripts
|
// browse the DOM tree to retrieve scripts
|
||||||
@@ -628,30 +609,32 @@ pub const Page = struct {
|
|||||||
|
|
||||||
const u = try std.Uri.resolve_inplace(self.uri, res_src, &b);
|
const u = try std.Uri.resolve_inplace(self.uri, res_src, &b);
|
||||||
|
|
||||||
var fetchres = try self.session.loader.get(arena, u);
|
var request = try self.session.http_client.request(.GET, u);
|
||||||
defer fetchres.deinit();
|
defer request.deinit();
|
||||||
|
var response = try request.sendSync(.{});
|
||||||
|
|
||||||
const resp = fetchres.req.response;
|
log.info("fetch {any}: {d}", .{ u, response.header.status });
|
||||||
|
|
||||||
log.info("fetch {any}: {d}", .{ u, resp.status });
|
if (response.header.status != 200) {
|
||||||
|
|
||||||
if (resp.status != .ok) {
|
|
||||||
return FetchError.BadStatusCode;
|
return FetchError.BadStatusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var arr: std.ArrayListUnmanaged(u8) = .{};
|
||||||
|
while (try response.next()) |data| {
|
||||||
|
try arr.appendSlice(arena, try arena.dupe(u8, data));
|
||||||
|
}
|
||||||
|
|
||||||
// TODO check content-type
|
// TODO check content-type
|
||||||
const body = try fetchres.req.reader().readAllAlloc(arena, 16 * 1024 * 1024);
|
|
||||||
|
|
||||||
// check no body
|
// check no body
|
||||||
if (body.len == 0) {
|
if (arr.items.len == 0) {
|
||||||
return FetchError.NoBody;
|
return FetchError.NoBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
return body;
|
return arr.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchScript senf a GET request to the src and execute the script
|
|
||||||
// received.
|
|
||||||
fn fetchScript(self: *const Page, s: *const Script) !void {
|
fn fetchScript(self: *const Page, s: *const Script) !void {
|
||||||
const arena = self.arena;
|
const arena = self.arena;
|
||||||
const body = try self.fetchData(arena, s.src, null);
|
const body = try self.fetchData(arena, s.src, null);
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
|
|
||||||
//
|
|
||||||
// Francis Bouvier <francis@lightpanda.io>
|
|
||||||
// Pierre Tachoire <pierre@lightpanda.io>
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as
|
|
||||||
// published by the Free Software Foundation, either version 3 of the
|
|
||||||
// License, or (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const Client = @import("../http/Client.zig");
|
|
||||||
|
|
||||||
const user_agent = @import("browser.zig").user_agent;
|
|
||||||
|
|
||||||
pub const Loader = struct {
|
|
||||||
client: Client,
|
|
||||||
// use 64KB for headers buffer size.
|
|
||||||
server_header_buffer: [1024 * 64]u8 = undefined,
|
|
||||||
|
|
||||||
pub const Response = struct {
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
req: *Client.Request,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Response) void {
|
|
||||||
self.req.deinit();
|
|
||||||
self.alloc.destroy(self.req);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator) Loader {
|
|
||||||
return Loader{
|
|
||||||
.client = Client{
|
|
||||||
.allocator = alloc,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Loader) void {
|
|
||||||
self.client.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 resp = Response{
|
|
||||||
.alloc = alloc,
|
|
||||||
.req = try alloc.create(Client.Request),
|
|
||||||
};
|
|
||||||
errdefer alloc.destroy(resp.req);
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
try resp.req.send();
|
|
||||||
try resp.req.finish();
|
|
||||||
try resp.req.wait();
|
|
||||||
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
test "loader: get" {
|
|
||||||
const alloc = std.testing.allocator;
|
|
||||||
var loader = Loader.init(alloc);
|
|
||||||
defer loader.deinit();
|
|
||||||
|
|
||||||
const uri = try std.Uri.parse("http://localhost:9582/loader");
|
|
||||||
var result = try loader.get(alloc, uri);
|
|
||||||
defer result.deinit();
|
|
||||||
|
|
||||||
try std.testing.expectEqual(.ok, result.req.response.status);
|
|
||||||
|
|
||||||
var res: [128]u8 = undefined;
|
|
||||||
const size = try result.req.readAll(&res);
|
|
||||||
try std.testing.expectEqual(6, size);
|
|
||||||
try std.testing.expectEqualStrings("Hello!", res[0..6]);
|
|
||||||
}
|
|
||||||
@@ -73,13 +73,13 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
pub const Browser = TypeProvider.Browser;
|
pub const Browser = TypeProvider.Browser;
|
||||||
pub const Session = TypeProvider.Session;
|
pub const Session = TypeProvider.Session;
|
||||||
|
|
||||||
pub fn init(app: *App, client: TypeProvider.Client) Self {
|
pub fn init(app: *App, client: TypeProvider.Client) !Self {
|
||||||
const allocator = app.allocator;
|
const allocator = app.allocator;
|
||||||
return .{
|
return .{
|
||||||
.client = client,
|
.client = client,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.browser_context = null,
|
.browser_context = null,
|
||||||
.browser = Browser.init(app),
|
.browser = try Browser.init(app),
|
||||||
.message_arena = std.heap.ArenaAllocator.init(allocator),
|
.message_arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
.browser_context_pool = std.heap.MemoryPool(BrowserContext(Self)).init(allocator),
|
.browser_context_pool = std.heap.MemoryPool(BrowserContext(Self)).init(allocator),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const Browser = struct {
|
|||||||
session: ?*Session = null,
|
session: ?*Session = null,
|
||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
|
|
||||||
pub fn init(app: *App) Browser {
|
pub fn init(app: *App) !Browser {
|
||||||
return .{
|
return .{
|
||||||
.arena = std.heap.ArenaAllocator.init(app.allocator),
|
.arena = std.heap.ArenaAllocator.init(app.allocator),
|
||||||
};
|
};
|
||||||
|
|||||||
1776
src/http/Client.zig
1776
src/http/Client.zig
File diff suppressed because it is too large
Load Diff
1350
src/http/client.zig
Normal file
1350
src/http/client.zig
Normal file
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,6 @@ const storage = @import("storage/storage.zig");
|
|||||||
const url = @import("url/url.zig");
|
const url = @import("url/url.zig");
|
||||||
const URL = url.URL;
|
const URL = url.URL;
|
||||||
const urlquery = @import("url/query.zig");
|
const urlquery = @import("url/query.zig");
|
||||||
const Client = @import("asyncio").Client;
|
|
||||||
const Location = @import("html/location.zig").Location;
|
const Location = @import("html/location.zig").Location;
|
||||||
|
|
||||||
const documentTestExecFn = @import("dom/document.zig").testExecFn;
|
const documentTestExecFn = @import("dom/document.zig").testExecFn;
|
||||||
@@ -89,12 +88,12 @@ fn testExecFn(
|
|||||||
std.debug.print("documentHTMLClose error: {s}\n", .{@errorName(err)});
|
std.debug.print("documentHTMLClose error: {s}\n", .{@errorName(err)});
|
||||||
};
|
};
|
||||||
|
|
||||||
var cli = Client{ .allocator = alloc };
|
var http_client = try @import("http/client.zig").Client.init(alloc, 5);
|
||||||
defer cli.deinit();
|
defer http_client.deinit();
|
||||||
|
|
||||||
try js_env.setUserContext(.{
|
try js_env.setUserContext(.{
|
||||||
.document = doc,
|
.document = doc,
|
||||||
.httpClient = &cli,
|
.http_client = &http_client,
|
||||||
});
|
});
|
||||||
|
|
||||||
// alias global as self and window
|
// alias global as self and window
|
||||||
@@ -220,6 +219,11 @@ pub fn main() !void {
|
|||||||
if (run == .all or run == .unit) {
|
if (run == .all or run == .unit) {
|
||||||
std.debug.print("\n", .{});
|
std.debug.print("\n", .{});
|
||||||
for (builtin.test_functions) |test_fn| {
|
for (builtin.test_functions) |test_fn| {
|
||||||
|
if (std.mem.startsWith(u8, test_fn.name, "http.client.test")) {
|
||||||
|
// covered by unit test, needs a dummy server started, which
|
||||||
|
// main_test doesn't do.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try parser.init();
|
try parser.init();
|
||||||
defer parser.deinit();
|
defer parser.deinit();
|
||||||
|
|
||||||
|
|||||||
@@ -2181,21 +2181,28 @@ fn parseParams(enc: ?[:0]const u8) c.dom_hubbub_parser_params {
|
|||||||
|
|
||||||
fn parseData(parser: *c.dom_hubbub_parser, reader: anytype) !void {
|
fn parseData(parser: *c.dom_hubbub_parser, reader: anytype) !void {
|
||||||
var err: c.hubbub_error = undefined;
|
var err: c.hubbub_error = undefined;
|
||||||
var buffer: [1024]u8 = undefined;
|
const TI = @typeInfo(@TypeOf(reader));
|
||||||
var ln = buffer.len;
|
if (TI == .pointer and @hasDecl(TI.pointer.child, "next")) {
|
||||||
while (ln > 0) {
|
while (try reader.next()) |data| {
|
||||||
ln = try reader.read(&buffer);
|
err = c.dom_hubbub_parser_parse_chunk(parser, data.ptr, data.len);
|
||||||
err = c.dom_hubbub_parser_parse_chunk(parser, &buffer, ln);
|
try parserErr(err);
|
||||||
// TODO handle encoding change error return.
|
}
|
||||||
// When the HTML contains a META tag with a different encoding than the
|
} else {
|
||||||
// original one, a c.DOM_HUBBUB_HUBBUB_ERR_ENCODINGCHANGE error is
|
var buffer: [1024]u8 = undefined;
|
||||||
// returned.
|
var ln = buffer.len;
|
||||||
// In this case, we must restart the parsing with the new detected
|
while (ln > 0) {
|
||||||
// encoding. The detected encoding is stored in the document and we can
|
ln = try reader.read(&buffer);
|
||||||
// get it with documentGetInputEncoding().
|
err = c.dom_hubbub_parser_parse_chunk(parser, &buffer, ln);
|
||||||
try parserErr(err);
|
// TODO handle encoding change error return.
|
||||||
|
// When the HTML contains a META tag with a different encoding than the
|
||||||
|
// original one, a c.DOM_HUBBUB_HUBBUB_ERR_ENCODINGCHANGE error is
|
||||||
|
// returned.
|
||||||
|
// In this case, we must restart the parsing with the new detected
|
||||||
|
// encoding. The detected encoding is stored in the document and we can
|
||||||
|
// get it with documentGetInputEncoding().
|
||||||
|
try parserErr(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.dom_hubbub_parser_completed(parser);
|
err = c.dom_hubbub_parser_completed(parser);
|
||||||
try parserErr(err);
|
try parserErr(err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ pub const Client = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.mode = .websocket;
|
self.mode = .websocket;
|
||||||
self.cdp = CDP.init(self.server.app, self);
|
self.cdp = try CDP.init(self.server.app, self);
|
||||||
return self.send(arena, response);
|
return self.send(arena, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ pub fn expectEqual(expected: anytype, actual: anytype) !void {
|
|||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
.optional => {
|
.optional => {
|
||||||
if (actual == null) {
|
if (@typeInfo(@TypeOf(expected)) == .null) {
|
||||||
return std.testing.expectEqual(null, expected);
|
return std.testing.expectEqual(null, actual);
|
||||||
}
|
}
|
||||||
return expectEqual(expected, actual.?);
|
return expectEqual(expected, actual.?);
|
||||||
},
|
},
|
||||||
@@ -141,3 +141,36 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
|
|||||||
pub fn app(_: anytype) *App {
|
pub fn app(_: anytype) *App {
|
||||||
return App.init(allocator, .serve) catch unreachable;
|
return App.init(allocator, .serve) catch unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const Random = struct {
|
||||||
|
var instance: ?std.Random.DefaultPrng = null;
|
||||||
|
|
||||||
|
pub fn fill(buf: []u8) void {
|
||||||
|
var r = random();
|
||||||
|
r.bytes(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fillAtLeast(buf: []u8, min: usize) []u8 {
|
||||||
|
var r = random();
|
||||||
|
const l = r.intRangeAtMost(usize, min, buf.len);
|
||||||
|
r.bytes(buf[0..l]);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn intRange(comptime T: type, min: T, max: T) T {
|
||||||
|
var r = random();
|
||||||
|
return r.intRangeAtMost(T, min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random() std.Random {
|
||||||
|
if (instance == null) {
|
||||||
|
var seed: u64 = undefined;
|
||||||
|
std.posix.getrandom(std.mem.asBytes(&seed)) catch unreachable;
|
||||||
|
instance = std.Random.DefaultPrng.init(seed);
|
||||||
|
// instance = std.Random.DefaultPrng.init(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
return instance.?.random();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
>>>>>>> eaccbd0 (replace zig-async-io and std.http.Client with a custom HTTP client)
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const http_thread = blk: {
|
const http_thread = blk: {
|
||||||
const address = try std.net.Address.parseIp("127.0.0.1", 9582);
|
const address = try std.net.Address.parseIp("127.0.0.1", 9582);
|
||||||
const thread = try std.Thread.spawn(.{}, serveHTTP, .{address});
|
const thread = try std.Thread.spawn(.{}, serveHTTP, .{ allocator, address });
|
||||||
break :blk thread;
|
break :blk thread;
|
||||||
};
|
};
|
||||||
defer http_thread.join();
|
defer http_thread.join();
|
||||||
@@ -323,12 +323,18 @@ fn isUnnamed(t: std.builtin.TestFn) bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serveHTTP(address: std.net.Address) !void {
|
fn serveHTTP(allocator: Allocator, address: std.net.Address) !void {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
var listener = try address.listen(.{ .reuse_address = true });
|
var listener = try address.listen(.{ .reuse_address = true });
|
||||||
defer listener.deinit();
|
defer listener.deinit();
|
||||||
|
|
||||||
var read_buffer: [1024]u8 = undefined;
|
var read_buffer: [1024]u8 = undefined;
|
||||||
ACCEPT: while (true) {
|
ACCEPT: while (true) {
|
||||||
|
defer _ = arena.reset(.{ .retain_with_limit = 1024 });
|
||||||
|
const aa = arena.allocator();
|
||||||
|
|
||||||
var conn = try listener.accept();
|
var conn = try listener.accept();
|
||||||
defer conn.stream.close();
|
defer conn.stream.close();
|
||||||
var server = std.http.Server.init(conn, &read_buffer);
|
var server = std.http.Server.init(conn, &read_buffer);
|
||||||
@@ -344,8 +350,23 @@ fn serveHTTP(address: std.net.Address) !void {
|
|||||||
|
|
||||||
const path = request.head.target;
|
const path = request.head.target;
|
||||||
if (std.mem.eql(u8, path, "/loader")) {
|
if (std.mem.eql(u8, path, "/loader")) {
|
||||||
try writeResponse(&request, .{
|
try request.respond("Hello!", .{});
|
||||||
.body = "Hello!",
|
} else if (std.mem.eql(u8, path, "/http_client/simple")) {
|
||||||
|
try request.respond("", .{});
|
||||||
|
} else if (std.mem.eql(u8, path, "/http_client/body")) {
|
||||||
|
var headers: std.ArrayListUnmanaged(std.http.Header) = .{};
|
||||||
|
|
||||||
|
var it = request.iterateHeaders();
|
||||||
|
while (it.next()) |hdr| {
|
||||||
|
try headers.append(aa, .{
|
||||||
|
.name = try std.fmt.allocPrint(aa, "_{s}", .{hdr.name}),
|
||||||
|
.value = hdr.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try request.respond("over 9000!", .{
|
||||||
|
.status = .created,
|
||||||
|
.extra_headers = headers.items,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,26 +381,16 @@ fn serveCDP(app: *App, address: std.net.Address) !void {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const Response = struct {
|
|
||||||
body: []const u8 = "",
|
|
||||||
status: std.http.Status = .ok,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn writeResponse(req: *std.http.Server.Request, res: Response) !void {
|
|
||||||
try req.respond(res.body, .{ .status = res.status });
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
test {
|
||||||
std.testing.refAllDecls(@import("url/query.zig"));
|
std.testing.refAllDecls(@import("url/query.zig"));
|
||||||
std.testing.refAllDecls(@import("browser/dump.zig"));
|
std.testing.refAllDecls(@import("browser/dump.zig"));
|
||||||
std.testing.refAllDecls(@import("browser/loader.zig"));
|
|
||||||
std.testing.refAllDecls(@import("browser/mime.zig"));
|
std.testing.refAllDecls(@import("browser/mime.zig"));
|
||||||
std.testing.refAllDecls(@import("css/css.zig"));
|
std.testing.refAllDecls(@import("css/css.zig"));
|
||||||
std.testing.refAllDecls(@import("css/libdom_test.zig"));
|
std.testing.refAllDecls(@import("css/libdom_test.zig"));
|
||||||
std.testing.refAllDecls(@import("css/match_test.zig"));
|
std.testing.refAllDecls(@import("css/match_test.zig"));
|
||||||
std.testing.refAllDecls(@import("css/parser.zig"));
|
std.testing.refAllDecls(@import("css/parser.zig"));
|
||||||
std.testing.refAllDecls(@import("generate.zig"));
|
std.testing.refAllDecls(@import("generate.zig"));
|
||||||
std.testing.refAllDecls(@import("http/Client.zig"));
|
std.testing.refAllDecls(@import("http/client.zig"));
|
||||||
std.testing.refAllDecls(@import("storage/storage.zig"));
|
std.testing.refAllDecls(@import("storage/storage.zig"));
|
||||||
std.testing.refAllDecls(@import("storage/cookie.zig"));
|
std.testing.refAllDecls(@import("storage/cookie.zig"));
|
||||||
std.testing.refAllDecls(@import("iterator/iterator.zig"));
|
std.testing.refAllDecls(@import("iterator/iterator.zig"));
|
||||||
@@ -388,4 +399,5 @@ test {
|
|||||||
std.testing.refAllDecls(@import("log.zig"));
|
std.testing.refAllDecls(@import("log.zig"));
|
||||||
std.testing.refAllDecls(@import("datetime.zig"));
|
std.testing.refAllDecls(@import("datetime.zig"));
|
||||||
std.testing.refAllDecls(@import("telemetry/telemetry.zig"));
|
std.testing.refAllDecls(@import("telemetry/telemetry.zig"));
|
||||||
|
std.testing.refAllDecls(@import("http/client.zig"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const parser = @import("netsurf");
|
const parser = @import("netsurf");
|
||||||
const Client = @import("asyncio").Client;
|
const Client = @import("http/client.zig").Client;
|
||||||
|
|
||||||
pub const UserContext = struct {
|
pub const UserContext = struct {
|
||||||
document: *parser.DocumentHTML,
|
document: *parser.DocumentHTML,
|
||||||
httpClient: *Client,
|
http_client: *Client,
|
||||||
};
|
};
|
||||||
|
|||||||
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 Mime = @import("../browser/mime.zig").Mime;
|
||||||
|
|
||||||
const Loop = jsruntime.Loop;
|
const Loop = jsruntime.Loop;
|
||||||
const Client = @import("asyncio").Client;
|
const http = @import("../http/client.zig");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("netsurf");
|
||||||
|
|
||||||
@@ -95,14 +95,12 @@ pub const XMLHttpRequestBodyInit = union(XMLHttpRequestBodyInitTag) {
|
|||||||
pub const XMLHttpRequest = struct {
|
pub const XMLHttpRequest = struct {
|
||||||
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
|
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
cli: *Client,
|
client: *http.Client,
|
||||||
io: Client.IO,
|
request: ?http.Request = null,
|
||||||
|
|
||||||
priv_state: PrivState = .new,
|
priv_state: PrivState = .new,
|
||||||
req: ?Client.Request = null,
|
|
||||||
ctx: ?Client.Ctx = null,
|
|
||||||
|
|
||||||
method: std.http.Method,
|
method: http.Request.Method,
|
||||||
state: State,
|
state: State,
|
||||||
url: ?[]const u8,
|
url: ?[]const u8,
|
||||||
uri: std.Uri,
|
uri: std.Uri,
|
||||||
@@ -125,7 +123,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
withCredentials: bool = false,
|
withCredentials: bool = false,
|
||||||
// TODO: response readonly attribute any response;
|
// TODO: response readonly attribute any response;
|
||||||
response_bytes: ?[]const u8 = null,
|
response_bytes: std.ArrayListUnmanaged(u8) = .{},
|
||||||
response_type: ResponseType = .Empty,
|
response_type: ResponseType = .Empty,
|
||||||
response_headers: Headers,
|
response_headers: Headers,
|
||||||
|
|
||||||
@@ -133,7 +131,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
// use 16KB for headers buffer size.
|
// use 16KB for headers buffer size.
|
||||||
response_header_buffer: [1024 * 16]u8 = undefined,
|
response_header_buffer: [1024 * 16]u8 = undefined,
|
||||||
|
|
||||||
response_status: u10 = 0,
|
response_status: u16 = 0,
|
||||||
|
|
||||||
// TODO uncomment this field causes casting issue with
|
// TODO uncomment this field causes casting issue with
|
||||||
// XMLHttpRequestEventTarget. I think it's dueto an alignement issue, but
|
// 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 {
|
fn all(self: Headers) []std.http.Header {
|
||||||
return self.list.items;
|
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) {
|
const Response = union(ResponseType) {
|
||||||
@@ -290,17 +281,16 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
const min_delay: u64 = 50000000; // 50ms
|
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 .{
|
return .{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.headers = Headers.init(alloc),
|
.headers = Headers.init(alloc),
|
||||||
.response_headers = Headers.init(alloc),
|
.response_headers = Headers.init(alloc),
|
||||||
.io = Client.IO.init(loop),
|
|
||||||
.method = undefined,
|
.method = undefined,
|
||||||
.url = null,
|
.url = null,
|
||||||
.uri = undefined,
|
.uri = undefined,
|
||||||
.state = .unsent,
|
.state = .unsent,
|
||||||
.cli = userctx.httpClient,
|
.client = userctx.http_client,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,7 +301,6 @@ pub const XMLHttpRequest = struct {
|
|||||||
if (self.payload) |v| alloc.free(v);
|
if (self.payload) |v| alloc.free(v);
|
||||||
self.payload = null;
|
self.payload = null;
|
||||||
|
|
||||||
if (self.response_bytes) |v| alloc.free(v);
|
|
||||||
if (self.response_obj) |v| v.deinit();
|
if (self.response_obj) |v| v.deinit();
|
||||||
|
|
||||||
self.response_obj = null;
|
self.response_obj = null;
|
||||||
@@ -329,12 +318,6 @@ pub const XMLHttpRequest = struct {
|
|||||||
self.send_flag = false;
|
self.send_flag = false;
|
||||||
|
|
||||||
self.priv_state = .new;
|
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 {
|
pub fn deinit(self: *XMLHttpRequest, alloc: std.mem.Allocator) void {
|
||||||
@@ -449,7 +432,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const methods = [_]struct {
|
const methods = [_]struct {
|
||||||
tag: std.http.Method,
|
tag: http.Request.Method,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
}{
|
}{
|
||||||
.{ .tag = .DELETE, .name = "DELETE" },
|
.{ .tag = .DELETE, .name = "DELETE" },
|
||||||
@@ -461,7 +444,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
};
|
};
|
||||||
const methods_forbidden = [_][]const u8{ "CONNECT", "TRACE", "TRACK" };
|
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| {
|
for (methods) |method| {
|
||||||
if (std.ascii.eqlIgnoreCase(method.name, m)) {
|
if (std.ascii.eqlIgnoreCase(method.name, m)) {
|
||||||
return method.tag;
|
return method.tag;
|
||||||
@@ -485,7 +468,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO body can be either a XMLHttpRequestBodyInit or a document
|
// 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.state != .opened) return DOMError.InvalidState;
|
||||||
if (self.send_flag) return DOMError.InvalidState;
|
if (self.send_flag) return DOMError.InvalidState;
|
||||||
|
|
||||||
@@ -515,153 +498,77 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
self.priv_state = .open;
|
self.priv_state = .open;
|
||||||
|
|
||||||
self.req = try self.cli.create(self.method, self.uri, .{
|
self.request = try self.client.request(self.method, self.uri);
|
||||||
.server_header_buffer = &self.response_header_buffer,
|
|
||||||
.extra_headers = self.headers.all(),
|
|
||||||
});
|
|
||||||
errdefer {
|
|
||||||
self.req.?.deinit();
|
|
||||||
self.req = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.ctx = try Client.Ctx.init(&self.io, &self.req.?);
|
var request = &self.request.?;
|
||||||
errdefer {
|
errdefer request.deinit();
|
||||||
self.ctx.?.deinit();
|
|
||||||
self.ctx = null;
|
|
||||||
}
|
|
||||||
self.ctx.?.userData = self;
|
|
||||||
|
|
||||||
try self.cli.async_open(
|
for (self.headers.list.items) |hdr| {
|
||||||
self.method,
|
try request.addHeader(hdr.name, hdr.value, .{});
|
||||||
self.uri,
|
}
|
||||||
.{ .server_header_buffer = &self.response_header_buffer },
|
request.body = self.payload;
|
||||||
&self.ctx.?,
|
try request.sendAsync(loop, self, .{});
|
||||||
onRequestConnect,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn onRequestWait(ctx: *Client.Ctx, res: anyerror!void) !void {
|
pub fn onHttpResponse(self: *XMLHttpRequest, progress_: http.Error!http.Progress) !void {
|
||||||
var self = selfCtx(ctx);
|
const progress = progress_ catch |err| {
|
||||||
res catch |err| return self.onErr(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;
|
self.priv_state = .done;
|
||||||
var it = self.req.?.response.iterateHeaders();
|
|
||||||
self.response_headers.load(&it) catch |e| return self.onErr(e);
|
|
||||||
|
|
||||||
// extract a mime type from headers.
|
for (header.headers.items) |hdr| {
|
||||||
const ct = self.response_headers.getFirstValue("Content-Type") orelse "text/xml";
|
try self.response_headers.append(hdr.name, hdr.value);
|
||||||
self.response_mime = Mime.parse(self.alloc, ct) catch |e| return self.onErr(e);
|
}
|
||||||
|
|
||||||
// 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;
|
// TODO handle override mime type
|
||||||
self.dispatchEvt("readystatechange");
|
self.state = .headers_received;
|
||||||
|
|
||||||
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;
|
|
||||||
self.dispatchEvt("readystatechange");
|
self.dispatchEvt("readystatechange");
|
||||||
|
|
||||||
// dispatch a progress event progress.
|
self.response_status = header.status;
|
||||||
self.dispatchProgressEvent("progress", .{
|
|
||||||
.loaded = loaded,
|
|
||||||
.total = total,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
self.response_bytes = buf.items;
|
|
||||||
self.send_flag = false;
|
|
||||||
|
|
||||||
|
// 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.state = .done;
|
||||||
self.dispatchEvt("readystatechange");
|
self.dispatchEvt("readystatechange");
|
||||||
|
|
||||||
// dispatch a progress event load.
|
// 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.
|
// dispatch a progress event loadend.
|
||||||
self.dispatchProgressEvent("loadend", .{ .loaded = loaded, .total = total });
|
self.dispatchProgressEvent("loadend", .{ .loaded = total_len, .total = total_len });
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn onErr(self: *XMLHttpRequest, err: anyerror) void {
|
fn onErr(self: *XMLHttpRequest, err: anyerror) void {
|
||||||
@@ -675,12 +582,6 @@ pub const XMLHttpRequest = struct {
|
|||||||
self.dispatchProgressEvent("loadend", .{});
|
self.dispatchProgressEvent("loadend", .{});
|
||||||
|
|
||||||
log.debug("{any} {any} {any}", .{ self.method, self.uri, self.err });
|
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 {
|
pub fn _abort(self: *XMLHttpRequest) void {
|
||||||
@@ -803,7 +704,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (self.response_type == .JSON) {
|
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
|
// TODO Let jsonObject be the result of running parse JSON from bytes
|
||||||
// on this’s received bytes. If that threw an exception, then return
|
// on this’s received bytes. If that threw an exception, then return
|
||||||
@@ -841,7 +742,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
};
|
};
|
||||||
defer alloc.free(ccharset);
|
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 {
|
const doc = parser.documentHTMLParse(fbs.reader(), ccharset) catch {
|
||||||
self.response_obj = .{ .Failure = true };
|
self.response_obj = .{ .Failure = true };
|
||||||
return;
|
return;
|
||||||
@@ -862,7 +763,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
const p = std.json.parseFromSlice(
|
const p = std.json.parseFromSlice(
|
||||||
JSONValue,
|
JSONValue,
|
||||||
alloc,
|
alloc,
|
||||||
self.response_bytes.?,
|
self.response_bytes.items,
|
||||||
.{},
|
.{},
|
||||||
) catch |e| {
|
) catch |e| {
|
||||||
log.err("parse JSON: {}", .{e});
|
log.err("parse JSON: {}", .{e});
|
||||||
@@ -875,8 +776,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
pub fn get_responseText(self: *XMLHttpRequest) ![]const u8 {
|
pub fn get_responseText(self: *XMLHttpRequest) ![]const u8 {
|
||||||
if (self.response_type != .Empty and self.response_type != .Text) return DOMError.InvalidState;
|
if (self.response_type != .Empty and self.response_type != .Text) return DOMError.InvalidState;
|
||||||
|
return self.response_bytes.items;
|
||||||
return if (self.response_bytes) |v| v else "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _getResponseHeader(self: *XMLHttpRequest, name: []const u8) ?[]const u8 {
|
pub fn _getResponseHeader(self: *XMLHttpRequest, name: []const u8) ?[]const u8 {
|
||||||
|
|||||||
1
vendor/tls.zig
vendored
1
vendor/tls.zig
vendored
Submodule vendor/tls.zig deleted from 7eb35dabf8
Reference in New Issue
Block a user