mirror of
https://github.com/caddyserver/caddy.git
synced 2025-02-08 17:16:36 +01:00
Implement etag; fix related bugs in encode and templates middlewares
This commit is contained in:
parent
2b22d2e6ea
commit
a63cb3e3fd
3 changed files with 65 additions and 9 deletions
|
@ -135,6 +135,17 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// before we write to the response, we need to make
|
||||||
|
// sure the header is written exactly once; we do
|
||||||
|
// that by checking if a status code has been set,
|
||||||
|
// and if so, that means we haven't written the
|
||||||
|
// header OR the default status code will be written
|
||||||
|
// by the standard library
|
||||||
|
if rw.statusCode > 0 {
|
||||||
|
rw.ResponseWriter.WriteHeader(rw.statusCode)
|
||||||
|
rw.statusCode = 0
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case rw.w != nil:
|
case rw.w != nil:
|
||||||
n, err = rw.w.Write(p)
|
n, err = rw.w.Write(p)
|
||||||
|
@ -148,7 +159,7 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// init should be called before we write a response, if rw.buf is not nil.
|
// init should be called before we write a response, if rw.buf has contents.
|
||||||
func (rw *responseWriter) init() {
|
func (rw *responseWriter) init() {
|
||||||
if rw.Header().Get("Content-Encoding") == "" && rw.buf.Len() >= rw.config.MinLength {
|
if rw.Header().Get("Content-Encoding") == "" && rw.buf.Len() >= rw.config.MinLength {
|
||||||
rw.w = rw.config.writerPools[rw.encodingName].Get().(Encoder)
|
rw.w = rw.config.writerPools[rw.encodingName].Get().(Encoder)
|
||||||
|
@ -157,11 +168,6 @@ func (rw *responseWriter) init() {
|
||||||
rw.Header().Set("Content-Encoding", rw.encodingName)
|
rw.Header().Set("Content-Encoding", rw.encodingName)
|
||||||
}
|
}
|
||||||
rw.Header().Del("Accept-Ranges") // we don't know ranges for dynamically-encoded content
|
rw.Header().Del("Accept-Ranges") // we don't know ranges for dynamically-encoded content
|
||||||
status := rw.statusCode
|
|
||||||
if status == 0 {
|
|
||||||
status = http.StatusOK
|
|
||||||
}
|
|
||||||
rw.ResponseWriter.WriteHeader(status)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close writes any remaining buffered response and
|
// Close writes any remaining buffered response and
|
||||||
|
@ -187,6 +193,14 @@ func (rw *responseWriter) Close() error {
|
||||||
default:
|
default:
|
||||||
_, err = rw.ResponseWriter.Write(p)
|
_, err = rw.ResponseWriter.Write(p)
|
||||||
}
|
}
|
||||||
|
} else if rw.statusCode != 0 {
|
||||||
|
// it is possible that a body was not written, and
|
||||||
|
// a header was not even written yet, even though
|
||||||
|
// we are closing; ensure the proper status code is
|
||||||
|
// written exactly once, or we risk breaking requests
|
||||||
|
// that rely on If-None-Match, for example
|
||||||
|
rw.ResponseWriter.WriteHeader(rw.statusCode)
|
||||||
|
rw.statusCode = 0
|
||||||
}
|
}
|
||||||
if rw.w != nil {
|
if rw.w != nil {
|
||||||
err2 := rw.w.Close()
|
err2 := rw.w.Close()
|
||||||
|
|
|
@ -184,7 +184,9 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) error
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
// TODO: Etag
|
// set the ETag - note that a conditional If-None-Match request is handled
|
||||||
|
// by http.ServeContent below, which checks against this ETag value
|
||||||
|
w.Header().Set("ETag", calculateEtag(info))
|
||||||
|
|
||||||
if w.Header().Get("Content-Type") == "" {
|
if w.Header().Get("Content-Type") == "" {
|
||||||
mtyp := mime.TypeByExtension(filepath.Ext(filename))
|
mtyp := mime.TypeByExtension(filepath.Ext(filename))
|
||||||
|
@ -419,6 +421,17 @@ func fileHidden(filename string, hide []string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calculateEtag produces a strong etag by default, although, for
|
||||||
|
// efficiency reasons, it does not actually consume the contents
|
||||||
|
// of the file to make a hash of all the bytes. ¯\_(ツ)_/¯
|
||||||
|
// Prefix the etag with "W/" to convert it into a weak etag.
|
||||||
|
// See: https://tools.ietf.org/html/rfc7232#section-2.3
|
||||||
|
func calculateEtag(d os.FileInfo) string {
|
||||||
|
t := strconv.FormatInt(d.ModTime().Unix(), 36)
|
||||||
|
s := strconv.FormatInt(d.Size(), 36)
|
||||||
|
return `"` + t + s + `"`
|
||||||
|
}
|
||||||
|
|
||||||
var defaultIndexNames = []string{"index.html"}
|
var defaultIndexNames = []string{"index.html"}
|
||||||
|
|
||||||
const minBackoff, maxBackoff = 2, 5
|
const minBackoff, maxBackoff = 2, 5
|
||||||
|
|
|
@ -22,9 +22,18 @@ func init() {
|
||||||
// Templates is a middleware which execute response bodies as templates.
|
// Templates is a middleware which execute response bodies as templates.
|
||||||
type Templates struct {
|
type Templates struct {
|
||||||
FileRoot string `json:"file_root,omitempty"`
|
FileRoot string `json:"file_root,omitempty"`
|
||||||
|
MIMETypes []string `json:"mime_types,omitempty"`
|
||||||
Delimiters []string `json:"delimiters,omitempty"`
|
Delimiters []string `json:"delimiters,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Provision provisions t.
|
||||||
|
func (t *Templates) Provision(ctx caddy.Context) error {
|
||||||
|
if t.MIMETypes == nil {
|
||||||
|
t.MIMETypes = defaultMIMETypes
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Validate ensures t has a valid configuration.
|
// Validate ensures t has a valid configuration.
|
||||||
func (t *Templates) Validate() error {
|
func (t *Templates) Validate() error {
|
||||||
if len(t.Delimiters) != 0 && len(t.Delimiters) != 2 {
|
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
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
defer bufPool.Put(buf)
|
defer bufPool.Put(buf)
|
||||||
|
|
||||||
|
// shouldBuf determines whether to execute templates on this response,
|
||||||
|
// since generally we will not want to execute for images or CSS, etc.
|
||||||
shouldBuf := func(status int) bool {
|
shouldBuf := func(status int) bool {
|
||||||
return strings.HasPrefix(w.Header().Get("Content-Type"), "text/")
|
ct := w.Header().Get("Content-Type")
|
||||||
|
for _, mt := range t.MIMETypes {
|
||||||
|
if strings.Contains(ct, mt) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
rec := caddyhttp.NewResponseRecorder(w, buf, shouldBuf)
|
rec := caddyhttp.NewResponseRecorder(w, buf, shouldBuf)
|
||||||
|
@ -59,9 +76,14 @@ func (t *Templates) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy
|
||||||
|
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
|
w.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
|
||||||
w.Header().Del("Accept-Ranges") // we don't know ranges for dynamically-created content
|
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.Header().Del("Last-Modified") // useless for dynamic content since it's always changing
|
||||||
|
|
||||||
|
// we don't know a way to guickly generate etag for dynamic content,
|
||||||
|
// but we can convert this to a weak etag to kind of indicate that
|
||||||
|
if etag := w.Header().Get("ETag"); etag != "" {
|
||||||
|
w.Header().Set("ETag", "W/"+etag)
|
||||||
|
}
|
||||||
|
|
||||||
w.WriteHeader(rec.Status())
|
w.WriteHeader(rec.Status())
|
||||||
io.Copy(w, buf)
|
io.Copy(w, buf)
|
||||||
|
|
||||||
|
@ -110,8 +132,15 @@ func (vrw *virtualResponseWriter) Write(data []byte) (int, error) {
|
||||||
return vrw.body.Write(data)
|
return vrw.body.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var defaultMIMETypes = []string{
|
||||||
|
"text/html",
|
||||||
|
"text/plain",
|
||||||
|
"text/markdown",
|
||||||
|
}
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
|
_ caddy.Provisioner = (*Templates)(nil)
|
||||||
_ caddy.Validator = (*Templates)(nil)
|
_ caddy.Validator = (*Templates)(nil)
|
||||||
_ caddyhttp.MiddlewareHandler = (*Templates)(nil)
|
_ caddyhttp.MiddlewareHandler = (*Templates)(nil)
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue