@marcel91200 / caddy Mirror · read-only / commits / a63cb3e3
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

Implement etag; fix related bugs in encode and templates middlewares

Matthew Holt committed Jun 27, 2019 at 13:09 UTC a63cb3e3fdea70991a95c3f0bc8f3866a5aec6ef
3 files changed +65 -9
modules/caddyhttp/encode/encode.go
+20 -6
@@ -135,6 +135,17 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
135 }()
136 }
137
138 + // before we write to the response, we need to make
139 + // sure the header is written exactly once; we do
140 + // that by checking if a status code has been set,
141 + // and if so, that means we haven't written the
142 + // header OR the default status code will be written
143 + // by the standard library
144 + if rw.statusCode > 0 {
145 + rw.ResponseWriter.WriteHeader(rw.statusCode)
146 + rw.statusCode = 0
147 + }
148 +
149 switch {
150 case rw.w != nil:
151 n, err = rw.w.Write(p)
@@ -148,7 +159,7 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
159 return n, err
160 }
161
151 -// init should be called before we write a response, if rw.buf is not nil.
162 +// init should be called before we write a response, if rw.buf has contents.
163 func (rw *responseWriter) init() {
164 if rw.Header().Get("Content-Encoding") == "" && rw.buf.Len() >= rw.config.MinLength {
165 rw.w = rw.config.writerPools[rw.encodingName].Get().(Encoder)
@@ -157,11 +168,6 @@ func (rw *responseWriter) init() {
168 rw.Header().Set("Content-Encoding", rw.encodingName)
169 }
170 rw.Header().Del("Accept-Ranges") // we don't know ranges for dynamically-encoded content
160 - status := rw.statusCode
161 - if status == 0 {
162 - status = http.StatusOK
163 - }
164 - rw.ResponseWriter.WriteHeader(status)
171 }
172
173 // Close writes any remaining buffered response and
@@ -187,6 +193,14 @@ func (rw *responseWriter) Close() error {
193 default:
194 _, err = rw.ResponseWriter.Write(p)
195 }
196 + } else if rw.statusCode != 0 {
197 + // it is possible that a body was not written, and
198 + // a header was not even written yet, even though
199 + // we are closing; ensure the proper status code is
200 + // written exactly once, or we risk breaking requests
201 + // that rely on If-None-Match, for example
202 + rw.ResponseWriter.WriteHeader(rw.statusCode)
203 + rw.statusCode = 0
204 }
205 if rw.w != nil {
206 err2 := rw.w.Close()
modules/caddyhttp/fileserver/staticfiles.go
+14 -1
@@ -184,7 +184,9 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) error
184 }
185 defer file.Close()
186
187 - // TODO: Etag
187 + // set the ETag - note that a conditional If-None-Match request is handled
188 + // by http.ServeContent below, which checks against this ETag value
189 + w.Header().Set("ETag", calculateEtag(info))
190
191 if w.Header().Get("Content-Type") == "" {
192 mtyp := mime.TypeByExtension(filepath.Ext(filename))
@@ -419,6 +421,17 @@ func fileHidden(filename string, hide []string) bool {
421 return false
422 }
423
424 +// calculateEtag produces a strong etag by default, although, for
425 +// efficiency reasons, it does not actually consume the contents
426 +// of the file to make a hash of all the bytes. ¯\_(ツ)_/¯
427 +// Prefix the etag with "W/" to convert it into a weak etag.
428 +// See: https://tools.ietf.org/html/rfc7232#section-2.3
429 +func calculateEtag(d os.FileInfo) string {
430 + t := strconv.FormatInt(d.ModTime().Unix(), 36)
431 + s := strconv.FormatInt(d.Size(), 36)
432 + return `"` + t + s + `"`
433 +}
434 +
435 var defaultIndexNames = []string{"index.html"}
436
437 const minBackoff, maxBackoff = 2, 5
modules/caddyhttp/templates/templates.go
+31 -2
@@ -22,9 +22,18 @@ func init() {
22 // Templates is a middleware which execute response bodies as templates.
23 type Templates struct {
24 FileRoot string `json:"file_root,omitempty"`
25 + MIMETypes []string `json:"mime_types,omitempty"`
26 Delimiters []string `json:"delimiters,omitempty"`
27 }
28
29 +// Provision provisions t.
30 +func (t *Templates) Provision(ctx caddy.Context) error {
31 + if t.MIMETypes == nil {
32 + t.MIMETypes = defaultMIMETypes
33 + }
34 + return nil
35 +}
36 +
37 // Validate ensures t has a valid configuration.
38 func (t *Templates) Validate() error {
39 if len(t.Delimiters) != 0 && len(t.Delimiters) != 2 {
@@ -38,8 +47,16 @@ func (t *Templates) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy
47 buf.Reset()
48 defer bufPool.Put(buf)
49
50 + // shouldBuf determines whether to execute templates on this response,
51 + // since generally we will not want to execute for images or CSS, etc.
52 shouldBuf := func(status int) bool {
42 - return strings.HasPrefix(w.Header().Get("Content-Type"), "text/")
53 + ct := w.Header().Get("Content-Type")
54 + for _, mt := range t.MIMETypes {
55 + if strings.Contains(ct, mt) {
56 + return true
57 + }
58 + }
59 + return false
60 }
61
62 rec := caddyhttp.NewResponseRecorder(w, buf, shouldBuf)
@@ -59,9 +76,14 @@ func (t *Templates) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy
76
77 w.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
78 w.Header().Del("Accept-Ranges") // we don't know ranges for dynamically-created content
62 - w.Header().Del("Etag") // don't know a way to quickly generate etag for dynamic content
79 w.Header().Del("Last-Modified") // useless for dynamic content since it's always changing
80
81 + // we don't know a way to guickly generate etag for dynamic content,
82 + // but we can convert this to a weak etag to kind of indicate that
83 + if etag := w.Header().Get("ETag"); etag != "" {
84 + w.Header().Set("ETag", "W/"+etag)
85 + }
86 +
87 w.WriteHeader(rec.Status())
88 io.Copy(w, buf)
89
@@ -110,8 +132,15 @@ func (vrw *virtualResponseWriter) Write(data []byte) (int, error) {
132 return vrw.body.Write(data)
133 }
134
135 +var defaultMIMETypes = []string{
136 + "text/html",
137 + "text/plain",
138 + "text/markdown",
139 +}
140 +
141 // Interface guards
142 var (
143 + _ caddy.Provisioner = (*Templates)(nil)
144 _ caddy.Validator = (*Templates)(nil)
145 _ caddyhttp.MiddlewareHandler = (*Templates)(nil)
146 )