Skip to content

Add FileRoute for serving files #20198

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 39 commits into from
Jun 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5010813
fix FileRoute response type
Jarred-Sumner Jun 5, 2025
4292a58
`bun scripts/glob-sources.mjs`
Jarred-Sumner Jun 5, 2025
a7f8bca
`bun run zig-format`
Jarred-Sumner Jun 5, 2025
a973467
Updated
Jarred-Sumner Jun 5, 2025
26cbc43
`bun scripts/glob-sources.mjs`
Jarred-Sumner Jun 5, 2025
32a6e87
`bun run zig-format`
Jarred-Sumner Jun 5, 2025
1943cda
`bun run prettier`
Jarred-Sumner Jun 5, 2025
74579aa
More and better tests
Jarred-Sumner Jun 5, 2025
61e5219
a
Jarred-Sumner Jun 5, 2025
a6399e8
Merge branch 'main' into codex/implement-fileroute-for-bun.serve
Jarred-Sumner Jun 5, 2025
dfe08a0
Merge branch 'main' into codex/implement-fileroute-for-bun.serve
Jarred-Sumner Jun 6, 2025
2bf3129
Explicitly ref the FileReader. And add a micro optimization
Jarred-Sumner Jun 6, 2025
a8ea971
Avoid negative numbers
Jarred-Sumner Jun 6, 2025
8408c5b
The fix
Jarred-Sumner Jun 6, 2025
e2225ac
Update FileRoute.zig
Jarred-Sumner Jun 6, 2025
85ef6bc
Update bun-serve-file.test.ts
Jarred-Sumner Jun 6, 2025
dd20f58
Update FileRoute.zig
Jarred-Sumner Jun 6, 2025
801ebd3
Make it better at detecting when it ends
Jarred-Sumner Jun 6, 2025
0c5f847
Update FileRoute.zig
Jarred-Sumner Jun 6, 2025
fa9bc58
Try this
Jarred-Sumner Jun 6, 2025
247dd8b
Merge branch 'main' into codex/implement-fileroute-for-bun.serve
dylan-conway Jun 7, 2025
363a2a2
Update FileRoute.zig
dylan-conway Jun 7, 2025
587eb4b
Merge branch 'main' into codex/implement-fileroute-for-bun.serve
Jarred-Sumner Jun 7, 2025
8e34916
Update src/bun.js/api/server/FileRoute.zig
Jarred-Sumner Jun 7, 2025
e136715
`bun run prettier`
Jarred-Sumner Jun 7, 2025
b35482b
test
cirospaciari Jun 10, 2025
6aab118
test
cirospaciari Jun 10, 2025
fe8e084
test
cirospaciari Jun 10, 2025
0461f5e
more
cirospaciari Jun 10, 2025
adde716
opsie
cirospaciari Jun 10, 2025
862b0a0
opsie 2
cirospaciari Jun 10, 2025
fa1bc36
fix ban words
cirospaciari Jun 10, 2025
ccc9e90
reduce file size so we dont timeout on CI
cirospaciari Jun 10, 2025
ebe1375
Update src/bun.js/api/server/FileRoute.zig
dylan-conway Jun 10, 2025
9006146
Update http.zig
dylan-conway Jun 11, 2025
e181163
Merge branch 'main' into codex/implement-fileroute-for-bun.serve
dylan-conway Jun 11, 2025
1c37bb5
dont finish inside read wait for onReaderDonee
cirospaciari Jun 11, 2025
174f970
fix windows
cirospaciari Jun 11, 2025
b687868
more fixes
cirospaciari Jun 10, 2025
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
2 changes: 2 additions & 0 deletions cmake/sources/ZigSources.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ src/bun.js/api/JSBundler.zig
src/bun.js/api/JSTranspiler.zig
src/bun.js/api/server.zig
src/bun.js/api/server/AnyRequestContext.zig
src/bun.js/api/server/FileRoute.zig
src/bun.js/api/server/HTMLBundle.zig
src/bun.js/api/server/HTTPStatusText.zig
src/bun.js/api/server/InspectorBunFrontendDevServerAgent.zig
Expand Down Expand Up @@ -473,6 +474,7 @@ src/fd.zig
src/feature_flags.zig
src/fmt.zig
src/fs.zig
src/fs/stat_hash.zig
src/futex.zig
src/generated_perf_trace_events.zig
src/generated_versions_list.zig
Expand Down
2 changes: 1 addition & 1 deletion packages/bun-uws/src/AsyncSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ struct AsyncSocket {
* since written < buffer_len is very likely to be true
*/
if(written < max_flush_len) {
[[likely]]
[[likely]]
/* Cannot write more at this time, return what we've written so far */
return total_written;
}
Expand Down
6 changes: 3 additions & 3 deletions packages/bun-uws/src/HttpContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -456,10 +456,9 @@ struct HttpContext {
size_t bufferedAmount = asyncSocket->getBufferedAmount();
if (bufferedAmount > 0) {
/* Try to flush pending data from the socket's buffer to the network */
bufferedAmount -= asyncSocket->flush();

asyncSocket->flush();
/* Check if there's still data waiting to be sent after flush attempt */
if (bufferedAmount > 0) {
if (asyncSocket->getBufferedAmount() > 0) {
/* Socket buffer is not completely empty yet
* - Reset the timeout to prevent premature connection closure
* - This allows time for another writable event or new request
Expand Down Expand Up @@ -498,6 +497,7 @@ struct HttpContext {
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) {
if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) == 0) {
if (asyncSocket->getBufferedAmount() == 0) {

asyncSocket->shutdown();
/* We need to force close after sending FIN since we want to hinder
* clients from keeping to send their huge data */
Expand Down
4 changes: 1 addition & 3 deletions packages/bun-uws/src/HttpResponse.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ struct HttpResponse : public AsyncSocket<SSL> {
* one party must tell the other one so.
*
* This check also serves to limit writing the header only once. */
if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) == 0) {
if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) == 0 && !(httpResponseData->state & (HttpResponseData<SSL>::HTTP_WRITE_CALLED))) {
writeHeader("Connection", "close");
}

Expand All @@ -132,7 +132,6 @@ struct HttpResponse : public AsyncSocket<SSL> {

/* Terminating 0 chunk */
Super::write("0\r\n\r\n", 5);

httpResponseData->markDone();

/* We need to check if we should close this socket here now */
Expand Down Expand Up @@ -586,7 +585,6 @@ struct HttpResponse : public AsyncSocket<SSL> {
if (writtenPtr) {
*writtenPtr = total_written;
}

/* If we did not fail the write, accept more */
return !has_failed;
}
Expand Down
1 change: 0 additions & 1 deletion packages/bun-uws/src/LoopData.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ struct alignas(16) LoopData {
time_t now = time(0);
struct tm tstruct = {};
#ifdef _WIN32
/* Micro, fucking soft never follows spec. */
gmtime_s(&tstruct, &now);
#else
gmtime_r(&now, &tstruct);
Expand Down
13 changes: 13 additions & 0 deletions src/bun.js/api/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,16 @@ pub fn writeStatus(comptime ssl: bool, resp_ptr: ?*uws.NewApp(ssl).Response, sta

// TODO: rename to StaticBlobRoute? the html bundle is sometimes a static route
pub const StaticRoute = @import("./server/StaticRoute.zig");
pub const FileRoute = @import("./server/FileRoute.zig");

const HTMLBundle = JSC.API.HTMLBundle;

pub const AnyRoute = union(enum) {
/// Serve a static file
/// "/robots.txt": new Response(...),
static: *StaticRoute,
/// Serve a file from disk
file: *FileRoute,
/// Bundle an HTML import
/// import html from "./index.html";
/// "/": html,
Expand All @@ -82,6 +85,7 @@ pub const AnyRoute = union(enum) {
pub fn memoryCost(this: AnyRoute) usize {
return switch (this) {
.static => |static_route| static_route.memoryCost(),
.file => |file_route| file_route.memoryCost(),
.html => |html_bundle_route| html_bundle_route.data.memoryCost(),
.framework_router => @sizeOf(bun.bake.Framework.FileSystemRouterType),
};
Expand All @@ -90,6 +94,7 @@ pub const AnyRoute = union(enum) {
pub fn setServer(this: AnyRoute, server: ?AnyServer) void {
switch (this) {
.static => |static_route| static_route.server = server,
.file => |file_route| file_route.server = server,
.html => |html_bundle_route| html_bundle_route.server = server,
.framework_router => {}, // DevServer contains .server field
}
Expand All @@ -98,6 +103,7 @@ pub const AnyRoute = union(enum) {
pub fn deref(this: AnyRoute) void {
switch (this) {
.static => |static_route| static_route.deref(),
.file => |file_route| file_route.deref(),
.html => |html_bundle_route| html_bundle_route.deref(),
.framework_router => {}, // not reference counted
}
Expand All @@ -106,6 +112,7 @@ pub const AnyRoute = union(enum) {
pub fn ref(this: AnyRoute) void {
switch (this) {
.static => |static_route| static_route.ref(),
.file => |file_route| file_route.ref(),
.html => |html_bundle_route| html_bundle_route.ref(),
.framework_router => {}, // not reference counted
}
Expand Down Expand Up @@ -182,6 +189,9 @@ pub const AnyRoute = union(enum) {
}
}

if (try FileRoute.fromJS(global, argument)) |file_route| {
return .{ .file = file_route };
}
return .{ .static = try StaticRoute.fromJS(global, argument) orelse return null };
}
};
Expand Down Expand Up @@ -2511,6 +2521,9 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
.static => |static_route| {
ServerConfig.applyStaticRoute(any_server, ssl_enabled, app, *StaticRoute, static_route, entry.path, entry.method);
},
.file => |file_route| {
ServerConfig.applyStaticRoute(any_server, ssl_enabled, app, *FileRoute, file_route, entry.path, entry.method);
},
.html => |html_bundle_route| {
ServerConfig.applyStaticRoute(any_server, ssl_enabled, app, *HTMLBundle.Route, html_bundle_route.data, entry.path, entry.method);
if (dev_server) |dev| {
Expand Down
Loading