Skip to content

Commit 2d24e3c

Browse files
Merge pull request #972 from lightpanda-io/fetch
Fetch + ReadableStream
2 parents cdb3f46 + f6f0e14 commit 2d24e3c

23 files changed

+1718
-730
lines changed

src/browser/env.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ const WebApis = struct {
3636
@import("xhr/form_data.zig").Interfaces,
3737
@import("xhr/File.zig"),
3838
@import("xmlserializer/xmlserializer.zig").Interfaces,
39+
@import("fetch/fetch.zig").Interfaces,
40+
@import("streams/streams.zig").Interfaces,
3941
});
4042
};
4143

src/browser/fetch/Headers.zig

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
2+
//
3+
// Francis Bouvier <[email protected]>
4+
// Pierre Tachoire <[email protected]>
5+
//
6+
// This program is free software: you can redistribute it and/or modify
7+
// it under the terms of the GNU Affero General Public License as
8+
// published by the Free Software Foundation, either version 3 of the
9+
// License, or (at your option) any later version.
10+
//
11+
// This program is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU Affero General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU Affero General Public License
17+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
19+
const std = @import("std");
20+
const log = @import("../../log.zig");
21+
const URL = @import("../../url.zig").URL;
22+
const Page = @import("../page.zig").Page;
23+
24+
const iterator = @import("../iterator/iterator.zig");
25+
26+
const v8 = @import("v8");
27+
const Env = @import("../env.zig").Env;
28+
29+
// https://developer.mozilla.org/en-US/docs/Web/API/Headers
30+
const Headers = @This();
31+
32+
// Case-Insensitive String HashMap.
33+
// This allows us to avoid having to allocate lowercase keys all the time.
34+
const HeaderHashMap = std.HashMapUnmanaged([]const u8, []const u8, struct {
35+
pub fn hash(_: @This(), s: []const u8) u64 {
36+
var buf: [64]u8 = undefined;
37+
var hasher = std.hash.Wyhash.init(s.len);
38+
39+
var key = s;
40+
while (key.len >= 64) {
41+
const lower = std.ascii.lowerString(buf[0..], key[0..64]);
42+
hasher.update(lower);
43+
key = key[64..];
44+
}
45+
46+
if (key.len > 0) {
47+
const lower = std.ascii.lowerString(buf[0..key.len], key);
48+
hasher.update(lower);
49+
}
50+
51+
return hasher.final();
52+
}
53+
54+
pub fn eql(_: @This(), a: []const u8, b: []const u8) bool {
55+
return std.ascii.eqlIgnoreCase(a, b);
56+
}
57+
}, 80);
58+
59+
headers: HeaderHashMap = .empty,
60+
61+
// They can either be:
62+
//
63+
// 1. An array of string pairs.
64+
// 2. An object with string keys to string values.
65+
// 3. Another Headers object.
66+
pub const HeadersInit = union(enum) {
67+
// List of Pairs of []const u8
68+
strings: []const [2][]const u8,
69+
// Headers
70+
headers: *Headers,
71+
// Mappings
72+
object: Env.JsObject,
73+
};
74+
75+
pub fn constructor(_init: ?HeadersInit, page: *Page) !Headers {
76+
const arena = page.arena;
77+
var headers: HeaderHashMap = .empty;
78+
79+
if (_init) |init| {
80+
switch (init) {
81+
.strings => |kvs| {
82+
for (kvs) |pair| {
83+
const key = try arena.dupe(u8, pair[0]);
84+
const value = try arena.dupe(u8, pair[1]);
85+
86+
try headers.put(arena, key, value);
87+
}
88+
},
89+
.headers => |hdrs| {
90+
var iter = hdrs.headers.iterator();
91+
while (iter.next()) |entry| {
92+
try headers.put(arena, entry.key_ptr.*, entry.value_ptr.*);
93+
}
94+
},
95+
.object => |obj| {
96+
var iter = obj.nameIterator();
97+
while (try iter.next()) |name_value| {
98+
const name = try name_value.toString(arena);
99+
const value = try obj.get(name);
100+
const value_string = try value.toString(arena);
101+
102+
try headers.put(arena, name, value_string);
103+
}
104+
},
105+
}
106+
}
107+
108+
return .{
109+
.headers = headers,
110+
};
111+
}
112+
113+
pub fn append(self: *Headers, name: []const u8, value: []const u8, allocator: std.mem.Allocator) !void {
114+
const key = try allocator.dupe(u8, name);
115+
const gop = try self.headers.getOrPut(allocator, key);
116+
117+
if (gop.found_existing) {
118+
// If we found it, append the value.
119+
const new_value = try std.fmt.allocPrint(allocator, "{s}, {s}", .{ gop.value_ptr.*, value });
120+
gop.value_ptr.* = new_value;
121+
} else {
122+
// Otherwise, we should just put it in.
123+
gop.value_ptr.* = try allocator.dupe(u8, value);
124+
}
125+
}
126+
127+
pub fn _append(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void {
128+
const arena = page.arena;
129+
try self.append(name, value, arena);
130+
}
131+
132+
pub fn _delete(self: *Headers, name: []const u8) void {
133+
_ = self.headers.remove(name);
134+
}
135+
136+
pub const HeadersEntryIterator = struct {
137+
slot: [2][]const u8,
138+
iter: HeaderHashMap.Iterator,
139+
140+
// TODO: these SHOULD be in lexigraphical order but I'm not sure how actually
141+
// important that is.
142+
pub fn _next(self: *HeadersEntryIterator) ?[2][]const u8 {
143+
if (self.iter.next()) |entry| {
144+
self.slot[0] = entry.key_ptr.*;
145+
self.slot[1] = entry.value_ptr.*;
146+
return self.slot;
147+
} else {
148+
return null;
149+
}
150+
}
151+
};
152+
153+
pub fn _entries(self: *const Headers) HeadersEntryIterable {
154+
return .{
155+
.inner = .{
156+
.slot = undefined,
157+
.iter = self.headers.iterator(),
158+
},
159+
};
160+
}
161+
162+
pub fn _forEach(self: *Headers, callback_fn: Env.Function, this_arg: ?Env.JsObject) !void {
163+
var iter = self.headers.iterator();
164+
165+
const cb = if (this_arg) |this| try callback_fn.withThis(this) else callback_fn;
166+
167+
while (iter.next()) |entry| {
168+
try cb.call(void, .{ entry.key_ptr.*, entry.value_ptr.*, self });
169+
}
170+
}
171+
172+
pub fn _get(self: *const Headers, name: []const u8) ?[]const u8 {
173+
return self.headers.get(name);
174+
}
175+
176+
pub fn _has(self: *const Headers, name: []const u8) bool {
177+
return self.headers.contains(name);
178+
}
179+
180+
pub const HeadersKeyIterator = struct {
181+
iter: HeaderHashMap.KeyIterator,
182+
183+
pub fn _next(self: *HeadersKeyIterator) ?[]const u8 {
184+
if (self.iter.next()) |key| {
185+
return key.*;
186+
} else {
187+
return null;
188+
}
189+
}
190+
};
191+
192+
pub fn _keys(self: *const Headers) HeadersKeyIterable {
193+
return .{ .inner = .{ .iter = self.headers.keyIterator() } };
194+
}
195+
196+
pub fn _set(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void {
197+
const arena = page.arena;
198+
199+
const key = try arena.dupe(u8, name);
200+
const gop = try self.headers.getOrPut(arena, key);
201+
gop.value_ptr.* = try arena.dupe(u8, value);
202+
}
203+
204+
pub const HeadersValueIterator = struct {
205+
iter: HeaderHashMap.ValueIterator,
206+
207+
pub fn _next(self: *HeadersValueIterator) ?[]const u8 {
208+
if (self.iter.next()) |value| {
209+
return value.*;
210+
} else {
211+
return null;
212+
}
213+
}
214+
};
215+
216+
pub fn _values(self: *const Headers) HeadersValueIterable {
217+
return .{ .inner = .{ .iter = self.headers.valueIterator() } };
218+
}
219+
220+
pub const HeadersKeyIterable = iterator.Iterable(HeadersKeyIterator, "HeadersKeyIterator");
221+
pub const HeadersValueIterable = iterator.Iterable(HeadersValueIterator, "HeadersValueIterator");
222+
pub const HeadersEntryIterable = iterator.Iterable(HeadersEntryIterator, "HeadersEntryIterator");
223+
224+
const testing = @import("../../testing.zig");
225+
test "fetch: Headers" {
226+
try testing.htmlRunner("fetch/headers.html");
227+
}

0 commit comments

Comments
 (0)