feat: serve files using embed.FS (#4847)

This commit is contained in:
shaouai
2025-05-10 23:43:13 +08:00
committed by GitHub
parent e55158b0f7
commit 5564c43197
4 changed files with 179 additions and 0 deletions

View File

@@ -2,6 +2,7 @@ package fileserver
import (
"net/http"
pathpkg "path"
"strings"
"sync"
)
@@ -29,6 +30,18 @@ func createFileChecker(fs http.FileSystem) func(string) bool {
fileChecker := make(map[string]bool)
return func(path string) bool {
// Emulate http.Dir.Opens path normalization for embed.FS.Open.
// http.FileServer redirects any request ending in "/index.html"
// to the same path without the final "index.html".
// So the path here may be empty or end with a "/".
// http.Dir.Open uses this logic to clean the path,
// correctly handling those two cases.
// embed.FS doesnt perform this normalization, so we apply the same logic here.
path = pathpkg.Clean("/" + path)[1:]
if path == "" {
path = "."
}
lock.RLock()
exist, ok := fileChecker[path]
lock.RUnlock()

View File

@@ -1,6 +1,8 @@
package fileserver
import (
"embed"
"io/fs"
"net/http"
"net/http/httptest"
"testing"
@@ -61,6 +63,46 @@ func TestMiddleware(t *testing.T) {
requestPath: "/ws",
expectedStatus: http.StatusAlreadyReported,
},
// http.FileServer redirects any request ending in "/index.html"
// to the same path, without the final "index.html".
{
name: "Serve index.html",
path: "/static",
dir: "testdata",
requestPath: "/static/index.html",
expectedStatus: http.StatusMovedPermanently,
},
{
name: "Serve index.html with path with trailing slash",
path: "/static/",
dir: "testdata",
requestPath: "/static/index.html",
expectedStatus: http.StatusMovedPermanently,
},
{
name: "Serve index.html in a nested directory",
path: "/static",
dir: "testdata",
requestPath: "/static/nested/index.html",
expectedStatus: http.StatusMovedPermanently,
},
{
name: "Request index.html indirectly",
path: "/static",
dir: "testdata",
requestPath: "/static/",
expectedStatus: http.StatusOK,
expectedContent: "hello",
},
{
name: "Request index.html in a nested directory indirectly",
path: "/static",
dir: "testdata",
requestPath: "/static/nested/",
expectedStatus: http.StatusOK,
expectedContent: "hello",
},
}
for _, tt := range tests {
@@ -87,6 +129,128 @@ func TestMiddleware(t *testing.T) {
}
}
var (
//go:embed testdata
testdataFS embed.FS
)
func TestMiddleware_embedFS(t *testing.T) {
tests := []struct {
name string
path string
requestPath string
expectedStatus int
expectedContent string
}{
{
name: "Serve static file",
path: "/static",
requestPath: "/static/example.txt",
expectedStatus: http.StatusOK,
expectedContent: "1",
},
{
name: "Path with trailing slash",
path: "/static/",
requestPath: "/static/example.txt",
expectedStatus: http.StatusOK,
expectedContent: "1",
},
{
name: "Root path",
path: "/",
requestPath: "/example.txt",
expectedStatus: http.StatusOK,
expectedContent: "1",
},
{
name: "Pass through non-matching path",
path: "/static/",
requestPath: "/other/path",
expectedStatus: http.StatusAlreadyReported,
},
{
name: "Not exist file",
path: "/assets",
requestPath: "/assets/not-exist.txt",
expectedStatus: http.StatusAlreadyReported,
},
{
name: "Not exist file in root",
path: "/",
requestPath: "/not-exist.txt",
expectedStatus: http.StatusAlreadyReported,
},
{
name: "websocket request",
path: "/",
requestPath: "/ws",
expectedStatus: http.StatusAlreadyReported,
},
// http.FileServer redirects any request ending in "/index.html"
// to the same path, without the final "index.html".
{
name: "Serve index.html",
path: "/static",
requestPath: "/static/index.html",
expectedStatus: http.StatusMovedPermanently,
},
{
name: "Serve index.html with path with trailing slash",
path: "/static/",
requestPath: "/static/index.html",
expectedStatus: http.StatusMovedPermanently,
},
{
name: "Serve index.html in a nested directory",
path: "/static",
requestPath: "/static/nested/index.html",
expectedStatus: http.StatusMovedPermanently,
},
{
name: "Request index.html indirectly",
path: "/static",
requestPath: "/static/",
expectedStatus: http.StatusOK,
expectedContent: "hello",
},
{
name: "Request index.html in a nested directory indirectly",
path: "/static",
requestPath: "/static/nested/",
expectedStatus: http.StatusOK,
expectedContent: "hello",
},
}
subFS, err := fs.Sub(testdataFS, "testdata")
assert.Nil(t, err)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
middleware := Middleware(tt.path, http.FS(subFS))
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusAlreadyReported)
})
handlerToTest := middleware(nextHandler)
for i := 0; i < 2; i++ {
req := httptest.NewRequest(http.MethodGet, tt.requestPath, nil)
rr := httptest.NewRecorder()
handlerToTest.ServeHTTP(rr, req)
assert.Equal(t, tt.expectedStatus, rr.Code)
if len(tt.expectedContent) > 0 {
assert.Equal(t, tt.expectedContent, rr.Body.String())
}
}
})
}
}
func TestEnsureTrailingSlash(t *testing.T) {
tests := []struct {
input string

View File

@@ -0,0 +1 @@
hello

View File

@@ -0,0 +1 @@
hello