mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 20:09:04 +00:00
[bun dev] Automatically set origin - improve support for proxying Bun
Previously, when running Bun behind a reverse proxy, you had to pass an explicit `--origin` arg and it could only run behind one proxy at a time. Now, Bun automatically determines the origin from the request if possible. It reads `Forwarded`, `X-Forwarded-Proto`, `X-Forwarded-Host`, `Origin`, and lastly `Host`. If none are available, it falls back to the `--origin` CLI arg. This change is important for usecases like Replit which shows multiple iframes in different origins.
This commit is contained in:
@@ -10,7 +10,6 @@ const default_allocator = _global.default_allocator;
|
||||
const StoredFileDescriptorType = _global.StoredFileDescriptorType;
|
||||
const FeatureFlags = _global.FeatureFlags;
|
||||
const C = _global.C;
|
||||
|
||||
const std = @import("std");
|
||||
const lex = @import("js_lexer.zig");
|
||||
const logger = @import("logger.zig");
|
||||
@@ -49,6 +48,7 @@ const NewBunQueue = @import("./bun_queue.zig").NewBunQueue;
|
||||
const NodeFallbackModules = @import("./node_fallbacks.zig");
|
||||
const CacheEntry = @import("./cache.zig").FsCacheEntry;
|
||||
const Analytics = @import("./analytics/analytics_thread.zig");
|
||||
const URL = @import("./query_string_map.zig").URL;
|
||||
|
||||
const Linker = linker.Linker;
|
||||
const Resolver = _resolver.Resolver;
|
||||
@@ -2166,6 +2166,7 @@ pub const Bundler = struct {
|
||||
comptime WatcherType: type,
|
||||
watcher: *WatcherType,
|
||||
client_entry_point: ?*ClientEntryPoint,
|
||||
origin: URL,
|
||||
) !BuildResolveResultPair {
|
||||
if (resolve_result.is_external) {
|
||||
return BuildResolveResultPair{
|
||||
@@ -2230,6 +2231,7 @@ pub const Bundler = struct {
|
||||
allocator,
|
||||
bundler.log,
|
||||
&bundler.linker,
|
||||
origin,
|
||||
)).written;
|
||||
} else {
|
||||
break :brk (try CSSBundler.bundle(
|
||||
@@ -2243,6 +2245,7 @@ pub const Bundler = struct {
|
||||
allocator,
|
||||
bundler.log,
|
||||
&bundler.linker,
|
||||
origin,
|
||||
)).written;
|
||||
}
|
||||
},
|
||||
@@ -2274,7 +2277,7 @@ pub const Bundler = struct {
|
||||
return BuildResolveResultPair{ .written = 0, .input_fd = result.input_fd, .empty = true };
|
||||
}
|
||||
|
||||
try bundler.linker.link(file_path, &result, import_path_format, false);
|
||||
try bundler.linker.link(file_path, &result, origin, import_path_format, false);
|
||||
|
||||
if (bundler.options.platform.isBun()) {
|
||||
return BuildResolveResultPair{
|
||||
@@ -2382,6 +2385,7 @@ pub const Bundler = struct {
|
||||
try bundler.linker.link(
|
||||
file_path,
|
||||
&result,
|
||||
bundler.options.origin,
|
||||
import_path_format,
|
||||
false,
|
||||
);
|
||||
@@ -2411,11 +2415,15 @@ pub const Bundler = struct {
|
||||
output_file.value = .{ .move = file_op };
|
||||
},
|
||||
.css => {
|
||||
const CSSBuildContext = struct {
|
||||
origin: URL,
|
||||
};
|
||||
var build_ctx = CSSBuildContext{ .origin = bundler.options.origin };
|
||||
const CSSWriter = Css.NewWriter(
|
||||
std.fs.File,
|
||||
@TypeOf(&bundler.linker),
|
||||
import_path_format,
|
||||
void,
|
||||
CSSBuildContext,
|
||||
);
|
||||
const entry = bundler.resolver.caches.fs.readFile(
|
||||
bundler.fs,
|
||||
@@ -2434,6 +2442,7 @@ pub const Bundler = struct {
|
||||
&bundler.linker,
|
||||
bundler.log,
|
||||
);
|
||||
css_writer.buildCtx = build_ctx;
|
||||
var did_warn = false;
|
||||
try css_writer.run(bundler.log, bundler.allocator, &did_warn);
|
||||
output_file.size = css_writer.written;
|
||||
|
||||
@@ -19,7 +19,7 @@ const logger = @import("./logger.zig");
|
||||
const Options = options;
|
||||
const resolver = @import("./resolver/resolver.zig");
|
||||
const _linker = @import("./linker.zig");
|
||||
|
||||
const URL = @import("./query_string_map.zig").URL;
|
||||
const replacementCharacter: CodePoint = 0xFFFD;
|
||||
|
||||
pub const Chunk = struct {
|
||||
@@ -1020,6 +1020,7 @@ pub fn NewWriter(
|
||||
import.text.utf8,
|
||||
chunk.range,
|
||||
import_record.ImportKind.at,
|
||||
writer.buildCtx.origin,
|
||||
Options.BundleOptions.ImportPathFormat.absolute_path,
|
||||
true,
|
||||
) catch |err| {
|
||||
@@ -1066,6 +1067,7 @@ pub fn NewWriter(
|
||||
url.utf8,
|
||||
chunk.range,
|
||||
import_record.ImportKind.url,
|
||||
writer.buildCtx.origin,
|
||||
import_path_format,
|
||||
true,
|
||||
);
|
||||
@@ -1078,6 +1080,7 @@ pub fn NewWriter(
|
||||
import.text.utf8,
|
||||
chunk.range,
|
||||
import_record.ImportKind.at,
|
||||
writer.buildCtx.origin,
|
||||
import_path_format,
|
||||
false,
|
||||
);
|
||||
@@ -1155,6 +1158,8 @@ pub fn NewBundler(
|
||||
fs_reader: FileReader,
|
||||
fs: FSType,
|
||||
allocator: std.mem.Allocator,
|
||||
origin: URL = URL{},
|
||||
|
||||
pub fn bundle(
|
||||
absolute_path: string,
|
||||
fs: FSType,
|
||||
@@ -1166,6 +1171,7 @@ pub fn NewBundler(
|
||||
allocator: std.mem.Allocator,
|
||||
log: *logger.Log,
|
||||
linker: Linker,
|
||||
origin: URL,
|
||||
) !CodeCount {
|
||||
if (!has_set_global_queue) {
|
||||
global_queued = QueuedList.init(default_allocator);
|
||||
@@ -1186,7 +1192,7 @@ pub fn NewBundler(
|
||||
.writer = writer,
|
||||
.fs_reader = fs_reader,
|
||||
.fs = fs,
|
||||
|
||||
.origin = origin,
|
||||
.allocator = allocator,
|
||||
.watcher = watcher,
|
||||
};
|
||||
|
||||
151
src/http.zig
151
src/http.zig
@@ -94,6 +94,7 @@ pub const RequestContext = struct {
|
||||
watcher: *Watcher,
|
||||
timer: std.time.Timer,
|
||||
matched_route: ?Router.Match = null,
|
||||
origin: ZigURL,
|
||||
|
||||
full_url: [:0]const u8 = "",
|
||||
res_headers_count: usize = 0,
|
||||
@@ -101,10 +102,92 @@ pub const RequestContext = struct {
|
||||
/// --disable-bun.js propagates here
|
||||
pub var fallback_only = false;
|
||||
|
||||
fn parseOrigin(this: *RequestContext) void {
|
||||
var protocol: ?string = null;
|
||||
var host: ?string = null;
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded
|
||||
if (this.header("Forwarded")) |forwarded| {
|
||||
if (strings.indexOf(forwarded, "host=")) |host_start| {
|
||||
const host_i = host_start + "host=".len;
|
||||
const host_ = forwarded[host_i..][0 .. strings.indexOfChar(forwarded[host_i..], ';') orelse forwarded[host_i..].len];
|
||||
if (host_.len > 0) {
|
||||
host = host_;
|
||||
}
|
||||
}
|
||||
|
||||
if (strings.indexOf(forwarded, "proto=")) |protocol_start| {
|
||||
const protocol_i = protocol_start + "proto=".len;
|
||||
if (strings.eqlComptime(forwarded[protocol_i..][0 .. strings.indexOfChar(forwarded[protocol_i..], ';') orelse forwarded[protocol_i..].len], "https")) {
|
||||
protocol = "https";
|
||||
} else {
|
||||
protocol = "http";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (protocol == null) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
|
||||
determine_protocol: {
|
||||
if (this.header("X-Forwarded-Proto")) |proto| {
|
||||
if (strings.eqlComptime(proto, "https")) {
|
||||
protocol = "https";
|
||||
break :determine_protocol;
|
||||
}
|
||||
}
|
||||
|
||||
// Microsoft IIS
|
||||
if (this.header("Front-End-Https")) |proto| {
|
||||
if (strings.eqlComptime(proto, "on")) {
|
||||
protocol = "https";
|
||||
break :determine_protocol;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (host == null) {
|
||||
determine_host: {
|
||||
if (this.header("X-Forwarded-Host")) |_host| {
|
||||
host = _host;
|
||||
break :determine_host;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.header("Origin")) |origin| {
|
||||
this.origin = ZigURL.parse(origin);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (host != null or protocol != null) {
|
||||
// Proxies like Caddy might only send X-Forwarded-Proto if the host matches
|
||||
// In that case,
|
||||
const display_protocol = protocol orelse @as(string, "http");
|
||||
var display_host = host orelse
|
||||
(if (protocol != null) this.header("Host") else null) orelse
|
||||
@as(string, this.origin.host);
|
||||
|
||||
var display_port = if (this.origin.port.len > 0) this.origin.port else @as(string, "3000");
|
||||
|
||||
if (strings.indexOfChar(display_host, ':')) |colon| {
|
||||
display_port = display_host[colon + 1 .. display_host.len];
|
||||
display_host = display_host[0..colon];
|
||||
} else if (this.bundler.options.origin.port_was_automatically_set and protocol != null) {
|
||||
if (strings.eqlComptime(display_protocol, "https")) {
|
||||
display_port = "443";
|
||||
} else {
|
||||
display_port = "80";
|
||||
}
|
||||
}
|
||||
this.origin = ZigURL.parse(std.fmt.allocPrint(this.allocator, "{s}://{s}:{s}/", .{ display_protocol, display_host, display_port }) catch unreachable);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getFullURL(this: *RequestContext) [:0]const u8 {
|
||||
if (this.full_url.len == 0) {
|
||||
if (this.bundler.options.origin.isAbsolute()) {
|
||||
this.full_url = std.fmt.allocPrintZ(this.allocator, "{s}{s}", .{ this.bundler.options.origin.origin, this.request.path }) catch unreachable;
|
||||
if (this.origin.isAbsolute()) {
|
||||
this.full_url = std.fmt.allocPrintZ(this.allocator, "{s}{s}", .{ this.origin.origin, this.request.path }) catch unreachable;
|
||||
} else {
|
||||
this.full_url = this.allocator.dupeZ(u8, this.request.path) catch unreachable;
|
||||
}
|
||||
@@ -120,7 +203,11 @@ pub const RequestContext = struct {
|
||||
try this.flushHeaders();
|
||||
}
|
||||
|
||||
pub fn header(ctx: *RequestContext, comptime name: anytype) ?Header {
|
||||
pub fn header(ctx: *RequestContext, comptime name: anytype) ?[]const u8 {
|
||||
return (ctx.headerEntry(name) orelse return null).value;
|
||||
}
|
||||
|
||||
pub fn headerEntry(ctx: *RequestContext, comptime name: anytype) ?Header {
|
||||
for (ctx.request.headers) |head| {
|
||||
if (strings.eqlCaseInsensitiveASCII(head.name, name, true)) {
|
||||
return head;
|
||||
@@ -130,6 +217,18 @@ pub const RequestContext = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn headerEntryFirst(ctx: *RequestContext, comptime name: []const string) ?Header {
|
||||
for (ctx.request.headers) |head| {
|
||||
inline for (name) |match| {
|
||||
if (strings.eqlCaseInsensitiveASCII(head.name, match, true)) {
|
||||
return head;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn renderFallback(
|
||||
this: *RequestContext,
|
||||
allocator: std.mem.Allocator,
|
||||
@@ -175,7 +274,7 @@ pub const RequestContext = struct {
|
||||
bundler_parse_options,
|
||||
@as(?*bundler.FallbackEntryPoint, &fallback_entry_point),
|
||||
)) |*result| {
|
||||
try bundler_.linker.link(fallback_entry_point.source.path, result, .absolute_url, false);
|
||||
try bundler_.linker.link(fallback_entry_point.source.path, result, this.origin, .absolute_url, false);
|
||||
var buffer_writer = try js_printer.BufferWriter.init(default_allocator);
|
||||
var writer = js_printer.BufferPrinter.init(buffer_writer);
|
||||
_ = try bundler_.print(
|
||||
@@ -535,6 +634,7 @@ pub const RequestContext = struct {
|
||||
.method = Method.which(req.method) orelse return error.InvalidMethod,
|
||||
.watcher = watcher_,
|
||||
.timer = timer,
|
||||
.origin = bundler_.options.origin,
|
||||
};
|
||||
|
||||
return ctx;
|
||||
@@ -542,7 +642,7 @@ pub const RequestContext = struct {
|
||||
|
||||
pub inline fn isBrowserNavigation(req: *RequestContext) bool {
|
||||
if (req.header("Sec-Fetch-Mode")) |mode| {
|
||||
return strings.eqlComptime(mode.value, "navigate");
|
||||
return strings.eqlComptime(mode, "navigate");
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -621,7 +721,7 @@ pub const RequestContext = struct {
|
||||
ctx.appendHeader("Cache-Control", "immutable, max-age=99999");
|
||||
|
||||
if (ctx.header("If-None-Match")) |etag_header| {
|
||||
if (std.mem.eql(u8, node_modules_bundle.bundle.etag, etag_header.value)) {
|
||||
if (strings.eqlLong(node_modules_bundle.bundle.etag, etag_header, true)) {
|
||||
try ctx.sendNotModified();
|
||||
return;
|
||||
}
|
||||
@@ -680,6 +780,7 @@ pub const RequestContext = struct {
|
||||
printer: js_printer.BufferPrinter,
|
||||
timer: std.time.Timer,
|
||||
count: usize = 0,
|
||||
origin: ZigURL,
|
||||
pub const WatchBuildResult = struct {
|
||||
value: Value,
|
||||
id: u32,
|
||||
@@ -779,6 +880,7 @@ pub const RequestContext = struct {
|
||||
this.bundler.linker.link(
|
||||
Fs.Path.init(file_path_str),
|
||||
&parse_result,
|
||||
this.origin,
|
||||
.absolute_url,
|
||||
false,
|
||||
) catch return WatchBuildResult{
|
||||
@@ -865,6 +967,7 @@ pub const RequestContext = struct {
|
||||
this.allocator,
|
||||
&log,
|
||||
&this.bundler.linker,
|
||||
this.origin,
|
||||
);
|
||||
} else {
|
||||
break :brk CSSBundler.bundle(
|
||||
@@ -878,6 +981,7 @@ pub const RequestContext = struct {
|
||||
this.allocator,
|
||||
&log,
|
||||
&this.bundler.linker,
|
||||
this.origin,
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
@@ -1275,9 +1379,10 @@ pub const RequestContext = struct {
|
||||
|
||||
var handler: *JavaScriptHandler = try channel.readItem();
|
||||
JavaScript.VirtualMachine.vm.tick();
|
||||
|
||||
JavaScript.VirtualMachine.vm.preflush();
|
||||
|
||||
const original_origin = vm.origin;
|
||||
vm.origin = handler.ctx.origin;
|
||||
defer vm.origin = original_origin;
|
||||
JavaScript.EventListenerMixin.emitFetchEvent(
|
||||
vm,
|
||||
&handler.ctx,
|
||||
@@ -1388,6 +1493,7 @@ pub const RequestContext = struct {
|
||||
clone.message_buffer = try MutableString.init(server.allocator, 0);
|
||||
clone.ctx.conn = &clone.conn;
|
||||
clone.ctx.log = logger.Log.init(server.allocator);
|
||||
clone.ctx.origin = ZigURL.parse(server.allocator.dupe(u8, ctx.origin.href) catch unreachable);
|
||||
var printer_writer = try js_printer.BufferWriter.init(server.allocator);
|
||||
|
||||
clone.builder = WatchBuilder{
|
||||
@@ -1396,6 +1502,7 @@ pub const RequestContext = struct {
|
||||
.printer = js_printer.BufferPrinter.init(printer_writer),
|
||||
.timer = ctx.timer,
|
||||
.watcher = ctx.watcher,
|
||||
.origin = clone.ctx.origin,
|
||||
};
|
||||
|
||||
clone.websocket = Websocket.Websocket.create(&clone.conn, SOCKET_FLAGS);
|
||||
@@ -1763,14 +1870,14 @@ pub const RequestContext = struct {
|
||||
var request: *RequestContext = &self.ctx;
|
||||
const upgrade_header = request.header("Upgrade") orelse return error.BadRequest;
|
||||
|
||||
if (!strings.eqlComptime(upgrade_header.value, "websocket")) {
|
||||
if (!strings.eqlComptime(upgrade_header, "websocket")) {
|
||||
return error.BadRequest; // Can only upgrade to websocket
|
||||
}
|
||||
|
||||
// Some proxies/load balancers will mess with the connection header
|
||||
// and browsers also send multiple values here
|
||||
const connection_header = request.header("Connection") orelse return error.BadRequest;
|
||||
var it = std.mem.split(u8, connection_header.value, ",");
|
||||
var it = std.mem.split(u8, connection_header, ",");
|
||||
while (it.next()) |part| {
|
||||
const conn = std.mem.trim(u8, part, " ");
|
||||
if (strings.eqlCaseInsensitiveASCII(conn, "upgrade", true)) {
|
||||
@@ -1788,8 +1895,8 @@ pub const RequestContext = struct {
|
||||
Output.prettyErrorln("HMR WebSocket error: missing Sec-WebSocket-Version header", .{});
|
||||
return error.BadRequest;
|
||||
};
|
||||
return std.fmt.parseInt(u8, v.value, 10) catch {
|
||||
Output.prettyErrorln("HMR WebSocket error: Sec-WebSocket-Version is invalid {s}", .{v.value});
|
||||
return std.fmt.parseInt(u8, v, 10) catch {
|
||||
Output.prettyErrorln("HMR WebSocket error: Sec-WebSocket-Version is invalid {s}", .{v});
|
||||
return error.BadRequest;
|
||||
};
|
||||
}
|
||||
@@ -1798,7 +1905,7 @@ pub const RequestContext = struct {
|
||||
self: *WebsocketHandler,
|
||||
) ![]const u8 {
|
||||
var request: *RequestContext = &self.ctx;
|
||||
const key = (request.header("Sec-WebSocket-Key") orelse return error.BadRequest).value;
|
||||
const key = (request.header("Sec-WebSocket-Key") orelse return error.BadRequest);
|
||||
if (key.len < 8) {
|
||||
Output.prettyErrorln("HMR WebSocket error: Sec-WebSocket-Key is less than 8 characters long: {s}", .{key});
|
||||
return error.BadRequest;
|
||||
@@ -1822,7 +1929,7 @@ pub const RequestContext = struct {
|
||||
this.appendHeader("ETag", etag_content_slice);
|
||||
|
||||
if (this.header("If-None-Match")) |etag_header| {
|
||||
if (std.mem.eql(u8, etag_content_slice, etag_header.value)) {
|
||||
if (strings.eqlLong(etag_content_slice, etag_header, true)) {
|
||||
try this.sendNotModified();
|
||||
return true;
|
||||
}
|
||||
@@ -1951,7 +2058,7 @@ pub const RequestContext = struct {
|
||||
chunky.rctx.appendHeader("ETag", etag_content_slice);
|
||||
|
||||
if (chunky.rctx.header("If-None-Match")) |etag_header| {
|
||||
if (std.mem.eql(u8, etag_content_slice, etag_header.value)) {
|
||||
if (std.mem.eql(u8, etag_content_slice, etag_header)) {
|
||||
try chunky.rctx.sendNotModified();
|
||||
return;
|
||||
}
|
||||
@@ -2012,6 +2119,7 @@ pub const RequestContext = struct {
|
||||
Watcher,
|
||||
ctx.watcher,
|
||||
client_entry_point_,
|
||||
ctx.origin,
|
||||
) catch |err| {
|
||||
ctx.sendInternalError(err) catch {};
|
||||
return;
|
||||
@@ -2052,7 +2160,7 @@ pub const RequestContext = struct {
|
||||
ctx.appendHeader("ETag", etag_content_slice);
|
||||
|
||||
if (ctx.header("If-None-Match")) |etag_header| {
|
||||
if (std.mem.eql(u8, etag_content_slice, etag_header.value)) {
|
||||
if (std.mem.eql(u8, etag_content_slice, etag_header)) {
|
||||
try ctx.sendNotModified();
|
||||
return;
|
||||
}
|
||||
@@ -2120,7 +2228,7 @@ pub const RequestContext = struct {
|
||||
ctx.appendHeader("ETag", complete_weak_etag);
|
||||
|
||||
if (ctx.header("If-None-Match")) |etag_header| {
|
||||
if (strings.eql(complete_weak_etag, etag_header.value)) {
|
||||
if (strings.eql(complete_weak_etag, etag_header)) {
|
||||
try ctx.sendNotModified();
|
||||
return;
|
||||
}
|
||||
@@ -2227,7 +2335,7 @@ pub const RequestContext = struct {
|
||||
|
||||
if (strings.eqlComptime(path, "_api.hmr")) {
|
||||
if (ctx.header("Upgrade")) |upgrade| {
|
||||
if (strings.eqlCaseInsensitiveASCII(upgrade.value, "websocket", true)) {
|
||||
if (strings.eqlCaseInsensitiveASCII(upgrade, "websocket", true)) {
|
||||
try ctx.handleWebsocket(server);
|
||||
return;
|
||||
}
|
||||
@@ -2296,8 +2404,8 @@ pub const RequestContext = struct {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Dest
|
||||
pub fn isScriptOrStyleRequest(ctx: *RequestContext) bool {
|
||||
const header_ = ctx.header("Sec-Fetch-Dest") orelse return false;
|
||||
return strings.eqlComptime(header_.value, "script") or
|
||||
strings.eqlComptime(header_.value, "style");
|
||||
return strings.eqlComptime(header_, "script") or
|
||||
strings.eqlComptime(header_, "style");
|
||||
}
|
||||
|
||||
fn handleSrcURL(ctx: *RequestContext, _: *Server) !void {
|
||||
@@ -2944,6 +3052,7 @@ pub const Server = struct {
|
||||
|
||||
const is_navigation_request = req_ctx_.isBrowserNavigation();
|
||||
defer if (is_navigation_request) Analytics.enqueue(Analytics.EventName.http_build);
|
||||
req_ctx.parseOrigin();
|
||||
|
||||
if (req_ctx.url.needs_redirect) {
|
||||
req_ctx.handleRedirect(req_ctx.url.path) catch |err| {
|
||||
@@ -3028,7 +3137,7 @@ pub const Server = struct {
|
||||
|
||||
if (comptime FeatureFlags.keep_alive) {
|
||||
if (req_ctx.header("Connection")) |connection| {
|
||||
req_ctx.keep_alive = strings.eqlInsensitive(connection.value, "keep-alive");
|
||||
req_ctx.keep_alive = strings.eqlInsensitive(connection, "keep-alive");
|
||||
}
|
||||
|
||||
conn.client.setKeepAlive(req_ctx.keep_alive) catch {};
|
||||
|
||||
@@ -390,11 +390,12 @@ pub fn getScriptSrcString(
|
||||
&entry_point_tempbuf,
|
||||
Fs.PathName.init(file_path),
|
||||
),
|
||||
VirtualMachine.vm.origin,
|
||||
ScriptSrcStream.Writer,
|
||||
writer,
|
||||
);
|
||||
} else {
|
||||
JavaScript.Bun.getPublicPath(file_path, ScriptSrcStream.Writer, writer);
|
||||
JavaScript.Bun.getPublicPath(file_path, VirtualMachine.vm.origin, ScriptSrcStream.Writer, writer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ const ErrorableZigString = @import("javascript_core").ErrorableZigString;
|
||||
const ZigGlobalObject = @import("javascript_core").ZigGlobalObject;
|
||||
const VM = @import("javascript_core").VM;
|
||||
const Config = @import("./config.zig");
|
||||
|
||||
const URL = @import("../../query_string_map.zig").URL;
|
||||
pub const GlobalClasses = [_]type{
|
||||
Request.Class,
|
||||
Response.Class,
|
||||
@@ -105,7 +105,7 @@ pub const Bun = struct {
|
||||
pub fn onImportCSS(
|
||||
resolve_result: *const Resolver.Result,
|
||||
import_record: *ImportRecord,
|
||||
_: string,
|
||||
origin: URL,
|
||||
) void {
|
||||
if (!css_imports_buf_loaded) {
|
||||
css_imports_buf = std.ArrayList(u8).initCapacity(
|
||||
@@ -121,7 +121,7 @@ pub const Bun = struct {
|
||||
.offset = @truncate(u32, offset),
|
||||
.length = 0,
|
||||
};
|
||||
getPublicPath(resolve_result.path_pair.primary.text, @TypeOf(writer), writer);
|
||||
getPublicPath(resolve_result.path_pair.primary.text, origin, @TypeOf(writer), writer);
|
||||
const length = css_imports_buf.items.len - offset;
|
||||
css_imports_list[css_imports_list_tail].length = @truncate(u32, length);
|
||||
css_imports_list_tail += 1;
|
||||
@@ -198,7 +198,7 @@ pub const Bun = struct {
|
||||
_: js.JSStringRef,
|
||||
_: js.ExceptionRef,
|
||||
) js.JSValueRef {
|
||||
return ZigString.init(VirtualMachine.vm.bundler.options.origin.origin).toValue(VirtualMachine.vm.global).asRef();
|
||||
return ZigString.init(VirtualMachine.vm.origin.origin).toValue(VirtualMachine.vm.global).asRef();
|
||||
}
|
||||
|
||||
pub fn enableANSIColors(
|
||||
@@ -509,10 +509,10 @@ pub const Bun = struct {
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn getPublicPath(to: string, comptime Writer: type, writer: Writer) void {
|
||||
pub fn getPublicPath(to: string, origin: URL, comptime Writer: type, writer: Writer) void {
|
||||
const relative_path = VirtualMachine.vm.bundler.fs.relativeTo(to);
|
||||
if (VirtualMachine.vm.bundler.options.origin.isAbsolute()) {
|
||||
VirtualMachine.vm.bundler.options.origin.joinWrite(
|
||||
if (origin.isAbsolute()) {
|
||||
origin.joinWrite(
|
||||
Writer,
|
||||
writer,
|
||||
VirtualMachine.vm.bundler.options.routes.asset_prefix_path,
|
||||
@@ -558,7 +558,7 @@ pub const Bun = struct {
|
||||
|
||||
var stream = std.io.fixedBufferStream(&public_path_temp_str);
|
||||
var writer = stream.writer();
|
||||
getPublicPath(to, @TypeOf(&writer), &writer);
|
||||
getPublicPath(to, VirtualMachine.vm.origin, @TypeOf(&writer), &writer);
|
||||
return ZigString.init(stream.buffer[0..stream.pos]).toValueGC(VirtualMachine.vm.global).asRef();
|
||||
}
|
||||
|
||||
@@ -820,6 +820,7 @@ pub const VirtualMachine = struct {
|
||||
blobs: *Blob.Group = undefined,
|
||||
flush_list: std.ArrayList(string),
|
||||
entry_point: ServerEntryPoint = undefined,
|
||||
origin: URL = URL{},
|
||||
|
||||
arena: *std.heap.ArenaAllocator = undefined,
|
||||
has_loaded: bool = false,
|
||||
@@ -941,6 +942,7 @@ pub const VirtualMachine = struct {
|
||||
.log = log,
|
||||
.flush_list = std.ArrayList(string).init(allocator),
|
||||
.blobs = try Blob.Group.init(allocator),
|
||||
.origin = bundler.options.origin,
|
||||
|
||||
.macros = MacroMap.init(allocator),
|
||||
.macro_entry_points = @TypeOf(VirtualMachine.vm.macro_entry_points).init(allocator),
|
||||
@@ -1078,6 +1080,7 @@ pub const VirtualMachine = struct {
|
||||
try bundler.linker.link(
|
||||
file_path,
|
||||
&parse_result,
|
||||
vm.origin,
|
||||
.absolute_path,
|
||||
false,
|
||||
);
|
||||
@@ -1205,6 +1208,7 @@ pub const VirtualMachine = struct {
|
||||
try vm.bundler.linker.link(
|
||||
path,
|
||||
&parse_result,
|
||||
vm.origin,
|
||||
.absolute_path,
|
||||
false,
|
||||
);
|
||||
@@ -1384,19 +1388,33 @@ pub const VirtualMachine = struct {
|
||||
pub fn normalizeSpecifier(slice_: string) string {
|
||||
var slice = slice_;
|
||||
if (slice.len == 0) return slice;
|
||||
|
||||
if (strings.startsWith(slice, VirtualMachine.vm.bundler.options.origin.host)) {
|
||||
slice = slice[VirtualMachine.vm.bundler.options.origin.host.len..];
|
||||
var was_http = false;
|
||||
if (strings.hasPrefix(slice, "https://")) {
|
||||
slice = slice["https://".len..];
|
||||
was_http = true;
|
||||
}
|
||||
|
||||
if (VirtualMachine.vm.bundler.options.origin.path.len > 1) {
|
||||
if (strings.startsWith(slice, VirtualMachine.vm.bundler.options.origin.path)) {
|
||||
slice = slice[VirtualMachine.vm.bundler.options.origin.path.len..];
|
||||
if (strings.hasPrefix(slice, "http://")) {
|
||||
slice = slice["http://".len..];
|
||||
was_http = true;
|
||||
}
|
||||
|
||||
if (strings.hasPrefix(slice, VirtualMachine.vm.origin.host)) {
|
||||
slice = slice[VirtualMachine.vm.origin.host.len..];
|
||||
} else if (was_http) {
|
||||
if (strings.indexOfChar(slice, '/')) |i| {
|
||||
slice = slice[i..];
|
||||
}
|
||||
}
|
||||
|
||||
if (VirtualMachine.vm.origin.path.len > 1) {
|
||||
if (strings.hasPrefix(slice, VirtualMachine.vm.origin.path)) {
|
||||
slice = slice[VirtualMachine.vm.origin.path.len..];
|
||||
}
|
||||
}
|
||||
|
||||
if (VirtualMachine.vm.bundler.options.routes.asset_prefix_path.len > 0) {
|
||||
if (strings.startsWith(slice, VirtualMachine.vm.bundler.options.routes.asset_prefix_path)) {
|
||||
if (strings.hasPrefix(slice, VirtualMachine.vm.bundler.options.routes.asset_prefix_path)) {
|
||||
slice = slice[VirtualMachine.vm.bundler.options.routes.asset_prefix_path.len..];
|
||||
}
|
||||
}
|
||||
@@ -1742,7 +1760,7 @@ pub const VirtualMachine = struct {
|
||||
),
|
||||
frame.sourceURLFormatter(
|
||||
vm.bundler.fs.top_level_dir,
|
||||
&vm.bundler.options.origin,
|
||||
&vm.origin,
|
||||
allow_ansi_colors,
|
||||
),
|
||||
},
|
||||
|
||||
@@ -1663,7 +1663,7 @@ pub const Request = struct {
|
||||
_: js.ExceptionRef,
|
||||
) js.JSValueRef {
|
||||
if (this.request_context.header("Referrer")) |referrer| {
|
||||
return ZigString.init(referrer.value).toValueGC(VirtualMachine.vm.global).asRef();
|
||||
return ZigString.init(referrer).toValueGC(VirtualMachine.vm.global).asRef();
|
||||
} else {
|
||||
return ZigString.init("").toValueGC(VirtualMachine.vm.global).asRef();
|
||||
}
|
||||
|
||||
@@ -35,10 +35,10 @@ const Bundler = _bundler.Bundler;
|
||||
const ResolveQueue = _bundler.ResolveQueue;
|
||||
const ResolverType = Resolver.Resolver;
|
||||
const Runtime = @import("./runtime.zig").Runtime;
|
||||
|
||||
const URL = @import("query_string_map.zig").URL;
|
||||
pub const CSSResolveError = error{ResolveError};
|
||||
|
||||
pub const OnImportCallback = fn (resolve_result: *const Resolver.Result, import_record: *ImportRecord, source_dir: string) void;
|
||||
pub const OnImportCallback = fn (resolve_result: *const Resolver.Result, import_record: *ImportRecord, origin: URL) void;
|
||||
|
||||
pub const Linker = struct {
|
||||
const HashedFileNameMap = std.AutoHashMap(u64, string);
|
||||
@@ -117,6 +117,7 @@ pub const Linker = struct {
|
||||
url: string,
|
||||
range: logger.Range,
|
||||
kind: ImportKind,
|
||||
origin: URL,
|
||||
comptime import_path_format: Options.BundleOptions.ImportPathFormat,
|
||||
comptime resolve_only: bool,
|
||||
) !string {
|
||||
@@ -133,7 +134,7 @@ pub const Linker = struct {
|
||||
|
||||
const loader = this.options.loaders.get(resolve_result.path_pair.primary.name.ext) orelse .file;
|
||||
|
||||
this.processImportRecord(loader, dir, &resolve_result, &import_record, import_path_format) catch unreachable;
|
||||
this.processImportRecord(loader, dir, &resolve_result, &import_record, origin, import_path_format) catch unreachable;
|
||||
return import_record.path.text;
|
||||
},
|
||||
.at_conditional => {
|
||||
@@ -145,7 +146,7 @@ pub const Linker = struct {
|
||||
var import_record = ImportRecord{ .range = range, .path = resolve_result.path_pair.primary, .kind = kind };
|
||||
const loader = this.options.loaders.get(resolve_result.path_pair.primary.name.ext) orelse .file;
|
||||
|
||||
this.processImportRecord(loader, dir, &resolve_result, &import_record, import_path_format) catch unreachable;
|
||||
this.processImportRecord(loader, dir, &resolve_result, &import_record, origin, import_path_format) catch unreachable;
|
||||
return import_record.path.text;
|
||||
},
|
||||
.url => {
|
||||
@@ -157,7 +158,7 @@ pub const Linker = struct {
|
||||
var import_record = ImportRecord{ .range = range, .path = resolve_result.path_pair.primary, .kind = kind };
|
||||
const loader = this.options.loaders.get(resolve_result.path_pair.primary.name.ext) orelse .file;
|
||||
|
||||
this.processImportRecord(loader, dir, &resolve_result, &import_record, import_path_format) catch unreachable;
|
||||
this.processImportRecord(loader, dir, &resolve_result, &import_record, origin, import_path_format) catch unreachable;
|
||||
return import_record.path.text;
|
||||
},
|
||||
else => unreachable,
|
||||
@@ -165,13 +166,10 @@ pub const Linker = struct {
|
||||
unreachable;
|
||||
}
|
||||
|
||||
pub inline fn nodeModuleBundleImportPath(this: *const ThisLinker) string {
|
||||
pub inline fn nodeModuleBundleImportPath(this: *const ThisLinker, origin: URL) string {
|
||||
if (this.options.platform.isBun()) return "/node_modules.server.bun";
|
||||
|
||||
return if (this.options.node_modules_bundle_url.len > 0)
|
||||
this.options.node_modules_bundle_url
|
||||
else
|
||||
this.options.node_modules_bundle.?.bundle.import_from_name;
|
||||
return std.fmt.allocPrint(this.allocator, "{s}://{}{s}", .{ origin.displayProtocol(), origin.displayHost(), this.options.node_modules_bundle.?.bundle.import_from_name }) catch unreachable;
|
||||
}
|
||||
|
||||
// pub const Scratch = struct {
|
||||
@@ -193,6 +191,7 @@ pub const Linker = struct {
|
||||
linker: *ThisLinker,
|
||||
file_path: Fs.Path,
|
||||
result: *_bundler.ParseResult,
|
||||
origin: URL,
|
||||
comptime import_path_format: Options.BundleOptions.ImportPathFormat,
|
||||
comptime ignore_runtime: bool,
|
||||
) !void {
|
||||
@@ -201,6 +200,7 @@ pub const Linker = struct {
|
||||
var needs_bundle = false;
|
||||
var had_resolve_errors = false;
|
||||
var needs_require = false;
|
||||
var node_module_bundle_import_path: ?string = null;
|
||||
|
||||
// Step 1. Resolve imports & requires
|
||||
switch (result.loader) {
|
||||
@@ -213,15 +213,17 @@ pub const Linker = struct {
|
||||
if (strings.eqlComptime(import_record.path.text, Runtime.Imports.Name)) {
|
||||
// runtime is included in the bundle, so we don't need to dynamically import it
|
||||
if (linker.options.node_modules_bundle != null) {
|
||||
import_record.path.text = linker.nodeModuleBundleImportPath();
|
||||
node_module_bundle_import_path = node_module_bundle_import_path orelse
|
||||
linker.nodeModuleBundleImportPath(origin);
|
||||
import_record.path.text = node_module_bundle_import_path.?;
|
||||
result.ast.runtime_import_record_id = record_index;
|
||||
} else {
|
||||
import_record.path = try linker.generateImportPath(
|
||||
source_dir,
|
||||
linker.runtime_source_path,
|
||||
Runtime.version(),
|
||||
false,
|
||||
"bun",
|
||||
origin,
|
||||
import_path_format,
|
||||
);
|
||||
result.ast.runtime_import_record_id = record_index;
|
||||
@@ -287,7 +289,9 @@ pub const Linker = struct {
|
||||
}
|
||||
|
||||
import_record.is_bundled = true;
|
||||
import_record.path.text = linker.nodeModuleBundleImportPath();
|
||||
node_module_bundle_import_path = node_module_bundle_import_path orelse
|
||||
linker.nodeModuleBundleImportPath(origin);
|
||||
import_record.path.text = node_module_bundle_import_path.?;
|
||||
import_record.module_id = found_module.id;
|
||||
needs_bundle = true;
|
||||
continue;
|
||||
@@ -303,6 +307,7 @@ pub const Linker = struct {
|
||||
source_dir,
|
||||
resolved_import,
|
||||
import_record,
|
||||
origin,
|
||||
import_path_format,
|
||||
) catch continue;
|
||||
|
||||
@@ -407,14 +412,14 @@ pub const Linker = struct {
|
||||
import_records[import_records.len - 1] = ImportRecord{
|
||||
.kind = .stmt,
|
||||
.path = if (linker.options.node_modules_bundle != null)
|
||||
Fs.Path.init(linker.nodeModuleBundleImportPath())
|
||||
Fs.Path.init(node_module_bundle_import_path orelse linker.nodeModuleBundleImportPath(origin))
|
||||
else
|
||||
try linker.generateImportPath(
|
||||
source_dir,
|
||||
linker.runtime_source_path,
|
||||
Runtime.version(),
|
||||
false,
|
||||
"bun",
|
||||
origin,
|
||||
import_path_format,
|
||||
),
|
||||
.range = logger.Range{ .loc = logger.Loc{ .start = 0 }, .len = 0 },
|
||||
@@ -460,9 +465,9 @@ pub const Linker = struct {
|
||||
linker: *ThisLinker,
|
||||
source_dir: string,
|
||||
source_path: string,
|
||||
_: ?string,
|
||||
use_hashed_name: bool,
|
||||
namespace: string,
|
||||
origin: URL,
|
||||
comptime import_path_format: Options.BundleOptions.ImportPathFormat,
|
||||
) !Fs.Path {
|
||||
switch (import_path_format) {
|
||||
@@ -532,7 +537,7 @@ pub const Linker = struct {
|
||||
// assumption: already starts with "node:"
|
||||
"{s}/{s}",
|
||||
.{
|
||||
linker.options.origin.origin,
|
||||
origin,
|
||||
source_path,
|
||||
},
|
||||
));
|
||||
@@ -559,7 +564,7 @@ pub const Linker = struct {
|
||||
basename = try linker.getHashedFilename(basepath, null);
|
||||
}
|
||||
|
||||
return Fs.Path.init(try linker.options.origin.joinAlloc(
|
||||
return Fs.Path.init(try origin.joinAlloc(
|
||||
linker.allocator,
|
||||
linker.options.routes.asset_prefix_path,
|
||||
dirname,
|
||||
@@ -580,6 +585,7 @@ pub const Linker = struct {
|
||||
source_dir: string,
|
||||
resolve_result: *const Resolver.Result,
|
||||
import_record: *ImportRecord,
|
||||
origin: URL,
|
||||
comptime import_path_format: Options.BundleOptions.ImportPathFormat,
|
||||
) !void {
|
||||
linker.import_counter += 1;
|
||||
@@ -594,16 +600,16 @@ pub const Linker = struct {
|
||||
import_record.path = try linker.generateImportPath(
|
||||
source_dir,
|
||||
if (path.is_symlink and import_path_format == .absolute_url and linker.options.platform.isNotBun()) path.pretty else path.text,
|
||||
if (resolve_result.package_json) |package_json| package_json.version else "",
|
||||
Bundler.isCacheEnabled and loader == .file,
|
||||
path.namespace,
|
||||
origin,
|
||||
import_path_format,
|
||||
);
|
||||
|
||||
switch (loader) {
|
||||
.css => {
|
||||
if (linker.onImportCSS) |callback| {
|
||||
callback(resolve_result, import_record, source_dir);
|
||||
callback(resolve_result, import_record, origin);
|
||||
}
|
||||
// This saves us a less reliable string check
|
||||
import_record.print_mode = .css;
|
||||
|
||||
@@ -15,7 +15,11 @@ const C = _global.C;
|
||||
// This is close to WHATWG URL, but we don't want the validation errors
|
||||
pub const URL = struct {
|
||||
hash: string = "",
|
||||
/// hostname, but with a port
|
||||
/// `localhost:3000`
|
||||
host: string = "",
|
||||
/// hostname does not have a port
|
||||
/// `localhost`
|
||||
hostname: string = "",
|
||||
href: string = "",
|
||||
origin: string = "",
|
||||
@@ -86,6 +90,36 @@ pub const URL = struct {
|
||||
return "localhost";
|
||||
}
|
||||
|
||||
pub const HostFormatter = struct {
|
||||
host: string,
|
||||
port: string,
|
||||
is_https: bool = false,
|
||||
|
||||
pub fn format(formatter: HostFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
if (strings.indexOfChar(formatter.host, ':') != null) {
|
||||
try writer.writeAll(formatter.host);
|
||||
return;
|
||||
}
|
||||
|
||||
try writer.writeAll(formatter.host);
|
||||
|
||||
const is_port_optional = (formatter.is_https and (formatter.port.len == 0 or strings.eqlComptime(formatter.port, "443"))) or
|
||||
(!formatter.is_https and (formatter.port.len == 0 or strings.eqlComptime(formatter.port, "80")));
|
||||
if (!is_port_optional) {
|
||||
try writer.writeAll(":");
|
||||
try writer.writeAll(formatter.port);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
pub fn displayHost(this: *const URL) HostFormatter {
|
||||
return HostFormatter{
|
||||
.host = if (this.host.len > 0) this.host else this.displayHostname(),
|
||||
.port = this.port,
|
||||
.is_https = this.isHTTPS(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn hasHTTPLikeProtocol(this: *const URL) bool {
|
||||
return strings.eqlComptime(this.protocol, "http") or strings.eqlComptime(this.protocol, "https");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user