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) }) } }