mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-22 16:46:53 +01:00
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
This commit is contained in:
parent
f1f7a22674
commit
48d723c07c
1 changed files with 52 additions and 76 deletions
|
@ -20,7 +20,6 @@
|
||||||
package encode
|
package encode
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
@ -160,13 +159,12 @@ func (enc *Encode) openResponseWriter(encodingName string, w http.ResponseWriter
|
||||||
// initResponseWriter initializes the responseWriter instance
|
// initResponseWriter initializes the responseWriter instance
|
||||||
// allocated in openResponseWriter, enabling mid-stack inlining.
|
// allocated in openResponseWriter, enabling mid-stack inlining.
|
||||||
func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, wrappedRW http.ResponseWriter) *responseWriter {
|
func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, wrappedRW http.ResponseWriter) *responseWriter {
|
||||||
buf := bufPool.Get().(*bytes.Buffer)
|
if httpInterfaces, ok := wrappedRW.(caddyhttp.HTTPInterfaces); ok {
|
||||||
buf.Reset()
|
rw.HTTPInterfaces = httpInterfaces
|
||||||
|
} else {
|
||||||
// The allocation of ResponseWriterWrapper might be optimized as well.
|
rw.HTTPInterfaces = &caddyhttp.ResponseWriterWrapper{ResponseWriter: wrappedRW}
|
||||||
rw.ResponseWriterWrapper = &caddyhttp.ResponseWriterWrapper{ResponseWriter: wrappedRW}
|
}
|
||||||
rw.encodingName = encodingName
|
rw.encodingName = encodingName
|
||||||
rw.buf = buf
|
|
||||||
rw.config = enc
|
rw.config = enc
|
||||||
|
|
||||||
return rw
|
return rw
|
||||||
|
@ -176,10 +174,9 @@ func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName string, w
|
||||||
// using the encoding represented by encodingName and
|
// using the encoding represented by encodingName and
|
||||||
// configured by config.
|
// configured by config.
|
||||||
type responseWriter struct {
|
type responseWriter struct {
|
||||||
*caddyhttp.ResponseWriterWrapper
|
caddyhttp.HTTPInterfaces
|
||||||
encodingName string
|
encodingName string
|
||||||
w Encoder
|
w Encoder
|
||||||
buf *bytes.Buffer
|
|
||||||
config *Encode
|
config *Encode
|
||||||
statusCode int
|
statusCode int
|
||||||
wroteHeader bool
|
wroteHeader bool
|
||||||
|
@ -206,28 +203,33 @@ func (rw *responseWriter) Flush() {
|
||||||
// to rw.Write (see bug in #4314)
|
// to rw.Write (see bug in #4314)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rw.ResponseWriterWrapper.Flush()
|
rw.HTTPInterfaces.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes to the response. If the response qualifies,
|
// Write writes to the response. If the response qualifies,
|
||||||
// it is encoded using the encoder, which is initialized
|
// it is encoded using the encoder, which is initialized
|
||||||
// if not done so already.
|
// if not done so already.
|
||||||
func (rw *responseWriter) Write(p []byte) (int, error) {
|
func (rw *responseWriter) Write(p []byte) (int, error) {
|
||||||
var n, written int
|
// ignore zero data writes, probably head request
|
||||||
var err error
|
if len(p) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
if rw.buf != nil && rw.config.MinLength > 0 {
|
// sniff content-type and determine content-length
|
||||||
written = rw.buf.Len()
|
if !rw.wroteHeader && rw.config.MinLength > 0 {
|
||||||
_, err := rw.buf.Write(p)
|
var gtMinLength bool
|
||||||
if err != nil {
|
if len(p) > rw.config.MinLength {
|
||||||
return 0, err
|
gtMinLength = true
|
||||||
|
} else if cl, err := strconv.Atoi(rw.Header().Get("Content-Length")); err == nil && cl > rw.config.MinLength {
|
||||||
|
gtMinLength = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if gtMinLength {
|
||||||
|
if rw.Header().Get("Content-Type") == "" {
|
||||||
|
rw.Header().Set("Content-Type", http.DetectContentType(p))
|
||||||
|
}
|
||||||
|
rw.init()
|
||||||
}
|
}
|
||||||
rw.init()
|
|
||||||
p = rw.buf.Bytes()
|
|
||||||
defer func() {
|
|
||||||
bufPool.Put(rw.buf)
|
|
||||||
rw.buf = nil
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// before we write to the response, we need to make
|
// before we write to the response, we need to make
|
||||||
|
@ -236,63 +238,44 @@ func (rw *responseWriter) Write(p []byte) (int, error) {
|
||||||
// and if so, that means we haven't written the
|
// and if so, that means we haven't written the
|
||||||
// header OR the default status code will be written
|
// header OR the default status code will be written
|
||||||
// by the standard library
|
// by the standard library
|
||||||
if rw.statusCode > 0 {
|
if !rw.wroteHeader {
|
||||||
rw.ResponseWriter.WriteHeader(rw.statusCode)
|
if rw.statusCode != 0 {
|
||||||
rw.statusCode = 0
|
rw.HTTPInterfaces.WriteHeader(rw.statusCode)
|
||||||
|
} else {
|
||||||
|
rw.HTTPInterfaces.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
rw.wroteHeader = true
|
rw.wroteHeader = true
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
if rw.w != nil {
|
||||||
case rw.w != nil:
|
return rw.w.Write(p)
|
||||||
n, err = rw.w.Write(p)
|
} else {
|
||||||
default:
|
return rw.HTTPInterfaces.Write(p)
|
||||||
n, err = rw.ResponseWriter.Write(p)
|
|
||||||
}
|
}
|
||||||
n -= written
|
|
||||||
if n < 0 {
|
|
||||||
n = 0
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close writes any remaining buffered response and
|
// Close writes any remaining buffered response and
|
||||||
// deallocates any active resources.
|
// deallocates any active resources.
|
||||||
func (rw *responseWriter) Close() error {
|
func (rw *responseWriter) Close() error {
|
||||||
var err error
|
// didn't write, probably head request
|
||||||
// only attempt to write the remaining buffered response
|
if !rw.wroteHeader {
|
||||||
// if there are any bytes left to write; otherwise, if
|
cl, err := strconv.Atoi(rw.Header().Get("Content-Length"))
|
||||||
// the handler above us returned an error without writing
|
if err == nil && cl > rw.config.MinLength {
|
||||||
// anything, we'd write to the response when we instead
|
rw.init()
|
||||||
// should simply let the error propagate back down; this
|
}
|
||||||
// is why the check for rw.buf.Len() > 0 is crucial
|
|
||||||
if rw.buf != nil && rw.buf.Len() > 0 {
|
if rw.statusCode != 0 {
|
||||||
rw.init()
|
rw.HTTPInterfaces.WriteHeader(rw.statusCode)
|
||||||
p := rw.buf.Bytes()
|
} else {
|
||||||
defer func() {
|
rw.HTTPInterfaces.WriteHeader(http.StatusOK)
|
||||||
bufPool.Put(rw.buf)
|
|
||||||
rw.buf = nil
|
|
||||||
}()
|
|
||||||
switch {
|
|
||||||
case rw.w != nil:
|
|
||||||
_, err = rw.w.Write(p)
|
|
||||||
default:
|
|
||||||
_, 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
|
|
||||||
rw.wroteHeader = true
|
rw.wroteHeader = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
if rw.w != nil {
|
if rw.w != nil {
|
||||||
err2 := rw.w.Close()
|
err = rw.w.Close()
|
||||||
if err2 != nil && err == nil {
|
rw.w.Reset(nil)
|
||||||
err = err2
|
|
||||||
}
|
|
||||||
rw.config.writerPools[rw.encodingName].Put(rw.w)
|
rw.config.writerPools[rw.encodingName].Put(rw.w)
|
||||||
rw.w = nil
|
rw.w = nil
|
||||||
}
|
}
|
||||||
|
@ -302,16 +285,15 @@ func (rw *responseWriter) Close() error {
|
||||||
// init should be called before we write a response, if rw.buf has contents.
|
// 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") == "" &&
|
if rw.Header().Get("Content-Encoding") == "" &&
|
||||||
rw.buf.Len() >= rw.config.MinLength &&
|
|
||||||
rw.config.Match(rw) {
|
rw.config.Match(rw) {
|
||||||
|
|
||||||
rw.w = rw.config.writerPools[rw.encodingName].Get().(Encoder)
|
rw.w = rw.config.writerPools[rw.encodingName].Get().(Encoder)
|
||||||
rw.w.Reset(rw.ResponseWriter)
|
rw.w.Reset(rw.HTTPInterfaces)
|
||||||
rw.Header().Del("Content-Length") // https://github.com/golang/go/issues/14975
|
rw.Header().Del("Content-Length") // https://github.com/golang/go/issues/14975
|
||||||
rw.Header().Set("Content-Encoding", rw.encodingName)
|
rw.Header().Set("Content-Encoding", rw.encodingName)
|
||||||
rw.Header().Add("Vary", "Accept-Encoding")
|
rw.Header().Add("Vary", "Accept-Encoding")
|
||||||
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptedEncodings returns the list of encodings that the
|
// AcceptedEncodings returns the list of encodings that the
|
||||||
|
@ -417,12 +399,6 @@ type Precompressed interface {
|
||||||
Suffix() string
|
Suffix() string
|
||||||
}
|
}
|
||||||
|
|
||||||
var bufPool = sync.Pool{
|
|
||||||
New: func() any {
|
|
||||||
return new(bytes.Buffer)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// defaultMinLength is the minimum length at which to compress content.
|
// defaultMinLength is the minimum length at which to compress content.
|
||||||
const defaultMinLength = 512
|
const defaultMinLength = 512
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue