diff --git a/static.go b/static.go new file mode 100644 index 0000000..63fdffc --- /dev/null +++ b/static.go @@ -0,0 +1,108 @@ +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) { + fs.ServeHTTP(rw, req) +} + +// 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) + }) + } +}