Skip to content

Commit 407bc93

Browse files
committed
feat: add support to serve fs.FS
1 parent d275c03 commit 407bc93

File tree

4 files changed

+89
-7
lines changed

4 files changed

+89
-7
lines changed

group.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package router
22

3-
import "github.com/valyala/fasthttp"
3+
import (
4+
"io/fs"
5+
6+
"github.com/valyala/fasthttp"
7+
)
48

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

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

108+
// ServeFS serves files from the given file system.
109+
// The path must end with "/{filepath:*}", files are then served from the local
110+
// path /defined/root/dir/{filepath:*}.
111+
// For example if root is "/etc" and {filepath:*} is "passwd", the local file
112+
// "/etc/passwd" would be served.
113+
// Internally a fasthttp.FSHandler is used, therefore http.NotFound is used instead
114+
// Use:
115+
//
116+
// router.ServeFS("/src/{filepath:*}", myFilesystem)
117+
func (g *Group) ServeFS(path string, filesystem fs.FS) {
118+
validatePath(path)
119+
120+
g.router.ServeFS(g.prefix+path, filesystem)
121+
}
122+
104123
// ServeFilesCustom serves files from the given file system settings.
105124
// The path must end with "/{filepath:*}", files are then served from the local
106125
// path /defined/root/dir/{filepath:*}.

group_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ func TestGroup(t *testing.T) {
9494
ctx.SetStatusCode(fasthttp.StatusOK)
9595
})
9696
r6.ServeFiles("/static/{filepath:*}", "./")
97+
r6.ServeFS("/static/fs/{filepath:*}", fsTestFilesystem)
9798
r6.ServeFilesCustom("/custom/static/{filepath:*}", &fasthttp.FS{Root: "./"})
9899

99100
uris := []string{
@@ -110,6 +111,8 @@ func TestGroup(t *testing.T) {
110111
"POST /moo/foo/foo/bar HTTP/1.1\r\n\r\n",
111112
// testing multiple sub-router group - r6 (grouped from r5) to serve files
112113
"GET /moo/foo/foo/static/router.go HTTP/1.1\r\n\r\n",
114+
// testing multiple sub-router group - r6 (grouped from r5) to serve fs
115+
"GET /moo/foo/foo/static/fs/LICENSE HTTP/1.1\r\n\r\n",
113116
// testing multiple sub-router group - r6 (grouped from r5) to serve files with custom settings
114117
"GET /moo/foo/foo/custom/static/router.go HTTP/1.1\r\n\r\n",
115118
}

router.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package router
22

33
import (
44
"fmt"
5+
"io/fs"
56
"strings"
67

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

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

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

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

185+
// ServeFS serves files from the given file system.
186+
// The path must end with "/{filepath:*}", files are then served from the local
187+
// path /defined/root/dir/{filepath:*}.
188+
// For example if root is "/etc" and {filepath:*} is "passwd", the local file
189+
// "/etc/passwd" would be served.
190+
// Internally a fasthttp.FSHandler is used, therefore fasthttp.NotFound is used instead
191+
// Use:
192+
//
193+
// router.ServeFS("/src/{filepath:*}", myFilesystem)
194+
func (r *Router) ServeFS(path string, filesystem fs.FS) {
195+
r.ServeFilesCustom(path, &fasthttp.FS{
196+
FS: filesystem,
197+
Root: "",
198+
AllowEmptyRoot: true,
199+
GenerateIndexPages: true,
200+
AcceptByteRange: true,
201+
Compress: true,
202+
CompressBrotli: true,
203+
})
204+
}
205+
185206
// ServeFilesCustom serves files from the given file system settings.
186207
// The path must end with "/{filepath:*}", files are then served from the local
187208
// path /defined/root/dir/{filepath:*}.
@@ -193,7 +214,7 @@ func (r *Router) ServeFiles(path string, rootPath string) {
193214
//
194215
// router.ServeFilesCustom("/src/{filepath:*}", *customFS)
195216
func (r *Router) ServeFilesCustom(path string, fs *fasthttp.FS) {
196-
suffix := "/{filepath:*}"
217+
const suffix = "/{filepath:*}"
197218

198219
if !strings.HasSuffix(path, suffix) {
199220
panic("path must end with " + suffix + " in path '" + path + "'")

router_test.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package router
33
import (
44
"bufio"
55
"bytes"
6+
"embed"
67
"fmt"
78
"io/ioutil"
89
"math/rand"
@@ -37,6 +38,9 @@ var httpMethods = []string{
3738
"CUSTOM",
3839
}
3940

41+
//go:embed LICENSE
42+
var fsTestFilesystem embed.FS
43+
4044
func randomHTTPMethod() string {
4145
method := httpMethods[rand.Intn(len(httpMethods)-1)]
4246

@@ -934,8 +938,11 @@ func TestRouterServeFiles(t *testing.T) {
934938
if recv == nil {
935939
t.Fatal("registering path not ending with '{filepath:*}' did not panic")
936940
}
941+
937942
body := []byte("fake ico")
938-
ioutil.WriteFile(os.TempDir()+"/favicon.ico", body, 0644)
943+
if err := os.WriteFile(os.TempDir()+"/favicon.ico", body, 0644); err != nil {
944+
t.Fatal(err)
945+
}
939946

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

@@ -954,6 +961,38 @@ func TestRouterServeFiles(t *testing.T) {
954961
})
955962
}
956963

964+
func TestRouterServeFS(t *testing.T) {
965+
r := New()
966+
967+
recv := catchPanic(func() {
968+
r.ServeFS("/noFilepath", fsTestFilesystem)
969+
})
970+
if recv == nil {
971+
t.Fatal("registering path not ending with '{filepath:*}' did not panic")
972+
}
973+
974+
body, err := os.ReadFile("LICENSE")
975+
if err != nil {
976+
t.Fatal(err)
977+
}
978+
979+
r.ServeFS("/{filepath:*}", fsTestFilesystem)
980+
981+
assertWithTestServer(t, "GET /LICENSE HTTP/1.1\r\n\r\n", r.Handler, func(rw *readWriter) {
982+
br := bufio.NewReader(&rw.w)
983+
var resp fasthttp.Response
984+
if err := resp.Read(br); err != nil {
985+
t.Fatalf("Unexpected error when reading response: %s", err)
986+
}
987+
if resp.Header.StatusCode() != 200 {
988+
t.Fatalf("Unexpected status code %d. Expected %d", resp.Header.StatusCode(), 200)
989+
}
990+
if !bytes.Equal(resp.Body(), body) {
991+
t.Fatalf("Unexpected body %q. Expected %q", resp.Body(), string(body))
992+
}
993+
})
994+
}
995+
957996
func TestRouterServeFilesCustom(t *testing.T) {
958997
r := New()
959998

0 commit comments

Comments
 (0)