Skip to content

Commit

Permalink
feat: add support to serve fs.FS
Browse files Browse the repository at this point in the history
  • Loading branch information
savsgio committed Mar 4, 2024
1 parent d275c03 commit 407bc93
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 7 deletions.
23 changes: 21 additions & 2 deletions group.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package router

import "github.com/valyala/fasthttp"
import (
"io/fs"

"github.com/valyala/fasthttp"
)

// Group returns a new group.
// Path auto-correction, including trailing slashes, is enabled by default.
Expand Down Expand Up @@ -86,7 +90,7 @@ func (g *Group) ANY(path string, handler fasthttp.RequestHandler) {
g.router.ANY(g.prefix+path, handler)
}

// ServeFiles serves files from the given file system root.
// ServeFiles serves files from the given file system root path.
// The path must end with "/{filepath:*}", files are then served from the local
// path /defined/root/dir/{filepath:*}.
// For example if root is "/etc" and {filepath:*} is "passwd", the local file
Expand All @@ -101,6 +105,21 @@ func (g *Group) ServeFiles(path string, rootPath string) {
g.router.ServeFiles(g.prefix+path, rootPath)
}

// ServeFS serves files from the given file system.
// The path must end with "/{filepath:*}", files are then served from the local
// path /defined/root/dir/{filepath:*}.
// For example if root is "/etc" and {filepath:*} is "passwd", the local file
// "/etc/passwd" would be served.
// Internally a fasthttp.FSHandler is used, therefore http.NotFound is used instead
// Use:
//
// router.ServeFS("/src/{filepath:*}", myFilesystem)
func (g *Group) ServeFS(path string, filesystem fs.FS) {
validatePath(path)

g.router.ServeFS(g.prefix+path, filesystem)
}

// ServeFilesCustom serves files from the given file system settings.
// The path must end with "/{filepath:*}", files are then served from the local
// path /defined/root/dir/{filepath:*}.
Expand Down
3 changes: 3 additions & 0 deletions group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func TestGroup(t *testing.T) {
ctx.SetStatusCode(fasthttp.StatusOK)
})
r6.ServeFiles("/static/{filepath:*}", "./")
r6.ServeFS("/static/fs/{filepath:*}", fsTestFilesystem)
r6.ServeFilesCustom("/custom/static/{filepath:*}", &fasthttp.FS{Root: "./"})

uris := []string{
Expand All @@ -110,6 +111,8 @@ func TestGroup(t *testing.T) {
"POST /moo/foo/foo/bar HTTP/1.1\r\n\r\n",
// testing multiple sub-router group - r6 (grouped from r5) to serve files
"GET /moo/foo/foo/static/router.go HTTP/1.1\r\n\r\n",
// testing multiple sub-router group - r6 (grouped from r5) to serve fs
"GET /moo/foo/foo/static/fs/LICENSE HTTP/1.1\r\n\r\n",
// testing multiple sub-router group - r6 (grouped from r5) to serve files with custom settings
"GET /moo/foo/foo/custom/static/router.go HTTP/1.1\r\n\r\n",
}
Expand Down
29 changes: 25 additions & 4 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package router

import (
"fmt"
"io/fs"
"strings"

"github.com/fasthttp/router/radix"
Expand All @@ -15,8 +16,7 @@ import (
const MethodWild = "*"

var (
defaultContentType = []byte("text/plain; charset=utf-8")
questionMark = byte('?')
questionMark = byte('?')

// MatchedRoutePathParam is the param name under which the path of the matched
// route is stored, if Router.SaveMatchedRoutePath is set.
Expand Down Expand Up @@ -164,7 +164,7 @@ func (r *Router) ANY(path string, handler fasthttp.RequestHandler) {
r.Handle(MethodWild, path, handler)
}

// ServeFiles serves files from the given file system root.
// ServeFiles serves files from the given file system root path.
// The path must end with "/{filepath:*}", files are then served from the local
// path /defined/root/dir/{filepath:*}.
// For example if root is "/etc" and {filepath:*} is "passwd", the local file
Expand All @@ -182,6 +182,27 @@ func (r *Router) ServeFiles(path string, rootPath string) {
})
}

// ServeFS serves files from the given file system.
// The path must end with "/{filepath:*}", files are then served from the local
// path /defined/root/dir/{filepath:*}.
// For example if root is "/etc" and {filepath:*} is "passwd", the local file
// "/etc/passwd" would be served.
// Internally a fasthttp.FSHandler is used, therefore fasthttp.NotFound is used instead
// Use:
//
// router.ServeFS("/src/{filepath:*}", myFilesystem)
func (r *Router) ServeFS(path string, filesystem fs.FS) {
r.ServeFilesCustom(path, &fasthttp.FS{
FS: filesystem,
Root: "",
AllowEmptyRoot: true,
GenerateIndexPages: true,
AcceptByteRange: true,
Compress: true,
CompressBrotli: true,
})
}

// ServeFilesCustom serves files from the given file system settings.
// The path must end with "/{filepath:*}", files are then served from the local
// path /defined/root/dir/{filepath:*}.
Expand All @@ -193,7 +214,7 @@ func (r *Router) ServeFiles(path string, rootPath string) {
//
// router.ServeFilesCustom("/src/{filepath:*}", *customFS)
func (r *Router) ServeFilesCustom(path string, fs *fasthttp.FS) {
suffix := "/{filepath:*}"
const suffix = "/{filepath:*}"

if !strings.HasSuffix(path, suffix) {
panic("path must end with " + suffix + " in path '" + path + "'")
Expand Down
41 changes: 40 additions & 1 deletion router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package router
import (
"bufio"
"bytes"
"embed"
"fmt"
"io/ioutil"
"math/rand"
Expand Down Expand Up @@ -37,6 +38,9 @@ var httpMethods = []string{
"CUSTOM",
}

//go:embed LICENSE
var fsTestFilesystem embed.FS

func randomHTTPMethod() string {
method := httpMethods[rand.Intn(len(httpMethods)-1)]

Expand Down Expand Up @@ -934,8 +938,11 @@ func TestRouterServeFiles(t *testing.T) {
if recv == nil {
t.Fatal("registering path not ending with '{filepath:*}' did not panic")
}

body := []byte("fake ico")
ioutil.WriteFile(os.TempDir()+"/favicon.ico", body, 0644)
if err := os.WriteFile(os.TempDir()+"/favicon.ico", body, 0644); err != nil {
t.Fatal(err)
}

r.ServeFiles("/{filepath:*}", os.TempDir())

Expand All @@ -954,6 +961,38 @@ func TestRouterServeFiles(t *testing.T) {
})
}

func TestRouterServeFS(t *testing.T) {
r := New()

recv := catchPanic(func() {
r.ServeFS("/noFilepath", fsTestFilesystem)
})
if recv == nil {
t.Fatal("registering path not ending with '{filepath:*}' did not panic")
}

body, err := os.ReadFile("LICENSE")
if err != nil {
t.Fatal(err)
}

r.ServeFS("/{filepath:*}", fsTestFilesystem)

assertWithTestServer(t, "GET /LICENSE HTTP/1.1\r\n\r\n", r.Handler, func(rw *readWriter) {
br := bufio.NewReader(&rw.w)
var resp fasthttp.Response
if err := resp.Read(br); err != nil {
t.Fatalf("Unexpected error when reading response: %s", err)
}
if resp.Header.StatusCode() != 200 {
t.Fatalf("Unexpected status code %d. Expected %d", resp.Header.StatusCode(), 200)
}
if !bytes.Equal(resp.Body(), body) {
t.Fatalf("Unexpected body %q. Expected %q", resp.Body(), string(body))
}
})
}

func TestRouterServeFilesCustom(t *testing.T) {
r := New()

Expand Down

0 comments on commit 407bc93

Please sign in to comment.