1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
// Copyright 2015 Light Code Labs, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// 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 ct != "" && 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.Itoa(buf.Len()))
// delete the headers related to cache
w.Header().Del("ETag")
w.Header().Del("Last-Modified")
// get the modification time in preparation for http.ServeContent
modTime, _ := time.Parse(http.TimeFormat, w.Header().Get("Last-Modified"))
// at last, write the rendered template to the response; make sure to use
// use the proper status code, since ServeContent hard-codes 2xx codes...
http.ServeContent(rb.StatusCodeWriter(w), r, templateName, modTime, bytes.NewReader(buf.Bytes()))
return 0, 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
}
|