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)