2017-01-02 20:20:17 +01:00
|
|
|
package iris
|
|
|
|
|
|
|
|
import (
|
2017-03-01 18:17:32 +01:00
|
|
|
"mime"
|
2017-01-02 20:20:17 +01:00
|
|
|
"net/http"
|
|
|
|
"os"
|
2017-02-16 21:19:44 +01:00
|
|
|
"path/filepath"
|
2017-01-02 20:20:17 +01:00
|
|
|
"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
|
2017-02-14 04:54:11 +01:00
|
|
|
Except(r ...RouteInfo) StaticHandlerBuilder
|
2017-01-02 20:20:17 +01:00
|
|
|
Build() HandlerFunc
|
|
|
|
}
|
|
|
|
|
2017-03-01 18:17:32 +01:00
|
|
|
// +------------------------------------------------------------+
|
|
|
|
// | |
|
|
|
|
// | Static Builder |
|
|
|
|
// | |
|
|
|
|
// +------------------------------------------------------------+
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
type fsHandler struct {
|
2017-01-02 20:20:17 +01:00
|
|
|
// 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
|
2017-02-14 04:54:11 +01:00
|
|
|
exceptions []RouteInfo
|
2017-01-02 20:20:17 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-02-16 21:19:44 +01:00
|
|
|
// abs calls filepath.Abs but ignores the error and
|
2017-02-17 09:45:47 +01:00
|
|
|
// returns the original value if any error occurred.
|
2017-02-16 21:19:44 +01:00
|
|
|
func abs(path string) string {
|
|
|
|
absPath, err := filepath.Abs(path)
|
|
|
|
if err != nil {
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
return absPath
|
|
|
|
}
|
|
|
|
|
2017-01-02 20:20:17 +01:00
|
|
|
// 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 {
|
2017-02-14 04:54:11 +01:00
|
|
|
return &fsHandler{
|
2017-02-16 21:19:44 +01:00
|
|
|
directory: http.Dir(abs(dir)),
|
2017-01-02 20:20:17 +01:00
|
|
|
// 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
|
2017-02-14 04:54:11 +01:00
|
|
|
func (w *fsHandler) Path(requestRoutePath string) StaticHandlerBuilder {
|
2017-01-02 20:20:17 +01:00
|
|
|
w.requestPath = toWebPath(requestRoutePath)
|
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
|
|
|
// Gzip if enable is true then gzip compression is enabled for this static directory
|
|
|
|
// Defaults to false
|
2017-02-14 04:54:11 +01:00
|
|
|
func (w *fsHandler) Gzip(enable bool) StaticHandlerBuilder {
|
2017-01-02 20:20:17 +01:00
|
|
|
w.gzip = enable
|
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
|
|
|
// Listing turn on/off the 'show files and directories'.
|
|
|
|
// Defaults to false
|
2017-02-14 04:54:11 +01:00
|
|
|
func (w *fsHandler) Listing(listDirectoriesOnOff bool) StaticHandlerBuilder {
|
2017-01-02 20:20:17 +01:00
|
|
|
w.listDirectories = listDirectoriesOnOff
|
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
func (w *fsHandler) StripPath(yesNo bool) StaticHandlerBuilder {
|
2017-01-02 20:20:17 +01:00
|
|
|
w.stripPath = yesNo
|
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
2017-01-12 09:24:27 +01:00
|
|
|
// Except add a route exception,
|
|
|
|
// gives priority to that Route over the static handler.
|
2017-02-14 04:54:11 +01:00
|
|
|
func (w *fsHandler) Except(r ...RouteInfo) StaticHandlerBuilder {
|
2017-01-12 09:24:27 +01:00
|
|
|
w.exceptions = append(w.exceptions, r...)
|
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
2017-01-02 20:20:17 +01:00
|
|
|
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.
|
2017-02-14 04:54:11 +01:00
|
|
|
func (w *fsHandler) Open(name string) (http.File, error) {
|
2017-01-02 20:20:17 +01:00
|
|
|
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
|
2017-02-14 04:54:11 +01:00
|
|
|
func (w *fsHandler) Build() HandlerFunc {
|
2017-01-02 20:20:17 +01:00
|
|
|
// we have to ensure that Build is called ONLY one time,
|
|
|
|
// one instance per one static directory.
|
|
|
|
w.once.Do(func() {
|
2017-02-14 04:54:11 +01:00
|
|
|
w.filesystem = w.directory
|
2017-01-02 20:20:17 +01:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2017-01-12 09:24:27 +01:00
|
|
|
h := func(ctx *Context) {
|
2017-01-04 14:16:53 +01:00
|
|
|
writer := ctx.ResponseWriter
|
2017-01-02 20:20:17 +01:00
|
|
|
|
|
|
|
if w.gzip && ctx.clientAllowsGzip() {
|
|
|
|
ctx.ResponseWriter.Header().Add(varyHeader, acceptEncodingHeader)
|
|
|
|
ctx.SetHeader(contentEncodingHeader, "gzip")
|
2017-01-04 14:16:53 +01:00
|
|
|
gzipResWriter := acquireGzipResponseWriter(ctx.ResponseWriter) //.ResponseWriter)
|
2017-01-02 20:20:17 +01:00
|
|
|
writer = gzipResWriter
|
|
|
|
defer releaseGzipResponseWriter(gzipResWriter)
|
|
|
|
}
|
|
|
|
|
|
|
|
fsHandler.ServeHTTP(writer, ctx.Request)
|
|
|
|
}
|
2017-01-12 09:24:27 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2017-01-02 20:20:17 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
return w.handler
|
|
|
|
}
|
2017-01-12 09:24:27 +01:00
|
|
|
|
|
|
|
// 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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-03-01 18:17:32 +01:00
|
|
|
|
|
|
|
// typeByExtension returns the MIME type associated with the file extension ext.
|
|
|
|
// The extension ext should begin with a leading dot, as in ".html".
|
|
|
|
// When ext has no associated type, typeByExtension returns "".
|
|
|
|
//
|
|
|
|
// Extensions are looked up first case-sensitively, then case-insensitively.
|
|
|
|
//
|
|
|
|
// The built-in table is small but on unix it is augmented by the local
|
|
|
|
// system's mime.types file(s) if available under one or more of these
|
|
|
|
// names:
|
|
|
|
//
|
|
|
|
// /etc/mime.types
|
|
|
|
// /etc/apache2/mime.types
|
|
|
|
// /etc/apache/mime.types
|
|
|
|
//
|
|
|
|
// On Windows, MIME types are extracted from the registry.
|
|
|
|
//
|
|
|
|
// Text types have the charset parameter set to "utf-8" by default.
|
|
|
|
func typeByExtension(fullfilename string) (t string) {
|
|
|
|
ext := filepath.Ext(fullfilename)
|
|
|
|
//these should be found by the windows(registry) and unix(apache) but on windows some machines have problems on this part.
|
|
|
|
if t = mime.TypeByExtension(ext); t == "" {
|
|
|
|
// no use of map here because we will have to lock/unlock it, by hand is better, no problem:
|
|
|
|
if ext == ".json" {
|
|
|
|
t = "application/json"
|
|
|
|
} else if ext == ".js" {
|
|
|
|
t = "application/javascript"
|
|
|
|
} else if ext == ".zip" {
|
|
|
|
t = "application/zip"
|
|
|
|
} else if ext == ".3gp" {
|
|
|
|
t = "video/3gpp"
|
|
|
|
} else if ext == ".7z" {
|
|
|
|
t = "application/x-7z-compressed"
|
|
|
|
} else if ext == ".ace" {
|
|
|
|
t = "application/x-ace-compressed"
|
|
|
|
} else if ext == ".aac" {
|
|
|
|
t = "audio/x-aac"
|
|
|
|
} else if ext == ".ico" { // for any case
|
|
|
|
t = "image/x-icon"
|
|
|
|
} else if ext == ".png" {
|
|
|
|
t = "image/png"
|
|
|
|
} else {
|
|
|
|
t = "application/octet-stream"
|
|
|
|
}
|
|
|
|
// mime.TypeByExtension returns as text/plain; | charset=utf-8 the static .js (not always)
|
|
|
|
} else if t == "text/plain" || t == "text/plain; charset=utf-8" {
|
|
|
|
if ext == ".js" {
|
|
|
|
t = "application/javascript"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// directoryExists returns true if a directory(or file) exists, otherwise false
|
|
|
|
func directoryExists(dir string) bool {
|
|
|
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|