Expose FileFS
I have hit a situation where I want to re-use FileFS manually in my own abstractions which wraps fsFile but its not in the interface and is only allowed via echo.FileFS.
if indexFile == "" {
indexFile = DefaultIndexFile
}
echoRouter.GET("/*", func(c echo.Context) error {
if strings.HasPrefix(c.Request().URL.Path, "/api/") {
return echo.ErrNotFound
}
return c.FileFS(indexFile,fsys )
})
return nil
}
I want return c.FileFS(indexFile ,fsys ) but im having to copy your fsFile into my library...
You want to specify your own index file name?
something like
func FsFile(c Context, file string, filesystem fs.FS, indexFile string) error {
f, err := filesystem.Open(file)
if err != nil {
return ErrNotFound
}
defer f.Close()
fi, _ := f.Stat()
if fi.IsDir() {
file = filepath.ToSlash(filepath.Join(file, cmp.Or(indexFile, indexPage))) // ToSlash is necessary for Windows. fs.Open and os.Open are different in that aspect.
f, err = filesystem.Open(file)
if err != nil {
return ErrNotFound
}
defer f.Close()
if fi, err = f.Stat(); err != nil {
return err
}
}
ff, ok := f.(io.ReadSeeker)
if !ok {
return errors.New("file does not implement io.ReadSeeker")
}
http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), ff)
return nil
}
You want to specify your own index file name?
something like
func FsFile(c Context, file string, filesystem fs.FS, indexFile string) error { f, err := filesystem.Open(file) if err != nil { return ErrNotFound } defer f.Close()
fi, _ := f.Stat() if fi.IsDir() { file = filepath.ToSlash(filepath.Join(file, cmp.Or(indexFile, indexPage))) // ToSlash is necessary for Windows. fs.Open and os.Open are different in that aspect. f, err = filesystem.Open(file) if err != nil { return ErrNotFound } defer f.Close() if fi, err = f.Stat(); err != nil { return err } } ff, ok := f.(io.ReadSeeker) if !ok { return errors.New("file does not implement io.ReadSeeker") } http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), ff) return nil }
Just expose FileFS on the interface b/c you already implement in in the context struct. Nothing more is needed
contect.FileFs does not allow you to specify index page (line 36) to use when file points to directory and as I understand this is what you seek
https://github.com/labstack/echo/blob/5ac2f11f21b7884903db6126630e6786c8c22661/context_fs.go#L23-L37
ot allow you to specify index page (line 36)
thats a minor detail for me atm since it will be the same default anyways.
to be honest. I do not get why exposing that function func fsFile(c Context, file string, filesystem fs.FS) error { would be useful if you still need to pass in context - meaning you still need to have context around. If you have context around why not call c.FileFs(filename, fsys). changit it to echo.FileFs(c, filename, fsys) makes no difference.
to be honest. I do not get why exposing that function
func fsFile(c Context, file string, filesystem fs.FS) error {would be useful if you still need to pass in context - meaning you still need to have context around. If you have context around why not callc.FileFs(filename, fsys). changit it toecho.FileFs(c, filename, fsys)makes no difference.
FileFs is not exposed on the interface. its public on the context, but the context struct is private. so I have to copy the entire fsFile b/c i can't use echos without using a full route, and i am using my own route registration as I need more control.
- and
filenameis not static actually, as your current example is?
because if it is you could do
indexFile := "index.html"
indexFs := os.DirFS("main/website/")
indexHandler := echo.StaticFileHandler(indexFile, indexFs)
e.GET("/*", func(c echo.Context) error {
if strings.HasPrefix(c.Request().URL.Path, "/api/") {
return echo.ErrNotFound
}
return indexHandler(c)
})
- and there are more than one filesystem to read from
if it single fs that does not change, you could just do
indexFile := "index.html"
e.Filesystem = os.DirFS("main/website/")
e.GET("/*", func(c echo.Context) error {
if strings.HasPrefix(c.Request().URL.Path, "/api/") {
return echo.ErrNotFound
}
return c.File(indexFile) // <-- uses `e.Filesystem`
})
- and
filenameis not static actually, as your current example is?because if it is you could do
indexFile := "index.html" indexFs := os.DirFS("main/website/") indexHandler := echo.StaticFileHandler(indexFile, indexFs)
e.GET("/*", func(c echo.Context) error { if strings.HasPrefix(c.Request().URL.Path, "/api/") { return echo.ErrNotFound } return indexHandler(c) }) 2. and there are more than one filesystem to read from
if it single fs that does not change, you could just do
indexFile := "index.html" e.Filesystem = os.DirFS("main/website/")
e.GET("/*", func(c echo.Context) error { if strings.HasPrefix(c.Request().URL.Path, "/api/") { return echo.ErrNotFound } return c.File(indexFile) // <-- uses
e.Filesystem})
My files can be any fs.FS, and we can't just clobber e.Filesystem
See https://github.com/LumeWeb/portal-router/blob/d69faa4e67d41ee6b81339e4816c84baee11143b/static_helpers.go#L354
My files can be any fs.FS, and we can't just clobber e.Filesystem
but e.Filesystem type is fs.FS
so you can use other fs.FS implementations like embed.FS
//go:embed static/*
var content embed.FS
func main() {
e := echo.New()
e.Filesystem = content
e.GET("/*", func(c echo.Context) error {
if strings.HasPrefix(c.Request().URL.Path, "/api/") {
return echo.ErrNotFound
}
return c.File("static/index.html") // <-- embed fs needs prefix to work
})
if err := e.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) {
e.Logger.Fatal(err)
}
}
My files can be any fs.FS, and we can't just clobber e.Filesystem
but
e.Filesystemtype isfs.FSso you can use other fs.FS implementations like embed.FS
//go:embed static/* var content embed.FS
func main() { e := echo.New()
e.Filesystem = content
e.GET("/*", func(c echo.Context) error { if strings.HasPrefix(c.Request().URL.Path, "/api/") { return echo.ErrNotFound } return c.File("static/index.html") // <-- embed fs needs prefix to work })
if err := e.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) { e.Logger.Fatal(err) } }
My point is, there can be potentially multiple routes setup so assuming a singleton e.Filesystem for all uses just is a bad assumption. Im a bit bewildered as to why your against exposing an API thats already implemented, into the interface. That's all im requesting.
I am reluctant because that fsFile does more that your requirements are probably needing - it knows how to serve index page when the "filename" points to the folder. But your requirements seem to be to serve single static/hardcoded file/page from any fs.
moreover if we are to expose that function we should add additional parameter to it, to provide way to specify indexPage as this would be something that potentially needs also be configured.
another suggestion. Cast context to interface that has FileFS method that supports fs.FS like that, so you can access the context.FileFS method, which is public.
cfs := c.(interface {
FileFS(file string, filesystem fs.FS) error
})
full example:
//go:embed website/*
var embedFS embed.FS
func main() {
e := echo.New()
e.GET("/*", func(c echo.Context) error {
if strings.HasPrefix(c.Request().URL.Path, "/api/") {
return echo.ErrNotFound
}
cfs := c.(interface {
FileFS(file string, filesystem fs.FS) error
})
return cfs.FileFS("website/index.html", embedFS)
})
if err := e.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) {
e.Logger.Fatal(err)
}
}
I am reluctant because that
fsFiledoes more that your requirements are probably needing - it knows how to serve index page when the "filename" points to the folder. But your requirements seem to be to serve single static/hardcoded file/page from any fs.
Well... what I have deployed now works... so IMO your kind of arguing with me over details I don't consider relevant right now 🙃 .
And... I forgot golang allows defining implicit interfaces for anything 😅 . That will likely solve my need, however it would still be simplist to expose this, and change its API if you feel thats warranted.