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