-
Couldn't load subscription status.
- Fork 280
Fetch + ReadableStream #972
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
56c6e8b
remove polyfill and add req/resp
mookums df0b6d5
initial fetch in zig
mookums f9014bb
request url as null terminated
mookums dc2addb
fetch callback logging
mookums a133a71
proper fetch method and body setting
mookums 814e411
basic readable stream working
mookums c6e82d5
add json response method
mookums 1b3707a
add fetch to cdp domain
mookums 5ca41b5
more Headers compatibility
mookums 752e75e
add bodyUsed checks on Request and Response
mookums 6d88438
move fetch() into fetch.zig
mookums 38b922d
remove debug logging in ReadableStream
mookums 8dcba37
expand Headers interface
mookums 6225cb3
expand Request/Response interfaces
mookums 8743841
use proper Headers in fetch()
mookums 949479a
cleaning up various Headers routines
mookums 744b0bf
TypeError when Stream is locked
mookums 51ee313
working Header iterators
mookums 463440b
implement remaining ReadableStream functionality
mookums f68f184
jsValueToZig for fixed sized arrays
sjorsdonkers cf8f76b
remove length check of fixed size
sjorsdonkers a7848f4
avoid explicit memcpy
sjorsdonkers 7766892
retain value, avoid str alloc
sjorsdonkers 2659043
add logging on fetch error callback
mookums 9251180
support object as HeadersInit
mookums 20463a6
use destructor callback for FetchContext
mookums 9bbd06c
headers iterators should not allocate
mookums 618fff0
simplify Headers
mookums bb2595e
use call arena for json in Req/Resp
mookums fe89c2f
simplify cloning of Req/Resp
mookums af75ce7
deinit persistent promise resolver
mookums 76dae43
properly handle closed for ReadableStream
mookums 969bfb4
migrate fetch tests to htmlRunner
mookums a875ce4
copy our Request headers into the HTTP client
mookums ed11eab
use content length to reserve body size
mookums cd763a7
fix arena, add fetch test
karlseguin 24330a7
remove meaningless text from test
karlseguin c553a2c
use Env.PersistentPromiseResolver
mookums 2a0964f
htmlRunner for ReadableStream tests, fix ReadableStream enqueue
mookums 2a969f9
stop using destructor callback for fetch
mookums f22ee54
use fetch logging scope, clean some comments
mookums f6f0e14
PeristentPromiseResolver with page lifetime
mookums File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,227 @@ | ||
| // 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 log = @import("../../log.zig"); | ||
| const URL = @import("../../url.zig").URL; | ||
| const Page = @import("../page.zig").Page; | ||
|
|
||
| const iterator = @import("../iterator/iterator.zig"); | ||
|
|
||
| const v8 = @import("v8"); | ||
| const Env = @import("../env.zig").Env; | ||
|
|
||
| // https://developer.mozilla.org/en-US/docs/Web/API/Headers | ||
| const Headers = @This(); | ||
|
|
||
| // Case-Insensitive String HashMap. | ||
| // This allows us to avoid having to allocate lowercase keys all the time. | ||
| const HeaderHashMap = std.HashMapUnmanaged([]const u8, []const u8, struct { | ||
| pub fn hash(_: @This(), s: []const u8) u64 { | ||
| var buf: [64]u8 = undefined; | ||
| var hasher = std.hash.Wyhash.init(s.len); | ||
|
|
||
| var key = s; | ||
| while (key.len >= 64) { | ||
| const lower = std.ascii.lowerString(buf[0..], key[0..64]); | ||
| hasher.update(lower); | ||
| key = key[64..]; | ||
| } | ||
|
|
||
| if (key.len > 0) { | ||
| const lower = std.ascii.lowerString(buf[0..key.len], key); | ||
| hasher.update(lower); | ||
| } | ||
|
|
||
| return hasher.final(); | ||
| } | ||
|
|
||
| pub fn eql(_: @This(), a: []const u8, b: []const u8) bool { | ||
| return std.ascii.eqlIgnoreCase(a, b); | ||
| } | ||
| }, 80); | ||
|
|
||
| headers: HeaderHashMap = .empty, | ||
|
|
||
| // They can either be: | ||
| // | ||
| // 1. An array of string pairs. | ||
| // 2. An object with string keys to string values. | ||
| // 3. Another Headers object. | ||
| pub const HeadersInit = union(enum) { | ||
| // List of Pairs of []const u8 | ||
| strings: []const [2][]const u8, | ||
| // Headers | ||
| headers: *Headers, | ||
| // Mappings | ||
| object: Env.JsObject, | ||
| }; | ||
|
|
||
| pub fn constructor(_init: ?HeadersInit, page: *Page) !Headers { | ||
| const arena = page.arena; | ||
| var headers: HeaderHashMap = .empty; | ||
|
|
||
| if (_init) |init| { | ||
| switch (init) { | ||
| .strings => |kvs| { | ||
| for (kvs) |pair| { | ||
| const key = try arena.dupe(u8, pair[0]); | ||
| const value = try arena.dupe(u8, pair[1]); | ||
|
|
||
| try headers.put(arena, key, value); | ||
| } | ||
| }, | ||
| .headers => |hdrs| { | ||
| var iter = hdrs.headers.iterator(); | ||
| while (iter.next()) |entry| { | ||
| try headers.put(arena, entry.key_ptr.*, entry.value_ptr.*); | ||
| } | ||
| }, | ||
| .object => |obj| { | ||
| var iter = obj.nameIterator(); | ||
| while (try iter.next()) |name_value| { | ||
| const name = try name_value.toString(arena); | ||
| const value = try obj.get(name); | ||
| const value_string = try value.toString(arena); | ||
|
|
||
| try headers.put(arena, name, value_string); | ||
| } | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| return .{ | ||
| .headers = headers, | ||
| }; | ||
| } | ||
|
|
||
| pub fn append(self: *Headers, name: []const u8, value: []const u8, allocator: std.mem.Allocator) !void { | ||
| const key = try allocator.dupe(u8, name); | ||
| const gop = try self.headers.getOrPut(allocator, key); | ||
|
|
||
| if (gop.found_existing) { | ||
| // If we found it, append the value. | ||
| const new_value = try std.fmt.allocPrint(allocator, "{s}, {s}", .{ gop.value_ptr.*, value }); | ||
| gop.value_ptr.* = new_value; | ||
| } else { | ||
| // Otherwise, we should just put it in. | ||
| gop.value_ptr.* = try allocator.dupe(u8, value); | ||
| } | ||
| } | ||
|
|
||
| pub fn _append(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void { | ||
| const arena = page.arena; | ||
| try self.append(name, value, arena); | ||
| } | ||
|
|
||
| pub fn _delete(self: *Headers, name: []const u8) void { | ||
| _ = self.headers.remove(name); | ||
| } | ||
|
|
||
| pub const HeadersEntryIterator = struct { | ||
| slot: [2][]const u8, | ||
| iter: HeaderHashMap.Iterator, | ||
|
|
||
| // TODO: these SHOULD be in lexigraphical order but I'm not sure how actually | ||
| // important that is. | ||
| pub fn _next(self: *HeadersEntryIterator) ?[2][]const u8 { | ||
| if (self.iter.next()) |entry| { | ||
| self.slot[0] = entry.key_ptr.*; | ||
| self.slot[1] = entry.value_ptr.*; | ||
| return self.slot; | ||
| } else { | ||
| return null; | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| pub fn _entries(self: *const Headers) HeadersEntryIterable { | ||
| return .{ | ||
| .inner = .{ | ||
| .slot = undefined, | ||
| .iter = self.headers.iterator(), | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| pub fn _forEach(self: *Headers, callback_fn: Env.Function, this_arg: ?Env.JsObject) !void { | ||
| var iter = self.headers.iterator(); | ||
|
|
||
| const cb = if (this_arg) |this| try callback_fn.withThis(this) else callback_fn; | ||
|
|
||
| while (iter.next()) |entry| { | ||
| try cb.call(void, .{ entry.key_ptr.*, entry.value_ptr.*, self }); | ||
| } | ||
| } | ||
|
|
||
| pub fn _get(self: *const Headers, name: []const u8) ?[]const u8 { | ||
| return self.headers.get(name); | ||
| } | ||
|
|
||
| pub fn _has(self: *const Headers, name: []const u8) bool { | ||
| return self.headers.contains(name); | ||
| } | ||
|
|
||
| pub const HeadersKeyIterator = struct { | ||
| iter: HeaderHashMap.KeyIterator, | ||
|
|
||
| pub fn _next(self: *HeadersKeyIterator) ?[]const u8 { | ||
| if (self.iter.next()) |key| { | ||
| return key.*; | ||
| } else { | ||
| return null; | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| pub fn _keys(self: *const Headers) HeadersKeyIterable { | ||
| return .{ .inner = .{ .iter = self.headers.keyIterator() } }; | ||
| } | ||
|
|
||
| pub fn _set(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void { | ||
| const arena = page.arena; | ||
|
|
||
| const key = try arena.dupe(u8, name); | ||
| const gop = try self.headers.getOrPut(arena, key); | ||
| gop.value_ptr.* = try arena.dupe(u8, value); | ||
| } | ||
|
|
||
| pub const HeadersValueIterator = struct { | ||
| iter: HeaderHashMap.ValueIterator, | ||
|
|
||
| pub fn _next(self: *HeadersValueIterator) ?[]const u8 { | ||
| if (self.iter.next()) |value| { | ||
| return value.*; | ||
| } else { | ||
| return null; | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| pub fn _values(self: *const Headers) HeadersValueIterable { | ||
| return .{ .inner = .{ .iter = self.headers.valueIterator() } }; | ||
| } | ||
|
|
||
| pub const HeadersKeyIterable = iterator.Iterable(HeadersKeyIterator, "HeadersKeyIterator"); | ||
| pub const HeadersValueIterable = iterator.Iterable(HeadersValueIterator, "HeadersValueIterator"); | ||
| pub const HeadersEntryIterable = iterator.Iterable(HeadersEntryIterator, "HeadersEntryIterator"); | ||
|
|
||
| const testing = @import("../../testing.zig"); | ||
| test "fetch: Headers" { | ||
| try testing.htmlRunner("fetch/headers.html"); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.