// ServeHTTP implements the httpserver.Handler interface. func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range t.Rules { if !httpserver.Path(r.URL.Path).Matches(rule.Path) { continue } // Check for index files fpath := r.URL.Path if idx, ok := httpserver.IndexFile(t.FileSys, fpath, rule.IndexFiles); ok { fpath = idx } // Check the extension reqExt := path.Ext(fpath) for _, ext := range rule.Extensions { if reqExt == ext { // Create execution context ctx := httpserver.Context{Root: t.FileSys, Req: r, URL: r.URL} // New template templateName := filepath.Base(fpath) tpl := template.New(templateName) // Set delims if rule.Delims != [2]string{} { tpl.Delims(rule.Delims[0], rule.Delims[1]) } // Build the template templatePath := filepath.Join(t.Root, fpath) tpl, err := tpl.ParseFiles(templatePath) if err != nil { if os.IsNotExist(err) { return http.StatusNotFound, nil } else if os.IsPermission(err) { return http.StatusForbidden, nil } return http.StatusInternalServerError, err } // Execute it var buf bytes.Buffer err = tpl.Execute(&buf, ctx) if err != nil { return http.StatusInternalServerError, err } templateInfo, err := os.Stat(templatePath) if err == nil { // add the Last-Modified header if we were able to read the stamp httpserver.SetLastModifiedHeader(w, templateInfo.ModTime()) } buf.WriteTo(w) return http.StatusOK, nil } } } return t.Next.ServeHTTP(w, r) }
// ServeHTTP implements the http.Handler interface. func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { var cfg *Config for _, c := range md.Configs { if httpserver.Path(r.URL.Path).Matches(c.PathScope) { // not negated cfg = c break // or goto } } if cfg == nil { return md.Next.ServeHTTP(w, r) // exit early } // We only deal with HEAD/GET switch r.Method { case http.MethodGet, http.MethodHead: default: return http.StatusMethodNotAllowed, nil } var dirents []os.FileInfo var lastModTime time.Time fpath := r.URL.Path if idx, ok := httpserver.IndexFile(md.FileSys, fpath, md.IndexFiles); ok { // We're serving a directory index file, which may be a markdown // file with a template. Let's grab a list of files this directory // URL points to, and pass that in to any possible template invocations, // so that templates can customize the look and feel of a directory. fdp, err := md.FileSys.Open(fpath) switch { case err == nil: // nop case os.IsPermission(err): return http.StatusForbidden, err case os.IsExist(err): return http.StatusNotFound, nil default: // did we run out of FD? return http.StatusInternalServerError, err } defer fdp.Close() // Grab a possible set of directory entries. Note, we do not check // for errors here (unreadable directory, for example). It may // still be useful to have a directory template file, without the // directory contents being present. Note, the directory's last // modification is also present here (entry "."). dirents, _ = fdp.Readdir(-1) for _, d := range dirents { lastModTime = latest(lastModTime, d.ModTime()) } // Set path to found index file fpath = idx } // If not supported extension, pass on it if _, ok := cfg.Extensions[path.Ext(fpath)]; !ok { return md.Next.ServeHTTP(w, r) } // At this point we have a supported extension/markdown f, err := md.FileSys.Open(fpath) switch { case err == nil: // nop case os.IsPermission(err): return http.StatusForbidden, err case os.IsExist(err): return http.StatusNotFound, nil default: // did we run out of FD? return http.StatusInternalServerError, err } defer f.Close() fs, err := f.Stat() if err != nil { return http.StatusGone, nil } lastModTime = latest(lastModTime, fs.ModTime()) ctx := httpserver.Context{ Root: md.FileSys, Req: r, URL: r.URL, } html, err := cfg.Markdown(title(fpath), f, dirents, ctx) if err != nil { return http.StatusInternalServerError, err } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Length", strconv.FormatInt(int64(len(html)), 10)) httpserver.SetLastModifiedHeader(w, lastModTime) if r.Method == http.MethodGet { w.Write(html) } return http.StatusOK, nil }