diff options
Diffstat (limited to 'modules/caddyhttp/templates/templates.go')
-rw-r--r-- | modules/caddyhttp/templates/templates.go | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/modules/caddyhttp/templates/templates.go b/modules/caddyhttp/templates/templates.go new file mode 100644 index 0000000..56c3b66 --- /dev/null +++ b/modules/caddyhttp/templates/templates.go @@ -0,0 +1,146 @@ +package templates + +import ( + "bytes" + "fmt" + "io" + "net/http" + "strconv" + "text/template" + + "github.com/caddyserver/caddy" + "github.com/caddyserver/caddy/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(caddy.Module{ + Name: "http.middleware.templates", + New: func() interface{} { return new(Templates) }, + }) +} + +// Templates is a middleware which execute response bodies as templates. +type Templates struct { + FileRoot string `json:"file_root,omitempty"` + Delimiters []string `json:"delimiters,omitempty"` +} + +// Validate ensures t has a valid configuration. +func (t *Templates) Validate() error { + if len(t.Delimiters) != 0 && len(t.Delimiters) != 2 { + return fmt.Errorf("delimiters must consist of exactly two elements: opening and closing") + } + return nil +} + +func (t *Templates) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + buf := bufPool.Get().(*bytes.Buffer) + buf.Reset() + defer bufPool.Put(buf) + + wb := &responseBuffer{ + ResponseWriterWrapper: &caddyhttp.ResponseWriterWrapper{ResponseWriter: w}, + buf: buf, + } + + err := next.ServeHTTP(wb, r) + if err != nil { + return err + } + + err = t.executeTemplate(wb, r) + if err != nil { + return err + } + + w.Header().Set("Content-Length", strconv.Itoa(wb.buf.Len())) + w.Header().Del("Accept-Ranges") // we don't know ranges for dynamically-created content + w.Header().Del("Etag") // don't know a way to quickly generate etag for dynamic content + w.Header().Del("Last-Modified") // useless for dynamic content since it's always changing + + w.WriteHeader(wb.statusCode) + io.Copy(w, wb.buf) + + return nil +} + +// executeTemplate executes the template contianed +// in wb.buf and replaces it with the results. +func (t *Templates) executeTemplate(wb *responseBuffer, r *http.Request) error { + tpl := template.New(r.URL.Path) + + if len(t.Delimiters) == 2 { + tpl.Delims(t.Delimiters[0], t.Delimiters[1]) + } + + parsedTpl, err := tpl.Parse(wb.buf.String()) + if err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + + var fs http.FileSystem + if t.FileRoot != "" { + fs = http.Dir(t.FileRoot) + } + ctx := &templateContext{ + Root: fs, + Req: r, + RespHeader: tplWrappedHeader{wb.Header()}, + } + + wb.buf.Reset() // reuse buffer for output + err = parsedTpl.Execute(wb.buf, ctx) + if err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + + return nil +} + +// responseBuffer buffers the response so that it can be +// executed as a template. +type responseBuffer struct { + *caddyhttp.ResponseWriterWrapper + wroteHeader bool + statusCode int + buf *bytes.Buffer +} + +func (rb *responseBuffer) WriteHeader(statusCode int) { + if rb.wroteHeader { + return + } + rb.statusCode = statusCode + rb.wroteHeader = true +} + +func (rb *responseBuffer) Write(data []byte) (int, error) { + rb.WriteHeader(http.StatusOK) + return rb.buf.Write(data) +} + +// virtualResponseWriter is used in virtualized HTTP requests. +type virtualResponseWriter struct { + status int + header http.Header + body *bytes.Buffer +} + +func (vrw *virtualResponseWriter) Header() http.Header { + return vrw.header +} + +func (vrw *virtualResponseWriter) WriteHeader(statusCode int) { + vrw.status = statusCode +} + +func (vrw *virtualResponseWriter) Write(data []byte) (int, error) { + return vrw.body.Write(data) +} + +// Interface guards +var ( + _ caddy.Validator = (*Templates)(nil) + _ caddyhttp.MiddlewareHandler = (*Templates)(nil) + _ caddyhttp.HTTPInterfaces = (*responseBuffer)(nil) +) |