mirror of
https://github.com/labstack/echo.git
synced 2025-02-03 13:11:39 +02:00
Improve filesystem support.
This commit is contained in:
parent
af2a49dbbc
commit
b830c4ef95
@ -15,6 +15,11 @@ func (c *context) File(file string) error {
|
|||||||
return fsFile(c, file, c.echo.Filesystem)
|
return fsFile(c, file, c.echo.Filesystem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FileFS serves file from given file system.
|
||||||
|
//
|
||||||
|
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
|
||||||
|
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
|
||||||
|
// including `assets/images` as their prefix.
|
||||||
func (c *context) FileFS(file string, filesystem fs.FS) error {
|
func (c *context) FileFS(file string, filesystem fs.FS) error {
|
||||||
return fsFile(c, file, filesystem)
|
return fsFile(c, file, filesystem)
|
||||||
}
|
}
|
||||||
@ -28,7 +33,7 @@ func fsFile(c Context, file string, filesystem fs.FS) error {
|
|||||||
|
|
||||||
fi, _ := f.Stat()
|
fi, _ := f.Stat()
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
file = filepath.ToSlash(filepath.Join(file, indexPage))
|
file = filepath.Join(file, indexPage)
|
||||||
f, err = filesystem.Open(file)
|
f, err = filesystem.Open(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrNotFound
|
return ErrNotFound
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
package echo
|
package echo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
testify "github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -62,18 +62,18 @@ func TestContext_File(t *testing.T) {
|
|||||||
|
|
||||||
err := handler(c)
|
err := handler(c)
|
||||||
|
|
||||||
testify.Equal(t, tc.expectStatus, rec.Code)
|
assert.Equal(t, tc.expectStatus, rec.Code)
|
||||||
if tc.expectError != "" {
|
if tc.expectError != "" {
|
||||||
testify.EqualError(t, err, tc.expectError)
|
assert.EqualError(t, err, tc.expectError)
|
||||||
} else {
|
} else {
|
||||||
testify.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
body := rec.Body.Bytes()
|
body := rec.Body.Bytes()
|
||||||
if len(body) > len(tc.expectStartsWith) {
|
if len(body) > len(tc.expectStartsWith) {
|
||||||
body = body[:len(tc.expectStartsWith)]
|
body = body[:len(tc.expectStartsWith)]
|
||||||
}
|
}
|
||||||
testify.Equal(t, tc.expectStartsWith, body)
|
assert.Equal(t, tc.expectStartsWith, body)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,18 +118,18 @@ func TestContext_FileFS(t *testing.T) {
|
|||||||
|
|
||||||
err := handler(c)
|
err := handler(c)
|
||||||
|
|
||||||
testify.Equal(t, tc.expectStatus, rec.Code)
|
assert.Equal(t, tc.expectStatus, rec.Code)
|
||||||
if tc.expectError != "" {
|
if tc.expectError != "" {
|
||||||
testify.EqualError(t, err, tc.expectError)
|
assert.EqualError(t, err, tc.expectError)
|
||||||
} else {
|
} else {
|
||||||
testify.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
body := rec.Body.Bytes()
|
body := rec.Body.Bytes()
|
||||||
if len(body) > len(tc.expectStartsWith) {
|
if len(body) > len(tc.expectStartsWith) {
|
||||||
body = body[:len(tc.expectStartsWith)]
|
body = body[:len(tc.expectStartsWith)]
|
||||||
}
|
}
|
||||||
testify.Equal(t, tc.expectStartsWith, body)
|
assert.Equal(t, tc.expectStartsWith, body)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,10 @@ import (
|
|||||||
type filesystem struct {
|
type filesystem struct {
|
||||||
// Filesystem is file system used by Static and File handlers to access files.
|
// Filesystem is file system used by Static and File handlers to access files.
|
||||||
// Defaults to os.DirFS(".")
|
// Defaults to os.DirFS(".")
|
||||||
|
//
|
||||||
|
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
|
||||||
|
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
|
||||||
|
// including `assets/images` as their prefix.
|
||||||
Filesystem fs.FS
|
Filesystem fs.FS
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,12 +30,8 @@ func createFilesystem() filesystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Static registers a new route with path prefix to serve static files from the provided root directory.
|
// Static registers a new route with path prefix to serve static files from the provided root directory.
|
||||||
func (e *Echo) Static(pathPrefix, root string) *Route {
|
func (e *Echo) Static(pathPrefix, fsRoot string) *Route {
|
||||||
subFs, err := subFS(e.Filesystem, root)
|
subFs := MustSubFS(e.Filesystem, fsRoot)
|
||||||
if err != nil {
|
|
||||||
// happens when `root` contains invalid path according to `fs.ValidPath` rules and we are unable to create FS
|
|
||||||
panic(fmt.Errorf("invalid root given to echo.Static, err %w", err))
|
|
||||||
}
|
|
||||||
return e.Add(
|
return e.Add(
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
pathPrefix+"*",
|
pathPrefix+"*",
|
||||||
@ -40,11 +40,15 @@ func (e *Echo) Static(pathPrefix, root string) *Route {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StaticFS registers a new route with path prefix to serve static files from the provided file system.
|
// StaticFS registers a new route with path prefix to serve static files from the provided file system.
|
||||||
func (e *Echo) StaticFS(pathPrefix string, fileSystem fs.FS) *Route {
|
//
|
||||||
|
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
|
||||||
|
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
|
||||||
|
// including `assets/images` as their prefix.
|
||||||
|
func (e *Echo) StaticFS(pathPrefix string, filesystem fs.FS) *Route {
|
||||||
return e.Add(
|
return e.Add(
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
pathPrefix+"*",
|
pathPrefix+"*",
|
||||||
StaticDirectoryHandler(fileSystem, false),
|
StaticDirectoryHandler(filesystem, false),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,3 +129,17 @@ func subFS(currentFs fs.FS, root string) (fs.FS, error) {
|
|||||||
}
|
}
|
||||||
return fs.Sub(currentFs, root)
|
return fs.Sub(currentFs, root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MustSubFS creates sub FS from current filesystem or panic on failure.
|
||||||
|
// Panic happens when `fsRoot` contains invalid path according to `fs.ValidPath` rules.
|
||||||
|
//
|
||||||
|
// MustSubFS is helpful when dealing with `embed.FS` because for example `//go:embed assets/images` embeds files with
|
||||||
|
// paths including `assets/images` as their prefix. In that case use `fs := echo.MustSubFS(fs, "rootDirectory") to
|
||||||
|
// create sub fs which uses necessary prefix for directory path.
|
||||||
|
func MustSubFS(currentFs fs.FS, fsRoot string) fs.FS {
|
||||||
|
subFs, err := subFS(currentFs, fsRoot)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("can not create sub FS, invalid root given, err: %w", err))
|
||||||
|
}
|
||||||
|
return subFs
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ func TestEcho_StaticFS(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
givenPrefix string
|
givenPrefix string
|
||||||
givenFs fs.FS
|
givenFs fs.FS
|
||||||
|
givenFsRoot string
|
||||||
whenURL string
|
whenURL string
|
||||||
expectStatus int
|
expectStatus int
|
||||||
expectHeaderLocation string
|
expectHeaderLocation string
|
||||||
@ -31,6 +32,14 @@ func TestEcho_StaticFS(t *testing.T) {
|
|||||||
expectStatus: http.StatusOK,
|
expectStatus: http.StatusOK,
|
||||||
expectBodyStartsWith: string([]byte{0x89, 0x50, 0x4e, 0x47}),
|
expectBodyStartsWith: string([]byte{0x89, 0x50, 0x4e, 0x47}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "ok, from sub fs",
|
||||||
|
givenPrefix: "/images",
|
||||||
|
givenFs: MustSubFS(os.DirFS("./_fixture/"), "images"),
|
||||||
|
whenURL: "/images/walle.png",
|
||||||
|
expectStatus: http.StatusOK,
|
||||||
|
expectBodyStartsWith: string([]byte{0x89, 0x50, 0x4e, 0x47}),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "No file",
|
name: "No file",
|
||||||
givenPrefix: "/images",
|
givenPrefix: "/images",
|
||||||
@ -135,7 +144,12 @@ func TestEcho_StaticFS(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
e := New()
|
e := New()
|
||||||
e.StaticFS(tc.givenPrefix, tc.givenFs)
|
|
||||||
|
tmpFs := tc.givenFs
|
||||||
|
if tc.givenFsRoot != "" {
|
||||||
|
tmpFs = MustSubFS(tmpFs, tc.givenFsRoot)
|
||||||
|
}
|
||||||
|
e.StaticFS(tc.givenPrefix, tmpFs)
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
@ -229,12 +243,12 @@ func TestEcho_StaticPanic(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "panics for ../",
|
name: "panics for ../",
|
||||||
givenRoot: "../assets",
|
givenRoot: "../assets",
|
||||||
expectError: "invalid root given to echo.Static, err sub ../assets: invalid name",
|
expectError: "can not create sub FS, invalid root given, err: sub ../assets: invalid name",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "panics for /",
|
name: "panics for /",
|
||||||
givenRoot: "/assets",
|
givenRoot: "/assets",
|
||||||
expectError: "invalid root given to echo.Static, err sub /assets: invalid name",
|
expectError: "can not create sub FS, invalid root given, err: sub /assets: invalid name",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,27 +4,26 @@
|
|||||||
package echo
|
package echo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Static implements `Echo#Static()` for sub-routes within the Group.
|
// Static implements `Echo#Static()` for sub-routes within the Group.
|
||||||
func (g *Group) Static(pathPrefix, root string) {
|
func (g *Group) Static(pathPrefix, fsRoot string) {
|
||||||
subFs, err := subFS(g.echo.Filesystem, root)
|
subFs := MustSubFS(g.echo.Filesystem, fsRoot)
|
||||||
if err != nil {
|
|
||||||
// happens when `root` contains invalid path according to `fs.ValidPath` rules and we are unable to create FS
|
|
||||||
panic(fmt.Errorf("invalid root given to group.Static, err %w", err))
|
|
||||||
}
|
|
||||||
g.StaticFS(pathPrefix, subFs)
|
g.StaticFS(pathPrefix, subFs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StaticFS implements `Echo#StaticFS()` for sub-routes within the Group.
|
// StaticFS implements `Echo#StaticFS()` for sub-routes within the Group.
|
||||||
func (g *Group) StaticFS(pathPrefix string, fileSystem fs.FS) {
|
//
|
||||||
|
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
|
||||||
|
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
|
||||||
|
// including `assets/images` as their prefix.
|
||||||
|
func (g *Group) StaticFS(pathPrefix string, filesystem fs.FS) {
|
||||||
g.Add(
|
g.Add(
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
pathPrefix+"*",
|
pathPrefix+"*",
|
||||||
StaticDirectoryHandler(fileSystem, false),
|
StaticDirectoryHandler(filesystem, false),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,12 +82,12 @@ func TestGroup_StaticPanic(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "panics for ../",
|
name: "panics for ../",
|
||||||
givenRoot: "../images",
|
givenRoot: "../images",
|
||||||
expectError: "invalid root given to group.Static, err sub ../images: invalid name",
|
expectError: "can not create sub FS, invalid root given, err: sub ../images: invalid name",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "panics for /",
|
name: "panics for /",
|
||||||
givenRoot: "/images",
|
givenRoot: "/images",
|
||||||
expectError: "invalid root given to group.Static, err sub /images: invalid name",
|
expectError: "can not create sub FS, invalid root given, err: sub /images: invalid name",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user