diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/http.zig | 95 | ||||
| -rw-r--r-- | src/javascript/jsc/api/router.zig | 19 | ||||
| -rw-r--r-- | src/javascript/jsc/javascript.zig | 3 | ||||
| -rw-r--r-- | src/linker.zig | 2 | ||||
| -rw-r--r-- | src/query_string_map.zig | 178 | ||||
| -rw-r--r-- | src/router.zig | 14 |
6 files changed, 262 insertions, 49 deletions
diff --git a/src/http.zig b/src/http.zig index 3cb794e57..46ef9bdc9 100644 --- a/src/http.zig +++ b/src/http.zig @@ -1495,16 +1495,22 @@ pub const RequestContext = struct { } } - pub fn handleGet(ctx: *RequestContext) !void { + pub fn handleReservedRoutes(ctx: *RequestContext) !bool { if (strings.eqlComptime(ctx.url.extname, "jsb") and ctx.bundler.options.node_modules_bundle != null) { - return try ctx.sendJSB(); + try ctx.sendJSB(); + return true; } if (strings.eqlComptime(ctx.url.path, "_api.hmr")) { try ctx.handleWebsocket(); - return; + return true; } + return false; + } + + pub fn handleGet(ctx: *RequestContext) !void { + // errdefer ctx.auto500(); const result = try ctx.bundler.buildFile( @@ -1756,14 +1762,20 @@ pub const Server = struct { req_ctx.keep_alive = false; } + var finished = req_ctx.handleReservedRoutes() catch |err| { + Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); + return; + }; + if (comptime features.public_folder and features.filesystem_router) { - var finished = false; - if (req_ctx.matchPublicFolder()) |result| { - finished = true; - req_ctx.renderServeResult(result) catch |err| { - Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); - return; - }; + if (!finished) { + if (req_ctx.matchPublicFolder()) |result| { + finished = true; + req_ctx.renderServeResult(result) catch |err| { + Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); + return; + }; + } } if (!finished) { @@ -1780,13 +1792,14 @@ pub const Server = struct { }; } } else if (comptime features.public_folder) { - var finished = false; - if (req_ctx.matchPublicFolder()) |result| { - finished = true; - req_ctx.renderServeResult(result) catch |err| { - Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); - return; - }; + if (!finished) { + if (req_ctx.matchPublicFolder()) |result| { + finished = true; + req_ctx.renderServeResult(result) catch |err| { + Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); + return; + }; + } } if (!finished) { @@ -1803,29 +1816,33 @@ pub const Server = struct { }; } } else if (comptime features.filesystem_router) { - req_ctx.bundler.router.?.match(server, RequestContext, &req_ctx) catch |err| { - switch (err) { - error.ModuleNotFound => { - req_ctx.sendNotFound() catch {}; - }, - else => { - Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); - return; - }, - } - }; + if (!finished) { + req_ctx.bundler.router.?.match(server, RequestContext, &req_ctx) catch |err| { + switch (err) { + error.ModuleNotFound => { + req_ctx.sendNotFound() catch {}; + }, + else => { + Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); + return; + }, + } + }; + } } else { - req_ctx.handleRequest() catch |err| { - switch (err) { - error.ModuleNotFound => { - req_ctx.sendNotFound() catch {}; - }, - else => { - Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); - return; - }, - } - }; + if (!finished) { + req_ctx.handleRequest() catch |err| { + switch (err) { + error.ModuleNotFound => { + req_ctx.sendNotFound() catch {}; + }, + else => { + Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path }); + return; + }, + } + }; + } } if (!req_ctx.controlled) { diff --git a/src/javascript/jsc/api/router.zig b/src/javascript/jsc/api/router.zig index f1c892d94..14deaea66 100644 --- a/src/javascript/jsc/api/router.zig +++ b/src/javascript/jsc/api/router.zig @@ -5,6 +5,7 @@ const FilesystemRouter = @import("../../../router.zig"); const http = @import("../../../http.zig"); const JavaScript = @import("../javascript.zig"); const QueryStringMap = @import("../../../query_string_map.zig").QueryStringMap; +const CombinedScanner = @import("../../../query_string_map.zig").CombinedScanner; usingnamespace @import("../bindings/bindings.zig"); usingnamespace @import("../webcore/response.zig"); const Router = @This(); @@ -327,9 +328,21 @@ pub fn getQuery( exception: js.ExceptionRef, ) js.JSValueRef { if (this.query_string_map == null) { - if (QueryStringMap.init(getAllocator(ctx), this.route.query_string)) |map| { - this.query_string_map = map; - } else |err| {} + if (this.route.params.len > 0) { + if (QueryStringMap.initWithScanner(getAllocator(ctx), CombinedScanner.init( + this.route.query_string, + this.route.pathnameWithoutLeadingSlash(), + this.route.name, + + this.route.params, + ))) |map| { + this.query_string_map = map; + } else |err| {} + } else { + if (QueryStringMap.init(getAllocator(ctx), this.route.query_string)) |map| { + this.query_string_map = map; + } else |err| {} + } } // If it's still null, the query string has no names. diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index 1de47d46e..dccfdcf00 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -126,9 +126,6 @@ pub const VirtualMachine = struct { vm.console, ); VirtualMachine.vm_loaded = true; - std.debug.print("VM IS LOADED {}", .{ - VirtualMachine.vm_loaded, - }); return VirtualMachine.vm; } diff --git a/src/linker.zig b/src/linker.zig index 9afb6af60..f92d0c1a3 100644 --- a/src/linker.zig +++ b/src/linker.zig @@ -384,6 +384,8 @@ pub fn NewLinker(comptime BundlerType: type) type { }; } + // This is a bad idea + // I don't think it's safe to do this const ImportStatementSorter = struct { import_records: []ImportRecord, pub fn lessThan(ctx: @This(), lhs: js_ast.Stmt, rhs: js_ast.Stmt) bool { diff --git a/src/query_string_map.zig b/src/query_string_map.zig index bd90b2270..2640e8a73 100644 --- a/src/query_string_map.zig +++ b/src/query_string_map.zig @@ -132,6 +132,115 @@ pub const QueryStringMap = struct { pub const List = std.MultiArrayList(Param); }; + pub fn initWithScanner( + allocator: *std.mem.Allocator, + _scanner: CombinedScanner, + ) !?QueryStringMap { + var list = Param.List{}; + var scanner = _scanner; + + var estimated_str_len: usize = 0; + var count: usize = 0; + + var nothing_needs_decoding = true; + + while (scanner.pathname.next()) |result| { + if (result.name_needs_decoding or result.value_needs_decoding) { + nothing_needs_decoding = false; + } + estimated_str_len += result.name.length + result.value.length; + count += 1; + } + + std.debug.assert(count > 0); // We should not call initWithScanner when there are no path params + + while (scanner.query.next()) |result| { + if (result.name_needs_decoding or result.value_needs_decoding) { + nothing_needs_decoding = false; + } + estimated_str_len += result.name.length + result.value.length; + count += 1; + } + + if (count == 0) return null; + + try list.ensureTotalCapacity(allocator, count); + scanner.reset(); + + // this over-allocates + // TODO: refactor this to support multiple slices instead of copying the whole thing + var buf = try std.ArrayList(u8).initCapacity(allocator, estimated_str_len); + var writer = buf.writer(); + var buf_writer_pos: u32 = 0; + + const Writer = @TypeOf(writer); + while (scanner.pathname.next()) |result| { + var list_slice = list.slice(); + var name = result.name; + var value = result.value; + const name_slice = result.rawName(scanner.pathname.routename); + + name.length = @truncate(u32, name_slice.len); + name.offset = buf_writer_pos; + try writer.writeAll(name_slice); + buf_writer_pos += @truncate(u32, name_slice.len); + + var name_hash: u64 = std.hash.Wyhash.hash(0, name_slice); + + value.length = PercentEncoding.decode(Writer, writer, result.rawValue(scanner.pathname.pathname)) catch continue; + value.offset = buf_writer_pos; + buf_writer_pos += value.length; + + list.appendAssumeCapacity(Param{ .name = name, .value = value, .name_hash = name_hash }); + } + + const route_parameter_begin = list.len; + + while (scanner.query.next()) |result| { + var list_slice = list.slice(); + + var name = result.name; + var value = result.value; + var name_hash: u64 = undefined; + if (result.name_needs_decoding) { + name.length = PercentEncoding.decode(Writer, writer, scanner.query.query_string[name.offset..][0..name.length]) catch continue; + name.offset = buf_writer_pos; + buf_writer_pos += name.length; + name_hash = std.hash.Wyhash.hash(0, buf.items[name.offset..][0..name.length]); + } else { + name_hash = std.hash.Wyhash.hash(0, result.rawName(scanner.query.query_string)); + if (std.mem.indexOfScalar(u64, list_slice.items(.name_hash), name_hash)) |index| { + + // query string parameters should not override route parameters + // see https://nextjs.org/docs/routing/dynamic-routes + if (index < route_parameter_begin) { + continue; + } + + name = list_slice.items(.name)[index]; + } else { + name.length = PercentEncoding.decode(Writer, writer, scanner.query.query_string[name.offset..][0..name.length]) catch continue; + name.offset = buf_writer_pos; + buf_writer_pos += name.length; + } + } + + value.length = PercentEncoding.decode(Writer, writer, scanner.query.query_string[value.offset..][0..value.length]) catch continue; + value.offset = buf_writer_pos; + buf_writer_pos += value.length; + + list.appendAssumeCapacity(Param{ .name = name, .value = value, .name_hash = name_hash }); + } + + buf.expandToCapacity(); + return QueryStringMap{ + .list = list, + .buffer = buf.items, + .slice = buf.items[0..buf_writer_pos], + .allocator = allocator, + }; + } + pub fn init( allocator: *std.mem.Allocator, query_string: string, @@ -190,6 +299,7 @@ pub const QueryStringMap = struct { name.length = PercentEncoding.decode(Writer, writer, query_string[name.offset..][0..name.length]) catch continue; name.offset = buf_writer_pos; buf_writer_pos += name.length; + name_hash = std.hash.Wyhash.hash(0, buf.items[name.offset..][0..name.length]); } else { name_hash = std.hash.Wyhash.hash(0, result.rawName(query_string)); if (std.mem.indexOfScalar(u64, list_slice.items(.name_hash), name_hash)) |index| { @@ -259,16 +369,80 @@ pub const PercentEncoding = struct { } }; +const ParamsList = @import("./router.zig").Param.List; +pub const CombinedScanner = struct { + query: Scanner, + pathname: PathnameScanner, + pub fn init(query_string: string, pathname: string, routename: string, url_params: *ParamsList) CombinedScanner { + return CombinedScanner{ + .query = Scanner.init(query_string), + .pathname = PathnameScanner.init(pathname, routename, url_params), + }; + } + + pub fn reset(this: *CombinedScanner) void { + this.query.reset(); + this.pathname.reset(); + } + + pub fn next(this: *CombinedScanner) ?Scanner.Result { + return this.pathname.next() orelse this.query.next(); + } +}; + +pub const PathnameScanner = struct { + params: *ParamsList, + pathname: string, + routename: string, + i: usize = 0, + + pub inline fn isDone(this: *const PathnameScanner) bool { + return this.params.len <= this.i; + } + + pub fn reset(this: *PathnameScanner) void { + this.i = 0; + } + + pub fn init(pathname: string, routename: string, params: *ParamsList) PathnameScanner { + return PathnameScanner{ + .pathname = pathname, + .routename = routename, + .params = params, + }; + } + + pub fn next(this: *PathnameScanner) ?Scanner.Result { + if (this.isDone()) { + return null; + } + + defer this.i += 1; + const param = this.params.get(this.i); + return Scanner.Result{ + .name = param.key.toStringPointer(), + .name_needs_decoding = false, + .value = param.value.toStringPointer(), + .value_needs_decoding = std.mem.indexOfScalar(u8, param.value.str(this.pathname), '%') != null, + }; + } +}; + pub const Scanner = struct { query_string: string, i: usize, + start: usize = 0, pub fn init(query_string: string) Scanner { if (query_string.len > 0 and query_string[0] == '?') { - return Scanner{ .query_string = query_string, .i = 1 }; + return Scanner{ .query_string = query_string, .i = 1, .start = 1 }; } - return Scanner{ .query_string = query_string, .i = 0 }; + return Scanner{ .query_string = query_string, .i = 0, .start = 0 }; + } + + pub inline fn reset(this: *Scanner) void { + this.i = this.start; } pub const Result = struct { diff --git a/src/router.zig b/src/router.zig index 95458b92a..ffba58e5e 100644 --- a/src/router.zig +++ b/src/router.zig @@ -5,6 +5,7 @@ // All it does is resolve URL paths to the appropriate entry point and parse URL params/query. const Router = @This(); +const Api = @import("./api/schema.zig").Api; const std = @import("std"); usingnamespace @import("global.zig"); @@ -193,6 +194,13 @@ pub fn loadRoutes( const TinyPtr = packed struct { offset: u16 = 0, len: u16 = 0, + + pub inline fn str(this: TinyPtr, slice: string) string { + return if (this.len > 0) slice[this.offset .. this.offset + this.len] else ""; + } + pub inline fn toStringPointer(this: TinyPtr) Api.StringPointer { + return Api.StringPointer{ .offset = this.offset, .length = this.len }; + } }; pub const Param = struct { @@ -667,8 +675,6 @@ pub fn match(app: *Router, server: anytype, comptime RequestContextType: type, c std.debug.assert(route.path.len > 0); - // ??? render javascript ?? - if (server.watcher.watchloop_handle == null) { server.watcher.start() catch {}; } @@ -701,4 +707,8 @@ pub const Match = struct { params: *Param.List, redirect_path: ?string = null, query_string: string = "", + + pub fn pathnameWithoutLeadingSlash(this: *const Match) string { + return std.mem.trimLeft(u8, this.pathname, "/"); + } }; |
