Skip to content

Commit 5e7bde2

Browse files
committed
add bodyUsed checks on Request and Response
1 parent b91b1a9 commit 5e7bde2

File tree

2 files changed

+128
-133
lines changed

2 files changed

+128
-133
lines changed

src/browser/fetch/Request.zig

Lines changed: 87 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -25,29 +25,35 @@ const Page = @import("../page.zig").Page;
2525
const Response = @import("./Response.zig");
2626

2727
const Http = @import("../../http/Http.zig");
28-
const HttpClient = @import("../../http/Client.zig");
29-
const Mime = @import("../mime.zig").Mime;
3028

3129
const v8 = @import("v8");
3230
const Env = @import("../env.zig").Env;
3331

32+
const Headers = @import("Headers.zig");
33+
const HeadersInit = @import("Headers.zig").HeadersInit;
34+
3435
pub const RequestInput = union(enum) {
3536
string: []const u8,
36-
request: Request,
37+
request: *Request,
3738
};
3839

3940
// https://developer.mozilla.org/en-US/docs/Web/API/RequestInit
4041
pub const RequestInit = struct {
4142
method: ?[]const u8 = null,
4243
body: ?[]const u8 = null,
44+
integrity: ?[]const u8 = null,
45+
headers: ?HeadersInit = null,
4346
};
4447

4548
// https://developer.mozilla.org/en-US/docs/Web/API/Request/Request
4649
const Request = @This();
4750

4851
method: Http.Method,
4952
url: [:0]const u8,
53+
headers: Headers,
5054
body: ?[]const u8,
55+
body_used: bool = false,
56+
integrity: []const u8,
5157

5258
pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Request {
5359
const arena = page.arena;
@@ -77,165 +83,115 @@ pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Re
7783
};
7884

7985
const body = if (options.body) |body| try arena.dupe(u8, body) else null;
86+
const integrity = if (options.integrity) |integ| try arena.dupe(u8, integ) else "";
87+
const headers = if (options.headers) |hdrs| try Headers.constructor(hdrs, page) else Headers{};
8088

8189
return .{
8290
.method = method,
8391
.url = url,
92+
.headers = headers,
8493
.body = body,
94+
.integrity = integrity,
8595
};
8696
}
8797

88-
pub fn get_url(self: *const Request) []const u8 {
89-
return self.url;
98+
// pub fn get_body(self: *const Request) ?[]const u8 {
99+
// return self.body;
100+
// }
101+
102+
pub fn get_bodyUsed(self: *const Request) bool {
103+
return self.body_used;
104+
}
105+
106+
pub fn get_headers(self: *Request) *Headers {
107+
return &self.headers;
90108
}
91109

110+
pub fn get_integrity(self: *const Request) []const u8 {
111+
return self.integrity;
112+
}
113+
114+
// TODO: If we ever support the Navigation API, we need isHistoryNavigation
115+
// https://developer.mozilla.org/en-US/docs/Web/API/Request/isHistoryNavigation
116+
92117
pub fn get_method(self: *const Request) []const u8 {
93118
return @tagName(self.method);
94119
}
95120

96-
// pub fn get_body(self: *const Request) ?[]const u8 {
97-
// return self.body;
98-
// }
121+
pub fn get_url(self: *const Request) []const u8 {
122+
return self.url;
123+
}
99124

100-
const FetchContext = struct {
101-
arena: std.mem.Allocator,
102-
js_ctx: *Env.JsContext,
103-
promise_resolver: v8.Persistent(v8.PromiseResolver),
104-
105-
method: Http.Method,
106-
url: []const u8,
107-
body: std.ArrayListUnmanaged(u8) = .empty,
108-
headers: std.ArrayListUnmanaged([]const u8) = .empty,
109-
status: u16 = 0,
110-
mime: ?Mime = null,
111-
transfer: ?*HttpClient.Transfer = null,
112-
113-
/// This effectively takes ownership of the FetchContext.
114-
///
115-
/// We just return the underlying slices used for `headers`
116-
/// and for `body` here to avoid an allocation.
117-
pub fn toResponse(self: *const FetchContext) !Response {
118-
return Response{
119-
.status = self.status,
120-
.headers = self.headers.items,
121-
.mime = self.mime,
122-
.body = self.body.items,
123-
};
125+
pub fn _clone(self: *Request, page: *Page) !Request {
126+
// Not allowed to clone if the body was used.
127+
if (self.body_used) {
128+
return error.TypeError;
124129
}
125-
};
126130

127-
// https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch
128-
pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promise {
129131
const arena = page.arena;
130132

131-
const req = try Request.constructor(input, options, page);
133+
return Request{
134+
.body = if (self.body) |body| try arena.dupe(u8, body) else null,
135+
.body_used = self.body_used,
136+
.headers = try self.headers.clone(arena),
137+
.method = self.method,
138+
.integrity = try arena.dupe(u8, self.integrity),
139+
.url = try arena.dupeZ(u8, self.url),
140+
};
141+
}
142+
143+
pub fn _bytes(self: *Response, page: *Page) !Env.Promise {
144+
if (self.body_used) {
145+
return error.TypeError;
146+
}
132147

133148
const resolver = Env.PromiseResolver{
134149
.js_context = page.main_context,
135150
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
136151
};
137152

138-
var headers = try Http.Headers.init();
139-
try page.requestCookie(.{}).headersForRequest(arena, req.url, &headers);
140-
141-
const fetch_ctx = try arena.create(FetchContext);
142-
fetch_ctx.* = .{
143-
.arena = arena,
144-
.js_ctx = page.main_context,
145-
.promise_resolver = v8.Persistent(v8.PromiseResolver).init(
146-
page.main_context.isolate,
147-
resolver.resolver,
148-
),
149-
.method = req.method,
150-
.url = req.url,
151-
};
153+
try resolver.resolve(self.body);
154+
self.body_used = true;
155+
return resolver.promise();
156+
}
152157

153-
try page.http_client.request(.{
154-
.ctx = @ptrCast(fetch_ctx),
155-
.url = req.url,
156-
.method = req.method,
157-
.headers = headers,
158-
.body = req.body,
159-
.cookie_jar = page.cookie_jar,
160-
.resource_type = .fetch,
158+
pub fn _json(self: *Response, page: *Page) !Env.Promise {
159+
if (self.body_used) {
160+
return error.TypeError;
161+
}
161162

162-
.start_callback = struct {
163-
fn startCallback(transfer: *HttpClient.Transfer) !void {
164-
const self: *FetchContext = @alignCast(@ptrCast(transfer.ctx));
165-
log.debug(.http, "request start", .{ .method = self.method, .url = self.url, .source = "fetch" });
163+
const resolver = Env.PromiseResolver{
164+
.js_context = page.main_context,
165+
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
166+
};
166167

167-
self.transfer = transfer;
168-
}
169-
}.startCallback,
170-
.header_callback = struct {
171-
fn headerCallback(transfer: *HttpClient.Transfer) !void {
172-
const self: *FetchContext = @alignCast(@ptrCast(transfer.ctx));
173-
174-
const header = &transfer.response_header.?;
175-
176-
log.debug(.http, "request header", .{
177-
.source = "fetch",
178-
.method = self.method,
179-
.url = self.url,
180-
.status = header.status,
181-
});
182-
183-
if (header.contentType()) |ct| {
184-
self.mime = Mime.parse(ct) catch {
185-
return error.MimeParsing;
186-
};
187-
}
168+
const p = std.json.parseFromSliceLeaky(
169+
std.json.Value,
170+
page.arena,
171+
self.body,
172+
.{},
173+
) catch |e| {
174+
log.warn(.browser, "invalid json", .{ .err = e, .source = "Request" });
175+
return error.SyntaxError;
176+
};
188177

189-
var it = transfer.responseHeaderIterator();
190-
while (it.next()) |hdr| {
191-
const joined = try std.fmt.allocPrint(self.arena, "{s}: {s}", .{ hdr.name, hdr.value });
192-
try self.headers.append(self.arena, joined);
193-
}
178+
try resolver.resolve(p);
179+
self.body_used = true;
180+
return resolver.promise();
181+
}
194182

195-
self.status = header.status;
196-
}
197-
}.headerCallback,
198-
.data_callback = struct {
199-
fn dataCallback(transfer: *HttpClient.Transfer, data: []const u8) !void {
200-
const self: *FetchContext = @alignCast(@ptrCast(transfer.ctx));
201-
try self.body.appendSlice(self.arena, data);
202-
}
203-
}.dataCallback,
204-
.done_callback = struct {
205-
fn doneCallback(ctx: *anyopaque) !void {
206-
const self: *FetchContext = @alignCast(@ptrCast(ctx));
207-
208-
log.info(.http, "request complete", .{
209-
.source = "fetch",
210-
.method = self.method,
211-
.url = self.url,
212-
.status = self.status,
213-
});
214-
215-
const response = try self.toResponse();
216-
const promise_resolver: Env.PromiseResolver = .{
217-
.js_context = self.js_ctx,
218-
.resolver = self.promise_resolver.castToPromiseResolver(),
219-
};
220-
221-
try promise_resolver.resolve(response);
222-
}
223-
}.doneCallback,
224-
.error_callback = struct {
225-
fn errorCallback(ctx: *anyopaque, err: anyerror) void {
226-
const self: *FetchContext = @alignCast(@ptrCast(ctx));
227-
228-
self.transfer = null;
229-
const promise_resolver: Env.PromiseResolver = .{
230-
.js_context = self.js_ctx,
231-
.resolver = self.promise_resolver.castToPromiseResolver(),
232-
};
233-
234-
promise_resolver.reject(@errorName(err)) catch unreachable;
235-
}
236-
}.errorCallback,
237-
});
183+
pub fn _text(self: *Response, page: *Page) !Env.Promise {
184+
if (self.body_used) {
185+
return error.TypeError;
186+
}
187+
188+
const resolver = Env.PromiseResolver{
189+
.js_context = page.main_context,
190+
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
191+
};
238192

193+
try resolver.resolve(self.body);
194+
self.body_used = true;
239195
return resolver.promise();
240196
}
241197

src/browser/fetch/Response.zig

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ status: u16 = 0,
3535
headers: []const []const u8,
3636
mime: ?Mime = null,
3737
body: []const u8,
38+
body_used: bool = false,
39+
redirected: bool = false,
3840

3941
const ResponseInput = union(enum) {
4042
string: []const u8,
@@ -72,17 +74,38 @@ pub fn get_ok(self: *const Response) bool {
7274
return self.status >= 200 and self.status <= 299;
7375
}
7476

75-
pub fn _text(self: *const Response, page: *Page) !Env.Promise {
77+
pub fn get_bodyUsed(self: *const Response) bool {
78+
return self.body_used;
79+
}
80+
81+
pub fn get_redirected(self: *const Response) bool {
82+
return self.redirected;
83+
}
84+
85+
pub fn get_status(self: *const Response) u16 {
86+
return self.status;
87+
}
88+
89+
pub fn _bytes(self: *Response, page: *Page) !Env.Promise {
90+
if (self.body_used) {
91+
return error.TypeError;
92+
}
93+
7694
const resolver = Env.PromiseResolver{
7795
.js_context = page.main_context,
7896
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
7997
};
8098

8199
try resolver.resolve(self.body);
100+
self.body_used = true;
82101
return resolver.promise();
83102
}
84103

85-
pub fn _json(self: *const Response, page: *Page) !Env.Promise {
104+
pub fn _json(self: *Response, page: *Page) !Env.Promise {
105+
if (self.body_used) {
106+
return error.TypeError;
107+
}
108+
86109
const resolver = Env.PromiseResolver{
87110
.js_context = page.main_context,
88111
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
@@ -99,6 +122,22 @@ pub fn _json(self: *const Response, page: *Page) !Env.Promise {
99122
};
100123

101124
try resolver.resolve(p);
125+
self.body_used = true;
126+
return resolver.promise();
127+
}
128+
129+
pub fn _text(self: *Response, page: *Page) !Env.Promise {
130+
if (self.body_used) {
131+
return error.TypeError;
132+
}
133+
134+
const resolver = Env.PromiseResolver{
135+
.js_context = page.main_context,
136+
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
137+
};
138+
139+
try resolver.resolve(self.body);
140+
self.body_used = true;
102141
return resolver.promise();
103142
}
104143

0 commit comments

Comments
 (0)