Skip to content

Commit acf41a9

Browse files
committed
feat: enhance static file serving capabilities
- Add documentation for serving local files and embedded folders in README.md - Add new example code for serving embedded folders in Go - Create new `embed_folder.go` file with functions to serve embedded folders - Create new `embed_folder_test.go` file with tests for serving embedded folders - Add HTML template files for embedded server example - Rename `static.go` to `local_file.go` and remove unused code - Create new `local_file_test.go` file with tests for serving local files - Create new `serve.go` file with middleware handler for serving static files - Rename `static_test.go` to `serve_test.go` and refactor test functions - Remove redundant test case `TestListIndex` from `serve_test.go` Signed-off-by: Bo-Yi Wu <[email protected]>
2 parents 80d4aac + 45b3e72 commit acf41a9

File tree

11 files changed

+215
-65
lines changed

11 files changed

+215
-65
lines changed

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import "github.com/gin-contrib/static"
2727

2828
See the [example](_example)
2929

30+
#### Serve local file
31+
3032
```go
3133
package main
3234

@@ -48,6 +50,39 @@ func main() {
4850
c.String(200, "test")
4951
})
5052
// Listen and Server in 0.0.0.0:8080
53+
r.Run(":8080")
54+
}
55+
```
56+
57+
#### Serve embed folder
58+
59+
```go
60+
package main
61+
62+
import (
63+
"embed"
64+
"fmt"
65+
"net/http"
66+
67+
"github.com/gin-contrib/static"
68+
"github.com/gin-gonic/gin"
69+
)
70+
71+
//go:embed data
72+
var server embed.FS
73+
74+
func main() {
75+
r := gin.Default()
76+
r.Use(static.Serve("/", static.EmbedFolder(server, "data/server")))
77+
r.GET("/ping", func(c *gin.Context) {
78+
c.String(200, "test")
79+
})
80+
r.NoRoute(func(c *gin.Context) {
81+
fmt.Printf("%s doesn't exists, redirect on /\n", c.Request.URL.Path)
82+
c.Redirect(http.StatusMovedPermanently, "/")
83+
})
84+
// Listen and Server in 0.0.0.0:8080
85+
r.Run(":8080")
5186
if err := r.Run(":8080"); err != nil {
5287
log.Fatal(err)
5388
}

embed_folder.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package static
2+
3+
import (
4+
"embed"
5+
"io/fs"
6+
"log"
7+
"net/http"
8+
)
9+
10+
type embedFileSystem struct {
11+
http.FileSystem
12+
}
13+
14+
func (e embedFileSystem) Exists(prefix string, path string) bool {
15+
_, err := e.Open(path)
16+
return err == nil
17+
}
18+
19+
func EmbedFolder(fsEmbed embed.FS, targetPath string) ServeFileSystem {
20+
fsys, err := fs.Sub(fsEmbed, targetPath)
21+
if err != nil {
22+
log.Fatalf("static.EmbedFolder - Invalid targetPath value - %s", err)
23+
}
24+
return embedFileSystem{
25+
FileSystem: http.FS(fsys),
26+
}
27+
}

embed_folder_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package static
2+
3+
import (
4+
"embed"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/gin-gonic/gin"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
//go:embed test/data/server
13+
var server embed.FS
14+
15+
var embedTests = []struct {
16+
targetURL string // input
17+
httpCode int // expected http code
18+
httpBody string // expected http body
19+
name string // test name
20+
}{
21+
{"/404.html", 301, "<a href=\"/\">Moved Permanently</a>.\n\n", "Unknown file"},
22+
{"/", 200, "<h1>Hello Embed</h1>", "Root"},
23+
{"/index.html", 301, "", "Root by file name automatic redirect"},
24+
{"/static.html", 200, "<h1>Hello Gin Static</h1>", "Other file"},
25+
}
26+
27+
func TestEmbedFolder(t *testing.T) {
28+
router := gin.New()
29+
router.Use(Serve("/", EmbedFolder(server, "test/data/server")))
30+
router.NoRoute(func(c *gin.Context) {
31+
fmt.Printf("%s doesn't exists, redirect on /\n", c.Request.URL.Path)
32+
c.Redirect(301, "/")
33+
})
34+
35+
for _, tt := range embedTests {
36+
w := PerformRequest(router, "GET", tt.targetURL)
37+
assert.Equal(t, tt.httpCode, w.Code, tt.name)
38+
assert.Equal(t, tt.httpBody, w.Body.String(), tt.name)
39+
}
40+
}

example/embed/data/server/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<h1>Hello Embed</h1>

example/embed/example.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package main
2+
3+
import (
4+
"embed"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/gin-contrib/static"
9+
"github.com/gin-gonic/gin"
10+
)
11+
12+
//go:embed data
13+
var server embed.FS
14+
15+
func main() {
16+
r := gin.Default()
17+
r.Use(static.Serve("/", static.EmbedFolder(server, "data/server")))
18+
r.GET("/ping", func(c *gin.Context) {
19+
c.String(200, "test")
20+
})
21+
r.NoRoute(func(c *gin.Context) {
22+
fmt.Printf("%s doesn't exists, redirect on /\n", c.Request.URL.Path)
23+
c.Redirect(http.StatusMovedPermanently, "/")
24+
})
25+
// Listen and Server in 0.0.0.0:8080
26+
r.Run(":8080")
27+
}

static.go renamed to local_file.go

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ import (
1111

1212
const INDEX = "index.html"
1313

14-
type ServeFileSystem interface {
15-
http.FileSystem
16-
Exists(prefix string, path string) bool
17-
}
18-
1914
type localFileSystem struct {
2015
http.FileSystem
2116
root string
@@ -50,21 +45,3 @@ func (l *localFileSystem) Exists(prefix string, filepath string) bool {
5045
}
5146
return false
5247
}
53-
54-
func ServeRoot(urlPrefix, root string) gin.HandlerFunc {
55-
return Serve(urlPrefix, LocalFile(root, false))
56-
}
57-
58-
// Static returns a middleware handler that serves static files in the given directory.
59-
func Serve(urlPrefix string, fs ServeFileSystem) gin.HandlerFunc {
60-
fileserver := http.FileServer(fs)
61-
if urlPrefix != "" {
62-
fileserver = http.StripPrefix(urlPrefix, fileserver)
63-
}
64-
return func(c *gin.Context) {
65-
if fs.Exists(urlPrefix, c.Request.URL.Path) {
66-
fileserver.ServeHTTP(c.Writer, c.Request)
67-
c.Abort()
68-
}
69-
}
70-
}

local_file_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package static
2+
3+
import (
4+
"io/ioutil"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/gin-gonic/gin"
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestLocalFile(t *testing.T) {
14+
// SETUP file
15+
testRoot, _ := os.Getwd()
16+
f, err := ioutil.TempFile(testRoot, "")
17+
if err != nil {
18+
t.Error(err)
19+
}
20+
defer os.Remove(f.Name())
21+
f.WriteString("Gin Web Framework")
22+
f.Close()
23+
24+
dir, filename := filepath.Split(f.Name())
25+
router := gin.New()
26+
router.Use(Serve("/", LocalFile(dir, true)))
27+
28+
w := PerformRequest(router, "GET", "/"+filename)
29+
assert.Equal(t, w.Code, 200)
30+
assert.Equal(t, w.Body.String(), "Gin Web Framework")
31+
32+
w = PerformRequest(router, "GET", "/")
33+
assert.Contains(t, w.Body.String(), `<a href="`+filename)
34+
}

serve.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package static
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/gin-gonic/gin"
7+
)
8+
9+
type ServeFileSystem interface {
10+
http.FileSystem
11+
Exists(prefix string, path string) bool
12+
}
13+
14+
func ServeRoot(urlPrefix, root string) gin.HandlerFunc {
15+
return Serve(urlPrefix, LocalFile(root, false))
16+
}
17+
18+
// Serve returns a middleware handler that serves static files in the given directory.
19+
func Serve(urlPrefix string, fs ServeFileSystem) gin.HandlerFunc {
20+
fileserver := http.FileServer(fs)
21+
if urlPrefix != "" {
22+
fileserver = http.StripPrefix(urlPrefix, fileserver)
23+
}
24+
return func(c *gin.Context) {
25+
if fs.Exists(urlPrefix, c.Request.URL.Path) {
26+
fileserver.ServeHTTP(c.Writer, c.Request)
27+
c.Abort()
28+
}
29+
}
30+
}

static_test.go renamed to serve_test.go

Lines changed: 19 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
)
1616

1717
//nolint:unparam
18-
func performRequest(r http.Handler, method, path string) *httptest.ResponseRecorder {
18+
func PerformRequest(r http.Handler, method, path string) *httptest.ResponseRecorder {
1919
req, _ := http.NewRequestWithContext(context.Background(), method, path, nil)
2020
w := httptest.NewRecorder()
2121
r.ServeHTTP(w, req)
@@ -46,19 +46,19 @@ func TestEmptyDirectory(t *testing.T) {
4646
router.GET("/"+filename, func(c *gin.Context) {
4747
c.String(http.StatusOK, "this is not printed")
4848
})
49-
w := performRequest(router, "GET", "/")
50-
assert.Equal(t, w.Code, http.StatusOK)
49+
w := PerformRequest(router, "GET", "/")
50+
assert.Equal(t, w.Code, 200)
5151
assert.Equal(t, w.Body.String(), "index")
5252

53-
w = performRequest(router, "GET", "/"+filename)
54-
assert.Equal(t, w.Code, http.StatusOK)
53+
w = PerformRequest(router, "GET", "/"+filename)
54+
assert.Equal(t, w.Code, 200)
5555
assert.Equal(t, w.Body.String(), "Gin Web Framework")
5656

57-
w = performRequest(router, "GET", "/"+filename+"a")
57+
w = PerformRequest(router, "GET", "/"+filename+"a")
5858
assert.Equal(t, w.Code, 404)
5959

60-
w = performRequest(router, "GET", "/a")
61-
assert.Equal(t, w.Code, http.StatusOK)
60+
w = PerformRequest(router, "GET", "/a")
61+
assert.Equal(t, w.Code, 200)
6262
assert.Equal(t, w.Body.String(), "a")
6363

6464
router2 := gin.New()
@@ -67,24 +67,24 @@ func TestEmptyDirectory(t *testing.T) {
6767
c.String(http.StatusOK, "this is printed")
6868
})
6969

70-
w = performRequest(router2, "GET", "/")
70+
w = PerformRequest(router2, "GET", "/")
7171
assert.Equal(t, w.Code, 404)
7272

73-
w = performRequest(router2, "GET", "/static")
73+
w = PerformRequest(router2, "GET", "/static")
7474
assert.Equal(t, w.Code, 404)
7575
router2.GET("/static", func(c *gin.Context) {
7676
c.String(http.StatusOK, "index")
7777
})
7878

79-
w = performRequest(router2, "GET", "/static")
80-
assert.Equal(t, w.Code, http.StatusOK)
79+
w = PerformRequest(router2, "GET", "/static")
80+
assert.Equal(t, w.Code, 200)
8181

82-
w = performRequest(router2, "GET", "/"+filename)
83-
assert.Equal(t, w.Code, http.StatusOK)
82+
w = PerformRequest(router2, "GET", "/"+filename)
83+
assert.Equal(t, w.Code, 200)
8484
assert.Equal(t, w.Body.String(), "this is printed")
8585

86-
w = performRequest(router2, "GET", "/static/"+filename)
87-
assert.Equal(t, w.Code, http.StatusOK)
86+
w = PerformRequest(router2, "GET", "/static/"+filename)
87+
assert.Equal(t, w.Code, 200)
8888
assert.Equal(t, w.Body.String(), "Gin Web Framework")
8989
}
9090

@@ -104,33 +104,10 @@ func TestIndex(t *testing.T) {
104104
router := gin.New()
105105
router.Use(ServeRoot("/", dir))
106106

107-
w := performRequest(router, "GET", "/"+filename)
107+
w := PerformRequest(router, "GET", "/"+filename)
108108
assert.Equal(t, w.Code, 301)
109109

110-
w = performRequest(router, "GET", "/")
111-
assert.Equal(t, w.Code, http.StatusOK)
110+
w = PerformRequest(router, "GET", "/")
111+
assert.Equal(t, w.Code, 200)
112112
assert.Equal(t, w.Body.String(), "index")
113113
}
114-
115-
func TestListIndex(t *testing.T) {
116-
// SETUP file
117-
testRoot, _ := os.Getwd()
118-
f, err := ioutil.TempFile(testRoot, "")
119-
if err != nil {
120-
t.Error(err)
121-
}
122-
defer os.Remove(f.Name())
123-
_, _ = f.WriteString("Gin Web Framework")
124-
f.Close()
125-
126-
dir, filename := filepath.Split(f.Name())
127-
router := gin.New()
128-
router.Use(Serve("/", LocalFile(dir, true)))
129-
130-
w := performRequest(router, "GET", "/"+filename)
131-
assert.Equal(t, w.Code, http.StatusOK)
132-
assert.Equal(t, w.Body.String(), "Gin Web Framework")
133-
134-
w = performRequest(router, "GET", "/")
135-
assert.Contains(t, w.Body.String(), `<a href="`+filename)
136-
}

test/data/server/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<h1>Hello Embed</h1>

test/data/server/static.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<h1>Hello Gin Static</h1>

0 commit comments

Comments
 (0)