From d7233de6883fbb83cf445b19cdc95a119caa52b8 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 23 Feb 2016 12:40:41 +0100 Subject: Added support for serving the 404 page --- domain.go | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 85 insertions(+), 19 deletions(-) diff --git a/domain.go b/domain.go index 32a8b09c..49df3762 100644 --- a/domain.go +++ b/domain.go @@ -4,9 +4,12 @@ import ( "crypto/tls" "errors" "fmt" + "io" + "mime" "net/http" "os" "path/filepath" + "strconv" "strings" ) @@ -18,29 +21,54 @@ type domain struct { certificateError error } -func (d *domain) notFound(w http.ResponseWriter, r *http.Request) { - http.NotFound(w, r) -} - -func (d *domain) serveFile(w http.ResponseWriter, r *http.Request, fullPath string) bool { +func (d *domain) serveFile(w http.ResponseWriter, r *http.Request, fullPath string) error { // Open and serve content of file file, err := os.Open(fullPath) if err != nil { - return false + return err } defer file.Close() fi, err := file.Stat() if err != nil { - return false + return err } println("Serving", fullPath, "for", r.URL.Path) http.ServeContent(w, r, filepath.Base(file.Name()), fi.ModTime(), file) - return true + return nil } -func (d *domain) resolvePath(w http.ResponseWriter, r *http.Request, projectName, subPath string) (fullPath string, err error) { +func (d *domain) serveCustomFile(w http.ResponseWriter, r *http.Request, code int, fullPath string) error { + // Open and serve content of file + file, err := os.Open(fullPath) + if err != nil { + return err + } + defer file.Close() + + fi, err := file.Stat() + if err != nil { + return err + } + + println("Serving", fullPath, "for", r.URL.Path, "with", code) + + // Serve the file + _, haveType := w.Header()["Content-Type"] + if !haveType { + ctype := mime.TypeByExtension(filepath.Ext(fullPath)) + w.Header().Set("Content-Type", ctype) + } + w.Header().Set("Content-Length", strconv.FormatInt(fi.Size(), 10)) + w.WriteHeader(code) + if r.Method != "HEAD" { + io.CopyN(w, file, fi.Size()) + } + return nil +} + +func (d *domain) resolvePath(projectName, subPath string) (fullPath string, err error) { publicPath := filepath.Join(d.Group, projectName, "public") fullPath = filepath.Join(publicPath, subPath) @@ -56,6 +84,25 @@ func (d *domain) resolvePath(w http.ResponseWriter, r *http.Request, projectName return } +func (d *domain) tryNotFound(w http.ResponseWriter, r *http.Request, projectName string) error { + page404, err := d.resolvePath(projectName, "404.html") + if err != nil { + return err + } + + // Make sure that file is not symlink + fi, err := os.Lstat(page404) + if err != nil && !fi.Mode().IsRegular() { + return err + } + + err = d.serveCustomFile(w, r, http.StatusNotFound, page404) + if err != nil { + return err + } + return nil +} + func (d *domain) checkPath(w http.ResponseWriter, r *http.Request, path string) (fullPath string, err error) { fullPath = path fi, err := os.Lstat(fullPath) @@ -85,45 +132,64 @@ func (d *domain) checkPath(w http.ResponseWriter, r *http.Request, path string) return } -func (d *domain) tryFile(w http.ResponseWriter, r *http.Request, projectName, subPath string) bool { - path, err := d.resolvePath(w, r, projectName, subPath) +func (d *domain) tryFile(w http.ResponseWriter, r *http.Request, projectName, subPath string) error { + path, err := d.resolvePath(projectName, subPath) if err != nil { - return false + return err } path, err = d.checkPath(w, r, path) if err != nil { - return false + return err } return d.serveFile(w, r, path) } func (d *domain) serveFromGroup(w http.ResponseWriter, r *http.Request) { - // The Path always contains "/" at the beggining + // The Path always contains "/" at the beginning split := strings.SplitN(r.URL.Path, "/", 3) + // Try to serve file for http://group.example.com/subpath/... => /group/subpath/... if len(split) >= 2 { subPath := "" if len(split) >= 3 { subPath = split[2] } - if d.tryFile(w, r, split[1], subPath) { + if d.tryFile(w, r, split[1], subPath) == nil { return } } - if r.Host != "" && d.tryFile(w, r, strings.ToLower(r.Host), r.URL.Path) { + // Try to serve file for http://group.example.com/... => /group/group.example.com/... + if r.Host != "" && d.tryFile(w, r, strings.ToLower(r.Host), r.URL.Path) == nil { return } - d.notFound(w, r) + // Try serving not found page for http://group.example.com/subpath/ => /group/subpath/404.html + if len(split) >= 2 && d.tryNotFound(w, r, split[1]) == nil { + return + } + + // Try serving not found page for http://group.example.com/ => /group/group.example.com/404.html + if r.Host != "" && d.tryFile(w, r, strings.ToLower(r.Host), r.URL.Path) == nil { + return + } + + // Serve generic not found + http.NotFound(w, r) } func (d *domain) serveFromConfig(w http.ResponseWriter, r *http.Request) { - if d.tryFile(w, r, d.Project, r.URL.Path) { + // Try to serve file for http://host/... => /group/project/... + if d.tryFile(w, r, d.Project, r.URL.Path) == nil { return } - d.notFound(w, r) + // Try serving not found page for http://host/ => /group/project/404.html + if d.tryNotFound(w, r, d.Project) == nil { + return + } + + http.NotFound(w, r) } func (d *domain) ensureCertificate() (*tls.Certificate, error) { -- cgit v1.2.3