Implement loading HTML from http.FileSystem

This commit is contained in:
sunshineplan 2024-09-10 10:11:11 +08:00
parent 28e57f58b1
commit 3df1d433b3
6 changed files with 161 additions and 6 deletions

View File

@ -1341,13 +1341,17 @@ func main() {
### HTML rendering
Using LoadHTMLGlob() or LoadHTMLFiles()
Using LoadHTMLGlob() or LoadHTMLFiles() or LoadHTMLFS()
```go
//go:embed templates/*
var templates embed.FS
func main() {
router := gin.Default()
router.LoadHTMLGlob("templates/*")
//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
//router.LoadHTMLFS(http.FS(templates), "templates/template1.html", "templates/template2.html")
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "Main website",

17
fs.go
View File

@ -5,6 +5,7 @@
package gin
import (
"io/fs"
"net/http"
"os"
)
@ -25,6 +26,22 @@ func (o OnlyFilesFS) Open(name string) (http.File, error) {
return neutralizedReaddirFile{f}, nil
}
// OnlyHTMLFS implements an [fs.FS].
type OnlyHTMLFS struct {
FileSystem http.FileSystem
}
// Open passes `Open` to the upstream implementation and return an [fs.File].
func (o OnlyHTMLFS) Open(name string) (fs.File, error) {
f, err := o.FileSystem.Open(name)
if err != nil {
return nil, err
}
return fs.File(f), nil
}
// neutralizedReaddirFile wraps http.File with a specific implementation of `Readdir`.
type neutralizedReaddirFile struct {
http.File

12
gin.go
View File

@ -285,6 +285,18 @@ func (engine *Engine) LoadHTMLFiles(files ...string) {
engine.SetHTMLTemplate(templ)
}
// LoadHTMLFS loads an http.FileSystem and a slice of patterns
// and associates the result with HTML renderer.
func (engine *Engine) LoadHTMLFS(fs http.FileSystem, patterns ...string) {
if IsDebugging() {
engine.HTMLRender = render.HTMLDebug{FS: OnlyHTMLFS{fs}, Patterns: patterns, FuncMap: engine.FuncMap, Delims: engine.delims}
return
}
templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFS(OnlyHTMLFS{fs}, patterns...))
engine.SetHTMLTemplate(templ)
}
// SetHTMLTemplate associate a template with HTML renderer.
func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
if len(engine.trees) > 0 {

View File

@ -32,6 +32,11 @@ func LoadHTMLFiles(files ...string) {
engine().LoadHTMLFiles(files...)
}
// LoadHTMLFS is a wrapper for Engine.LoadHTMLFS.
func LoadHTMLFS(fs http.FileSystem, patterns ...string) {
engine().LoadHTMLFS(fs, patterns...)
}
// SetHTMLTemplate is a wrapper for Engine.SetHTMLTemplate.
func SetHTMLTemplate(templ *template.Template) {
engine().SetHTMLTemplate(templ)

View File

@ -6,6 +6,7 @@ package gin
import (
"crypto/tls"
"embed"
"fmt"
"html/template"
"io"
@ -325,6 +326,116 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
assert.Equal(t, "Date: 2017/07/01", string(resp))
}
//go:embed testdata/template/*
var htmlFS embed.FS
func TestLoadHTMLFSTestMode(t *testing.T) {
ts := setupHTMLFiles(
t,
TestMode,
false,
func(router *Engine) {
router.LoadHTMLFS(http.FS(htmlFS), "testdata/template/hello.tmpl", "testdata/template/raw.tmpl")
},
)
defer ts.Close()
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
if err != nil {
t.Error(err)
}
resp, _ := io.ReadAll(res.Body)
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
}
func TestLoadHTMLFSDebugMode(t *testing.T) {
ts := setupHTMLFiles(
t,
DebugMode,
false,
func(router *Engine) {
router.LoadHTMLFS(http.FS(htmlFS), "testdata/template/hello.tmpl", "testdata/template/raw.tmpl")
},
)
defer ts.Close()
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
if err != nil {
t.Error(err)
}
resp, _ := io.ReadAll(res.Body)
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
}
func TestLoadHTMLFSReleaseMode(t *testing.T) {
ts := setupHTMLFiles(
t,
ReleaseMode,
false,
func(router *Engine) {
router.LoadHTMLFS(http.FS(htmlFS), "testdata/template/hello.tmpl", "testdata/template/raw.tmpl")
},
)
defer ts.Close()
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
if err != nil {
t.Error(err)
}
resp, _ := io.ReadAll(res.Body)
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
}
func TestLoadHTMLFSUsingTLS(t *testing.T) {
ts := setupHTMLFiles(
t,
TestMode,
true,
func(router *Engine) {
router.LoadHTMLFS(http.FS(htmlFS), "testdata/template/hello.tmpl", "testdata/template/raw.tmpl")
},
)
defer ts.Close()
// Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
client := &http.Client{Transport: tr}
res, err := client.Get(fmt.Sprintf("%s/test", ts.URL))
if err != nil {
t.Error(err)
}
resp, _ := io.ReadAll(res.Body)
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
}
func TestLoadHTMLFSFuncMap(t *testing.T) {
ts := setupHTMLFiles(
t,
TestMode,
false,
func(router *Engine) {
router.LoadHTMLFS(http.FS(htmlFS), "testdata/template/hello.tmpl", "testdata/template/raw.tmpl")
},
)
defer ts.Close()
res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL))
if err != nil {
t.Error(err)
}
resp, _ := io.ReadAll(res.Body)
assert.Equal(t, "Date: 2017/07/01", string(resp))
}
func TestAddRoute(t *testing.T) {
router := New()
router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}})

View File

@ -6,6 +6,7 @@ package render
import (
"html/template"
"io/fs"
"net/http"
)
@ -31,10 +32,12 @@ type HTMLProduction struct {
// HTMLDebug contains template delims and pattern and function with file list.
type HTMLDebug struct {
Files []string
Glob string
Delims Delims
FuncMap template.FuncMap
Files []string
Glob string
FS fs.FS
Patterns []string
Delims Delims
FuncMap template.FuncMap
}
// HTML contains template reference and its name with given interface object.
@ -73,7 +76,10 @@ func (r HTMLDebug) loadTemplate() *template.Template {
if r.Glob != "" {
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob))
}
panic("the HTML debug render was created without files or glob pattern")
if r.FS != nil && len(r.Patterns) > 0 {
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFS(r.FS, r.Patterns...))
}
panic("the HTML debug render was created without files or glob pattern or file system with patterns")
}
// Render (HTML) executes template and writes its result with custom ContentType for response.