@marcel91200 / caddy Mirror · read-only / commits / e8ae80ad
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

fileserver: Don't persist parsed template (fix #4202)

Templates are parsed at request-time (like they are in the templates middleware) to allow live changes to the template while the server is running. Fixes race condition. Also refactored use of a buffer so a buffer put back in the pool will not continue to be used (written to client) in the meantime. A couple of benchmarks removed due to refactor, which is fine, since we know pooling helps here.

Matthew Holt committed Jun 16, 2021 at 14:28 UTC e8ae80adca5db7102e646954fcc53827732ceb83
3 files changed +20 -105
modules/caddyhttp/fileserver/browse.go
+20 -28
@@ -22,6 +22,7 @@ import (
22 "os"
23 "path"
24 "strings"
25 + "sync"
26 "text/template"
27
28 "github.com/caddyserver/caddy/v2"
@@ -34,8 +35,6 @@ import (
35 type Browse struct {
36 // Use this template file instead of the default browse template.
37 TemplateFile string `json:"template_file,omitempty"`
37 -
38 - template *template.Template
38 }
39
40 func (fsrv *FileServer) serveBrowse(root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
@@ -75,11 +74,14 @@ func (fsrv *FileServer) serveBrowse(root, dirPath string, w http.ResponseWriter,
74
75 fsrv.browseApplyQueryParams(w, r, &listing)
76
78 - // write response as either JSON or HTML
79 - var buf *bytes.Buffer
77 + buf := bufPool.Get().(*bytes.Buffer)
78 + defer bufPool.Put(buf)
79 +
80 acceptHeader := strings.ToLower(strings.Join(r.Header["Accept"], ","))
81 +
82 + // write response as either JSON or HTML
83 if strings.Contains(acceptHeader, "application/json") {
82 - if buf, err = fsrv.browseWriteJSON(listing); err != nil {
84 + if err := json.NewEncoder(buf).Encode(listing.Items); err != nil {
85 return caddyhttp.Error(http.StatusInternalServerError, err)
86 }
87 w.Header().Set("Content-Type", "application/json; charset=utf-8")
@@ -98,12 +100,11 @@ func (fsrv *FileServer) serveBrowse(root, dirPath string, w http.ResponseWriter,
100 browseTemplateContext: listing,
101 }
102
101 - err = fsrv.makeBrowseTemplate(tplCtx)
103 + tpl, err := fsrv.makeBrowseTemplate(tplCtx)
104 if err != nil {
105 return fmt.Errorf("parsing browse template: %v", err)
106 }
105 -
106 - if buf, err = fsrv.browseWriteHTML(tplCtx); err != nil {
107 + if err := tpl.Execute(buf, tplCtx); err != nil {
108 return caddyhttp.Error(http.StatusInternalServerError, err)
109 }
110 w.Header().Set("Content-Type", "text/html; charset=utf-8")
@@ -161,7 +162,7 @@ func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Re
162 }
163
164 // makeBrowseTemplate creates the template to be used for directory listings.
164 -func (fsrv *FileServer) makeBrowseTemplate(tplCtx *templateContext) error {
165 +func (fsrv *FileServer) makeBrowseTemplate(tplCtx *templateContext) (*template.Template, error) {
166 var tpl *template.Template
167 var err error
168
@@ -169,33 +170,17 @@ func (fsrv *FileServer) makeBrowseTemplate(tplCtx *templateContext) error {
170 tpl = tplCtx.NewTemplate(path.Base(fsrv.Browse.TemplateFile))
171 tpl, err = tpl.ParseFiles(fsrv.Browse.TemplateFile)
172 if err != nil {
172 - return fmt.Errorf("parsing browse template file: %v", err)
173 + return nil, fmt.Errorf("parsing browse template file: %v", err)
174 }
175 } else {
176 tpl = tplCtx.NewTemplate("default_listing")
177 tpl, err = tpl.Parse(defaultBrowseTemplate)
178 if err != nil {
178 - return fmt.Errorf("parsing default browse template: %v", err)
179 + return nil, fmt.Errorf("parsing default browse template: %v", err)
180 }
181 }
182
182 - fsrv.Browse.template = tpl
183 -
184 - return nil
185 -}
186 -
187 -func (fsrv *FileServer) browseWriteJSON(listing browseTemplateContext) (*bytes.Buffer, error) {
188 - buf := bufPool.Get().(*bytes.Buffer)
189 - defer bufPool.Put(buf)
190 - err := json.NewEncoder(buf).Encode(listing.Items)
191 - return buf, err
192 -}
193 -
194 -func (fsrv *FileServer) browseWriteHTML(tplCtx *templateContext) (*bytes.Buffer, error) {
195 - buf := bufPool.Get().(*bytes.Buffer)
196 - defer bufPool.Put(buf)
197 - err := fsrv.Browse.template.Execute(buf, tplCtx)
198 - return buf, err
183 + return tpl, nil
184 }
185
186 // isSymlink return true if f is a symbolic link
@@ -224,3 +209,10 @@ type templateContext struct {
209 templates.TemplateContext
210 browseTemplateContext
211 }
212 +
213 +// bufPool is used to increase the efficiency of file listings.
214 +var bufPool = sync.Pool{
215 + New: func() interface{} {
216 + return new(bytes.Buffer)
217 + },
218 +}
modules/caddyhttp/fileserver/browse_test.go deleted
-68
@@ -1,68 +0,0 @@
1 -// Copyright 2015 Matthew Holt and The Caddy Authors
2 -//
3 -// Licensed under the Apache License, Version 2.0 (the "License");
4 -// you may not use this file except in compliance with the License.
5 -// You may obtain a copy of the License at
6 -//
7 -// http://www.apache.org/licenses/LICENSE-2.0
8 -//
9 -// Unless required by applicable law or agreed to in writing, software
10 -// distributed under the License is distributed on an "AS IS" BASIS,
11 -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 -// See the License for the specific language governing permissions and
13 -// limitations under the License.
14 -
15 -package fileserver
16 -
17 -import (
18 - "testing"
19 - "text/template"
20 -)
21 -
22 -func BenchmarkBrowseWriteJSON(b *testing.B) {
23 - fsrv := new(FileServer)
24 - listing := browseTemplateContext{
25 - Name: "test",
26 - Path: "test",
27 - CanGoUp: false,
28 - Items: make([]fileInfo, 100),
29 - NumDirs: 42,
30 - NumFiles: 420,
31 - Sort: "",
32 - Order: "",
33 - Limit: 42,
34 - }
35 - b.ResetTimer()
36 -
37 - for n := 0; n < b.N; n++ {
38 - fsrv.browseWriteJSON(listing)
39 - }
40 -}
41 -
42 -func BenchmarkBrowseWriteHTML(b *testing.B) {
43 - fsrv := new(FileServer)
44 - fsrv.Browse = &Browse{
45 - TemplateFile: "",
46 - template: template.New("test"),
47 - }
48 - listing := browseTemplateContext{
49 - Name: "test",
50 - Path: "test",
51 - CanGoUp: false,
52 - Items: make([]fileInfo, 100),
53 - NumDirs: 42,
54 - NumFiles: 420,
55 - Sort: "",
56 - Order: "",
57 - Limit: 42,
58 - }
59 - tplCtx := &templateContext{
60 - browseTemplateContext: listing,
61 - }
62 - fsrv.makeBrowseTemplate(tplCtx)
63 - b.ResetTimer()
64 -
65 - for n := 0; n < b.N; n++ {
66 - fsrv.browseWriteHTML(tplCtx)
67 - }
68 -}
modules/caddyhttp/fileserver/staticfiles.go
-9
@@ -15,7 +15,6 @@
15 package fileserver
16
17 import (
18 - "bytes"
18 "fmt"
19 weakrand "math/rand"
20 "mime"
@@ -24,7 +23,6 @@ import (
23 "path/filepath"
24 "strconv"
25 "strings"
27 - "sync"
26 "time"
27
28 "github.com/caddyserver/caddy/v2"
@@ -178,7 +176,6 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
176 } else if os.IsPermission(err) {
177 return caddyhttp.Error(http.StatusForbidden, err)
178 }
181 - // TODO: treat this as resource exhaustion like with os.Open? Or unnecessary here?
179 return caddyhttp.Error(http.StatusInternalServerError, err)
180 }
181
@@ -555,12 +552,6 @@ func (wr statusOverrideResponseWriter) WriteHeader(int) {
552
553 var defaultIndexNames = []string{"index.html", "index.txt"}
554
558 -var bufPool = sync.Pool{
559 - New: func() interface{} {
560 - return new(bytes.Buffer)
561 - },
562 -}
563 -
555 const (
556 minBackoff, maxBackoff = 2, 5
557 separator = string(filepath.Separator)