Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 13, 11:45 UTC
encode: Fix Accept-Ranges header; HEAD requests (#5039)
* fix encode handler header manipulation also avoid implementing ReadFrom because it breaks when io.Copied to directly * strconv.Itoa should be tried as a last resort WriteHeader during Close
WeidiDeng committed
Sep 16, 2022 at 06:05 UTC
48d723c07c52d76755ee323581a20777699f1557
1 file changed
+52
-76
modules/caddyhttp/encode/encode.go
+52
-76
@@ -20,7 +20,6 @@
20
package encode
21
22
import (
23
- "bytes"
23
"fmt"
24
"io"
25
"math"
@@ -160,13 +159,12 @@ func (enc *Encode) openResponseWriter(encodingName string, w http.ResponseWriter
159
// initResponseWriter initializes the responseWriter instance
160
// allocated in openResponseWriter, enabling mid-stack inlining.
161
func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, wrappedRW http.ResponseWriter) *responseWriter {
163
- buf := bufPool.Get().(*bytes.Buffer)
164
- buf.Reset()
165
-
166
- // The allocation of ResponseWriterWrapper might be optimized as well.
167
- rw.ResponseWriterWrapper = &caddyhttp.ResponseWriterWrapper{ResponseWriter: wrappedRW}
162
+ if httpInterfaces, ok := wrappedRW.(caddyhttp.HTTPInterfaces); ok {
163
+ rw.HTTPInterfaces = httpInterfaces
164
+ } else {
165
+ rw.HTTPInterfaces = &caddyhttp.ResponseWriterWrapper{ResponseWriter: wrappedRW}
166
+ }
167
rw.encodingName = encodingName
169
- rw.buf = buf
168
rw.config = enc
169
170
return rw
@@ -176,10 +174,9 @@ func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, w
174
// using the encoding represented by encodingName and
175
// configured by config.
176
type responseWriter struct {
179
- *caddyhttp.ResponseWriterWrapper
177
+ caddyhttp.HTTPInterfaces
178
encodingName string
179
w Encoder
182
- buf *bytes.Buffer
180
config *Encode
181
statusCode int
182
wroteHeader bool
@@ -206,28 +203,33 @@ func (rw *responseWriter) Flush() {
203
// to rw.Write (see bug in #4314)
204
return
205
}
209
- rw.ResponseWriterWrapper.Flush()
206
+ rw.HTTPInterfaces.Flush()
207
}
208
209
// Write writes to the response. If the response qualifies,
210
// it is encoded using the encoder, which is initialized
211
// if not done so already.
212
func (rw *responseWriter) Write(p []byte) (int, error) {
216
- var n, written int
217
- var err error
213
+ // ignore zero data writes, probably head request
214
+ if len(p) == 0 {
215
+ return 0, nil
216
+ }
217
219
- if rw.buf != nil && rw.config.MinLength > 0 {
220
- written = rw.buf.Len()
221
- _, err := rw.buf.Write(p)
222
- if err != nil {
223
- return 0, err
218
+ // sniff content-type and determine content-length
219
+ if !rw.wroteHeader && rw.config.MinLength > 0 {
220
+ var gtMinLength bool
221
+ if len(p) > rw.config.MinLength {
222
+ gtMinLength = true
223
+ } else if cl, err := strconv.Atoi(rw.Header().Get("Content-Length")); err == nil && cl > rw.config.MinLength {
224
+ gtMinLength = true
225
+ }
226
+
227
+ if gtMinLength {
228
+ if rw.Header().Get("Content-Type") == "" {
229
+ rw.Header().Set("Content-Type", http.DetectContentType(p))
230
+ }
231
+ rw.init()
232
}
225
- rw.init()
226
- p = rw.buf.Bytes()
227
- defer func() {
228
- bufPool.Put(rw.buf)
229
- rw.buf = nil
230
- }()
233
}
234
235
// before we write to the response, we need to make
@@ -236,63 +238,44 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
238
// and if so, that means we haven't written the
239
// header OR the default status code will be written
240
// by the standard library
239
- if rw.statusCode > 0 {
240
- rw.ResponseWriter.WriteHeader(rw.statusCode)
241
- rw.statusCode = 0
241
+ if !rw.wroteHeader {
242
+ if rw.statusCode != 0 {
243
+ rw.HTTPInterfaces.WriteHeader(rw.statusCode)
244
+ } else {
245
+ rw.HTTPInterfaces.WriteHeader(http.StatusOK)
246
+ }
247
rw.wroteHeader = true
248
}
249
245
- switch {
246
- case rw.w != nil:
247
- n, err = rw.w.Write(p)
248
- default:
249
- n, err = rw.ResponseWriter.Write(p)
250
- }
251
- n -= written
252
- if n < 0 {
253
- n = 0
250
+ if rw.w != nil {
251
+ return rw.w.Write(p)
252
+ } else {
253
+ return rw.HTTPInterfaces.Write(p)
254
}
255
- return n, err
255
}
256
257
// Close writes any remaining buffered response and
258
// deallocates any active resources.
259
func (rw *responseWriter) Close() error {
261
- var err error
262
- // only attempt to write the remaining buffered response
263
- // if there are any bytes left to write; otherwise, if
264
- // the handler above us returned an error without writing
265
- // anything, we'd write to the response when we instead
266
- // should simply let the error propagate back down; this
267
- // is why the check for rw.buf.Len() > 0 is crucial
268
- if rw.buf != nil && rw.buf.Len() > 0 {
269
- rw.init()
270
- p := rw.buf.Bytes()
271
- defer func() {
272
- bufPool.Put(rw.buf)
273
- rw.buf = nil
274
- }()
275
- switch {
276
- case rw.w != nil:
277
- _, err = rw.w.Write(p)
278
- default:
279
- _, err = rw.ResponseWriter.Write(p)
260
+ // didn't write, probably head request
261
+ if !rw.wroteHeader {
262
+ cl, err := strconv.Atoi(rw.Header().Get("Content-Length"))
263
+ if err == nil && cl > rw.config.MinLength {
264
+ rw.init()
265
+ }
266
+
267
+ if rw.statusCode != 0 {
268
+ rw.HTTPInterfaces.WriteHeader(rw.statusCode)
269
+ } else {
270
+ rw.HTTPInterfaces.WriteHeader(http.StatusOK)
271
}
281
- } else if rw.statusCode != 0 {
282
- // it is possible that a body was not written, and
283
- // a header was not even written yet, even though
284
- // we are closing; ensure the proper status code is
285
- // written exactly once, or we risk breaking requests
286
- // that rely on If-None-Match, for example
287
- rw.ResponseWriter.WriteHeader(rw.statusCode)
288
- rw.statusCode = 0
272
rw.wroteHeader = true
273
}
274
+
275
+ var err error
276
if rw.w != nil {
292
- err2 := rw.w.Close()
293
- if err2 != nil && err == nil {
294
- err = err2
295
- }
277
+ err = rw.w.Close()
278
+ rw.w.Reset(nil)
279
rw.config.writerPools[rw.encodingName].Put(rw.w)
280
rw.w = nil
281
}
@@ -302,16 +285,15 @@ func (rw *responseWriter) Close() error {
285
// init should be called before we write a response, if rw.buf has contents.
286
func (rw *responseWriter) init() {
287
if rw.Header().Get("Content-Encoding") == "" &&
305
- rw.buf.Len() >= rw.config.MinLength &&
288
rw.config.Match(rw) {
289
290
rw.w = rw.config.writerPools[rw.encodingName].Get().(Encoder)
309
- rw.w.Reset(rw.ResponseWriter)
291
+ rw.w.Reset(rw.HTTPInterfaces)
292
rw.Header().Del("Content-Length") // https://github.com/golang/go/issues/14975
293
rw.Header().Set("Content-Encoding", rw.encodingName)
294
rw.Header().Add("Vary", "Accept-Encoding")
295
+ rw.Header().Del("Accept-Ranges") // we don't know ranges for dynamically-encoded content
296
}
314
- rw.Header().Del("Accept-Ranges") // we don't know ranges for dynamically-encoded content
297
}
298
299
// AcceptedEncodings returns the list of encodings that the
@@ -417,12 +399,6 @@ type Precompressed interface {
399
Suffix() string
400
}
401
420
-var bufPool = sync.Pool{
421
- New: func() any {
422
- return new(bytes.Buffer)
423
- },
424
-}
425
-
402
// defaultMinLength is the minimum length at which to compress content.
403
const defaultMinLength = 512
404