mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-01 21:24:23 +08:00
4b1b329edb
* templates: Execute template loaded by later middlewares This is the beginning of an attempt to make the staticfiles file server the only middleware that hits the disk and loads content. This may have unknown implications. But the goal is to reduce duplication without sacrificing performance. (We now call ServeContent here.) This change loses about 15% of the req/sec of the old way of doing it, but this way is arguably more correct since the file server is good at serving static files; duplicating that logic in every middleware that needs to hit the disk is not practical. * httpserver: Introduce ResponseRecorder as per Tw's suggestions It implements io.ReaderFrom and has some allocation-reducing optimizations baked into it * templates: Increase execution speed by ~10-15% after perf regression By using httpserver.ResponseBuffer, we can reduce allocations and still get what we want. It's a little tricky but it works so far.
129 lines
3.4 KiB
Go
129 lines
3.4 KiB
Go
// Package templates implements template execution for files to be
|
|
// dynamically rendered for the client.
|
|
package templates
|
|
|
|
import (
|
|
"bytes"
|
|
"mime"
|
|
"net/http"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/mholt/caddy/caddyhttp/httpserver"
|
|
)
|
|
|
|
// ServeHTTP implements the httpserver.Handler interface.
|
|
func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
|
// iterate rules, to find first one that matches the request path
|
|
for _, rule := range t.Rules {
|
|
if !httpserver.Path(r.URL.Path).Matches(rule.Path) {
|
|
continue
|
|
}
|
|
|
|
fpath := r.URL.Path
|
|
|
|
// get a buffer from the pool and make a response recorder
|
|
buf := t.BufPool.Get().(*bytes.Buffer)
|
|
buf.Reset()
|
|
defer t.BufPool.Put(buf)
|
|
|
|
// only buffer the response when we want to execute a template
|
|
shouldBuf := func(status int, header http.Header) bool {
|
|
// see if this request matches a template extension
|
|
reqExt := path.Ext(fpath)
|
|
for _, ext := range rule.Extensions {
|
|
if reqExt == "" {
|
|
// request has no extension, so check response Content-Type
|
|
ct := mime.TypeByExtension(ext)
|
|
if strings.Contains(header.Get("Content-Type"), ct) {
|
|
return true
|
|
}
|
|
} else if reqExt == ext {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// prepare a buffer to hold the response, if applicable
|
|
rb := httpserver.NewResponseBuffer(buf, w, shouldBuf)
|
|
|
|
// pass request up the chain to let another middleware provide us the template
|
|
code, err := t.Next.ServeHTTP(rb, r)
|
|
if !rb.Buffered() || code >= 300 || err != nil {
|
|
return code, err
|
|
}
|
|
|
|
// create a new template
|
|
templateName := filepath.Base(fpath)
|
|
tpl := template.New(templateName)
|
|
|
|
// set delimiters
|
|
if rule.Delims != [2]string{} {
|
|
tpl.Delims(rule.Delims[0], rule.Delims[1])
|
|
}
|
|
|
|
// add custom functions
|
|
tpl.Funcs(httpserver.TemplateFuncs)
|
|
|
|
// parse the template
|
|
parsedTpl, err := tpl.Parse(rb.Buffer.String())
|
|
if err != nil {
|
|
return http.StatusInternalServerError, err
|
|
}
|
|
|
|
// create execution context for the template template
|
|
ctx := httpserver.NewContextWithHeader(w.Header())
|
|
ctx.Root = t.FileSys
|
|
ctx.Req = r
|
|
ctx.URL = r.URL
|
|
|
|
// execute the template
|
|
buf.Reset()
|
|
err = parsedTpl.Execute(buf, ctx)
|
|
if err != nil {
|
|
return http.StatusInternalServerError, err
|
|
}
|
|
|
|
// copy the buffered header into the real ResponseWriter
|
|
rb.CopyHeader()
|
|
|
|
// set the actual content length now that the template was executed
|
|
w.Header().Set("Content-Length", strconv.FormatInt(int64(buf.Len()), 10))
|
|
|
|
// get the modification time in preparation to ServeContent
|
|
modTime, _ := time.Parse(http.TimeFormat, w.Header().Get("Last-Modified"))
|
|
|
|
// at last, write the rendered template to the response
|
|
http.ServeContent(w, r, templateName, modTime, bytes.NewReader(buf.Bytes()))
|
|
|
|
return http.StatusOK, nil
|
|
}
|
|
|
|
return t.Next.ServeHTTP(w, r)
|
|
}
|
|
|
|
// Templates is middleware to render templated files as the HTTP response.
|
|
type Templates struct {
|
|
Next httpserver.Handler
|
|
Rules []Rule
|
|
Root string
|
|
FileSys http.FileSystem
|
|
BufPool *sync.Pool // docs: "A Pool must not be copied after first use."
|
|
}
|
|
|
|
// Rule represents a template rule. A template will only execute
|
|
// with this rule if the request path matches the Path specified
|
|
// and requests a resource with one of the extensions specified.
|
|
type Rule struct {
|
|
Path string
|
|
Extensions []string
|
|
IndexFiles []string
|
|
Delims [2]string
|
|
}
|