iris/webfs.go

193 lines
5.1 KiB
Go
Raw Normal View History

package iris
import (
"net/http"
"os"
"strings"
"sync"
)
// StaticHandlerBuilder is the web file system's Handler builder
// use that or the iris.StaticHandler/StaticWeb methods
type StaticHandlerBuilder interface {
Path(requestRoutePath string) StaticHandlerBuilder
Gzip(enable bool) StaticHandlerBuilder
Listing(listDirectoriesOnOff bool) StaticHandlerBuilder
StripPath(yesNo bool) StaticHandlerBuilder
Except(r ...Route) StaticHandlerBuilder
Build() HandlerFunc
}
type webfs struct {
// user options, only directory is required.
directory http.Dir
requestPath string
stripPath bool
gzip bool
listDirectories bool
// these are init on the Build() call
filesystem http.FileSystem
once sync.Once
exceptions []Route
handler HandlerFunc
}
func toWebPath(systemPath string) string {
// winos slash to slash
webpath := strings.Replace(systemPath, "\\", slash, -1)
// double slashes to single
webpath = strings.Replace(webpath, slash+slash, slash, -1)
// remove all dots
webpath = strings.Replace(webpath, ".", "", -1)
return webpath
}
// NewStaticHandlerBuilder returns a new Handler which serves static files
// supports gzip, no listing and much more
// Note that, this static builder returns a Handler
// it doesn't cares about the rest of your iris configuration.
//
// Use the iris.StaticHandler/StaticWeb in order to serve static files on more automatic way
// this builder is used by people who have more complicated application
// structure and want a fluent api to work on.
func NewStaticHandlerBuilder(dir string) StaticHandlerBuilder {
return &webfs{
directory: http.Dir(dir),
// default route path is the same as the directory
requestPath: toWebPath(dir),
// enable strip path by-default
stripPath: true,
// gzip is disabled by default
gzip: false,
// list directories disabled by default
listDirectories: false,
}
}
// Path sets the request path.
// Defaults to same as system path
func (w *webfs) Path(requestRoutePath string) StaticHandlerBuilder {
w.requestPath = toWebPath(requestRoutePath)
return w
}
// Gzip if enable is true then gzip compression is enabled for this static directory
// Defaults to false
func (w *webfs) Gzip(enable bool) StaticHandlerBuilder {
w.gzip = enable
return w
}
// Listing turn on/off the 'show files and directories'.
// Defaults to false
func (w *webfs) Listing(listDirectoriesOnOff bool) StaticHandlerBuilder {
w.listDirectories = listDirectoriesOnOff
return w
}
func (w *webfs) StripPath(yesNo bool) StaticHandlerBuilder {
w.stripPath = yesNo
return w
}
// Except add a route exception,
// gives priority to that Route over the static handler.
func (w *webfs) Except(r ...Route) StaticHandlerBuilder {
w.exceptions = append(w.exceptions, r...)
return w
}
type (
noListFile struct {
http.File
}
)
// Overrides the Readdir of the http.File in order to disable showing a list of the dirs/files
func (n noListFile) Readdir(count int) ([]os.FileInfo, error) {
return nil, nil
}
// Implements the http.Filesystem
// Do not call it.
func (w *webfs) Open(name string) (http.File, error) {
info, err := w.filesystem.Open(name)
if err != nil {
return nil, err
}
if !w.listDirectories {
return noListFile{info}, nil
}
return info, nil
}
// Build the handler (once) and returns it
func (w *webfs) Build() HandlerFunc {
// we have to ensure that Build is called ONLY one time,
// one instance per one static directory.
w.once.Do(func() {
w.filesystem = http.Dir(w.directory)
// set the filesystem to itself in order to be recognised of listing property (can be change at runtime too)
fileserver := http.FileServer(w)
fsHandler := fileserver
if w.stripPath {
prefix := w.requestPath
fsHandler = http.StripPrefix(prefix, fileserver)
}
h := func(ctx *Context) {
writer := ctx.ResponseWriter
if w.gzip && ctx.clientAllowsGzip() {
ctx.ResponseWriter.Header().Add(varyHeader, acceptEncodingHeader)
ctx.SetHeader(contentEncodingHeader, "gzip")
gzipResWriter := acquireGzipResponseWriter(ctx.ResponseWriter) //.ResponseWriter)
writer = gzipResWriter
defer releaseGzipResponseWriter(gzipResWriter)
}
fsHandler.ServeHTTP(writer, ctx.Request)
}
if len(w.exceptions) > 0 {
middleware := make(Middleware, len(w.exceptions)+1)
for i := range w.exceptions {
middleware[i] = Prioritize(w.exceptions[i])
}
middleware[len(w.exceptions)] = HandlerFunc(h)
w.handler = func(ctx *Context) {
ctx.Middleware = append(middleware, ctx.Middleware...)
ctx.Do()
}
} else {
w.handler = h
}
})
return w.handler
}
// StripPrefix returns a handler that serves HTTP requests
// by removing the given prefix from the request URL's Path
// and invoking the handler h. StripPrefix handles a
// request for a path that doesn't begin with prefix by
// replying with an HTTP 404 not found error.
func StripPrefix(prefix string, h HandlerFunc) HandlerFunc {
if prefix == "" {
return h
}
return func(ctx *Context) {
if p := strings.TrimPrefix(ctx.Request.URL.Path, prefix); len(p) < len(ctx.Request.URL.Path) {
ctx.Request.URL.Path = p
h(ctx)
} else {
ctx.NotFound()
}
}
}