Skip to content

Commit 4b56e34

Browse files
committed
move fetch() into fetch.zig
1 parent 5e7bde2 commit 4b56e34

File tree

2 files changed

+160
-1
lines changed

2 files changed

+160
-1
lines changed

src/browser/fetch/fetch.zig

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,166 @@
1616
// You should have received a copy of the GNU Affero General Public License
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

19+
const std = @import("std");
20+
const log = @import("../../log.zig");
21+
22+
const v8 = @import("v8");
23+
const Env = @import("../env.zig").Env;
24+
const Page = @import("../page.zig").Page;
25+
26+
const Http = @import("../../http/Http.zig");
27+
const HttpClient = @import("../../http/Client.zig");
28+
const Mime = @import("../mime.zig").Mime;
29+
30+
const RequestInput = @import("Request.zig").RequestInput;
31+
const RequestInit = @import("Request.zig").RequestInit;
32+
const Request = @import("Request.zig");
33+
const Response = @import("./Response.zig");
34+
1935
pub const Interfaces = .{
2036
@import("Headers.zig"),
2137
@import("Request.zig"),
2238
@import("Response.zig"),
2339
};
40+
41+
const FetchContext = struct {
42+
arena: std.mem.Allocator,
43+
js_ctx: *Env.JsContext,
44+
promise_resolver: v8.Persistent(v8.PromiseResolver),
45+
46+
method: Http.Method,
47+
url: []const u8,
48+
body: std.ArrayListUnmanaged(u8) = .empty,
49+
headers: std.ArrayListUnmanaged([]const u8) = .empty,
50+
status: u16 = 0,
51+
mime: ?Mime = null,
52+
transfer: ?*HttpClient.Transfer = null,
53+
54+
/// This effectively takes ownership of the FetchContext.
55+
///
56+
/// We just return the underlying slices used for `headers`
57+
/// and for `body` here to avoid an allocation.
58+
pub fn toResponse(self: *const FetchContext) !Response {
59+
return Response{
60+
.status = self.status,
61+
.headers = self.headers.items,
62+
.mime = self.mime,
63+
.body = self.body.items,
64+
};
65+
}
66+
};
67+
68+
// https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch
69+
pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promise {
70+
const arena = page.arena;
71+
72+
const req = try Request.constructor(input, options, page);
73+
74+
const resolver = Env.PromiseResolver{
75+
.js_context = page.main_context,
76+
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
77+
};
78+
79+
var headers = try Http.Headers.init();
80+
try page.requestCookie(.{}).headersForRequest(arena, req.url, &headers);
81+
82+
const fetch_ctx = try arena.create(FetchContext);
83+
fetch_ctx.* = .{
84+
.arena = arena,
85+
.js_ctx = page.main_context,
86+
.promise_resolver = v8.Persistent(v8.PromiseResolver).init(
87+
page.main_context.isolate,
88+
resolver.resolver,
89+
),
90+
.method = req.method,
91+
.url = req.url,
92+
};
93+
94+
try page.http_client.request(.{
95+
.ctx = @ptrCast(fetch_ctx),
96+
.url = req.url,
97+
.method = req.method,
98+
.headers = headers,
99+
.body = req.body,
100+
.cookie_jar = page.cookie_jar,
101+
.resource_type = .fetch,
102+
103+
.start_callback = struct {
104+
fn startCallback(transfer: *HttpClient.Transfer) !void {
105+
const self: *FetchContext = @ptrCast(@alignCast(transfer.ctx));
106+
log.debug(.http, "request start", .{ .method = self.method, .url = self.url, .source = "fetch" });
107+
108+
self.transfer = transfer;
109+
}
110+
}.startCallback,
111+
.header_callback = struct {
112+
fn headerCallback(transfer: *HttpClient.Transfer) !void {
113+
const self: *FetchContext = @ptrCast(@alignCast(transfer.ctx));
114+
115+
const header = &transfer.response_header.?;
116+
117+
log.debug(.http, "request header", .{
118+
.source = "fetch",
119+
.method = self.method,
120+
.url = self.url,
121+
.status = header.status,
122+
});
123+
124+
if (header.contentType()) |ct| {
125+
self.mime = Mime.parse(ct) catch {
126+
return error.MimeParsing;
127+
};
128+
}
129+
130+
var it = transfer.responseHeaderIterator();
131+
while (it.next()) |hdr| {
132+
const joined = try std.fmt.allocPrint(self.arena, "{s}: {s}", .{ hdr.name, hdr.value });
133+
try self.headers.append(self.arena, joined);
134+
}
135+
136+
self.status = header.status;
137+
}
138+
}.headerCallback,
139+
.data_callback = struct {
140+
fn dataCallback(transfer: *HttpClient.Transfer, data: []const u8) !void {
141+
const self: *FetchContext = @ptrCast(@alignCast(transfer.ctx));
142+
try self.body.appendSlice(self.arena, data);
143+
}
144+
}.dataCallback,
145+
.done_callback = struct {
146+
fn doneCallback(ctx: *anyopaque) !void {
147+
const self: *FetchContext = @ptrCast(@alignCast(ctx));
148+
149+
log.info(.http, "request complete", .{
150+
.source = "fetch",
151+
.method = self.method,
152+
.url = self.url,
153+
.status = self.status,
154+
});
155+
156+
const response = try self.toResponse();
157+
const promise_resolver: Env.PromiseResolver = .{
158+
.js_context = self.js_ctx,
159+
.resolver = self.promise_resolver.castToPromiseResolver(),
160+
};
161+
162+
try promise_resolver.resolve(response);
163+
}
164+
}.doneCallback,
165+
.error_callback = struct {
166+
fn errorCallback(ctx: *anyopaque, err: anyerror) void {
167+
const self: *FetchContext = @ptrCast(@alignCast(ctx));
168+
169+
self.transfer = null;
170+
const promise_resolver: Env.PromiseResolver = .{
171+
.js_context = self.js_ctx,
172+
.resolver = self.promise_resolver.castToPromiseResolver(),
173+
};
174+
175+
promise_resolver.reject(@errorName(err)) catch unreachable;
176+
}
177+
}.errorCallback,
178+
});
179+
180+
return resolver.promise();
181+
}

src/browser/html/window.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const JsObject = Env.JsObject;
4141

4242
const v8 = @import("v8");
4343
const Request = @import("../fetch/Request.zig");
44+
const fetchFn = @import("../fetch/fetch.zig").fetch;
4445

4546
const storage = @import("../storage/storage.zig");
4647

@@ -99,7 +100,7 @@ pub const Window = struct {
99100
}
100101

101102
pub fn _fetch(_: *Window, input: Request.RequestInput, options: ?Request.RequestInit, page: *Page) !Env.Promise {
102-
return Request.fetch(input, options, page);
103+
return fetchFn(input, options, page);
103104
}
104105

105106
pub fn get_window(self: *Window) *Window {

0 commit comments

Comments
 (0)