Skip to content

feat: Add Support for Serving Embedded Static Files #3448

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

Open
wants to merge 1 commit into
base: v2
Choose a base branch
from

Conversation

miseelu
Copy link

@miseelu miseelu commented May 13, 2025

Description

This pull request introduces a new feature to Fiber v2 that enables serving static files directly from an embedded filesystem (embed.FS). The enhancement allows developers to bundle static resources with their Go binary using Go's embed functionality, eliminating the need for external file dependencies in production.

The main change adds a new StaticFilesystem method that accepts an embed.FS instance, making it seamless to serve embedded static files while maintaining consistency with Fiber's existing static file serving API.

  • Added StaticFilesystem method to support serving static resources from embed.FS

  • Implemented registerStaticFS helper function in router.go

  • Maintained backward compatibility with existing static file serving functionality

Fixes # (issue)

Changes introduced

List the new features or adjustments introduced in this pull request. Provide details on benchmarks, documentation updates, changelog entries, and if applicable, the migration guide.

  • Benchmarks: Describe any performance benchmarks and improvements related to the changes.
  • Documentation Update: Detail the updates made to the documentation and links to the changed files.
  • Changelog/What's New: Include a summary of the additions for the upcoming release notes.
  • Migration Guide: If necessary, provide a guide or steps for users to migrate their existing code to accommodate these changes.
  • API Alignment with Express: Explain how the changes align with the Express API.
  • API Longevity: Discuss the steps taken to ensure that the new or updated APIs are consistent and not prone to breaking changes.
  • Examples: Provide examples demonstrating the new features or changes in action.

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)
  • Enhancement (improvement to existing features and functionality)
  • Documentation update (changes to documentation)
  • Performance improvement (non-breaking change which improves efficiency)
  • Code consistency (non-breaking change which improves code reliability and robustness)

Checklist

Before you submit your pull request, please make sure you meet these requirements:

  • Followed the inspiration of the Express.js framework for new functionalities, making them similar in usage.
  • Conducted a self-review of the code and provided comments for complex or critical parts.
  • Updated the documentation in the /docs/ directory for Fiber's documentation.
  • Added or updated unit tests to validate the effectiveness of the changes or new features.
  • Ensured that new and existing unit tests pass locally with the changes.
  • Verified that any new dependencies are essential and have been agreed upon by the maintainers/community.
  • Aimed for optimal performance with minimal allocations in the new code.
  • Provided benchmarks for the new code to analyze and improve upon.

Commit formatting

Please use emojis in commit messages for an easy way to identify the purpose or intention of a commit. Check out the emoji cheatsheet here: CONTRIBUTING.md

@miseelu miseelu requested a review from a team as a code owner May 13, 2025 13:52
Copy link

welcome bot commented May 13, 2025

Thanks for opening this pull request! 🎉 Please check out our contributing guidelines. If you need help or want to chat with us, join us on Discord https://gofiber.io/discord

Copy link
Contributor

coderabbitai bot commented May 13, 2025

Walkthrough

A new method, StaticFilesystem, has been added to the App struct to enable serving static files from any fs.FS filesystem, including embedded filesystems. Supporting logic is implemented via a new internal method, registerStaticFS, which handles configuration and routing for static file serving.

Changes

File(s) Change Summary
app.go Added StaticFilesystem method to App struct for serving static files from an fs.FS filesystem.
router.go Introduced registerStaticFS method to handle static file serving from io/fs.FS filesystems.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App
    participant Router
    participant FSHandler

    User->>App: Call StaticFilesystem(prefix, filesystem, config)
    App->>Router: registerStaticFS(prefix, filesystem, config)
    Router->>FSHandler: Setup handler with config
    User->>App: HTTP GET /static/file
    App->>FSHandler: Serve file from fs.FS
    FSHandler-->>User: Return file content or next middleware
Loading

Possibly related issues

Possibly related PRs

  • middleware: add static middleware #3006: Both PRs focus on serving static files via io/fs, with this PR adding a method and registration logic, and the referenced PR restructuring middleware for unified static file support.

Suggested labels

✏️ Feature

Suggested reviewers

  • gaby
  • efectn
  • ReneWerner87

Poem

A filesystem hops into view,
With static files both old and new.
Embedded or not, we serve with glee,
From code to client, as quick as can be!
The bunny’s proud, the code is neat—
Static files now taste extra sweet! 🐇

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
app.go (1)

17-18: Unnecessary extra alias creates import-group churn

app.go imports the same io/fs package that router.go already brings in (aliased as iofs). Having two different aliases (fs vs iofs) across the project makes grepping harder and quickly leads to style drift.

-import (
-
-	"io/fs"
+import (
+
+	"io/fs" // TODO: decide on **one** project-wide alias, e.g. “iofs” or plain “fs”
router.go (4)

10-11: Choose a single alias for io/fs

The package is imported here as iofs but as plain fs in app.go. Pick one form to improve readability and keep IDE-based import grouping stable.


448-565: Large copy–paste block duplicates registerStatic; extract common helper

registerStaticFS is ~120 lines that are almost byte-for-byte identical to registerStatic, differing only in:

  • the source of the files (FS vs Root)
  • the default Root value (".")
  • the parameter types

Duplicating this logic multiplies maintenance cost (bug fixes, new flags, security tweaks). Consider extracting the shared parts into a single unexported helper that accepts an *fasthttp.FS already pre-configured, e.g.:

func (app *App) mountStaticHandler(prefix string, fsConf *fasthttp.FS, cfg ...Static)

Both registerStatic and registerStaticFS would then build the fasthttp.FS struct (differing in only two fields) and delegate the rest.

Benefits:

  • One canonical path-rewrite algorithm
  • One place to toggle new Static options
  • Smaller diff-footprint for future features

468-478: Root: "." is ignored when FS is set – remove to avoid confusion

According to fasthttp.FS docs, when the FS field is non-nil the Root field is ignored. Leaving Root: "." in place is harmless but can confuse future readers into thinking the path is relevant.

-		FS:                   filesystem,
-		Root:                 ".",
+		FS: filesystem,

500-517: Nil-pointer guard for caller mistakes

registerStaticFS dereferences config[0] multiple times without a length check after the first guard.
If the caller passes an empty slice and later iterations of the code are changed, this could regress.

Adding a short nil-safe accessor avoids the hazard:

var userCfg Static
if len(config) > 0 {
    userCfg = config[0]
}

and replace every config[0].Foo with userCfg.Foo.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fccff19 and d194b3f.

📒 Files selected for processing (2)
  • app.go (2 hunks)
  • router.go (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
app.go (1)
router.go (2)
  • App (207-229)
  • Router (23-47)

Comment on lines +784 to +790
// StaticFilesystem Supports Serving Static Resources from an Embed.FS File System
//
// The filesystem parameter can directly accept an embed.FS instance.
func (app *App) StaticFilesystem(prefix string, filesystem fs.FS, config ...Static) Router {
app.registerStaticFS(prefix, filesystem, config...)
return app
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Expose the new helper through the public Router interface

StaticFilesystem is now public on *App, but the Router interface (defined in router.go, lines 22-47) hasn’t been extended.
As a result:

var r fiber.Router = app // common pattern
r.StaticFilesystem("/assets", myFS) // ← does **not** compile

The omission also means *Group cannot forward the call, limiting feature parity with Static.

 type Router interface {
     …
     Static(prefix, root string, config ...Static) Router
+    StaticFilesystem(prefix string, filesystem fs.FS, config ...Static) Router
     …
 }

After extending the interface, forwarders for *Group can be added with two lines:

func (g *Group) StaticFilesystem(p string, fs fs.FS, cfg ...Static) Router {
    return g.app.StaticFilesystem(getGroupPath(g.Prefix, p), fs, cfg...)
}

🏁 Script executed:

#!/bin/bash
set -e

# Locate the Router interface declaration and its methods
rg "type Router interface" -A 20

# Confirm if StaticFilesystem is already declared in router.go
rg "StaticFilesystem" -n router.go || echo "No StaticFilesystem in router.go"

# Inspect Group type and existing Static methods
rg "type Group" -A 10
rg "func .*Group\).*Static" -n

Length of output: 1560


Expose StaticFilesystem on the Router interface and Group

The new StaticFilesystem helper is public on *App but isn’t declared on the Router interface (router.go) or forwarded by *Group (group.go), preventing common patterns like:

var r fiber.Router = app
r.StaticFilesystem("/assets", myFS) // does not compile

Please add:

• router.go (within type Router interface):

 type Router interface {
     …
     Static(prefix, root string, config ...Static) Router
+    StaticFilesystem(prefix string, filesystem fs.FS, config ...Static) Router
     …
 }

• group.go (alongside func (grp *Group) Static):

func (grp *Group) StaticFilesystem(p string, fs fs.FS, cfg ...Static) Router {
    return grp.app.StaticFilesystem(getGroupPath(grp.Prefix, p), fs, cfg...)
}

Comment on lines +520 to +547
// Don't execute middleware if Next returns true
if len(config) != 0 && config[0].Next != nil && config[0].Next(c) {
return c.Next()
}
// Serve file
fileHandler(c.fasthttp)
// Sets the response Content-Disposition header to attachment if the Download option is true
if len(config) > 0 && config[0].Download {
c.Attachment()
}
// Return request if found and not forbidden
status := c.fasthttp.Response.StatusCode()
if status != StatusNotFound && status != StatusForbidden {
if len(cacheControlValue) > 0 {
c.fasthttp.Response.Header.Set(HeaderCacheControl, cacheControlValue)
}
if modifyResponse != nil {
return modifyResponse(c)
}
return nil
}
// Reset response to default
c.fasthttp.SetContentType("") // Issue #420
c.fasthttp.Response.SetStatusCode(StatusOK)
c.fasthttp.Response.SetBodyString("")
// Next middleware
return c.Next()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing nil-filesystem validation

If a caller accidentally passes a nil fs.FS, the fast-http handler panics the first time it tries to open a file.

if filesystem == nil {
    panic("StaticFilesystem: filesystem cannot be nil")
}

Fail fast with a clear message rather than a runtime panic deep inside fasthttp.

Comment on lines +551 to +558
use: true,
root: isRoot,
path: prefix,
// Public data
Method: MethodGet,
Path: prefix,
Handlers: []Handler{handler},
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Route metadata sets use: true but only registers GET/HEAD

use: true traditionally means match all HTTP methods.
Because you later add the route only to GET & HEAD stacks, the flag is misleading for downstream tools that rely on it (e.g. route printers, metrics).

Set use: false or register the handler for all methods, mirroring the behaviour of app.Static.

@ReneWerner87
Copy link
Member

Thx for the effort
Not sure if we should adopt this
We actually have a feature stop for v2 so that all resources can focus on v3
Only bugs are still being fixed in v2

Is everything go 1.17 ready? V2 is supporting this old version

@ReneWerner87
Copy link
Member

@miseelu can you provide tests for this feature

@ReneWerner87
Copy link
Member

@miseelu just so you know, i'm currently trying to reduce the custom methods for adding routes
so that this part should be prevented
https://github.com/gofiber/fiber/blob/v2/router.go#L429-L444

in this issue
#3442 (comment)
so we may have to slightly modify your part again

the idea behind this is not to create the route struct in different places

@gaby
Copy link
Member

gaby commented May 15, 2025

@miseelu Please provide Unit-Tests and update the Documentation

@gaby gaby requested a review from Copilot May 15, 2025 12:43
@gaby gaby added the v2 label May 15, 2025
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds a new StaticFilesystem method to serve embedded static files from an embed.FS, aligning with Fiber’s existing static file API.

  • Introduces registerStaticFS in router.go to handle embed.FS file serving
  • Adds StaticFilesystem wrapper in app.go
  • Keeps backward compatibility with the original Static method

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
router.go Implements registerStaticFS with path rewriting and handler registration for embedded FS
app.go Exposes StaticFilesystem on App to call registerStaticFS
Comments suppressed due to low confidence (3)

router.go:448

  • [nitpick] Consider adding unit tests for registerStaticFS to cover path rewriting logic and embedded FS handling to ensure correct behavior.
func (app *App) registerStaticFS(prefix string, filesystem iofs.FS, config ...Static) {

router.go:476

  • The time package is not imported, causing a build error. Add import "time" at the top of the file.
CacheDuration:        10 * time.Second,

router.go:560

  • The sync/atomic package is not imported, causing a build error. Add import "sync/atomic" at the top of the file.
atomic.AddUint32(&app.handlersCount, 1)

Comment on lines +784 to +786
// StaticFilesystem Supports Serving Static Resources from an Embed.FS File System
//
// The filesystem parameter can directly accept an embed.FS instance.
Copy link
Preview

Copilot AI May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The doc comment should follow Go conventions and include usage examples. For example:

// StaticFilesystem serves embedded static files from an fs.FS.
// Example:
//   app.StaticFilesystem("/assets", embeddedFiles)
Suggested change
// StaticFilesystem Supports Serving Static Resources from an Embed.FS File System
//
// The filesystem parameter can directly accept an embed.FS instance.
// StaticFilesystem serves static resources from an fs.FS, such as an embed.FS instance.
//
// Example:
// // Embed static files using embed.FS
// //go:embed static/*
// var embeddedFiles embed.FS
//
// app.StaticFilesystem("/static", embeddedFiles)

Copilot uses AI. Check for mistakes.

}

// Create route metadata without pointer
route := Route{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ReneWerner87
Copy link
Member

@miseelu can you check our last hints

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants