Created
October 4, 2022 14:34
-
-
Save ahobson/92936d30adbffcadf207f5be8bbd73ea to your computer and use it in GitHub Desktop.
SPA Handler with neutered filesystem to not show directory listings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package handlers | |
import ( | |
"net/http" | |
"os" | |
"path/filepath" | |
"github.com/transcom/mymove/pkg/logging" | |
) | |
// This is straight from github.com/gorilla/mux | |
// SpaHandler implements the http.Handler interface, so we can use it | |
// to respond to HTTP requests. The path to the static directory and | |
// path to the index file within that static directory are used to | |
// serve the SPA in the given static directory. | |
type SpaHandler struct { | |
staticPath string | |
indexPath string | |
} | |
// NewSpaHandler returns a new handler for a Single Page App | |
func NewSpaHandler(staticPath string, indexPath string) http.Handler { | |
return SpaHandler{ | |
staticPath: staticPath, | |
indexPath: indexPath, | |
} | |
} | |
// from https://www.alexedwards.net/blog/disable-http-fileserver-directory-listings | |
type neuteredFileSystem struct { | |
fs http.FileSystem | |
indexPath string | |
} | |
func (nfs neuteredFileSystem) Open(path string) (http.File, error) { | |
f, err := nfs.fs.Open(path) | |
if err != nil { | |
return nil, err | |
} | |
s, err := f.Stat() | |
if s.IsDir() { | |
index := filepath.Join(path, nfs.indexPath) | |
if _, err := nfs.fs.Open(index); err != nil { | |
closeErr := f.Close() | |
if closeErr != nil { | |
return nil, closeErr | |
} | |
return nil, err | |
} | |
} | |
return f, nil | |
} | |
// ServeHTTP inspects the URL path to locate a file within the static dir | |
// on the SPA handler. If a file is found, it will be served. If not, the | |
// file located at the index path on the SPA handler will be served. This | |
// is suitable behavior for serving an SPA (single page application). | |
func (h SpaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
logger := logging.FromContext(r.Context()) | |
logger.Debug("Using SPA Handler for " + r.URL.Path) | |
// get the absolute path to prevent directory traversal | |
path, err := filepath.Abs(r.URL.Path) | |
if err != nil { | |
// if we failed to get the absolute path respond with a 400 bad request | |
// and stop | |
http.Error(w, err.Error(), http.StatusBadRequest) | |
return | |
} | |
// prepend the path with the path to the static directory | |
path = filepath.Join(h.staticPath, path) | |
// check whether a file exists at the given path | |
_, err = os.Stat(path) | |
if os.IsNotExist(err) { | |
// file does not exist, serve index.html | |
http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath)) | |
return | |
} else if err != nil { | |
// if we got an error (that wasn't that the file doesn't exist) stating the | |
// file, return a 500 internal server error and stop | |
http.Error(w, err.Error(), http.StatusInternalServerError) | |
return | |
} | |
// otherwise, use http.FileServer to serve the static dir | |
// use the neuteredFileSystem so that we do not expose directory listings | |
http.FileServer(neuteredFileSystem{http.Dir(h.staticPath), h.indexPath}).ServeHTTP(w, r) | |
} | |
// NewFileHandler serves up a single file | |
func NewFileHandler(entrypoint string) http.HandlerFunc { | |
return func(w http.ResponseWriter, r *http.Request) { | |
http.ServeFile(w, r, entrypoint) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment