114 lines
2.9 KiB
Go
114 lines
2.9 KiB
Go
package narco
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/codemodus/chain"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
type StaticFileServer struct {
|
|
dir http.FileSystem
|
|
prefix string
|
|
}
|
|
|
|
type StaticFileServerError struct {
|
|
error string
|
|
status int
|
|
}
|
|
|
|
func NewStaticFileServer(dir http.FileSystem, prefix string) *StaticFileServer {
|
|
return &StaticFileServer{
|
|
dir: dir,
|
|
prefix: prefix,
|
|
}
|
|
}
|
|
|
|
func (e StaticFileServerError) Error() string {
|
|
return fmt.Sprintf("%d: %s", e.status, e.error)
|
|
}
|
|
|
|
func (fs *StaticFileServer) ServeHTTPError(rw http.ResponseWriter, req *http.Request) error {
|
|
if req.Method != "GET" && req.Method != "HEAD" {
|
|
return StaticFileServerError{"Invalid " + req.Method + " method", http.StatusMethodNotAllowed}
|
|
}
|
|
|
|
fpath := req.URL.Path
|
|
if len(fs.prefix) != 0 {
|
|
if strings.HasPrefix(fpath, fs.prefix) == false {
|
|
return StaticFileServerError{"Invalid path " + fpath, http.StatusNotFound}
|
|
}
|
|
|
|
fpath = strings.TrimPrefix(fpath, fs.prefix)
|
|
}
|
|
|
|
f, err := fs.dir.Open(fpath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) == true {
|
|
return StaticFileServerError{"Unknown path " + fpath, http.StatusNotFound}
|
|
} else {
|
|
return StaticFileServerError{"Internal FS error", http.StatusInternalServerError}
|
|
}
|
|
}
|
|
defer f.Close()
|
|
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
if os.IsNotExist(err) == true {
|
|
return StaticFileServerError{"Unknown path " + fpath, http.StatusNotFound}
|
|
} else {
|
|
return StaticFileServerError{"Internal FS error", http.StatusInternalServerError}
|
|
}
|
|
}
|
|
|
|
if fi.IsDir() == true {
|
|
if fpath[len(fpath)-1] == '/' {
|
|
return StaticFileServerError{"Cannot index directory", http.StatusUnauthorized}
|
|
} else {
|
|
http.Redirect(rw, req, req.URL.Path+"/", http.StatusFound)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
http.ServeContent(rw, req, fpath, fi.ModTime(), f)
|
|
return nil
|
|
}
|
|
|
|
//http.Handler interface
|
|
func (fs *StaticFileServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|
err := fs.ServeHTTPError(rw, req)
|
|
if err != nil {
|
|
fsErr := err.(StaticFileServerError)
|
|
http.Error(rw, err.Error(), fsErr.status)
|
|
}
|
|
}
|
|
|
|
//chain handler interface
|
|
func (fs *StaticFileServer) ServeHTTPContext(ctx context.Context, rw http.ResponseWriter, req *http.Request) {
|
|
err := fs.ServeHTTPError(rw, req)
|
|
if err != nil {
|
|
fsErr := err.(StaticFileServerError)
|
|
//use our own context aware error handler
|
|
Error(ctx, rw, err.Error(), fsErr.status)
|
|
}
|
|
}
|
|
|
|
// chain wrapper. we just ignore the error, and send the request to
|
|
// the next middleware. I would prefer to use as an endpoint, i.e. we
|
|
// fail if its not good
|
|
|
|
func (fs *StaticFileServer) Wrap() func(chain.Handler) chain.Handler {
|
|
return func(other chain.Handler) chain.Handler {
|
|
return chain.HandlerFunc(func(ctx context.Context, rw http.ResponseWriter, req *http.Request) {
|
|
fs.ServeHTTPError(rw, req)
|
|
// todo ? put the error in the context ?? otherwise we
|
|
// should simply use endpoint, which is good for static
|
|
// resources we serve here !
|
|
other.ServeHTTPContext(ctx, rw, req)
|
|
})
|
|
}
|
|
}
|