Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 55 additions & 23 deletions src/browser/fetch/Request.zig
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,27 @@ pub const RequestCredentials = enum {
}
};

pub const RequestMode = enum {
cors,
@"no-cors",
@"same-origin",
navigate,

pub fn fromString(str: []const u8) ?RequestMode {
for (std.enums.values(RequestMode)) |cache| {
if (std.ascii.eqlIgnoreCase(str, @tagName(cache))) {
return cache;
}
} else {
return null;
}
}

pub fn toString(self: RequestMode) []const u8 {
return @tagName(self);
}
};

// https://developer.mozilla.org/en-US/docs/Web/API/RequestInit
pub const RequestInit = struct {
body: ?[]const u8 = null,
Expand All @@ -88,6 +109,7 @@ pub const RequestInit = struct {
headers: ?HeadersInit = null,
integrity: ?[]const u8 = null,
method: ?[]const u8 = null,
mode: ?[]const u8 = null,
};

// https://developer.mozilla.org/en-US/docs/Web/API/Request/Request
Expand All @@ -97,6 +119,8 @@ method: Http.Method,
url: [:0]const u8,
cache: RequestCache,
credentials: RequestCredentials,
// no-cors is default is not built with constructor.
mode: RequestMode = .@"no-cors",
headers: Headers,
body: ?[]const u8,
body_used: bool = false,
Expand All @@ -115,11 +139,11 @@ pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Re
},
};

const body = if (options.body) |body| try arena.dupe(u8, body) else null;
const cache = (if (options.cache) |cache| RequestCache.fromString(cache) else null) orelse RequestCache.default;
const credentials = (if (options.credentials) |creds| RequestCredentials.fromString(creds) else null) orelse RequestCredentials.@"same-origin";
const integrity = if (options.integrity) |integ| try arena.dupe(u8, integ) else "";
const headers: Headers = if (options.headers) |hdrs| try Headers.constructor(hdrs, page) else .{};
const mode = (if (options.mode) |mode| RequestMode.fromString(mode) else null) orelse RequestMode.cors;

const method: Http.Method = blk: {
if (options.method) |given_method| {
Expand All @@ -135,11 +159,19 @@ pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Re
}
};

// Can't have a body on .GET or .HEAD.
const body: ?[]const u8 = blk: {
if (method == .GET or method == .HEAD) {
break :blk null;
} else break :blk if (options.body) |body| try arena.dupe(u8, body) else null;
};

return .{
.method = method,
.url = url,
.cache = cache,
.credentials = credentials,
.mode = mode,
.headers = headers,
.body = body,
.integrity = integrity,
Expand Down Expand Up @@ -181,6 +213,10 @@ pub fn get_method(self: *const Request) []const u8 {
return @tagName(self.method);
}

pub fn get_mode(self: *const Request) RequestMode {
return self.mode;
}

pub fn get_url(self: *const Request) []const u8 {
return self.url;
}
Expand Down Expand Up @@ -210,10 +246,7 @@ pub fn _bytes(self: *Response, page: *Page) !Env.Promise {
return error.TypeError;
}

const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
};
const resolver = page.main_context.createPromiseResolver();

try resolver.resolve(self.body);
self.body_used = true;
Expand All @@ -225,22 +258,24 @@ pub fn _json(self: *Response, page: *Page) !Env.Promise {
return error.TypeError;
}

const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
};
const resolver = page.main_context.createPromiseResolver();

const p = std.json.parseFromSliceLeaky(
std.json.Value,
page.call_arena,
self.body,
.{},
) catch |e| {
log.info(.browser, "invalid json", .{ .err = e, .source = "Request" });
return error.SyntaxError;
};
if (self.body) |body| {
const p = std.json.parseFromSliceLeaky(
std.json.Value,
page.call_arena,
body,
.{},
) catch |e| {
log.info(.browser, "invalid json", .{ .err = e, .source = "Request" });
return error.SyntaxError;
};

try resolver.resolve(p);
} else {
try resolver.resolve(null);
}

try resolver.resolve(p);
self.body_used = true;
return resolver.promise();
}
Expand All @@ -250,10 +285,7 @@ pub fn _text(self: *Response, page: *Page) !Env.Promise {
return error.TypeError;
}

const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
};
const resolver = page.main_context.createPromiseResolver();

try resolver.resolve(self.body);
self.body_used = true;
Expand Down
73 changes: 51 additions & 22 deletions src/browser/fetch/Response.zig
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ status_text: []const u8 = "",
headers: Headers,
mime: ?Mime = null,
url: []const u8 = "",
body: []const u8 = "",
body: ?[]const u8 = null,
body_used: bool = false,
redirected: bool = false,
type: ResponseType = .basic,

const ResponseBody = union(enum) {
string: []const u8,
Expand All @@ -55,6 +56,28 @@ const ResponseOptions = struct {
headers: ?HeadersInit = null,
};

pub const ResponseType = enum {
basic,
cors,
@"error",
@"opaque",
opaqueredirect,

pub fn fromString(str: []const u8) ?ResponseType {
for (std.enums.values(ResponseType)) |cache| {
if (std.ascii.eqlIgnoreCase(str, @tagName(cache))) {
return cache;
}
} else {
return null;
}
}

pub fn toString(self: ResponseType) []const u8 {
return @tagName(self);
}
};

pub fn constructor(_input: ?ResponseBody, _options: ?ResponseOptions, page: *Page) !Response {
const arena = page.arena;

Expand All @@ -68,7 +91,7 @@ pub fn constructor(_input: ?ResponseBody, _options: ?ResponseOptions, page: *Pag
},
}
} else {
break :blk "";
break :blk null;
}
};

Expand All @@ -85,7 +108,9 @@ pub fn constructor(_input: ?ResponseBody, _options: ?ResponseOptions, page: *Pag

pub fn get_body(self: *const Response, page: *Page) !*ReadableStream {
const stream = try ReadableStream.constructor(null, null, page);
try stream.queue.append(page.arena, self.body);
if (self.body) |body| {
try stream.queue.append(page.arena, body);
}
return stream;
}

Expand Down Expand Up @@ -113,6 +138,10 @@ pub fn get_statusText(self: *const Response) []const u8 {
return self.status_text;
}

pub fn get_type(self: *const Response) ResponseType {
return self.type;
}

pub fn get_url(self: *const Response) []const u8 {
return self.url;
}
Expand All @@ -132,6 +161,7 @@ pub fn _clone(self: *const Response) !Response {
.redirected = self.redirected,
.status = self.status,
.url = self.url,
.type = self.type,
};
}

Expand All @@ -155,22 +185,24 @@ pub fn _json(self: *Response, page: *Page) !Env.Promise {
return error.TypeError;
}

const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
};

const p = std.json.parseFromSliceLeaky(
std.json.Value,
page.call_arena,
self.body,
.{},
) catch |e| {
log.info(.browser, "invalid json", .{ .err = e, .source = "Response" });
return error.SyntaxError;
};
const resolver = page.main_context.createPromiseResolver();

if (self.body) |body| {
const p = std.json.parseFromSliceLeaky(
std.json.Value,
page.call_arena,
body,
.{},
) catch |e| {
log.info(.browser, "invalid json", .{ .err = e, .source = "Response" });
return error.SyntaxError;
};

try resolver.resolve(p);
} else {
try resolver.resolve(null);
}

try resolver.resolve(p);
self.body_used = true;
return resolver.promise();
}
Expand All @@ -180,10 +212,7 @@ pub fn _text(self: *Response, page: *Page) !Env.Promise {
return error.TypeError;
}

const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
};
const resolver = page.main_context.createPromiseResolver();

try resolver.resolve(self.body);
self.body_used = true;
Expand Down
28 changes: 28 additions & 0 deletions src/browser/fetch/fetch.zig
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub const FetchContext = struct {
headers: std.ArrayListUnmanaged([]const u8) = .empty,
status: u16 = 0,
mime: ?Mime = null,
mode: Request.RequestMode,
transfer: ?*HttpClient.Transfer = null,

/// This effectively takes ownership of the FetchContext.
Expand All @@ -62,6 +63,19 @@ pub const FetchContext = struct {
pub fn toResponse(self: *const FetchContext) !Response {
var headers: Headers = .{};

// If the mode is "no-cors", we need to return this opaque/stripped Response.
// https://developer.mozilla.org/en-US/docs/Web/API/Response/type
if (self.mode == .@"no-cors") {
return Response{
.status = 0,
.headers = headers,
.mime = self.mime,
.body = null,
.url = self.url,
.type = .@"opaque",
};
}

// convert into Headers
for (self.headers.items) |hdr| {
var iter = std.mem.splitScalar(u8, hdr, ':');
Expand All @@ -70,12 +84,25 @@ pub const FetchContext = struct {
try headers.append(name, value, self.arena);
}

const resp_type: Response.ResponseType = blk: {
if (std.mem.startsWith(u8, self.url, "data:")) {
break :blk .basic;
}

break :blk switch (self.mode) {
.cors => .cors,
.@"same-origin", .navigate => .basic,
.@"no-cors" => unreachable,
};
};

return Response{
.status = self.status,
.headers = headers,
.mime = self.mime,
.body = self.body.items,
.url = self.url,
.type = resp_type,
};
}
};
Expand Down Expand Up @@ -110,6 +137,7 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi
.promise_resolver = resolver,
.method = req.method,
.url = req.url,
.mode = req.mode,
};

try page.http_client.request(.{
Expand Down
3 changes: 2 additions & 1 deletion src/runtime/js.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2843,7 +2843,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {

const T = @TypeOf(value);
switch (@typeInfo(T)) {
.void, .bool, .int, .comptime_int, .float, .comptime_float, .@"enum" => {
.void, .bool, .int, .comptime_int, .float, .comptime_float, .@"enum", .null => {
// Need to do this to keep the compiler happy
// simpleZigValueToJs handles all of these cases.
unreachable;
Expand Down Expand Up @@ -3634,6 +3634,7 @@ fn jsUnsignedIntToZig(comptime T: type, max: comptime_int, maybe: u32) !T {
fn simpleZigValueToJs(isolate: v8.Isolate, value: anytype, comptime fail: bool) if (fail) v8.Value else ?v8.Value {
switch (@typeInfo(@TypeOf(value))) {
.void => return v8.initUndefined(isolate).toValue(),
.null => return v8.initNull(isolate).toValue(),
.bool => return v8.getValue(if (value) v8.initTrue(isolate) else v8.initFalse(isolate)),
.int => |n| switch (n.signedness) {
.signed => {
Expand Down
1 change: 1 addition & 0 deletions src/tests/fetch/response.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
testing.expectEqual("no-cache", response2.headers.get("cache-control"));

let response3 = new Response("Created", { status: 201, statusText: "Created" });
testing.expectEqual("basic", response3.type);
testing.expectEqual(201, response3.status);
testing.expectEqual("Created", response3.statusText);
testing.expectEqual(true, response3.ok);
Expand Down