package main import ( "fmt" "io" "log" "net/http" "os" "path/filepath" "strconv" "strings" "time" ) type SessionProvider interface { Create(user User, expr time.Duration) (string, error) Get(key string) (User, error) Refresh(key string, user User, expr time.Duration) error } type PageProvider interface { Page(key string) (*Page, error) Save(key string, page *Page) error } type RootHandler struct { Sessions SessionProvider Pages PageProvider StaticDir string } type AdminHandler struct { Sessions SessionProvider } func (h *AdminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("admin route")) } func (h *RootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/admin") { (&AdminHandler{ Sessions: h.Sessions, }).ServeHTTP(w, r) return } // attempt to serve from the managed pages page, err := h.Pages.Page(r.URL.Path) if err == nil { w.Write(page.Contents) return } // fall back to serving out of the static directory, but: // 1. prevent the generated indexes from rendering if strings.HasSuffix(r.URL.Path, "/") { h.ErrorHandle(404, w) return } // 2. prevent hidden paths from rendering for _, seg := range strings.Split(r.URL.Path, "/") { if strings.HasPrefix(seg, ".") { h.ErrorHandle(404, w) return } } // 3. catch files that would 404 and serve our own 404 page if !staticFileExists(filepath.Join(h.StaticDir, r.URL.Path)) { h.ErrorHandle(404, w) return } // finally, use the built-in fileserver to serve fs := http.FileServer(http.Dir(h.StaticDir)) fs.ServeHTTP(w, r) } func (h *RootHandler) ErrorHandle(status int, w http.ResponseWriter) { f, err := os.Open(filepath.Join(h.StaticDir, strconv.Itoa(status)+".html")) if err == nil { w.WriteHeader(status) _, err = io.Copy(w, f) if err != nil { fmt.Fprintf(w, "Internal Server Error while loading %d page\n", status) } return } w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("X-Content-Type-Options", "nosniff") w.WriteHeader(404) fmt.Fprintf(w, "%d\n", status) } func staticFileExists(name string) bool { f, err := os.Open(name) if err != nil { return false } defer f.Close() _, err = f.Stat() return err == nil } func main() { handler := &RootHandler{ Sessions: &Sessions{}, Pages: &Index{}, StaticDir: "./static", } handler.Pages.Save("foo", &Page{ Contents: []byte("foobar"), }) handler.Pages.Save("index", &Page{ Contents: []byte("root"), }) err := http.ListenAndServe(":8080", handler) log.Fatalf("server error: %v", err) }