mirror of
https://github.com/valyala/fasthttp.git
synced 2026-06-20 16:57:31 +03:00
Add trailer support (#1165)
* Add trailer support * fix issue and add documentation * remove redundant code * add error return for add/set trailer method * fix lint error * fix bad trailer error return issue and update bad content-length error * update errNonNumericChars * update errNonNumericChars * fix issue about error and fix typo
This commit is contained in:
@@ -361,6 +361,13 @@ func visitArgs(args []argsKV, f func(k, v []byte)) {
|
||||
}
|
||||
}
|
||||
|
||||
func visitArgsKey(args []argsKV, f func(k []byte)) {
|
||||
for i, n := 0, len(args); i < n; i++ {
|
||||
kv := &args[i]
|
||||
f(kv.key)
|
||||
}
|
||||
}
|
||||
|
||||
func copyArgs(dst, src []argsKV) []argsKV {
|
||||
if cap(dst) < len(src) {
|
||||
tmp := make([]argsKV, len(src))
|
||||
|
||||
@@ -42,8 +42,9 @@ type ResponseHeader struct {
|
||||
contentType []byte
|
||||
server []byte
|
||||
|
||||
h []argsKV
|
||||
bufKV argsKV
|
||||
h []argsKV
|
||||
trailer []argsKV
|
||||
bufKV argsKV
|
||||
|
||||
cookies []argsKV
|
||||
}
|
||||
@@ -77,8 +78,9 @@ type RequestHeader struct {
|
||||
contentType []byte
|
||||
userAgent []byte
|
||||
|
||||
h []argsKV
|
||||
bufKV argsKV
|
||||
h []argsKV
|
||||
trailer []argsKV
|
||||
bufKV argsKV
|
||||
|
||||
cookies []argsKV
|
||||
|
||||
@@ -382,6 +384,117 @@ func (h *RequestHeader) SetMultipartFormBoundaryBytes(boundary []byte) {
|
||||
h.SetContentTypeBytes(h.bufKV.value)
|
||||
}
|
||||
|
||||
// SetTrailer sets header Trailer value for chunked response
|
||||
// to indicate which headers will be sent after the body.
|
||||
//
|
||||
// Use Set to set the trailer header later.
|
||||
//
|
||||
// Trailers are only supported with chunked transfer.
|
||||
// Trailers allow the sender to include additional headers at the end of chunked messages.
|
||||
//
|
||||
// The following trailers are forbidden:
|
||||
// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length),
|
||||
// 2. routing (e.g., Host),
|
||||
// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]),
|
||||
// 4. authentication (e.g., see [RFC7235] and [RFC6265]),
|
||||
// 5. response control data (e.g., see Section 7.1 of [RFC7231]),
|
||||
// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer)
|
||||
//
|
||||
// Return ErrBadTrailer if contain any forbidden trailers.
|
||||
func (h *ResponseHeader) SetTrailer(trailer string) error {
|
||||
return h.SetTrailerBytes(s2b(trailer))
|
||||
}
|
||||
|
||||
// SetTrailerBytes sets Trailer header value for chunked response
|
||||
// to indicate which headers will be sent after the body.
|
||||
//
|
||||
// Use Set to set the trailer header later.
|
||||
//
|
||||
// Trailers are only supported with chunked transfer.
|
||||
// Trailers allow the sender to include additional headers at the end of chunked messages.
|
||||
//
|
||||
// The following trailers are forbidden:
|
||||
// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length),
|
||||
// 2. routing (e.g., Host),
|
||||
// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]),
|
||||
// 4. authentication (e.g., see [RFC7235] and [RFC6265]),
|
||||
// 5. response control data (e.g., see Section 7.1 of [RFC7231]),
|
||||
// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer)
|
||||
//
|
||||
// Return ErrBadTrailer if contain any forbidden trailers.
|
||||
func (h *ResponseHeader) SetTrailerBytes(trailer []byte) error {
|
||||
h.trailer = h.trailer[:0]
|
||||
return h.AddTrailerBytes(trailer)
|
||||
}
|
||||
|
||||
// AddTrailer add Trailer header value for chunked response
|
||||
// to indicate which headers will be sent after the body.
|
||||
//
|
||||
// Use Set to set the trailer header later.
|
||||
//
|
||||
// Trailers are only supported with chunked transfer.
|
||||
// Trailers allow the sender to include additional headers at the end of chunked messages.
|
||||
//
|
||||
// The following trailers are forbidden:
|
||||
// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length),
|
||||
// 2. routing (e.g., Host),
|
||||
// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]),
|
||||
// 4. authentication (e.g., see [RFC7235] and [RFC6265]),
|
||||
// 5. response control data (e.g., see Section 7.1 of [RFC7231]),
|
||||
// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer)
|
||||
//
|
||||
// Return ErrBadTrailer if contain any forbidden trailers.
|
||||
func (h *ResponseHeader) AddTrailer(trailer string) error {
|
||||
return h.AddTrailerBytes(s2b(trailer))
|
||||
}
|
||||
|
||||
var ErrBadTrailer = errors.New("contain forbidden trailer")
|
||||
|
||||
// AddTrailerBytes add Trailer header value for chunked response
|
||||
// to indicate which headers will be sent after the body.
|
||||
//
|
||||
// Use Set to set the trailer header later.
|
||||
//
|
||||
// Trailers are only supported with chunked transfer.
|
||||
// Trailers allow the sender to include additional headers at the end of chunked messages.
|
||||
//
|
||||
// The following trailers are forbidden:
|
||||
// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length),
|
||||
// 2. routing (e.g., Host),
|
||||
// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]),
|
||||
// 4. authentication (e.g., see [RFC7235] and [RFC6265]),
|
||||
// 5. response control data (e.g., see Section 7.1 of [RFC7231]),
|
||||
// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer)
|
||||
//
|
||||
// Return ErrBadTrailer if contain any forbidden trailers.
|
||||
func (h *ResponseHeader) AddTrailerBytes(trailer []byte) error {
|
||||
var err error
|
||||
for i := -1; i+1 < len(trailer); {
|
||||
trailer = trailer[i+1:]
|
||||
i = bytes.IndexByte(trailer, ',')
|
||||
if i < 0 {
|
||||
i = len(trailer)
|
||||
}
|
||||
key := trailer[:i]
|
||||
for len(key) > 0 && key[0] == ' ' {
|
||||
key = key[1:]
|
||||
}
|
||||
for len(key) > 0 && key[len(key)-1] == ' ' {
|
||||
key = key[:len(key)-1]
|
||||
}
|
||||
// Forbidden by RFC 7230, section 4.1.2
|
||||
if isBadTrailer(key) {
|
||||
err = ErrBadTrailer
|
||||
continue
|
||||
}
|
||||
h.bufKV.key = append(h.bufKV.key[:0], key...)
|
||||
normalizeHeaderKey(h.bufKV.key, h.disableNormalizing)
|
||||
h.trailer = appendArgBytes(h.trailer, h.bufKV.key, nil, argsNoValue)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// MultipartFormBoundary returns boundary part
|
||||
// from 'multipart/form-data; boundary=...' Content-Type.
|
||||
func (h *RequestHeader) MultipartFormBoundary() []byte {
|
||||
@@ -530,6 +643,115 @@ func (h *RequestHeader) SetRequestURIBytes(requestURI []byte) {
|
||||
h.requestURI = append(h.requestURI[:0], requestURI...)
|
||||
}
|
||||
|
||||
// SetTrailer sets Trailer header value for chunked request
|
||||
// to indicate which headers will be sent after the body.
|
||||
//
|
||||
// Use Set to set the trailer header later.
|
||||
//
|
||||
// Trailers are only supported with chunked transfer.
|
||||
// Trailers allow the sender to include additional headers at the end of chunked messages.
|
||||
//
|
||||
// The following trailers are forbidden:
|
||||
// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length),
|
||||
// 2. routing (e.g., Host),
|
||||
// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]),
|
||||
// 4. authentication (e.g., see [RFC7235] and [RFC6265]),
|
||||
// 5. response control data (e.g., see Section 7.1 of [RFC7231]),
|
||||
// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer)
|
||||
//
|
||||
// Return ErrBadTrailer if contain any forbidden trailers.
|
||||
func (h *RequestHeader) SetTrailer(trailer string) error {
|
||||
return h.SetTrailerBytes(s2b(trailer))
|
||||
}
|
||||
|
||||
// SetTrailerBytes sets Trailer header value for chunked request
|
||||
// to indicate which headers will be sent after the body.
|
||||
//
|
||||
// Use Set to set the trailer header later.
|
||||
//
|
||||
// Trailers are only supported with chunked transfer.
|
||||
// Trailers allow the sender to include additional headers at the end of chunked messages.
|
||||
//
|
||||
// The following trailers are forbidden:
|
||||
// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length),
|
||||
// 2. routing (e.g., Host),
|
||||
// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]),
|
||||
// 4. authentication (e.g., see [RFC7235] and [RFC6265]),
|
||||
// 5. response control data (e.g., see Section 7.1 of [RFC7231]),
|
||||
// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer)
|
||||
//
|
||||
// Return ErrBadTrailer if contain any forbidden trailers.
|
||||
func (h *RequestHeader) SetTrailerBytes(trailer []byte) error {
|
||||
h.trailer = h.trailer[:0]
|
||||
return h.AddTrailerBytes(trailer)
|
||||
}
|
||||
|
||||
// AddTrailer add Trailer header value for chunked request
|
||||
// to indicate which headers will be sent after the body.
|
||||
//
|
||||
// Use Set to set the trailer header later.
|
||||
//
|
||||
// Trailers are only supported with chunked transfer.
|
||||
// Trailers allow the sender to include additional headers at the end of chunked messages.
|
||||
//
|
||||
// The following trailers are forbidden:
|
||||
// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length),
|
||||
// 2. routing (e.g., Host),
|
||||
// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]),
|
||||
// 4. authentication (e.g., see [RFC7235] and [RFC6265]),
|
||||
// 5. response control data (e.g., see Section 7.1 of [RFC7231]),
|
||||
// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer)
|
||||
//
|
||||
// Return ErrBadTrailer if contain any forbidden trailers.
|
||||
func (h *RequestHeader) AddTrailer(trailer string) error {
|
||||
return h.AddTrailerBytes(s2b(trailer))
|
||||
}
|
||||
|
||||
// AddTrailerBytes add Trailer header value for chunked request
|
||||
// to indicate which headers will be sent after the body.
|
||||
//
|
||||
// Use Set to set the trailer header later.
|
||||
//
|
||||
// Trailers are only supported with chunked transfer.
|
||||
// Trailers allow the sender to include additional headers at the end of chunked messages.
|
||||
//
|
||||
// The following trailers are forbidden:
|
||||
// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length),
|
||||
// 2. routing (e.g., Host),
|
||||
// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]),
|
||||
// 4. authentication (e.g., see [RFC7235] and [RFC6265]),
|
||||
// 5. response control data (e.g., see Section 7.1 of [RFC7231]),
|
||||
// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer)
|
||||
//
|
||||
// Return ErrBadTrailer if contain any forbidden trailers.
|
||||
func (h *RequestHeader) AddTrailerBytes(trailer []byte) error {
|
||||
var err error
|
||||
for i := -1; i+1 < len(trailer); {
|
||||
trailer = trailer[i+1:]
|
||||
i = bytes.IndexByte(trailer, ',')
|
||||
if i < 0 {
|
||||
i = len(trailer)
|
||||
}
|
||||
key := trailer[:i]
|
||||
for len(key) > 0 && key[0] == ' ' {
|
||||
key = key[1:]
|
||||
}
|
||||
for len(key) > 0 && key[len(key)-1] == ' ' {
|
||||
key = key[:len(key)-1]
|
||||
}
|
||||
// Forbidden by RFC 7230, section 4.1.2
|
||||
if isBadTrailer(key) {
|
||||
err = ErrBadTrailer
|
||||
continue
|
||||
}
|
||||
h.bufKV.key = append(h.bufKV.key[:0], key...)
|
||||
normalizeHeaderKey(h.bufKV.key, h.disableNormalizing)
|
||||
h.trailer = appendArgBytes(h.trailer, h.bufKV.key, nil, argsNoValue)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// IsGet returns true if request method is GET.
|
||||
func (h *RequestHeader) IsGet() bool {
|
||||
return string(h.Method()) == MethodGet
|
||||
@@ -718,6 +940,7 @@ func (h *ResponseHeader) resetSkipNormalize() {
|
||||
|
||||
h.h = h.h[:0]
|
||||
h.cookies = h.cookies[:0]
|
||||
h.trailer = h.trailer[:0]
|
||||
}
|
||||
|
||||
// Reset clears request header.
|
||||
@@ -739,6 +962,7 @@ func (h *RequestHeader) resetSkipNormalize() {
|
||||
h.host = h.host[:0]
|
||||
h.contentType = h.contentType[:0]
|
||||
h.userAgent = h.userAgent[:0]
|
||||
h.trailer = h.trailer[:0]
|
||||
|
||||
h.h = h.h[:0]
|
||||
h.cookies = h.cookies[:0]
|
||||
@@ -766,6 +990,7 @@ func (h *ResponseHeader) CopyTo(dst *ResponseHeader) {
|
||||
dst.server = append(dst.server, h.server...)
|
||||
dst.h = copyArgs(dst.h, h.h)
|
||||
dst.cookies = copyArgs(dst.cookies, h.cookies)
|
||||
dst.trailer = copyArgs(dst.trailer, h.trailer)
|
||||
}
|
||||
|
||||
// CopyTo copies all the headers to dst.
|
||||
@@ -784,6 +1009,7 @@ func (h *RequestHeader) CopyTo(dst *RequestHeader) {
|
||||
dst.host = append(dst.host, h.host...)
|
||||
dst.contentType = append(dst.contentType, h.contentType...)
|
||||
dst.userAgent = append(dst.userAgent, h.userAgent...)
|
||||
dst.trailer = append(dst.trailer, h.trailer...)
|
||||
dst.h = copyArgs(dst.h, h.h)
|
||||
dst.cookies = copyArgs(dst.cookies, h.cookies)
|
||||
dst.cookiesCollected = h.cookiesCollected
|
||||
@@ -811,12 +1037,29 @@ func (h *ResponseHeader) VisitAll(f func(key, value []byte)) {
|
||||
f(strSetCookie, v)
|
||||
})
|
||||
}
|
||||
if len(h.trailer) > 0 {
|
||||
f(strTrailer, appendArgsKeyBytes(nil, h.trailer, strCommaSpace))
|
||||
}
|
||||
visitArgs(h.h, f)
|
||||
if h.ConnectionClose() {
|
||||
f(strConnection, strClose)
|
||||
}
|
||||
}
|
||||
|
||||
// VisitAllTrailer calls f for each response Trailer.
|
||||
//
|
||||
// f must not retain references to value after returning.
|
||||
func (h *ResponseHeader) VisitAllTrailer(f func(value []byte)) {
|
||||
visitArgsKey(h.trailer, f)
|
||||
}
|
||||
|
||||
// VisitAllTrailer calls f for each request Trailer.
|
||||
//
|
||||
// f must not retain references to value after returning.
|
||||
func (h *RequestHeader) VisitAllTrailer(f func(value []byte)) {
|
||||
visitArgsKey(h.trailer, f)
|
||||
}
|
||||
|
||||
// VisitAllCookie calls f for each response cookie.
|
||||
//
|
||||
// Cookie name is passed in key and the whole Set-Cookie header value
|
||||
@@ -858,6 +1101,9 @@ func (h *RequestHeader) VisitAll(f func(key, value []byte)) {
|
||||
if len(userAgent) > 0 {
|
||||
f(strUserAgent, userAgent)
|
||||
}
|
||||
if len(h.trailer) > 0 {
|
||||
f(strTrailer, appendArgsKeyBytes(nil, h.trailer, strCommaSpace))
|
||||
}
|
||||
|
||||
h.collectCookies()
|
||||
if len(h.cookies) > 0 {
|
||||
@@ -914,6 +1160,8 @@ func (h *ResponseHeader) del(key []byte) {
|
||||
h.contentLengthBytes = h.contentLengthBytes[:0]
|
||||
case HeaderConnection:
|
||||
h.connectionClose = false
|
||||
case HeaderTrailer:
|
||||
h.trailer = h.trailer[:0]
|
||||
}
|
||||
h.h = delAllArgsBytes(h.h, key)
|
||||
}
|
||||
@@ -946,6 +1194,8 @@ func (h *RequestHeader) del(key []byte) {
|
||||
h.contentLengthBytes = h.contentLengthBytes[:0]
|
||||
case HeaderConnection:
|
||||
h.connectionClose = false
|
||||
case HeaderTrailer:
|
||||
h.trailer = h.trailer[:0]
|
||||
}
|
||||
h.h = delAllArgsBytes(h.h, key)
|
||||
}
|
||||
@@ -991,6 +1241,9 @@ func (h *ResponseHeader) setSpecialHeader(key, value []byte) bool {
|
||||
if caseInsensitiveCompare(strTransferEncoding, key) {
|
||||
// Transfer-Encoding is managed automatically.
|
||||
return true
|
||||
} else if caseInsensitiveCompare(strTrailer, key) {
|
||||
_ = h.SetTrailerBytes(value)
|
||||
return true
|
||||
}
|
||||
case 'd':
|
||||
if caseInsensitiveCompare(strDate, key) {
|
||||
@@ -1036,6 +1289,9 @@ func (h *RequestHeader) setSpecialHeader(key, value []byte) bool {
|
||||
if caseInsensitiveCompare(strTransferEncoding, key) {
|
||||
// Transfer-Encoding is managed automatically.
|
||||
return true
|
||||
} else if caseInsensitiveCompare(strTrailer, key) {
|
||||
_ = h.SetTrailerBytes(value)
|
||||
return true
|
||||
}
|
||||
case 'h':
|
||||
if caseInsensitiveCompare(strHost, key) {
|
||||
@@ -1060,6 +1316,9 @@ func (h *RequestHeader) setSpecialHeader(key, value []byte) bool {
|
||||
// the Content-Type, Content-Length, Connection, Server, Set-Cookie,
|
||||
// Transfer-Encoding and Date headers can only be set once and will
|
||||
// overwrite the previous value.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),
|
||||
// it will be sent after the chunked response body.
|
||||
func (h *ResponseHeader) Add(key, value string) {
|
||||
h.AddBytesKV(s2b(key), s2b(value))
|
||||
}
|
||||
@@ -1072,6 +1331,9 @@ func (h *ResponseHeader) Add(key, value string) {
|
||||
// the Content-Type, Content-Length, Connection, Server, Set-Cookie,
|
||||
// Transfer-Encoding and Date headers can only be set once and will
|
||||
// overwrite the previous value.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),
|
||||
// it will be sent after the chunked response body.
|
||||
func (h *ResponseHeader) AddBytesK(key []byte, value string) {
|
||||
h.AddBytesKV(key, s2b(value))
|
||||
}
|
||||
@@ -1084,6 +1346,9 @@ func (h *ResponseHeader) AddBytesK(key []byte, value string) {
|
||||
// the Content-Type, Content-Length, Connection, Server, Set-Cookie,
|
||||
// Transfer-Encoding and Date headers can only be set once and will
|
||||
// overwrite the previous value.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),
|
||||
// it will be sent after the chunked response body.
|
||||
func (h *ResponseHeader) AddBytesV(key string, value []byte) {
|
||||
h.AddBytesKV(s2b(key), value)
|
||||
}
|
||||
@@ -1096,6 +1361,9 @@ func (h *ResponseHeader) AddBytesV(key string, value []byte) {
|
||||
// the Content-Type, Content-Length, Connection, Server, Set-Cookie,
|
||||
// Transfer-Encoding and Date headers can only be set once and will
|
||||
// overwrite the previous value.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),
|
||||
// it will be sent after the chunked response body.
|
||||
func (h *ResponseHeader) AddBytesKV(key, value []byte) {
|
||||
if h.setSpecialHeader(key, value) {
|
||||
return
|
||||
@@ -1107,6 +1375,9 @@ func (h *ResponseHeader) AddBytesKV(key, value []byte) {
|
||||
|
||||
// Set sets the given 'key: value' header.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),
|
||||
// it will be sent after the chunked response body.
|
||||
//
|
||||
// Use Add for setting multiple header values under the same key.
|
||||
func (h *ResponseHeader) Set(key, value string) {
|
||||
initHeaderKV(&h.bufKV, key, value, h.disableNormalizing)
|
||||
@@ -1115,6 +1386,9 @@ func (h *ResponseHeader) Set(key, value string) {
|
||||
|
||||
// SetBytesK sets the given 'key: value' header.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),
|
||||
// it will be sent after the chunked response body.
|
||||
//
|
||||
// Use AddBytesK for setting multiple header values under the same key.
|
||||
func (h *ResponseHeader) SetBytesK(key []byte, value string) {
|
||||
h.bufKV.value = append(h.bufKV.value[:0], value...)
|
||||
@@ -1123,6 +1397,9 @@ func (h *ResponseHeader) SetBytesK(key []byte, value string) {
|
||||
|
||||
// SetBytesV sets the given 'key: value' header.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),
|
||||
// it will be sent after the chunked response body.
|
||||
//
|
||||
// Use AddBytesV for setting multiple header values under the same key.
|
||||
func (h *ResponseHeader) SetBytesV(key string, value []byte) {
|
||||
k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
|
||||
@@ -1131,6 +1408,9 @@ func (h *ResponseHeader) SetBytesV(key string, value []byte) {
|
||||
|
||||
// SetBytesKV sets the given 'key: value' header.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),
|
||||
// it will be sent after the chunked response body.
|
||||
//
|
||||
// Use AddBytesKV for setting multiple header values under the same key.
|
||||
func (h *ResponseHeader) SetBytesKV(key, value []byte) {
|
||||
h.bufKV.key = append(h.bufKV.key[:0], key...)
|
||||
@@ -1140,6 +1420,9 @@ func (h *ResponseHeader) SetBytesKV(key, value []byte) {
|
||||
|
||||
// SetCanonical sets the given 'key: value' header assuming that
|
||||
// key is in canonical form.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),
|
||||
// it will be sent after the chunked response body.
|
||||
func (h *ResponseHeader) SetCanonical(key, value []byte) {
|
||||
if h.setSpecialHeader(key, value) {
|
||||
return
|
||||
@@ -1253,6 +1536,9 @@ func (h *RequestHeader) DelAllCookies() {
|
||||
//
|
||||
// Multiple headers with the same key may be added with this function.
|
||||
// Use Set for setting a single header for the given key.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),
|
||||
// it will be sent after the chunked request body.
|
||||
func (h *RequestHeader) Add(key, value string) {
|
||||
h.AddBytesKV(s2b(key), s2b(value))
|
||||
}
|
||||
@@ -1261,6 +1547,9 @@ func (h *RequestHeader) Add(key, value string) {
|
||||
//
|
||||
// Multiple headers with the same key may be added with this function.
|
||||
// Use SetBytesK for setting a single header for the given key.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),
|
||||
// it will be sent after the chunked request body.
|
||||
func (h *RequestHeader) AddBytesK(key []byte, value string) {
|
||||
h.AddBytesKV(key, s2b(value))
|
||||
}
|
||||
@@ -1269,6 +1558,9 @@ func (h *RequestHeader) AddBytesK(key []byte, value string) {
|
||||
//
|
||||
// Multiple headers with the same key may be added with this function.
|
||||
// Use SetBytesV for setting a single header for the given key.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),
|
||||
// it will be sent after the chunked request body.
|
||||
func (h *RequestHeader) AddBytesV(key string, value []byte) {
|
||||
h.AddBytesKV(s2b(key), value)
|
||||
}
|
||||
@@ -1281,6 +1573,9 @@ func (h *RequestHeader) AddBytesV(key string, value []byte) {
|
||||
// the Content-Type, Content-Length, Connection, Cookie,
|
||||
// Transfer-Encoding, Host and User-Agent headers can only be set once
|
||||
// and will overwrite the previous value.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see AddTrailer for more details),
|
||||
// it will be sent after the chunked request body.
|
||||
func (h *RequestHeader) AddBytesKV(key, value []byte) {
|
||||
if h.setSpecialHeader(key, value) {
|
||||
return
|
||||
@@ -1292,6 +1587,9 @@ func (h *RequestHeader) AddBytesKV(key, value []byte) {
|
||||
|
||||
// Set sets the given 'key: value' header.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),
|
||||
// it will be sent after the chunked request body.
|
||||
//
|
||||
// Use Add for setting multiple header values under the same key.
|
||||
func (h *RequestHeader) Set(key, value string) {
|
||||
initHeaderKV(&h.bufKV, key, value, h.disableNormalizing)
|
||||
@@ -1300,6 +1598,9 @@ func (h *RequestHeader) Set(key, value string) {
|
||||
|
||||
// SetBytesK sets the given 'key: value' header.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),
|
||||
// it will be sent after the chunked request body.
|
||||
//
|
||||
// Use AddBytesK for setting multiple header values under the same key.
|
||||
func (h *RequestHeader) SetBytesK(key []byte, value string) {
|
||||
h.bufKV.value = append(h.bufKV.value[:0], value...)
|
||||
@@ -1308,6 +1609,9 @@ func (h *RequestHeader) SetBytesK(key []byte, value string) {
|
||||
|
||||
// SetBytesV sets the given 'key: value' header.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),
|
||||
// it will be sent after the chunked request body.
|
||||
//
|
||||
// Use AddBytesV for setting multiple header values under the same key.
|
||||
func (h *RequestHeader) SetBytesV(key string, value []byte) {
|
||||
k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
|
||||
@@ -1316,6 +1620,9 @@ func (h *RequestHeader) SetBytesV(key string, value []byte) {
|
||||
|
||||
// SetBytesKV sets the given 'key: value' header.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),
|
||||
// it will be sent after the chunked request body.
|
||||
//
|
||||
// Use AddBytesKV for setting multiple header values under the same key.
|
||||
func (h *RequestHeader) SetBytesKV(key, value []byte) {
|
||||
h.bufKV.key = append(h.bufKV.key[:0], key...)
|
||||
@@ -1325,6 +1632,9 @@ func (h *RequestHeader) SetBytesKV(key, value []byte) {
|
||||
|
||||
// SetCanonical sets the given 'key: value' header assuming that
|
||||
// key is in canonical form.
|
||||
//
|
||||
// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),
|
||||
// it will be sent after the chunked request body.
|
||||
func (h *RequestHeader) SetCanonical(key, value []byte) {
|
||||
if h.setSpecialHeader(key, value) {
|
||||
return
|
||||
@@ -1390,6 +1700,8 @@ func (h *ResponseHeader) peek(key []byte) []byte {
|
||||
return h.contentLengthBytes
|
||||
case HeaderSetCookie:
|
||||
return appendResponseCookieBytes(nil, h.cookies)
|
||||
case HeaderTrailer:
|
||||
return appendArgsKeyBytes(nil, h.trailer, strCommaSpace)
|
||||
default:
|
||||
return peekArgBytes(h.h, key)
|
||||
}
|
||||
@@ -1415,6 +1727,8 @@ func (h *RequestHeader) peek(key []byte) []byte {
|
||||
return appendRequestCookieBytes(nil, h.cookies)
|
||||
}
|
||||
return peekArgBytes(h.h, key)
|
||||
case HeaderTrailer:
|
||||
return appendArgsKeyBytes(nil, h.trailer, strCommaSpace)
|
||||
default:
|
||||
return peekArgBytes(h.h, key)
|
||||
}
|
||||
@@ -1498,6 +1812,61 @@ func (h *ResponseHeader) tryRead(r *bufio.Reader, n int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadTrailer reads response trailer header from r.
|
||||
//
|
||||
// io.EOF is returned if r is closed before reading the first byte.
|
||||
func (h *ResponseHeader) ReadTrailer(r *bufio.Reader) error {
|
||||
n := 1
|
||||
for {
|
||||
err := h.tryReadTrailer(r, n)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if err != errNeedMore {
|
||||
return err
|
||||
}
|
||||
n = r.Buffered() + 1
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ResponseHeader) tryReadTrailer(r *bufio.Reader, n int) error {
|
||||
b, err := r.Peek(n)
|
||||
if len(b) == 0 {
|
||||
// Return ErrTimeout on any timeout.
|
||||
if x, ok := err.(interface{ Timeout() bool }); ok && x.Timeout() {
|
||||
return ErrTimeout
|
||||
}
|
||||
|
||||
if n == 1 || err == io.EOF {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
// This is for go 1.6 bug. See https://github.com/golang/go/issues/14121 .
|
||||
if err == bufio.ErrBufferFull {
|
||||
if h.secureErrorLogMessage {
|
||||
return &ErrSmallBuffer{
|
||||
error: fmt.Errorf("error when reading response trailer"),
|
||||
}
|
||||
}
|
||||
return &ErrSmallBuffer{
|
||||
error: fmt.Errorf("error when reading response trailer: %s", errSmallBuffer),
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("error when reading response trailer: %s", err)
|
||||
}
|
||||
b = mustPeekBuffered(r)
|
||||
headersLen, errParse := h.parseTrailer(b)
|
||||
if errParse != nil {
|
||||
if err == io.EOF {
|
||||
return err
|
||||
}
|
||||
return headerError("response", err, errParse, b, h.secureErrorLogMessage)
|
||||
}
|
||||
mustDiscard(r, headersLen)
|
||||
return nil
|
||||
}
|
||||
|
||||
func headerError(typ string, err, errParse error, b []byte, secureErrorLogMessage bool) error {
|
||||
if errParse != errNeedMore {
|
||||
return headerErrorMsg(typ, errParse, b, secureErrorLogMessage)
|
||||
@@ -1552,6 +1921,61 @@ func (h *RequestHeader) readLoop(r *bufio.Reader, waitForMore bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
// ReadTrailer reads request trailer header from r.
|
||||
//
|
||||
// io.EOF is returned if r is closed before reading the first byte.
|
||||
func (h *RequestHeader) ReadTrailer(r *bufio.Reader) error {
|
||||
n := 1
|
||||
for {
|
||||
err := h.tryReadTrailer(r, n)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if err != errNeedMore {
|
||||
return err
|
||||
}
|
||||
n = r.Buffered() + 1
|
||||
}
|
||||
}
|
||||
|
||||
func (h *RequestHeader) tryReadTrailer(r *bufio.Reader, n int) error {
|
||||
b, err := r.Peek(n)
|
||||
if len(b) == 0 {
|
||||
// Return ErrTimeout on any timeout.
|
||||
if x, ok := err.(interface{ Timeout() bool }); ok && x.Timeout() {
|
||||
return ErrTimeout
|
||||
}
|
||||
|
||||
if n == 1 || err == io.EOF {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
// This is for go 1.6 bug. See https://github.com/golang/go/issues/14121 .
|
||||
if err == bufio.ErrBufferFull {
|
||||
if h.secureErrorLogMessage {
|
||||
return &ErrSmallBuffer{
|
||||
error: fmt.Errorf("error when reading request trailer"),
|
||||
}
|
||||
}
|
||||
return &ErrSmallBuffer{
|
||||
error: fmt.Errorf("error when reading request trailer: %s", errSmallBuffer),
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("error when reading request trailer: %s", err)
|
||||
}
|
||||
b = mustPeekBuffered(r)
|
||||
headersLen, errParse := h.parseTrailer(b)
|
||||
if errParse != nil {
|
||||
if err == io.EOF {
|
||||
return err
|
||||
}
|
||||
return headerError("request", err, errParse, b, h.secureErrorLogMessage)
|
||||
}
|
||||
mustDiscard(r, headersLen)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *RequestHeader) tryRead(r *bufio.Reader, n int) error {
|
||||
h.resetSkipNormalize()
|
||||
b, err := r.Peek(n)
|
||||
@@ -1648,6 +2072,8 @@ func (h *ResponseHeader) WriteTo(w io.Writer) (int64, error) {
|
||||
|
||||
// Header returns response header representation.
|
||||
//
|
||||
// Headers that set as Trailer will not represent. Use TrailerHeader for trailers.
|
||||
//
|
||||
// The returned value is valid until the request is released,
|
||||
// either though ReleaseRequest or your request handler returning.
|
||||
// Do not store references to returned value. Make copies instead.
|
||||
@@ -1656,6 +2082,29 @@ func (h *ResponseHeader) Header() []byte {
|
||||
return h.bufKV.value
|
||||
}
|
||||
|
||||
// writeTrailer writes response trailer to w.
|
||||
func (h *ResponseHeader) writeTrailer(w *bufio.Writer) error {
|
||||
_, err := w.Write(h.TrailerHeader())
|
||||
return err
|
||||
}
|
||||
|
||||
// TrailerHeader returns response trailer header representation.
|
||||
//
|
||||
// Trailers will only be received with chunked transfer.
|
||||
//
|
||||
// The returned value is valid until the request is released,
|
||||
// either though ReleaseRequest or your request handler returning.
|
||||
// Do not store references to returned value. Make copies instead.
|
||||
func (h *ResponseHeader) TrailerHeader() []byte {
|
||||
h.bufKV.value = h.bufKV.value[:0]
|
||||
for _, t := range h.trailer {
|
||||
value := h.peek(t.key)
|
||||
h.bufKV.value = appendHeaderLine(h.bufKV.value, t.key, value)
|
||||
}
|
||||
h.bufKV.value = append(h.bufKV.value, strCRLF...)
|
||||
return h.bufKV.value
|
||||
}
|
||||
|
||||
// String returns response header representation.
|
||||
func (h *ResponseHeader) String() string {
|
||||
return string(h.Header())
|
||||
@@ -1702,11 +2151,24 @@ func (h *ResponseHeader) AppendBytes(dst []byte) []byte {
|
||||
|
||||
for i, n := 0, len(h.h); i < n; i++ {
|
||||
kv := &h.h[i]
|
||||
if h.noDefaultDate || !bytes.Equal(kv.key, strDate) {
|
||||
|
||||
// Exclude trailer from header
|
||||
exclude := false
|
||||
for _, t := range h.trailer {
|
||||
if bytes.Equal(kv.key, t.key) {
|
||||
exclude = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exclude && (h.noDefaultDate || !bytes.Equal(kv.key, strDate)) {
|
||||
dst = appendHeaderLine(dst, kv.key, kv.value)
|
||||
}
|
||||
}
|
||||
|
||||
if len(h.trailer) > 0 {
|
||||
dst = appendHeaderLine(dst, strTrailer, appendArgsKeyBytes(nil, h.trailer, strCommaSpace))
|
||||
}
|
||||
|
||||
n := len(h.cookies)
|
||||
if n > 0 {
|
||||
for i := 0; i < n; i++ {
|
||||
@@ -1738,6 +2200,8 @@ func (h *RequestHeader) WriteTo(w io.Writer) (int64, error) {
|
||||
|
||||
// Header returns request header representation.
|
||||
//
|
||||
// Headers that set as Trailer will not represent. Use TrailerHeader for trailers.
|
||||
//
|
||||
// The returned value is valid until the request is released,
|
||||
// either though ReleaseRequest or your request handler returning.
|
||||
// Do not store references to returned value. Make copies instead.
|
||||
@@ -1746,6 +2210,29 @@ func (h *RequestHeader) Header() []byte {
|
||||
return h.bufKV.value
|
||||
}
|
||||
|
||||
// writeTrailer writes request trailer to w.
|
||||
func (h *RequestHeader) writeTrailer(w *bufio.Writer) error {
|
||||
_, err := w.Write(h.TrailerHeader())
|
||||
return err
|
||||
}
|
||||
|
||||
// TrailerHeader returns request trailer header representation.
|
||||
//
|
||||
// Trailers will only be received with chunked transfer.
|
||||
//
|
||||
// The returned value is valid until the request is released,
|
||||
// either though ReleaseRequest or your request handler returning.
|
||||
// Do not store references to returned value. Make copies instead.
|
||||
func (h *RequestHeader) TrailerHeader() []byte {
|
||||
h.bufKV.value = h.bufKV.value[:0]
|
||||
for _, t := range h.trailer {
|
||||
value := h.peek(t.key)
|
||||
h.bufKV.value = appendHeaderLine(h.bufKV.value, t.key, value)
|
||||
}
|
||||
h.bufKV.value = append(h.bufKV.value, strCRLF...)
|
||||
return h.bufKV.value
|
||||
}
|
||||
|
||||
// RawHeaders returns raw header key/value bytes.
|
||||
//
|
||||
// Depending on server configuration, header keys may be normalized to
|
||||
@@ -1798,7 +2285,21 @@ func (h *RequestHeader) AppendBytes(dst []byte) []byte {
|
||||
|
||||
for i, n := 0, len(h.h); i < n; i++ {
|
||||
kv := &h.h[i]
|
||||
dst = appendHeaderLine(dst, kv.key, kv.value)
|
||||
// Exclude trailer from header
|
||||
exclude := false
|
||||
for _, t := range h.trailer {
|
||||
if bytes.Equal(kv.key, t.key) {
|
||||
exclude = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exclude {
|
||||
dst = appendHeaderLine(dst, kv.key, kv.value)
|
||||
}
|
||||
}
|
||||
|
||||
if len(h.trailer) > 0 {
|
||||
dst = appendHeaderLine(dst, strTrailer, appendArgsKeyBytes(nil, h.trailer, strCommaSpace))
|
||||
}
|
||||
|
||||
// there is no need in h.collectCookies() here, since if cookies aren't collected yet,
|
||||
@@ -1837,6 +2338,37 @@ func (h *ResponseHeader) parse(buf []byte) (int, error) {
|
||||
return m + n, nil
|
||||
}
|
||||
|
||||
func (h *ResponseHeader) parseTrailer(buf []byte) (int, error) {
|
||||
if buf[0] == '0' {
|
||||
buf = buf[len(strCRLF)+1:]
|
||||
}
|
||||
var s headerScanner
|
||||
s.b = buf
|
||||
s.disableNormalizing = h.disableNormalizing
|
||||
var err error
|
||||
for s.next() {
|
||||
if len(s.key) > 0 {
|
||||
if bytes.IndexByte(s.key, ' ') != -1 || bytes.IndexByte(s.key, '\t') != -1 {
|
||||
err = fmt.Errorf("invalid trailer key %q", s.key)
|
||||
continue
|
||||
}
|
||||
// Forbidden by RFC 7230, section 4.1.2
|
||||
if isBadTrailer(s.key) {
|
||||
err = fmt.Errorf("forbidden trailer key %q", s.key)
|
||||
continue
|
||||
}
|
||||
}
|
||||
h.h = appendArgBytes(h.h, s.key, s.value, argsHasValue)
|
||||
}
|
||||
if s.err != nil {
|
||||
return 0, s.err
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return s.hLen, nil
|
||||
}
|
||||
|
||||
func (h *RequestHeader) ignoreBody() bool {
|
||||
return h.IsGet() || h.IsHead()
|
||||
}
|
||||
@@ -1859,6 +2391,77 @@ func (h *RequestHeader) parse(buf []byte) (int, error) {
|
||||
return m + n, nil
|
||||
}
|
||||
|
||||
func (h *RequestHeader) parseTrailer(buf []byte) (int, error) {
|
||||
if buf[0] == '0' {
|
||||
buf = buf[len(strCRLF)+1:]
|
||||
}
|
||||
var s headerScanner
|
||||
s.b = buf
|
||||
s.disableNormalizing = h.disableNormalizing
|
||||
var err error
|
||||
for s.next() {
|
||||
if len(s.key) > 0 {
|
||||
if bytes.IndexByte(s.key, ' ') != -1 || bytes.IndexByte(s.key, '\t') != -1 {
|
||||
err = fmt.Errorf("invalid trailer key %q", s.key)
|
||||
continue
|
||||
}
|
||||
// Forbidden by RFC 7230, section 4.1.2
|
||||
if isBadTrailer(s.key) {
|
||||
err = fmt.Errorf("forbidden trailer key %q", s.key)
|
||||
continue
|
||||
}
|
||||
h.h = appendArgBytes(h.h, s.key, s.value, argsHasValue)
|
||||
}
|
||||
}
|
||||
if s.err != nil {
|
||||
return 0, s.err
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return s.hLen, nil
|
||||
}
|
||||
|
||||
func isBadTrailer(key []byte) bool {
|
||||
switch key[0] | 0x20 {
|
||||
case 'a':
|
||||
return caseInsensitiveCompare(key, strAuthorization)
|
||||
case 'c':
|
||||
if len(key) > len(HeaderContentType) && caseInsensitiveCompare(key[:8], strContentType[:8]) {
|
||||
// skip compare prefix 'Content-'
|
||||
return caseInsensitiveCompare(key[8:], strContentEncoding[8:]) ||
|
||||
caseInsensitiveCompare(key[8:], strContentLength[8:]) ||
|
||||
caseInsensitiveCompare(key[8:], strContentType[8:]) ||
|
||||
caseInsensitiveCompare(key[8:], strContentRange[8:])
|
||||
}
|
||||
return caseInsensitiveCompare(key, strConnection)
|
||||
case 'e':
|
||||
return caseInsensitiveCompare(key, strExpect)
|
||||
case 'h':
|
||||
return caseInsensitiveCompare(key, strHost)
|
||||
case 'k':
|
||||
return caseInsensitiveCompare(key, strKeepAlive)
|
||||
case 'm':
|
||||
return caseInsensitiveCompare(key, strMaxForwards)
|
||||
case 'p':
|
||||
if len(key) > len(HeaderProxyConnection) && caseInsensitiveCompare(key[:6], strProxyConnection[:6]) {
|
||||
// skip compare prefix 'Proxy-'
|
||||
return caseInsensitiveCompare(key[6:], strProxyConnection[6:]) ||
|
||||
caseInsensitiveCompare(key[6:], strProxyAuthenticate[6:]) ||
|
||||
caseInsensitiveCompare(key[6:], strProxyAuthorization[6:])
|
||||
}
|
||||
case 'r':
|
||||
return caseInsensitiveCompare(key, strRange)
|
||||
case 't':
|
||||
return caseInsensitiveCompare(key, strTE) ||
|
||||
caseInsensitiveCompare(key, strTrailer) ||
|
||||
caseInsensitiveCompare(key, strTransferEncoding)
|
||||
case 'w':
|
||||
return caseInsensitiveCompare(key, strWWWAuthenticate)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *ResponseHeader) parseFirstLine(buf []byte) (int, error) {
|
||||
bNext := buf
|
||||
var b []byte
|
||||
@@ -2028,6 +2631,10 @@ func (h *ResponseHeader) parseHeaders(buf []byte) (int, error) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
if caseInsensitiveCompare(s.key, strTrailer) {
|
||||
err = h.SetTrailerBytes(s.value)
|
||||
continue
|
||||
}
|
||||
}
|
||||
h.h = appendArgBytes(h.h, s.key, s.value, argsHasValue)
|
||||
}
|
||||
@@ -2050,7 +2657,7 @@ func (h *ResponseHeader) parseHeaders(buf []byte) (int, error) {
|
||||
h.connectionClose = !hasHeaderValue(v, strKeepAlive)
|
||||
}
|
||||
|
||||
return len(buf) - len(s.b), nil
|
||||
return len(buf) - len(s.b), err
|
||||
}
|
||||
|
||||
func (h *RequestHeader) parseHeaders(buf []byte) (int, error) {
|
||||
@@ -2116,6 +2723,10 @@ func (h *RequestHeader) parseHeaders(buf []byte) (int, error) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
if caseInsensitiveCompare(s.key, strTrailer) {
|
||||
err = h.SetTrailerBytes(s.value)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
h.h = appendArgBytes(h.h, s.key, s.value, argsHasValue)
|
||||
@@ -2159,13 +2770,15 @@ func (h *RequestHeader) collectCookies() {
|
||||
h.cookiesCollected = true
|
||||
}
|
||||
|
||||
var errNonNumericChars = errors.New("non-numeric chars found")
|
||||
|
||||
func parseContentLength(b []byte) (int, error) {
|
||||
v, n, err := parseUintBuf(b)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
return -1, fmt.Errorf("cannot parse Content-Length: %w", err)
|
||||
}
|
||||
if n != len(b) {
|
||||
return -1, fmt.Errorf("non-numeric chars at the end of Content-Length")
|
||||
return -1, fmt.Errorf("cannot parse Content-Length: %w", errNonNumericChars)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
@@ -2499,6 +3112,17 @@ func AppendNormalizedHeaderKeyBytes(dst, key []byte) []byte {
|
||||
return AppendNormalizedHeaderKey(dst, b2s(key))
|
||||
}
|
||||
|
||||
func appendArgsKeyBytes(dst []byte, args []argsKV, sep []byte) []byte {
|
||||
for i, n := 0, len(args); i < n; i++ {
|
||||
kv := &args[i]
|
||||
dst = append(dst, kv.key...)
|
||||
if i+1 < n {
|
||||
dst = append(dst, sep...)
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
var (
|
||||
errNeedMore = errors.New("need more data: cannot find trailing lf")
|
||||
errInvalidName = errors.New("invalid header name")
|
||||
|
||||
+223
-94
@@ -4,9 +4,9 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -535,6 +535,7 @@ func TestRequestHeaderDel(t *testing.T) {
|
||||
h.Set("User-Agent", "asdfas")
|
||||
h.Set("Content-Length", "1123")
|
||||
h.Set("Cookie", "foobar=baz")
|
||||
h.Set(HeaderTrailer, "foo, bar")
|
||||
|
||||
h.Del("foo-bar")
|
||||
h.Del("connection")
|
||||
@@ -543,6 +544,7 @@ func TestRequestHeaderDel(t *testing.T) {
|
||||
h.Del("user-agent")
|
||||
h.Del("content-length")
|
||||
h.Del("cookie")
|
||||
h.Del("trailer")
|
||||
|
||||
hv := h.Peek("aaa")
|
||||
if string(hv) != "bbb" {
|
||||
@@ -576,6 +578,10 @@ func TestRequestHeaderDel(t *testing.T) {
|
||||
if len(hv) > 0 {
|
||||
t.Fatalf("non-zero value: %q", hv)
|
||||
}
|
||||
hv = h.Peek(HeaderTrailer)
|
||||
if len(hv) > 0 {
|
||||
t.Fatalf("non-zero value: %q", hv)
|
||||
}
|
||||
|
||||
cv := h.Cookie("foobar")
|
||||
if len(cv) > 0 {
|
||||
@@ -596,6 +602,7 @@ func TestResponseHeaderDel(t *testing.T) {
|
||||
h.Set(HeaderContentType, "aaa")
|
||||
h.Set(HeaderServer, "aaabbb")
|
||||
h.Set(HeaderContentLength, "1123")
|
||||
h.Set(HeaderTrailer, "foo, bar")
|
||||
|
||||
var c Cookie
|
||||
c.SetKey("foo")
|
||||
@@ -608,6 +615,7 @@ func TestResponseHeaderDel(t *testing.T) {
|
||||
h.Del(HeaderServer)
|
||||
h.Del("content-length")
|
||||
h.Del("set-cookie")
|
||||
h.Del("trailer")
|
||||
|
||||
hv := h.Peek("aaa")
|
||||
if string(hv) != "bbb" {
|
||||
@@ -633,6 +641,10 @@ func TestResponseHeaderDel(t *testing.T) {
|
||||
if len(hv) > 0 {
|
||||
t.Fatalf("non-zero value: %q", hv)
|
||||
}
|
||||
hv = h.Peek(HeaderTrailer)
|
||||
if len(hv) > 0 {
|
||||
t.Fatalf("non-zero value: %q", hv)
|
||||
}
|
||||
|
||||
if h.Cookie(&c) {
|
||||
t.Fatalf("unexpected cookie obtianed: %q", &c)
|
||||
@@ -642,6 +654,51 @@ func TestResponseHeaderDel(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponseHeaderSetTrailerGetBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
h := &ResponseHeader{}
|
||||
h.noDefaultDate = true
|
||||
h.Set("Foo", "bar")
|
||||
h.Set(HeaderTrailer, "Baz")
|
||||
h.Set("Baz", "test")
|
||||
|
||||
headerBytes := h.Header()
|
||||
n, err := h.parseFirstLine(headerBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if string(headerBytes[n:]) != "Foo: bar\r\nTrailer: Baz\r\n\r\n" {
|
||||
t.Fatalf("Unexpected header: %q. Expected %s", headerBytes[n:], "Foo: bar\nTrailer: Baz\n\n")
|
||||
}
|
||||
if string(h.TrailerHeader()) != "Baz: test\r\n\r\n" {
|
||||
t.Fatalf("Unexpected trailer header: %q. Expected %s", h.TrailerHeader(), "Baz: test\r\n\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestHeaderSetTrailerGetBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
h := &RequestHeader{}
|
||||
h.Set("Foo", "bar")
|
||||
h.Set(HeaderTrailer, "Baz")
|
||||
h.Set("Baz", "test")
|
||||
|
||||
headerBytes := h.Header()
|
||||
n, err := h.parseFirstLine(headerBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if string(headerBytes[n:]) != "Foo: bar\r\nTrailer: Baz\r\n\r\n" {
|
||||
t.Fatalf("Unexpected header: %q. Expected %s", headerBytes[n:], "Foo: bar\nTrailer: Baz\n\n")
|
||||
}
|
||||
if string(h.TrailerHeader()) != "Baz: test\r\n\r\n" {
|
||||
t.Fatalf("Unexpected trailer header: %q. Expected %s", h.TrailerHeader(), "Baz: test\r\n\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendNormalizedHeaderKeyBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1218,6 +1275,7 @@ func TestResponseHeaderCopyTo(t *testing.T) {
|
||||
h.Set(HeaderSetCookie, "foo=bar")
|
||||
h.Set(HeaderContentType, "foobar")
|
||||
h.Set("AAA-BBB", "aaaa")
|
||||
h.Set(HeaderTrailer, "foo, bar")
|
||||
|
||||
var h1 ResponseHeader
|
||||
h.CopyTo(&h1)
|
||||
@@ -1230,6 +1288,9 @@ func TestResponseHeaderCopyTo(t *testing.T) {
|
||||
if !bytes.Equal(h1.Peek("aaa-bbb"), h.Peek("AAA-BBB")) {
|
||||
t.Fatalf("unexpected aaa-bbb %q. Expected %q", h1.Peek("aaa-bbb"), h.Peek("aaa-bbb"))
|
||||
}
|
||||
if !bytes.Equal(h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) {
|
||||
t.Fatalf("unexpected trailer %q. Expected %q", h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer))
|
||||
}
|
||||
|
||||
// flush buf
|
||||
h.bufKV = argsKV{}
|
||||
@@ -1249,6 +1310,7 @@ func TestRequestHeaderCopyTo(t *testing.T) {
|
||||
h.Set(HeaderContentType, "foobar")
|
||||
h.Set(HeaderHost, "aaaa")
|
||||
h.Set("aaaxxx", "123")
|
||||
h.Set(HeaderTrailer, "foo, bar")
|
||||
|
||||
var h1 RequestHeader
|
||||
h.CopyTo(&h1)
|
||||
@@ -1264,6 +1326,9 @@ func TestRequestHeaderCopyTo(t *testing.T) {
|
||||
if !bytes.Equal(h1.Peek("aaaxxx"), h.Peek("aaaxxx")) {
|
||||
t.Fatalf("unexpected aaaxxx %q. Expected %q", h1.Peek("aaaxxx"), h.Peek("aaaxxx"))
|
||||
}
|
||||
if !bytes.Equal(h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer)) {
|
||||
t.Fatalf("unexpected trailer %q. Expected %q", h1.Peek(HeaderTrailer), h.Peek(HeaderTrailer))
|
||||
}
|
||||
|
||||
// flush buf
|
||||
h.bufKV = argsKV{}
|
||||
@@ -1421,14 +1486,14 @@ func TestResponseHeaderVisitAll(t *testing.T) {
|
||||
|
||||
var h ResponseHeader
|
||||
|
||||
r := bytes.NewBufferString("HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 123\r\nSet-Cookie: aa=bb; path=/foo/bar\r\nSet-Cookie: ccc\r\n\r\n")
|
||||
r := bytes.NewBufferString("HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 123\r\nSet-Cookie: aa=bb; path=/foo/bar\r\nSet-Cookie: ccc\r\nTrailer: Foo, Bar\r\n\r\n")
|
||||
br := bufio.NewReader(r)
|
||||
if err := h.Read(br); err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if h.Len() != 4 {
|
||||
t.Fatalf("Unexpected number of headers: %d. Expected 4", h.Len())
|
||||
if h.Len() != 5 {
|
||||
t.Fatalf("Unexpected number of headers: %d. Expected 5", h.Len())
|
||||
}
|
||||
contentLengthCount := 0
|
||||
contentTypeCount := 0
|
||||
@@ -1455,6 +1520,10 @@ func TestResponseHeaderVisitAll(t *testing.T) {
|
||||
t.Fatalf("unexpected cookie header: %q. Expected %q", v, "ccc")
|
||||
}
|
||||
cookieCount++
|
||||
case HeaderTrailer:
|
||||
if v != "Foo, Bar" {
|
||||
t.Fatalf("Unexpected trailer header %q. Expected %q", v, "Foo, Bar")
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unexpected header %q=%q", k, v)
|
||||
}
|
||||
@@ -1475,14 +1544,14 @@ func TestRequestHeaderVisitAll(t *testing.T) {
|
||||
|
||||
var h RequestHeader
|
||||
|
||||
r := bytes.NewBufferString("GET / HTTP/1.1\r\nHost: aa.com\r\nXX: YYY\r\nXX: ZZ\r\nCookie: a=b; c=d\r\n\r\n")
|
||||
r := bytes.NewBufferString("GET / HTTP/1.1\r\nHost: aa.com\r\nXX: YYY\r\nXX: ZZ\r\nCookie: a=b; c=d\r\nTrailer: Foo, Bar\r\n\r\n")
|
||||
br := bufio.NewReader(r)
|
||||
if err := h.Read(br); err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if h.Len() != 4 {
|
||||
t.Fatalf("Unexpected number of header: %d. Expected 4", h.Len())
|
||||
if h.Len() != 5 {
|
||||
t.Fatalf("Unexpected number of header: %d. Expected 5", h.Len())
|
||||
}
|
||||
hostCount := 0
|
||||
xxCount := 0
|
||||
@@ -1509,6 +1578,10 @@ func TestRequestHeaderVisitAll(t *testing.T) {
|
||||
t.Fatalf("Unexpected cookie %q. Expected %q", v, "a=b; c=d")
|
||||
}
|
||||
cookieCount++
|
||||
case HeaderTrailer:
|
||||
if v != "Foo, Bar" {
|
||||
t.Fatalf("Unexpected trailer header %q. Expected %q", v, "Foo, Bar")
|
||||
}
|
||||
default:
|
||||
t.Fatalf("Unexpected header %q=%q", k, v)
|
||||
}
|
||||
@@ -1524,7 +1597,7 @@ func TestRequestHeaderVisitAll(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponseHeaderVisitAllInOrder(t *testing.T) {
|
||||
func TestRequestHeaderVisitAllInOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var h RequestHeader
|
||||
@@ -1567,6 +1640,38 @@ func TestResponseHeaderVisitAllInOrder(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestResponseHeaderAddTrailerError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var h ResponseHeader
|
||||
err := h.AddTrailer("Foo, Content-Length , Bar,Transfer-Encoding,")
|
||||
expectedTrailer := "Foo, Bar"
|
||||
|
||||
if !errors.Is(err, ErrBadTrailer) {
|
||||
t.Fatalf("unexpected err %q. Expected %q", err, ErrBadTrailer)
|
||||
}
|
||||
if trailer := string(h.Peek(HeaderTrailer)); trailer != expectedTrailer {
|
||||
t.Fatalf("unexpected trailer %q. Expected %q", trailer, expectedTrailer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRequestHeaderAddTrailerError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var h RequestHeader
|
||||
err := h.AddTrailer("Foo, Content-Length , Bar,Transfer-Encoding,")
|
||||
expectedTrailer := "Foo, Bar"
|
||||
|
||||
if !errors.Is(err, ErrBadTrailer) {
|
||||
t.Fatalf("unexpected err %q. Expected %q", err, ErrBadTrailer)
|
||||
}
|
||||
if trailer := string(h.Peek(HeaderTrailer)); trailer != expectedTrailer {
|
||||
t.Fatalf("unexpected trailer %q. Expected %q", trailer, expectedTrailer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestResponseHeaderCookie(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -2106,7 +2211,6 @@ func TestRequestHeaderBufioPeek(t *testing.T) {
|
||||
t.Fatalf("Unexpected error when reading request: %s", err)
|
||||
}
|
||||
verifyRequestHeader(t, h, -2, "/", "foobar.com", "", "")
|
||||
verifyTrailer(t, br, "aaaa")
|
||||
}
|
||||
|
||||
func TestResponseHeaderBufioPeek(t *testing.T) {
|
||||
@@ -2121,7 +2225,6 @@ func TestResponseHeaderBufioPeek(t *testing.T) {
|
||||
t.Fatalf("Unexpected error when reading response: %s", err)
|
||||
}
|
||||
verifyResponseHeader(t, h, 200, 10, "aaa")
|
||||
verifyTrailer(t, br, "0123456789")
|
||||
}
|
||||
|
||||
func getHeaders(n int) string {
|
||||
@@ -2139,143 +2242,127 @@ func TestResponseHeaderReadSuccess(t *testing.T) {
|
||||
|
||||
// straight order of content-length and content-type
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n",
|
||||
200, 123, "text/html", "")
|
||||
200, 123, "text/html")
|
||||
if h.ConnectionClose() {
|
||||
t.Fatalf("unexpected connection: close")
|
||||
}
|
||||
|
||||
// reverse order of content-length and content-type
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 202 OK\r\nContent-Type: text/plain; encoding=utf-8\r\nContent-Length: 543\r\nConnection: close\r\n\r\n",
|
||||
202, 543, "text/plain; encoding=utf-8", "")
|
||||
202, 543, "text/plain; encoding=utf-8")
|
||||
if !h.ConnectionClose() {
|
||||
t.Fatalf("expecting connection: close")
|
||||
}
|
||||
|
||||
// tranfer-encoding: chunked
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 505 Internal error\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n",
|
||||
505, -1, "text/html", "")
|
||||
505, -1, "text/html")
|
||||
if h.ConnectionClose() {
|
||||
t.Fatalf("unexpected connection: close")
|
||||
}
|
||||
|
||||
// reverse order of content-type and tranfer-encoding
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 343 foobar\r\nTransfer-Encoding: chunked\r\nContent-Type: text/json\r\n\r\n",
|
||||
343, -1, "text/json", "")
|
||||
343, -1, "text/json")
|
||||
|
||||
// additional headers
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 100 Continue\r\nFoobar: baz\r\nContent-Type: aaa/bbb\r\nUser-Agent: x\r\nContent-Length: 123\r\nZZZ: werer\r\n\r\n",
|
||||
100, 123, "aaa/bbb", "")
|
||||
|
||||
// trailer (aka body)
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 32245\r\n\r\nqwert aaa",
|
||||
200, 32245, "text/plain", "qwert aaa")
|
||||
100, 123, "aaa/bbb")
|
||||
|
||||
// ancient http protocol
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/0.9 300 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\nqqqq",
|
||||
300, 123, "text/html", "qqqq")
|
||||
300, 123, "text/html")
|
||||
|
||||
// lf instead of crlf
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\nContent-Length: 123\nContent-Type: text/html\n\n",
|
||||
200, 123, "text/html", "")
|
||||
200, 123, "text/html")
|
||||
|
||||
// Zero-length headers with mixed crlf and lf
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\nContent-Length: 345\nZero-Value: \r\nContent-Type: aaa\n: zero-key\r\n\r\nooa",
|
||||
400, 345, "aaa", "ooa")
|
||||
400, 345, "aaa")
|
||||
|
||||
// No space after colon
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\nContent-Length:34\nContent-Type: sss\n\naaaa",
|
||||
200, 34, "sss", "aaaa")
|
||||
200, 34, "sss")
|
||||
|
||||
// invalid case
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\nconTEnt-leNGTH: 123\nConTENT-TYPE: ass\n\n",
|
||||
400, 123, "ass", "")
|
||||
400, 123, "ass")
|
||||
|
||||
// duplicate content-length
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 456\r\nContent-Type: foo/bar\r\nContent-Length: 321\r\n\r\n",
|
||||
200, 321, "foo/bar", "")
|
||||
200, 321, "foo/bar")
|
||||
|
||||
// duplicate content-type
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 234\r\nContent-Type: foo/bar\r\nContent-Type: baz/bar\r\n\r\n",
|
||||
200, 234, "baz/bar", "")
|
||||
|
||||
// both transfer-encoding: chunked and content-length
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\nContent-Length: 123\r\nTransfer-Encoding: chunked\r\n\r\n",
|
||||
200, -1, "foo/bar", "")
|
||||
200, 234, "baz/bar")
|
||||
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 300 OK\r\nContent-Type: foo/barr\r\nTransfer-Encoding: chunked\r\nContent-Length: 354\r\n\r\n",
|
||||
300, -1, "foo/barr", "")
|
||||
300, -1, "foo/barr")
|
||||
|
||||
// duplicate transfer-encoding: chunked
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\nTransfer-Encoding: chunked\r\n\r\n",
|
||||
200, -1, "text/html", "")
|
||||
200, -1, "text/html")
|
||||
|
||||
// no reason string in the first line
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 456\r\nContent-Type: xxx/yyy\r\nContent-Length: 134\r\n\r\naaaxxx",
|
||||
456, 134, "xxx/yyy", "aaaxxx")
|
||||
456, 134, "xxx/yyy")
|
||||
|
||||
// blank lines before the first line
|
||||
testResponseHeaderReadSuccess(t, h, "\r\nHTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 0\r\n\r\nsss",
|
||||
200, 0, "aa", "sss")
|
||||
200, 0, "aa")
|
||||
if h.ConnectionClose() {
|
||||
t.Fatalf("unexpected connection: close")
|
||||
}
|
||||
|
||||
// no content-length (informational responses)
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 101 OK\r\n\r\n",
|
||||
101, -2, "text/plain; charset=utf-8", "")
|
||||
101, -2, "text/plain; charset=utf-8")
|
||||
if h.ConnectionClose() {
|
||||
t.Fatalf("expecting connection: keep-alive for informational response")
|
||||
}
|
||||
|
||||
// no content-length (no-content responses)
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 204 OK\r\n\r\n",
|
||||
204, -2, "text/plain; charset=utf-8", "")
|
||||
204, -2, "text/plain; charset=utf-8")
|
||||
if h.ConnectionClose() {
|
||||
t.Fatalf("expecting connection: keep-alive for no-content response")
|
||||
}
|
||||
|
||||
// no content-length (not-modified responses)
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 304 OK\r\n\r\n",
|
||||
304, -2, "text/plain; charset=utf-8", "")
|
||||
304, -2, "text/plain; charset=utf-8")
|
||||
if h.ConnectionClose() {
|
||||
t.Fatalf("expecting connection: keep-alive for not-modified response")
|
||||
}
|
||||
|
||||
// no content-length (identity transfer-encoding)
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\n\r\nabcdefg",
|
||||
200, -2, "foo/bar", "abcdefg")
|
||||
200, -2, "foo/bar")
|
||||
if !h.ConnectionClose() {
|
||||
t.Fatalf("expecting connection: close for identity response")
|
||||
}
|
||||
|
||||
// non-numeric content-length
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: faaa\r\nContent-Type: text/html\r\n\r\nfoobar",
|
||||
200, -2, "text/html", "foobar")
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 201 OK\r\nContent-Length: 123aa\r\nContent-Type: text/ht\r\n\r\naaa",
|
||||
201, -2, "text/ht", "aaa")
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Length: aa124\r\nContent-Type: html\r\n\r\nxx",
|
||||
200, -2, "html", "xx")
|
||||
|
||||
// no content-type
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\r\nContent-Length: 123\r\n\r\nfoiaaa",
|
||||
400, 123, string(defaultContentType), "foiaaa")
|
||||
400, 123, string(defaultContentType))
|
||||
|
||||
// no content-type and no default
|
||||
h.SetNoDefaultContentType(true)
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\r\nContent-Length: 123\r\n\r\nfoiaaa",
|
||||
400, 123, "", "foiaaa")
|
||||
400, 123, "")
|
||||
h.SetNoDefaultContentType(false)
|
||||
|
||||
// no headers
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\n\r\naaaabbb",
|
||||
200, -2, string(defaultContentType), "aaaabbb")
|
||||
200, -2, string(defaultContentType))
|
||||
if !h.IsHTTP11() {
|
||||
t.Fatalf("expecting http/1.1 protocol")
|
||||
}
|
||||
|
||||
// ancient http protocol
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.0 203 OK\r\nContent-Length: 123\r\nContent-Type: foobar\r\n\r\naaa",
|
||||
203, 123, "foobar", "aaa")
|
||||
203, 123, "foobar")
|
||||
if h.IsHTTP11() {
|
||||
t.Fatalf("ancient protocol must be non-http/1.1")
|
||||
}
|
||||
@@ -2285,7 +2372,7 @@ func TestResponseHeaderReadSuccess(t *testing.T) {
|
||||
|
||||
// ancient http protocol with 'Connection: keep-alive' header.
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.0 403 aa\r\nContent-Length: 0\r\nContent-Type: 2\r\nConnection: Keep-Alive\r\n\r\nww",
|
||||
403, 0, "2", "ww")
|
||||
403, 0, "2")
|
||||
if h.IsHTTP11() {
|
||||
t.Fatalf("ancient protocol must be non-http/1.1")
|
||||
}
|
||||
@@ -2301,21 +2388,21 @@ func TestRequestHeaderReadSuccess(t *testing.T) {
|
||||
|
||||
// simple headers
|
||||
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nHost: google.com\r\n\r\n",
|
||||
-2, "/foo/bar", "google.com", "", "", "")
|
||||
-2, "/foo/bar", "google.com", "", "", nil)
|
||||
if h.ConnectionClose() {
|
||||
t.Fatalf("unexpected connection: close header")
|
||||
}
|
||||
|
||||
// simple headers with body
|
||||
testRequestHeaderReadSuccess(t, h, "GET /a/bar HTTP/1.1\r\nHost: gole.com\r\nconneCTION: close\r\n\r\nfoobar",
|
||||
-2, "/a/bar", "gole.com", "", "", "foobar")
|
||||
-2, "/a/bar", "gole.com", "", "", nil)
|
||||
if !h.ConnectionClose() {
|
||||
t.Fatalf("connection: close unset")
|
||||
}
|
||||
|
||||
// ancient http protocol
|
||||
testRequestHeaderReadSuccess(t, h, "GET /bar HTTP/1.0\r\nHost: gole\r\n\r\npppp",
|
||||
-2, "/bar", "gole", "", "", "pppp")
|
||||
-2, "/bar", "gole", "", "", nil)
|
||||
if h.IsHTTP11() {
|
||||
t.Fatalf("ancient http protocol cannot be http/1.1")
|
||||
}
|
||||
@@ -2325,7 +2412,7 @@ func TestRequestHeaderReadSuccess(t *testing.T) {
|
||||
|
||||
// ancient http protocol with 'Connection: keep-alive' header
|
||||
testRequestHeaderReadSuccess(t, h, "GET /aa HTTP/1.0\r\nHost: bb\r\nConnection: keep-alive\r\n\r\nxxx",
|
||||
-2, "/aa", "bb", "", "", "xxx")
|
||||
-2, "/aa", "bb", "", "", nil)
|
||||
if h.IsHTTP11() {
|
||||
t.Fatalf("ancient http protocol cannot be http/1.1")
|
||||
}
|
||||
@@ -2335,7 +2422,7 @@ func TestRequestHeaderReadSuccess(t *testing.T) {
|
||||
|
||||
// complex headers with body
|
||||
testRequestHeaderReadSuccess(t, h, "GET /aabar HTTP/1.1\r\nAAA: bbb\r\nHost: ole.com\r\nAA: bb\r\n\r\nzzz",
|
||||
-2, "/aabar", "ole.com", "", "", "zzz")
|
||||
-2, "/aabar", "ole.com", "", "", nil)
|
||||
if !h.IsHTTP11() {
|
||||
t.Fatalf("expecting http/1.1 protocol")
|
||||
}
|
||||
@@ -2345,103 +2432,103 @@ func TestRequestHeaderReadSuccess(t *testing.T) {
|
||||
|
||||
// lf instead of crlf
|
||||
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\nHost: google.com\n\n",
|
||||
-2, "/foo/bar", "google.com", "", "", "")
|
||||
-2, "/foo/bar", "google.com", "", "", nil)
|
||||
|
||||
// post method
|
||||
testRequestHeaderReadSuccess(t, h, "POST /aaa?bbb HTTP/1.1\r\nHost: foobar.com\r\nContent-Length: 1235\r\nContent-Type: aaa\r\n\r\nabcdef",
|
||||
1235, "/aaa?bbb", "foobar.com", "", "aaa", "abcdef")
|
||||
1235, "/aaa?bbb", "foobar.com", "", "aaa", nil)
|
||||
|
||||
// zero-length headers with mixed crlf and lf
|
||||
testRequestHeaderReadSuccess(t, h, "GET /a HTTP/1.1\nHost: aaa\r\nZero: \n: Zero-Value\n\r\nxccv",
|
||||
-2, "/a", "aaa", "", "", "xccv")
|
||||
-2, "/a", "aaa", "", "", nil)
|
||||
|
||||
// no space after colon
|
||||
testRequestHeaderReadSuccess(t, h, "GET /a HTTP/1.1\nHost:aaaxd\n\nsdfds",
|
||||
-2, "/a", "aaaxd", "", "", "sdfds")
|
||||
-2, "/a", "aaaxd", "", "", nil)
|
||||
|
||||
// get with zero content-length
|
||||
testRequestHeaderReadSuccess(t, h, "GET /xxx HTTP/1.1\nHost: aaa.com\nContent-Length: 0\n\n",
|
||||
0, "/xxx", "aaa.com", "", "", "")
|
||||
0, "/xxx", "aaa.com", "", "", nil)
|
||||
|
||||
// get with non-zero content-length
|
||||
testRequestHeaderReadSuccess(t, h, "GET /xxx HTTP/1.1\nHost: aaa.com\nContent-Length: 123\n\n",
|
||||
123, "/xxx", "aaa.com", "", "", "")
|
||||
123, "/xxx", "aaa.com", "", "", nil)
|
||||
|
||||
// invalid case
|
||||
testRequestHeaderReadSuccess(t, h, "GET /aaa HTTP/1.1\nhoST: bbb.com\n\naas",
|
||||
-2, "/aaa", "bbb.com", "", "", "aas")
|
||||
-2, "/aaa", "bbb.com", "", "", nil)
|
||||
|
||||
// referer
|
||||
testRequestHeaderReadSuccess(t, h, "GET /asdf HTTP/1.1\nHost: aaa.com\nReferer: bb.com\n\naaa",
|
||||
-2, "/asdf", "aaa.com", "bb.com", "", "aaa")
|
||||
-2, "/asdf", "aaa.com", "bb.com", "", nil)
|
||||
|
||||
// duplicate host
|
||||
testRequestHeaderReadSuccess(t, h, "GET /aa HTTP/1.1\r\nHost: aaaaaa.com\r\nHost: bb.com\r\n\r\n",
|
||||
-2, "/aa", "bb.com", "", "", "")
|
||||
-2, "/aa", "bb.com", "", "", nil)
|
||||
|
||||
// post with duplicate content-type
|
||||
testRequestHeaderReadSuccess(t, h, "POST /a HTTP/1.1\r\nHost: aa\r\nContent-Type: ab\r\nContent-Length: 123\r\nContent-Type: xx\r\n\r\n",
|
||||
123, "/a", "aa", "", "xx", "")
|
||||
123, "/a", "aa", "", "xx", nil)
|
||||
|
||||
// post with duplicate content-length
|
||||
testRequestHeaderReadSuccess(t, h, "POST /xx HTTP/1.1\r\nHost: aa\r\nContent-Type: s\r\nContent-Length: 13\r\nContent-Length: 1\r\n\r\n",
|
||||
1, "/xx", "aa", "", "s", "")
|
||||
1, "/xx", "aa", "", "s", nil)
|
||||
|
||||
// non-post with content-type
|
||||
testRequestHeaderReadSuccess(t, h, "GET /aaa HTTP/1.1\r\nHost: bbb.com\r\nContent-Type: aaab\r\n\r\n",
|
||||
-2, "/aaa", "bbb.com", "", "aaab", "")
|
||||
-2, "/aaa", "bbb.com", "", "aaab", nil)
|
||||
|
||||
// non-post with content-length
|
||||
testRequestHeaderReadSuccess(t, h, "HEAD / HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 123\r\n\r\n",
|
||||
123, "/", "aaa.com", "", "", "")
|
||||
123, "/", "aaa.com", "", "", nil)
|
||||
|
||||
// non-post with content-type and content-length
|
||||
testRequestHeaderReadSuccess(t, h, "GET /aa HTTP/1.1\r\nHost: aa.com\r\nContent-Type: abd/test\r\nContent-Length: 123\r\n\r\n",
|
||||
123, "/aa", "aa.com", "", "abd/test", "")
|
||||
123, "/aa", "aa.com", "", "abd/test", nil)
|
||||
|
||||
// request uri with hostname
|
||||
testRequestHeaderReadSuccess(t, h, "GET http://gooGle.com/foO/%20bar?xxx#aaa HTTP/1.1\r\nHost: aa.cOM\r\n\r\ntrail",
|
||||
-2, "http://gooGle.com/foO/%20bar?xxx#aaa", "aa.cOM", "", "", "trail")
|
||||
-2, "http://gooGle.com/foO/%20bar?xxx#aaa", "aa.cOM", "", "", nil)
|
||||
|
||||
// no protocol in the first line
|
||||
testRequestHeaderReadSuccess(t, h, "GET /foo/bar\r\nHost: google.com\r\n\r\nisdD",
|
||||
-2, "/foo/bar", "google.com", "", "", "isdD")
|
||||
-2, "/foo/bar", "google.com", "", "", nil)
|
||||
|
||||
// blank lines before the first line
|
||||
testRequestHeaderReadSuccess(t, h, "\r\n\n\r\nGET /aaa HTTP/1.1\r\nHost: aaa.com\r\n\r\nsss",
|
||||
-2, "/aaa", "aaa.com", "", "", "sss")
|
||||
-2, "/aaa", "aaa.com", "", "", nil)
|
||||
|
||||
// request uri with spaces
|
||||
testRequestHeaderReadSuccess(t, h, "GET /foo/ bar baz HTTP/1.1\r\nHost: aa.com\r\n\r\nxxx",
|
||||
-2, "/foo/ bar baz", "aa.com", "", "", "xxx")
|
||||
-2, "/foo/ bar baz", "aa.com", "", "", nil)
|
||||
|
||||
// no host
|
||||
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nFOObar: assdfd\r\n\r\naaa",
|
||||
-2, "/foo/bar", "", "", "", "aaa")
|
||||
-2, "/foo/bar", "", "", "", nil)
|
||||
|
||||
// no host, no headers
|
||||
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\n\r\nfoobar",
|
||||
-2, "/foo/bar", "", "", "", "foobar")
|
||||
-2, "/foo/bar", "", "", "", nil)
|
||||
|
||||
// post without content-length and content-type
|
||||
testRequestHeaderReadSuccess(t, h, "POST /aaa HTTP/1.1\r\nHost: aaa.com\r\n\r\nzxc",
|
||||
-2, "/aaa", "aaa.com", "", "", "zxc")
|
||||
-2, "/aaa", "aaa.com", "", "", nil)
|
||||
|
||||
// post without content-type
|
||||
testRequestHeaderReadSuccess(t, h, "POST /abc HTTP/1.1\r\nHost: aa.com\r\nContent-Length: 123\r\n\r\npoiuy",
|
||||
123, "/abc", "aa.com", "", "", "poiuy")
|
||||
123, "/abc", "aa.com", "", "", nil)
|
||||
|
||||
// post without content-length
|
||||
testRequestHeaderReadSuccess(t, h, "POST /abc HTTP/1.1\r\nHost: aa.com\r\nContent-Type: adv\r\n\r\n123456",
|
||||
-2, "/abc", "aa.com", "", "adv", "123456")
|
||||
-2, "/abc", "aa.com", "", "adv", nil)
|
||||
|
||||
// invalid method
|
||||
testRequestHeaderReadSuccess(t, h, "POST /foo/bar HTTP/1.1\r\nHost: google.com\r\n\r\nmnbv",
|
||||
-2, "/foo/bar", "google.com", "", "", "mnbv")
|
||||
-2, "/foo/bar", "google.com", "", "", nil)
|
||||
|
||||
// put request
|
||||
testRequestHeaderReadSuccess(t, h, "PUT /faa HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 123\r\nContent-Type: aaa\r\n\r\nxwwere",
|
||||
123, "/faa", "aaa.com", "", "aaa", "xwwere")
|
||||
123, "/faa", "aaa.com", "", "aaa", nil)
|
||||
}
|
||||
|
||||
func TestResponseHeaderReadError(t *testing.T) {
|
||||
@@ -2462,11 +2549,19 @@ func TestResponseHeaderReadError(t *testing.T) {
|
||||
testResponseHeaderReadError(t, h, "HTTP/1.1 123foobar OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n")
|
||||
testResponseHeaderReadError(t, h, "HTTP/1.1 foobar344 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n\r\n")
|
||||
|
||||
// non-numeric content-length
|
||||
testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: faaa\r\nContent-Type: text/html\r\n\r\nfoobar")
|
||||
testResponseHeaderReadError(t, h, "HTTP/1.1 201 OK\r\nContent-Length: 123aa\r\nContent-Type: text/ht\r\n\r\naaa")
|
||||
testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: aa124\r\nContent-Type: html\r\n\r\nxx")
|
||||
|
||||
// no headers
|
||||
testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\n")
|
||||
|
||||
// no trailing crlf
|
||||
testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: 123\r\nContent-Type: text/html\r\n")
|
||||
|
||||
// forbidden trailer
|
||||
testResponseHeaderReadError(t, h, "HTTP/1.1 200 OK\r\nContent-Length: -1\r\nTrailer: Foo, Content-Length\r\n\r\n")
|
||||
}
|
||||
|
||||
func TestResponseHeaderReadErrorSecureLog(t *testing.T) {
|
||||
@@ -2511,6 +2606,9 @@ func TestRequestHeaderReadError(t *testing.T) {
|
||||
|
||||
// post with invalid content-length
|
||||
testRequestHeaderReadError(t, h, "POST /a HTTP/1.1\r\nHost: bb\r\nContent-Type: aa\r\nContent-Length: dff\r\n\r\nqwerty")
|
||||
|
||||
// forbidden trailer
|
||||
testRequestHeaderReadError(t, h, "POST /a HTTP/1.1\r\nContent-Length: -1\r\nTrailer: Foo, Content-Length\r\n\r\n")
|
||||
}
|
||||
|
||||
func TestRequestHeaderReadSecuredError(t *testing.T) {
|
||||
@@ -2541,7 +2639,7 @@ func testResponseHeaderReadError(t *testing.T, h *ResponseHeader, headers string
|
||||
}
|
||||
// make sure response header works after error
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\nContent-Length: 12345\r\n\r\nsss",
|
||||
200, 12345, "foo/bar", "sss")
|
||||
200, 12345, "foo/bar")
|
||||
}
|
||||
|
||||
func testResponseHeaderReadSecuredError(t *testing.T, h *ResponseHeader, headers string) {
|
||||
@@ -2556,7 +2654,7 @@ func testResponseHeaderReadSecuredError(t *testing.T, h *ResponseHeader, headers
|
||||
}
|
||||
// make sure response header works after error
|
||||
testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\nContent-Length: 12345\r\n\r\nsss",
|
||||
200, 12345, "foo/bar", "sss")
|
||||
200, 12345, "foo/bar")
|
||||
}
|
||||
|
||||
func testRequestHeaderReadError(t *testing.T, h *RequestHeader, headers string) {
|
||||
@@ -2569,7 +2667,7 @@ func testRequestHeaderReadError(t *testing.T, h *RequestHeader, headers string)
|
||||
|
||||
// make sure request header works after error
|
||||
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nHost: aaaa\r\n\r\nxxx",
|
||||
-2, "/foo/bar", "aaaa", "", "", "xxx")
|
||||
-2, "/foo/bar", "aaaa", "", "", nil)
|
||||
}
|
||||
|
||||
func testRequestHeaderReadSecuredError(t *testing.T, h *RequestHeader, headers string) {
|
||||
@@ -2584,11 +2682,11 @@ func testRequestHeaderReadSecuredError(t *testing.T, h *RequestHeader, headers s
|
||||
}
|
||||
// make sure request header works after error
|
||||
testRequestHeaderReadSuccess(t, h, "GET /foo/bar HTTP/1.1\r\nHost: aaaa\r\n\r\nxxx",
|
||||
-2, "/foo/bar", "aaaa", "", "", "xxx")
|
||||
-2, "/foo/bar", "aaaa", "", "", nil)
|
||||
}
|
||||
|
||||
func testResponseHeaderReadSuccess(t *testing.T, h *ResponseHeader, headers string, expectedStatusCode, expectedContentLength int,
|
||||
expectedContentType, expectedTrailer string) {
|
||||
expectedContentType string) {
|
||||
r := bytes.NewBufferString(headers)
|
||||
br := bufio.NewReader(r)
|
||||
err := h.Read(br)
|
||||
@@ -2596,11 +2694,10 @@ func testResponseHeaderReadSuccess(t *testing.T, h *ResponseHeader, headers stri
|
||||
t.Fatalf("Unexpected error when parsing response headers: %s. headers=%q", err, headers)
|
||||
}
|
||||
verifyResponseHeader(t, h, expectedStatusCode, expectedContentLength, expectedContentType)
|
||||
verifyTrailer(t, br, expectedTrailer)
|
||||
}
|
||||
|
||||
func testRequestHeaderReadSuccess(t *testing.T, h *RequestHeader, headers string, expectedContentLength int,
|
||||
expectedRequestURI, expectedHost, expectedReferer, expectedContentType, expectedTrailer string) {
|
||||
expectedRequestURI, expectedHost, expectedReferer, expectedContentType string, expectedTrailer map[string]string) {
|
||||
r := bytes.NewBufferString(headers)
|
||||
br := bufio.NewReader(r)
|
||||
err := h.Read(br)
|
||||
@@ -2608,7 +2705,6 @@ func testRequestHeaderReadSuccess(t *testing.T, h *RequestHeader, headers string
|
||||
t.Fatalf("Unexpected error when parsing request headers: %s. headers=%q", err, headers)
|
||||
}
|
||||
verifyRequestHeader(t, h, expectedContentLength, expectedRequestURI, expectedHost, expectedReferer, expectedContentType)
|
||||
verifyTrailer(t, br, expectedTrailer)
|
||||
}
|
||||
|
||||
func verifyResponseHeader(t *testing.T, h *ResponseHeader, expectedStatusCode, expectedContentLength int, expectedContentType string) {
|
||||
@@ -2648,12 +2744,45 @@ func verifyRequestHeader(t *testing.T, h *RequestHeader, expectedContentLength i
|
||||
}
|
||||
}
|
||||
|
||||
func verifyTrailer(t *testing.T, r *bufio.Reader, expectedTrailer string) {
|
||||
trailer, err := ioutil.ReadAll(r)
|
||||
func verifyResponseTrailer(t *testing.T, h *ResponseHeader, expectedTrailers map[string]string) {
|
||||
for k, v := range expectedTrailers {
|
||||
got := h.Peek(k)
|
||||
if !bytes.Equal(got, []byte(v)) {
|
||||
t.Fatalf("Unexpected trailer %s. Expected %s. Got %q", k, v, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifyRequestTrailer(t *testing.T, h *RequestHeader, expectedTrailers map[string]string) {
|
||||
for k, v := range expectedTrailers {
|
||||
got := h.Peek(k)
|
||||
if !bytes.Equal(got, []byte(v)) {
|
||||
t.Fatalf("Unexpected trailer %s. Expected %s. Got %q", k, v, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifyTrailer(t *testing.T, r *bufio.Reader, expectedTrailers map[string]string, isReq bool) {
|
||||
if isReq {
|
||||
req := Request{}
|
||||
err := req.Header.ReadTrailer(r)
|
||||
if err == io.EOF && expectedTrailers == nil {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot read trailer: %s", err)
|
||||
}
|
||||
verifyRequestTrailer(t, &req.Header, expectedTrailers)
|
||||
return
|
||||
}
|
||||
|
||||
resp := Response{}
|
||||
err := resp.Header.ReadTrailer(r)
|
||||
if err == io.EOF && expectedTrailers == nil {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot read trailer: %s", err)
|
||||
}
|
||||
if !bytes.Equal(trailer, []byte(expectedTrailer)) {
|
||||
t.Fatalf("Unexpected trailer %q. Expected %q", trailer, expectedTrailer)
|
||||
}
|
||||
verifyResponseTrailer(t, &resp.Header, expectedTrailers)
|
||||
}
|
||||
|
||||
+3
-2
@@ -36,8 +36,9 @@ const (
|
||||
HeaderVary = "Vary"
|
||||
|
||||
// Connection management
|
||||
HeaderConnection = "Connection"
|
||||
HeaderKeepAlive = "Keep-Alive"
|
||||
HeaderConnection = "Connection"
|
||||
HeaderKeepAlive = "Keep-Alive"
|
||||
HeaderProxyConnection = "Proxy-Connection"
|
||||
|
||||
// Content negotiation
|
||||
HeaderAccept = "Accept"
|
||||
|
||||
@@ -1136,7 +1136,17 @@ func (req *Request) ContinueReadBody(r *bufio.Reader, maxBodySize int, preParseM
|
||||
return nil
|
||||
}
|
||||
|
||||
return req.ReadBody(r, contentLength, maxBodySize)
|
||||
if err = req.ReadBody(r, contentLength, maxBodySize); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if req.Header.ContentLength() == -1 {
|
||||
err = req.Header.ReadTrailer(r)
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadBody reads request body from the given r, limiting the body size.
|
||||
@@ -1146,12 +1156,22 @@ func (req *Request) ContinueReadBody(r *bufio.Reader, maxBodySize int, preParseM
|
||||
func (req *Request) ReadBody(r *bufio.Reader, contentLength int, maxBodySize int) (err error) {
|
||||
bodyBuf := req.bodyBuffer()
|
||||
bodyBuf.Reset()
|
||||
bodyBuf.B, err = readBody(r, contentLength, maxBodySize, bodyBuf.B)
|
||||
|
||||
if contentLength >= 0 {
|
||||
bodyBuf.B, err = readBody(r, contentLength, maxBodySize, bodyBuf.B)
|
||||
|
||||
} else if contentLength == -1 {
|
||||
bodyBuf.B, err = readBodyChunked(r, maxBodySize, bodyBuf.B)
|
||||
|
||||
} else {
|
||||
bodyBuf.B, err = readBodyIdentity(r, maxBodySize, bodyBuf.B)
|
||||
req.Header.SetContentLength(len(bodyBuf.B))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
req.Reset()
|
||||
return err
|
||||
}
|
||||
req.Header.SetContentLength(len(bodyBuf.B))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1197,12 +1217,12 @@ func (req *Request) ContinueReadBodyStream(r *bufio.Reader, maxBodySize int, pre
|
||||
if err == ErrBodyTooLarge {
|
||||
req.Header.SetContentLength(contentLength)
|
||||
req.body = bodyBuf
|
||||
req.bodyStream = acquireRequestStream(bodyBuf, r, contentLength)
|
||||
req.bodyStream = acquireRequestStream(bodyBuf, r, &req.Header)
|
||||
return nil
|
||||
}
|
||||
if err == errChunkedStream {
|
||||
req.body = bodyBuf
|
||||
req.bodyStream = acquireRequestStream(bodyBuf, r, -1)
|
||||
req.bodyStream = acquireRequestStream(bodyBuf, r, &req.Header)
|
||||
return nil
|
||||
}
|
||||
req.Reset()
|
||||
@@ -1210,7 +1230,7 @@ func (req *Request) ContinueReadBodyStream(r *bufio.Reader, maxBodySize int, pre
|
||||
}
|
||||
|
||||
req.body = bodyBuf
|
||||
req.bodyStream = acquireRequestStream(bodyBuf, r, contentLength)
|
||||
req.bodyStream = acquireRequestStream(bodyBuf, r, &req.Header)
|
||||
req.Header.SetContentLength(contentLength)
|
||||
return nil
|
||||
}
|
||||
@@ -1245,7 +1265,17 @@ func (resp *Response) ReadLimitBody(r *bufio.Reader, maxBodySize int) error {
|
||||
}
|
||||
|
||||
if !resp.mustSkipBody() {
|
||||
return resp.ReadBody(r, maxBodySize)
|
||||
err = resp.ReadBody(r, maxBodySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if resp.Header.ContentLength() == -1 {
|
||||
err = resp.Header.ReadTrailer(r)
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1257,12 +1287,19 @@ func (resp *Response) ReadLimitBody(r *bufio.Reader, maxBodySize int) error {
|
||||
func (resp *Response) ReadBody(r *bufio.Reader, maxBodySize int) (err error) {
|
||||
bodyBuf := resp.bodyBuffer()
|
||||
bodyBuf.Reset()
|
||||
bodyBuf.B, err = readBody(r, resp.Header.ContentLength(), maxBodySize, bodyBuf.B)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
contentLength := resp.Header.ContentLength()
|
||||
if contentLength >= 0 {
|
||||
bodyBuf.B, err = readBody(r, contentLength, maxBodySize, bodyBuf.B)
|
||||
|
||||
} else if contentLength == -1 {
|
||||
bodyBuf.B, err = readBodyChunked(r, maxBodySize, bodyBuf.B)
|
||||
|
||||
} else {
|
||||
bodyBuf.B, err = readBodyIdentity(r, maxBodySize, bodyBuf.B)
|
||||
resp.Header.SetContentLength(len(bodyBuf.B))
|
||||
}
|
||||
resp.Header.SetContentLength(len(bodyBuf.B))
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (resp *Response) mustSkipBody() bool {
|
||||
@@ -1723,9 +1760,13 @@ func (req *Request) writeBodyStream(w *bufio.Writer) error {
|
||||
}
|
||||
} else {
|
||||
req.Header.SetContentLength(-1)
|
||||
if err = req.Header.Write(w); err == nil {
|
||||
err = req.Header.Write(w)
|
||||
if err == nil {
|
||||
err = writeBodyChunked(w, req.bodyStream)
|
||||
}
|
||||
if err == nil {
|
||||
err = req.Header.writeTrailer(w)
|
||||
}
|
||||
}
|
||||
err1 := req.closeBodyStream()
|
||||
if err == nil {
|
||||
@@ -1779,6 +1820,9 @@ func (resp *Response) writeBodyStream(w *bufio.Writer, sendBody bool) (err error
|
||||
if err == nil && sendBody {
|
||||
err = writeBodyChunked(w, resp.bodyStream)
|
||||
}
|
||||
if err == nil {
|
||||
err = resp.Header.writeTrailer(w)
|
||||
}
|
||||
}
|
||||
}
|
||||
err1 := resp.closeBodyStream()
|
||||
@@ -1927,12 +1971,13 @@ func writeChunk(w *bufio.Writer, b []byte) error {
|
||||
if _, err := w.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := w.Write(strCRLF)
|
||||
err1 := w.Flush()
|
||||
if err == nil {
|
||||
err = err1
|
||||
// If is end chunk, write CRLF after writing trailer
|
||||
if n > 0 {
|
||||
if _, err := w.Write(strCRLF); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
// ErrBodyTooLarge is returned if either request or response body exceeds
|
||||
@@ -1940,17 +1985,10 @@ func writeChunk(w *bufio.Writer, b []byte) error {
|
||||
var ErrBodyTooLarge = errors.New("body size exceeds the given limit")
|
||||
|
||||
func readBody(r *bufio.Reader, contentLength int, maxBodySize int, dst []byte) ([]byte, error) {
|
||||
dst = dst[:0]
|
||||
if contentLength >= 0 {
|
||||
if maxBodySize > 0 && contentLength > maxBodySize {
|
||||
return dst, ErrBodyTooLarge
|
||||
}
|
||||
return appendBodyFixedSize(r, dst, contentLength)
|
||||
if maxBodySize > 0 && contentLength > maxBodySize {
|
||||
return dst, ErrBodyTooLarge
|
||||
}
|
||||
if contentLength == -1 {
|
||||
return readBodyChunked(r, maxBodySize, dst)
|
||||
}
|
||||
return readBodyIdentity(r, maxBodySize, dst)
|
||||
return appendBodyFixedSize(r, dst, contentLength)
|
||||
}
|
||||
|
||||
var errChunkedStream = errors.New("chunked stream")
|
||||
@@ -2067,6 +2105,9 @@ func readBodyChunked(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, erro
|
||||
if err != nil {
|
||||
return dst, err
|
||||
}
|
||||
if chunkSize == 0 {
|
||||
return dst, err
|
||||
}
|
||||
if maxBodySize > 0 && len(dst)+chunkSize > maxBodySize {
|
||||
return dst, ErrBodyTooLarge
|
||||
}
|
||||
@@ -2080,9 +2121,6 @@ func readBodyChunked(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, erro
|
||||
}
|
||||
}
|
||||
dst = dst[:len(dst)-strCRLFLen]
|
||||
if chunkSize == 0 {
|
||||
return dst, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2098,8 +2136,9 @@ func parseChunkSize(r *bufio.Reader) (int, error) {
|
||||
error: fmt.Errorf("cannot read '\r' char at the end of chunk size: %s", err),
|
||||
}
|
||||
}
|
||||
// Skip any trailing whitespace after chunk size.
|
||||
if c == ' ' {
|
||||
// Skip chunk extension after chunk size.
|
||||
// Add support later if anyone needs it.
|
||||
if c != '\r' {
|
||||
continue
|
||||
}
|
||||
if err := r.UnreadByte(); err != nil {
|
||||
|
||||
+311
-81
@@ -155,6 +155,122 @@ func testResponseCopyTo(t *testing.T, src *Response) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestBodyStreamWithTrailer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testRequestBodyStreamWithTrailer(t, nil, false)
|
||||
|
||||
body := createFixedBody(1e5)
|
||||
testRequestBodyStreamWithTrailer(t, body, false)
|
||||
testRequestBodyStreamWithTrailer(t, body, true)
|
||||
}
|
||||
|
||||
func testRequestBodyStreamWithTrailer(t *testing.T, body []byte, disableNormalizing bool) {
|
||||
expectedTrailer := map[string]string{
|
||||
"foo": "testfoo",
|
||||
"bar": "testbar",
|
||||
}
|
||||
|
||||
var req1 Request
|
||||
req1.Header.disableNormalizing = disableNormalizing
|
||||
req1.SetHost("google.com")
|
||||
req1.SetBodyStream(bytes.NewBuffer(body), -1)
|
||||
for k, v := range expectedTrailer {
|
||||
err := req1.Header.AddTrailer(k)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
req1.Header.Set(k, v)
|
||||
}
|
||||
|
||||
w := &bytes.Buffer{}
|
||||
bw := bufio.NewWriter(w)
|
||||
if err := req1.Write(bw); err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if err := bw.Flush(); err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
var req2 Request
|
||||
req2.Header.disableNormalizing = disableNormalizing
|
||||
br := bufio.NewReader(w)
|
||||
if err := req2.Read(br); err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
reqBody := req2.Body()
|
||||
if !bytes.Equal(reqBody, body) {
|
||||
t.Fatalf("unexpected body: %q. Expecting %q", reqBody, body)
|
||||
}
|
||||
|
||||
for k, v := range expectedTrailer {
|
||||
kBytes := []byte(k)
|
||||
normalizeHeaderKey(kBytes, disableNormalizing)
|
||||
r := req2.Header.Peek(k)
|
||||
if string(r) != v {
|
||||
t.Fatalf("unexpected trailer header %q: %q. Expecting %s", kBytes, r, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponseBodyStreamWithTrailer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testResponseBodyStreamWithTrailer(t, nil, false)
|
||||
|
||||
body := createFixedBody(1e5)
|
||||
testResponseBodyStreamWithTrailer(t, body, false)
|
||||
testResponseBodyStreamWithTrailer(t, body, true)
|
||||
}
|
||||
|
||||
func testResponseBodyStreamWithTrailer(t *testing.T, body []byte, disableNormalizing bool) {
|
||||
expectedTrailer := map[string]string{
|
||||
"foo": "testfoo",
|
||||
"bar": "testbar",
|
||||
}
|
||||
var resp1 Response
|
||||
resp1.Header.disableNormalizing = disableNormalizing
|
||||
resp1.SetBodyStream(bytes.NewReader(body), -1)
|
||||
for k, v := range expectedTrailer {
|
||||
err := resp1.Header.AddTrailer(k)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
resp1.Header.Set(k, v)
|
||||
}
|
||||
|
||||
w := &bytes.Buffer{}
|
||||
bw := bufio.NewWriter(w)
|
||||
if err := resp1.Write(bw); err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if err := bw.Flush(); err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
var resp2 Response
|
||||
resp2.Header.disableNormalizing = disableNormalizing
|
||||
br := bufio.NewReader(w)
|
||||
if err := resp2.Read(br); err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
respBody := resp2.Body()
|
||||
if !bytes.Equal(respBody, body) {
|
||||
t.Fatalf("unexpected body: %q. Expecting %q", respBody, body)
|
||||
}
|
||||
|
||||
for k, v := range expectedTrailer {
|
||||
kBytes := []byte(k)
|
||||
normalizeHeaderKey(kBytes, disableNormalizing)
|
||||
r := resp2.Header.Peek(k)
|
||||
if string(r) != v {
|
||||
t.Fatalf("unexpected trailer header %q: %q. Expecting %s", kBytes, r, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponseBodyStreamDeflate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1344,17 +1460,19 @@ func TestResponseReadLimitBody(t *testing.T) {
|
||||
// response with content-length
|
||||
testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 10\r\n\r\n9876543210", 10)
|
||||
testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 10\r\n\r\n9876543210", 100)
|
||||
testResponseReadLimitBodyError(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 10\r\n\r\n9876543210", 9)
|
||||
testResponseReadLimitBodyError(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nContent-Length: 10\r\n\r\n9876543210", 9, ErrBodyTooLarge)
|
||||
|
||||
// chunked response
|
||||
testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 9)
|
||||
testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\nFoo: bar\r\n\r\n", 9)
|
||||
testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 100)
|
||||
testResponseReadLimitBodyError(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 2)
|
||||
testResponseReadLimitBodySuccess(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\nfoobar\r\n\r\n", 100)
|
||||
testResponseReadLimitBodyError(t, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 2, ErrBodyTooLarge)
|
||||
|
||||
// identity response
|
||||
testResponseReadLimitBodySuccess(t, "HTTP/1.1 400 OK\r\nContent-Type: aa\r\n\r\n123456", 6)
|
||||
testResponseReadLimitBodySuccess(t, "HTTP/1.1 400 OK\r\nContent-Type: aa\r\n\r\n123456", 106)
|
||||
testResponseReadLimitBodyError(t, "HTTP/1.1 400 OK\r\nContent-Type: aa\r\n\r\n123456", 5)
|
||||
testResponseReadLimitBodyError(t, "HTTP/1.1 400 OK\r\nContent-Type: aa\r\n\r\n123456", 5, ErrBodyTooLarge)
|
||||
}
|
||||
|
||||
func TestRequestReadLimitBody(t *testing.T) {
|
||||
@@ -1363,15 +1481,17 @@ func TestRequestReadLimitBody(t *testing.T) {
|
||||
// request with content-length
|
||||
testRequestReadLimitBodySuccess(t, "POST /foo HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 9\r\nContent-Type: aaa\r\n\r\n123456789", 9)
|
||||
testRequestReadLimitBodySuccess(t, "POST /foo HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 9\r\nContent-Type: aaa\r\n\r\n123456789", 92)
|
||||
testRequestReadLimitBodyError(t, "POST /foo HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 9\r\nContent-Type: aaa\r\n\r\n123456789", 5)
|
||||
testRequestReadLimitBodyError(t, "POST /foo HTTP/1.1\r\nHost: aaa.com\r\nContent-Length: 9\r\nContent-Type: aaa\r\n\r\n123456789", 5, ErrBodyTooLarge)
|
||||
|
||||
// chunked request
|
||||
testRequestReadLimitBodySuccess(t, "POST /a HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 9)
|
||||
testRequestReadLimitBodySuccess(t, "POST /a HTTP/1.1\nHost: a.com\nTransfer-Encoding: chunked\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\nFoo: bar\r\n\r\n", 9)
|
||||
testRequestReadLimitBodySuccess(t, "POST /a HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 999)
|
||||
testRequestReadLimitBodyError(t, "POST /a HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 8)
|
||||
testRequestReadLimitBodySuccess(t, "POST /a HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\nfoobar\r\n\r\n", 999)
|
||||
testRequestReadLimitBodyError(t, "POST /a HTTP/1.1\r\nHost: a.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa\r\n\r\n6\r\nfoobar\r\n3\r\nbaz\r\n0\r\n\r\n", 8, ErrBodyTooLarge)
|
||||
}
|
||||
|
||||
func testResponseReadLimitBodyError(t *testing.T, s string, maxBodySize int) {
|
||||
func testResponseReadLimitBodyError(t *testing.T, s string, maxBodySize int, expectedErr error) {
|
||||
var req Response
|
||||
r := bytes.NewBufferString(s)
|
||||
br := bufio.NewReader(r)
|
||||
@@ -1379,8 +1499,8 @@ func testResponseReadLimitBodyError(t *testing.T, s string, maxBodySize int) {
|
||||
if err == nil {
|
||||
t.Fatalf("expecting error. s=%q, maxBodySize=%d", s, maxBodySize)
|
||||
}
|
||||
if err != ErrBodyTooLarge {
|
||||
t.Fatalf("unexpected error: %s. Expecting %s. s=%q, maxBodySize=%d", err, ErrBodyTooLarge, s, maxBodySize)
|
||||
if err != expectedErr {
|
||||
t.Fatalf("unexpected error: %s. Expecting %s. s=%q, maxBodySize=%d", err, expectedErr, s, maxBodySize)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1393,7 +1513,7 @@ func testResponseReadLimitBodySuccess(t *testing.T, s string, maxBodySize int) {
|
||||
}
|
||||
}
|
||||
|
||||
func testRequestReadLimitBodyError(t *testing.T, s string, maxBodySize int) {
|
||||
func testRequestReadLimitBodyError(t *testing.T, s string, maxBodySize int, expectedErr error) {
|
||||
var req Request
|
||||
r := bytes.NewBufferString(s)
|
||||
br := bufio.NewReader(r)
|
||||
@@ -1401,8 +1521,8 @@ func testRequestReadLimitBodyError(t *testing.T, s string, maxBodySize int) {
|
||||
if err == nil {
|
||||
t.Fatalf("expecting error. s=%q, maxBodySize=%d", s, maxBodySize)
|
||||
}
|
||||
if err != ErrBodyTooLarge {
|
||||
t.Fatalf("unexpected error: %s. Expecting %s. s=%q, maxBodySize=%d", err, ErrBodyTooLarge, s, maxBodySize)
|
||||
if err != expectedErr {
|
||||
t.Fatalf("unexpected error: %s. Expecting %s. s=%q, maxBodySize=%d", err, expectedErr, s, maxBodySize)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1490,52 +1610,49 @@ func TestRequestWriteRequestURINoHost(t *testing.T) {
|
||||
func TestSetRequestBodyStreamFixedSize(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testSetRequestBodyStream(t, "a", false)
|
||||
testSetRequestBodyStream(t, string(createFixedBody(4097)), false)
|
||||
testSetRequestBodyStream(t, string(createFixedBody(100500)), false)
|
||||
testSetRequestBodyStream(t, "a")
|
||||
testSetRequestBodyStream(t, string(createFixedBody(4097)))
|
||||
testSetRequestBodyStream(t, string(createFixedBody(100500)))
|
||||
}
|
||||
|
||||
func TestSetResponseBodyStreamFixedSize(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testSetResponseBodyStream(t, "a", false)
|
||||
testSetResponseBodyStream(t, string(createFixedBody(4097)), false)
|
||||
testSetResponseBodyStream(t, string(createFixedBody(100500)), false)
|
||||
testSetResponseBodyStream(t, "a")
|
||||
testSetResponseBodyStream(t, string(createFixedBody(4097)))
|
||||
testSetResponseBodyStream(t, string(createFixedBody(100500)))
|
||||
}
|
||||
|
||||
func TestSetRequestBodyStreamChunked(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testSetRequestBodyStream(t, "", true)
|
||||
testSetRequestBodyStreamChunked(t, "", map[string]string{"Foo": "bar"})
|
||||
|
||||
body := "foobar baz aaa bbb ccc"
|
||||
testSetRequestBodyStream(t, body, true)
|
||||
testSetRequestBodyStreamChunked(t, body, nil)
|
||||
|
||||
body = string(createFixedBody(10001))
|
||||
testSetRequestBodyStream(t, body, true)
|
||||
testSetRequestBodyStreamChunked(t, body, map[string]string{"Foo": "test", "Bar": "test"})
|
||||
}
|
||||
|
||||
func TestSetResponseBodyStreamChunked(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testSetResponseBodyStream(t, "", true)
|
||||
testSetResponseBodyStreamChunked(t, "", map[string]string{"Foo": "bar"})
|
||||
|
||||
body := "foobar baz aaa bbb ccc"
|
||||
testSetResponseBodyStream(t, body, true)
|
||||
testSetResponseBodyStreamChunked(t, body, nil)
|
||||
|
||||
body = string(createFixedBody(10001))
|
||||
testSetResponseBodyStream(t, body, true)
|
||||
testSetResponseBodyStreamChunked(t, body, map[string]string{"Foo": "test", "Bar": "test"})
|
||||
}
|
||||
|
||||
func testSetRequestBodyStream(t *testing.T, body string, chunked bool) {
|
||||
func testSetRequestBodyStream(t *testing.T, body string) {
|
||||
var req Request
|
||||
req.Header.SetHost("foobar.com")
|
||||
req.Header.SetMethod(MethodPost)
|
||||
|
||||
bodySize := len(body)
|
||||
if chunked {
|
||||
bodySize = -1
|
||||
}
|
||||
if req.IsBodyStream() {
|
||||
t.Fatalf("IsBodyStream must return false")
|
||||
}
|
||||
@@ -1563,12 +1680,56 @@ func testSetRequestBodyStream(t *testing.T, body string, chunked bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func testSetResponseBodyStream(t *testing.T, body string, chunked bool) {
|
||||
func testSetRequestBodyStreamChunked(t *testing.T, body string, trailer map[string]string) {
|
||||
var req Request
|
||||
req.Header.SetHost("foobar.com")
|
||||
req.Header.SetMethod(MethodPost)
|
||||
|
||||
if req.IsBodyStream() {
|
||||
t.Fatalf("IsBodyStream must return false")
|
||||
}
|
||||
req.SetBodyStream(bytes.NewBufferString(body), -1)
|
||||
if !req.IsBodyStream() {
|
||||
t.Fatalf("IsBodyStream must return true")
|
||||
}
|
||||
|
||||
var w bytes.Buffer
|
||||
bw := bufio.NewWriter(&w)
|
||||
for k := range trailer {
|
||||
err := req.Header.AddTrailer(k)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
if err := req.Write(bw); err != nil {
|
||||
t.Fatalf("unexpected error when writing request: %s. body=%q", err, body)
|
||||
}
|
||||
for k, v := range trailer {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
if err := bw.Flush(); err != nil {
|
||||
t.Fatalf("unexpected error when flushing request: %s. body=%q", err, body)
|
||||
}
|
||||
|
||||
var req1 Request
|
||||
br := bufio.NewReader(&w)
|
||||
if err := req1.Read(br); err != nil {
|
||||
t.Fatalf("unexpected error when reading request: %s. body=%q", err, body)
|
||||
}
|
||||
if string(req1.Body()) != body {
|
||||
t.Fatalf("unexpected body %q. Expecting %q", req1.Body(), body)
|
||||
}
|
||||
for k, v := range trailer {
|
||||
r := req.Header.Peek(k)
|
||||
if string(r) != v {
|
||||
t.Fatalf("unexpected trailer %s. Expecting %s. Got %q", k, v, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testSetResponseBodyStream(t *testing.T, body string) {
|
||||
var resp Response
|
||||
bodySize := len(body)
|
||||
if chunked {
|
||||
bodySize = -1
|
||||
}
|
||||
if resp.IsBodyStream() {
|
||||
t.Fatalf("IsBodyStream must return false")
|
||||
}
|
||||
@@ -1596,6 +1757,50 @@ func testSetResponseBodyStream(t *testing.T, body string, chunked bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func testSetResponseBodyStreamChunked(t *testing.T, body string, trailer map[string]string) {
|
||||
var resp Response
|
||||
if resp.IsBodyStream() {
|
||||
t.Fatalf("IsBodyStream must return false")
|
||||
}
|
||||
resp.SetBodyStream(bytes.NewBufferString(body), -1)
|
||||
if !resp.IsBodyStream() {
|
||||
t.Fatalf("IsBodyStream must return true")
|
||||
}
|
||||
|
||||
var w bytes.Buffer
|
||||
bw := bufio.NewWriter(&w)
|
||||
for k := range trailer {
|
||||
err := resp.Header.AddTrailer(k)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
if err := resp.Write(bw); err != nil {
|
||||
t.Fatalf("unexpected error when writing response: %s. body=%q", err, body)
|
||||
}
|
||||
if err := bw.Flush(); err != nil {
|
||||
t.Fatalf("unexpected error when flushing response: %s. body=%q", err, body)
|
||||
}
|
||||
for k, v := range trailer {
|
||||
resp.Header.Set(k, v)
|
||||
}
|
||||
|
||||
var resp1 Response
|
||||
br := bufio.NewReader(&w)
|
||||
if err := resp1.Read(br); err != nil {
|
||||
t.Fatalf("unexpected error when reading response: %s. body=%q", err, body)
|
||||
}
|
||||
if string(resp1.Body()) != body {
|
||||
t.Fatalf("unexpected body %q. Expecting %q", resp1.Body(), body)
|
||||
}
|
||||
for k, v := range trailer {
|
||||
r := resp.Header.Peek(k)
|
||||
if string(r) != v {
|
||||
t.Fatalf("unexpected trailer %s. Expecting %s. Got %q", k, v, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRound2(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1622,7 +1827,7 @@ func TestRequestReadChunked(t *testing.T) {
|
||||
|
||||
var req Request
|
||||
|
||||
s := "POST /foo HTTP/1.1\r\nHost: google.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa/bb\r\n\r\n3\r\nabc\r\n5\r\n12345\r\n0\r\n\r\ntrail"
|
||||
s := "POST /foo HTTP/1.1\r\nHost: google.com\r\nTransfer-Encoding: chunked\r\nContent-Type: aa/bb\r\n\r\n3\r\nabc\r\n5\r\n12345\r\n0\r\n\r\nTrail: test\r\n\r\n"
|
||||
r := bytes.NewBufferString(s)
|
||||
rb := bufio.NewReader(r)
|
||||
err := req.Read(rb)
|
||||
@@ -1633,8 +1838,8 @@ func TestRequestReadChunked(t *testing.T) {
|
||||
if string(req.Body()) != expectedBody {
|
||||
t.Fatalf("Unexpected body %q. Expected %q", req.Body(), expectedBody)
|
||||
}
|
||||
verifyRequestHeader(t, &req.Header, 8, "/foo", "google.com", "", "aa/bb")
|
||||
verifyTrailer(t, rb, "trail")
|
||||
verifyRequestHeader(t, &req.Header, -1, "/foo", "google.com", "", "aa/bb")
|
||||
verifyTrailer(t, rb, map[string]string{"Trail": "test"}, true)
|
||||
}
|
||||
|
||||
// See: https://github.com/erikdubbelboer/fasthttp/issues/34
|
||||
@@ -1661,25 +1866,25 @@ func TestResponseReadWithoutBody(t *testing.T) {
|
||||
|
||||
var resp Response
|
||||
|
||||
testResponseReadWithoutBody(t, &resp, "HTTP/1.1 304 Not Modified\r\nContent-Type: aa\r\nContent-Length: 1235\r\n\r\nfoobar", false,
|
||||
304, 1235, "aa", "foobar")
|
||||
testResponseReadWithoutBody(t, &resp, "HTTP/1.1 304 Not Modified\r\nContent-Type: aa\r\nContent-Length: 1235\r\n\r\n", false,
|
||||
304, 1235, "aa", nil)
|
||||
|
||||
testResponseReadWithoutBody(t, &resp, "HTTP/1.1 204 Foo Bar\r\nContent-Type: aab\r\nTransfer-Encoding: chunked\r\n\r\n123\r\nss", false,
|
||||
204, -1, "aab", "123\r\nss")
|
||||
testResponseReadWithoutBody(t, &resp, "HTTP/1.1 204 Foo Bar\r\nContent-Type: aab\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nFoo: bar\r\n\r\n", false,
|
||||
204, -1, "aab", map[string]string{"Foo": "bar"})
|
||||
|
||||
testResponseReadWithoutBody(t, &resp, "HTTP/1.1 123 AAA\r\nContent-Type: xxx\r\nContent-Length: 3434\r\n\r\naaaa", false,
|
||||
123, 3434, "xxx", "aaaa")
|
||||
testResponseReadWithoutBody(t, &resp, "HTTP/1.1 123 AAA\r\nContent-Type: xxx\r\nContent-Length: 3434\r\n\r\n", false,
|
||||
123, 3434, "xxx", nil)
|
||||
|
||||
testResponseReadWithoutBody(t, &resp, "HTTP 200 OK\r\nContent-Type: text/xml\r\nContent-Length: 123\r\n\r\nxxxx", true,
|
||||
200, 123, "text/xml", "xxxx")
|
||||
testResponseReadWithoutBody(t, &resp, "HTTP 200 OK\r\nContent-Type: text/xml\r\nContent-Length: 123\r\n\r\nfoobar\r\n", true,
|
||||
200, 123, "text/xml", nil)
|
||||
|
||||
// '100 Continue' must be skipped.
|
||||
testResponseReadWithoutBody(t, &resp, "HTTP/1.1 100 Continue\r\nFoo-bar: baz\r\n\r\nHTTP/1.1 329 aaa\r\nContent-Type: qwe\r\nContent-Length: 894\r\n\r\nfoobar", true,
|
||||
329, 894, "qwe", "foobar")
|
||||
testResponseReadWithoutBody(t, &resp, "HTTP/1.1 100 Continue\r\nFoo-bar: baz\r\n\r\nHTTP/1.1 329 aaa\r\nContent-Type: qwe\r\nContent-Length: 894\r\n\r\n", true,
|
||||
329, 894, "qwe", nil)
|
||||
}
|
||||
|
||||
func testResponseReadWithoutBody(t *testing.T, resp *Response, s string, skipBody bool,
|
||||
expectedStatusCode, expectedContentLength int, expectedContentType, expectedTrailer string) {
|
||||
expectedStatusCode, expectedContentLength int, expectedContentType string, expectedTrailer map[string]string) {
|
||||
r := bytes.NewBufferString(s)
|
||||
rb := bufio.NewReader(r)
|
||||
resp.SkipBody = skipBody
|
||||
@@ -1691,12 +1896,12 @@ func testResponseReadWithoutBody(t *testing.T, resp *Response, s string, skipBod
|
||||
t.Fatalf("Unexpected response body %q. Expected %q. response=%q", resp.Body(), "", s)
|
||||
}
|
||||
verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType)
|
||||
verifyTrailer(t, rb, expectedTrailer)
|
||||
verifyResponseTrailer(t, &resp.Header, expectedTrailer)
|
||||
|
||||
// verify that ordinal response is read after null-body response
|
||||
resp.SkipBody = false
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nContent-Length: 5\r\nContent-Type: bar\r\n\r\n56789aaa",
|
||||
300, 5, "bar", "56789", "aaa")
|
||||
300, 5, "bar", "56789", nil)
|
||||
}
|
||||
|
||||
func TestRequestSuccess(t *testing.T) {
|
||||
@@ -1872,40 +2077,54 @@ func TestResponseReadSuccess(t *testing.T) {
|
||||
|
||||
// usual response
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Length: 10\r\nContent-Type: foo/bar\r\n\r\n0123456789",
|
||||
200, 10, "foo/bar", "0123456789", "")
|
||||
200, 10, "foo/bar", "0123456789", nil)
|
||||
|
||||
// zero response
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 500 OK\r\nContent-Length: 0\r\nContent-Type: foo/bar\r\n\r\n",
|
||||
500, 0, "foo/bar", "", "")
|
||||
500, 0, "foo/bar", "", nil)
|
||||
|
||||
// response with trailer
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nContent-Length: 5\r\nContent-Type: bar\r\n\r\n56789aaa",
|
||||
300, 5, "bar", "56789", "aaa")
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\nfoo: bar\r\n\r\n",
|
||||
300, -1, "bar", "56789", map[string]string{"Foo": "bar"})
|
||||
|
||||
// no conent-length ('identity' transfer-encoding)
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: foobar\r\n\r\nzxxc",
|
||||
200, 4, "foobar", "zxxc", "")
|
||||
// response with trailer disableNormalizing
|
||||
resp.Header.DisableNormalizing()
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 300 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: bar\r\n\r\n5\r\n56789\r\n0\r\nfoo: bar\r\n\r\n",
|
||||
300, -1, "bar", "56789", map[string]string{"foo": "bar"})
|
||||
|
||||
// no content-length ('identity' transfer-encoding)
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: foobar\r\n\r\nzxxxx",
|
||||
200, 5, "foobar", "zxxxx", nil)
|
||||
|
||||
// explicitly stated 'Transfer-Encoding: identity'
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 234 ss\r\nContent-Type: xxx\r\n\r\nxag",
|
||||
234, 3, "xxx", "xag", "")
|
||||
234, 3, "xxx", "xag", nil)
|
||||
|
||||
// big 'identity' response
|
||||
body := string(createFixedBody(100500))
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aa\r\n\r\n"+body,
|
||||
200, 100500, "aa", body, "")
|
||||
200, 100500, "aa", body, nil)
|
||||
|
||||
// chunked response
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nqwer\r\n2\r\nty\r\n0\r\n\r\nzzzzz",
|
||||
200, 6, "text/html", "qwerty", "zzzzz")
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nqwer\r\n2\r\nty\r\n0\r\nFoo2: bar2\r\n\r\n",
|
||||
200, -1, "text/html", "qwerty", map[string]string{"Foo2": "bar2"})
|
||||
|
||||
// chunked response with non-chunked Transfer-Encoding.
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 230 OK\r\nContent-Type: text\r\nTransfer-Encoding: aaabbb\r\n\r\n2\r\ner\r\n2\r\nty\r\n0\r\n\r\nwe",
|
||||
230, 4, "text", "erty", "we")
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 230 OK\r\nContent-Type: text\r\nTransfer-Encoding: aaabbb\r\n\r\n2\r\ner\r\n2\r\nty\r\n0\r\nFoo3: bar3\r\n\r\n",
|
||||
230, -1, "text", "erty", map[string]string{"Foo3": "bar3"})
|
||||
|
||||
// chunked response with content-length
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: foo/bar\r\nContent-Length: 123\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ntest\r\n0\r\nFoo4:bar4\r\n\r\n",
|
||||
200, -1, "foo/bar", "test", map[string]string{"Foo4": "bar4"})
|
||||
|
||||
// chunked response with empty body
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nFoo5: bar5\r\n\r\n",
|
||||
200, -1, "text/html", "", map[string]string{"Foo5": "bar5"})
|
||||
|
||||
// chunked response with chunk extension
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n3;ext\r\naaa\r\n0\r\nFoo6: bar6\r\n\r\n",
|
||||
200, -1, "text/html", "aaa", map[string]string{"Foo6": "bar6"})
|
||||
|
||||
// zero chunked response
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\nzzz",
|
||||
200, 0, "text/html", "", "zzz")
|
||||
}
|
||||
|
||||
func TestResponseReadError(t *testing.T) {
|
||||
@@ -1922,8 +2141,13 @@ func TestResponseReadError(t *testing.T) {
|
||||
// empty body
|
||||
testResponseReadError(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nContent-Length: 1234\r\n\r\n")
|
||||
|
||||
// short body
|
||||
// invalid chunked body
|
||||
testResponseReadError(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nContent-Length: 1234\r\n\r\nshort")
|
||||
|
||||
// chunked body without end chunk
|
||||
testResponseReadError(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nTransfer-Encoding: chunked\r\n\r\nfoo")
|
||||
|
||||
testResponseReadError(t, resp, "HTTP/1.1 200 OK\r\nContent-Type: aaa\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nfoo")
|
||||
}
|
||||
|
||||
func testResponseReadError(t *testing.T, resp *Response, response string) {
|
||||
@@ -1934,12 +2158,12 @@ func testResponseReadError(t *testing.T, resp *Response, response string) {
|
||||
t.Fatalf("Expecting error for response=%q", response)
|
||||
}
|
||||
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 303 Redisred sedfs sdf\r\nContent-Type: aaa\r\nContent-Length: 5\r\n\r\nHELLOaaa",
|
||||
303, 5, "aaa", "HELLO", "aaa")
|
||||
testResponseReadSuccess(t, resp, "HTTP/1.1 303 Redisred sedfs sdf\r\nContent-Type: aaa\r\nContent-Length: 5\r\n\r\nHELLO",
|
||||
303, 5, "aaa", "HELLO", nil)
|
||||
}
|
||||
|
||||
func testResponseReadSuccess(t *testing.T, resp *Response, response string, expectedStatusCode, expectedContentLength int,
|
||||
expectedContenType, expectedBody, expectedTrailer string) {
|
||||
expectedContentType, expectedBody string, expectedTrailer map[string]string) {
|
||||
|
||||
r := bytes.NewBufferString(response)
|
||||
rb := bufio.NewReader(r)
|
||||
@@ -1948,11 +2172,11 @@ func testResponseReadSuccess(t *testing.T, resp *Response, response string, expe
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContenType)
|
||||
verifyResponseHeader(t, &resp.Header, expectedStatusCode, expectedContentLength, expectedContentType)
|
||||
if !bytes.Equal(resp.Body(), []byte(expectedBody)) {
|
||||
t.Fatalf("Unexpected body %q. Expected %q", resp.Body(), []byte(expectedBody))
|
||||
}
|
||||
verifyTrailer(t, rb, expectedTrailer)
|
||||
verifyResponseTrailer(t, &resp.Header, expectedTrailer)
|
||||
}
|
||||
|
||||
func TestReadBodyFixedSize(t *testing.T) {
|
||||
@@ -2109,28 +2333,24 @@ func testRequestPostArgsSuccess(t *testing.T, req *Request, s string, expectedAr
|
||||
|
||||
func testReadBodyChunked(t *testing.T, bodySize int) {
|
||||
body := createFixedBody(bodySize)
|
||||
chunkedBody := createChunkedBody(body)
|
||||
expectedTrailer := []byte("chunked shit")
|
||||
chunkedBody = append(chunkedBody, expectedTrailer...)
|
||||
expectedTrailer := map[string]string{"Foo": "bar"}
|
||||
chunkedBody := createChunkedBody(body, expectedTrailer, true)
|
||||
|
||||
r := bytes.NewBuffer(chunkedBody)
|
||||
br := bufio.NewReader(r)
|
||||
b, err := readBody(br, -1, 0, nil)
|
||||
b, err := readBodyChunked(br, 0, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error for bodySize=%d: %s. body=%q, chunkedBody=%q", bodySize, err, body, chunkedBody)
|
||||
}
|
||||
if !bytes.Equal(b, body) {
|
||||
t.Fatalf("Unexpected response read for bodySize=%d: %q. Expected %q. chunkedBody=%q", bodySize, b, body, chunkedBody)
|
||||
}
|
||||
verifyTrailer(t, br, string(expectedTrailer))
|
||||
verifyTrailer(t, br, expectedTrailer, false)
|
||||
}
|
||||
|
||||
func testReadBodyFixedSize(t *testing.T, bodySize int) {
|
||||
body := createFixedBody(bodySize)
|
||||
expectedTrailer := []byte("traler aaaa")
|
||||
bodyWithTrailer := append(body, expectedTrailer...)
|
||||
|
||||
r := bytes.NewBuffer(bodyWithTrailer)
|
||||
r := bytes.NewBuffer(body)
|
||||
br := bufio.NewReader(r)
|
||||
b, err := readBody(br, bodySize, 0, nil)
|
||||
if err != nil {
|
||||
@@ -2139,7 +2359,7 @@ func testReadBodyFixedSize(t *testing.T, bodySize int) {
|
||||
if !bytes.Equal(b, body) {
|
||||
t.Fatalf("Unexpected response read for bodySize=%d: %q. Expected %q", bodySize, b, body)
|
||||
}
|
||||
verifyTrailer(t, br, string(expectedTrailer))
|
||||
verifyTrailer(t, br, nil, false)
|
||||
}
|
||||
|
||||
func createFixedBody(bodySize int) []byte {
|
||||
@@ -2150,7 +2370,7 @@ func createFixedBody(bodySize int) []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
func createChunkedBody(body []byte) []byte {
|
||||
func createChunkedBody(body []byte, trailer map[string]string, withEnd bool) []byte {
|
||||
var b []byte
|
||||
chunkSize := 1
|
||||
for len(body) > 0 {
|
||||
@@ -2163,7 +2383,17 @@ func createChunkedBody(body []byte) []byte {
|
||||
body = body[chunkSize:]
|
||||
chunkSize++
|
||||
}
|
||||
return append(b, []byte("0\r\n\r\n")...)
|
||||
if withEnd {
|
||||
b = append(b, "0\r\n"...)
|
||||
for k, v := range trailer {
|
||||
b = append(b, k...)
|
||||
b = append(b, ": "...)
|
||||
b = append(b, v...)
|
||||
b = append(b, "\r\n"...)
|
||||
}
|
||||
b = append(b, "\r\n"...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestWriteMultipartForm(t *testing.T) {
|
||||
|
||||
+62
-1
@@ -3609,7 +3609,7 @@ func TestStreamRequestBodyExceedMaxSize(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamBodyReqestContentLength(t *testing.T) {
|
||||
func TestStreamBodyRequestContentLength(t *testing.T) {
|
||||
t.Parallel()
|
||||
content := strings.Repeat("1", 1<<15) // 32K
|
||||
contentLength := len(content)
|
||||
@@ -3784,6 +3784,67 @@ func TestIncompleteBodyReturnsUnexpectedEOF(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerChunkedResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
trailer := map[string]string{
|
||||
"AtEnd1": "1111",
|
||||
"AtEnd2": "2222",
|
||||
"AtEnd3": "3333",
|
||||
}
|
||||
|
||||
h := func(ctx *RequestCtx) {
|
||||
ctx.Response.Header.DisableNormalizing()
|
||||
ctx.Response.Header.Set("Transfer-Encoding", "chunked")
|
||||
for k := range trailer {
|
||||
err := ctx.Response.Header.AddTrailer(k)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
ctx.Response.SetBodyStreamWriter(func(w *bufio.Writer) {
|
||||
for i := 0; i < 3; i++ {
|
||||
fmt.Fprintf(w, "message %d", i)
|
||||
if err := w.Flush(); err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
})
|
||||
for k, v := range trailer {
|
||||
ctx.Response.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
s := &Server{
|
||||
Handler: h,
|
||||
}
|
||||
|
||||
rw := &readWriter{}
|
||||
rw.r.WriteString("GET / HTTP/1.1\r\nHost: test.com\r\n\r\n")
|
||||
|
||||
if err := s.ServeConn(rw); err != nil {
|
||||
t.Fatalf("Unexpected error from serveConn: %s", err)
|
||||
}
|
||||
|
||||
br := bufio.NewReader(&rw.w)
|
||||
var resp Response
|
||||
if err := resp.Read(br); err != nil {
|
||||
t.Fatalf("Unexpected error when reading response: %s", err)
|
||||
}
|
||||
if resp.Header.ContentLength() != -1 {
|
||||
t.Fatalf("Unexpected Content-Length %d. Expected %d", resp.Header.ContentLength(), -1)
|
||||
}
|
||||
if !bytes.Equal(resp.Body(), []byte("message 0"+"message 1"+"message 2")) {
|
||||
t.Fatalf("Unexpected body %q. Expected %q", resp.Body(), "foobar")
|
||||
}
|
||||
for k, v := range trailer {
|
||||
h := resp.Header.Peek(k)
|
||||
if !bytes.Equal(resp.Header.Peek(k), []byte(v)) {
|
||||
t.Fatalf("Unexpected trailer %s. Expected %s. Got %q", k, v, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifyResponse(t *testing.T, r *bufio.Reader, expectedStatusCode int, expectedContentType, expectedBody string) *Response {
|
||||
var resp Response
|
||||
if err := resp.Read(r); err != nil {
|
||||
|
||||
+12
-13
@@ -10,10 +10,10 @@ import (
|
||||
)
|
||||
|
||||
type requestStream struct {
|
||||
header *RequestHeader
|
||||
prefetchedBytes *bytes.Reader
|
||||
reader *bufio.Reader
|
||||
totalBytesRead int
|
||||
contentLength int
|
||||
chunkLeft int
|
||||
}
|
||||
|
||||
@@ -22,18 +22,18 @@ func (rs *requestStream) Read(p []byte) (int, error) {
|
||||
n int
|
||||
err error
|
||||
)
|
||||
if rs.contentLength == -1 {
|
||||
if rs.header.contentLength == -1 {
|
||||
if rs.chunkLeft == 0 {
|
||||
chunkSize, err := parseChunkSize(rs.reader)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if chunkSize == 0 {
|
||||
err = readCrLf(rs.reader)
|
||||
if err == nil {
|
||||
err = io.EOF
|
||||
err = rs.header.ReadTrailer(rs.reader)
|
||||
if err != nil && err != io.EOF {
|
||||
return 0, err
|
||||
}
|
||||
return 0, err
|
||||
return 0, io.EOF
|
||||
}
|
||||
rs.chunkLeft = chunkSize
|
||||
}
|
||||
@@ -52,7 +52,7 @@ func (rs *requestStream) Read(p []byte) (int, error) {
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
if rs.totalBytesRead == rs.contentLength {
|
||||
if rs.totalBytesRead == rs.header.contentLength {
|
||||
return 0, io.EOF
|
||||
}
|
||||
prefetchedSize := int(rs.prefetchedBytes.Size())
|
||||
@@ -63,12 +63,12 @@ func (rs *requestStream) Read(p []byte) (int, error) {
|
||||
}
|
||||
n, err := rs.prefetchedBytes.Read(p)
|
||||
rs.totalBytesRead += n
|
||||
if n == rs.contentLength {
|
||||
if n == rs.header.contentLength {
|
||||
return n, io.EOF
|
||||
}
|
||||
return n, err
|
||||
} else {
|
||||
left := rs.contentLength - rs.totalBytesRead
|
||||
left := rs.header.contentLength - rs.totalBytesRead
|
||||
if len(p) > left {
|
||||
p = p[:left]
|
||||
}
|
||||
@@ -79,18 +79,17 @@ func (rs *requestStream) Read(p []byte) (int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if rs.totalBytesRead == rs.contentLength {
|
||||
if rs.totalBytesRead == rs.header.contentLength {
|
||||
err = io.EOF
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func acquireRequestStream(b *bytebufferpool.ByteBuffer, r *bufio.Reader, contentLength int) *requestStream {
|
||||
func acquireRequestStream(b *bytebufferpool.ByteBuffer, r *bufio.Reader, h *RequestHeader) *requestStream {
|
||||
rs := requestStreamPool.Get().(*requestStream)
|
||||
rs.prefetchedBytes = bytes.NewReader(b.B)
|
||||
rs.reader = r
|
||||
rs.contentLength = contentLength
|
||||
|
||||
rs.header = h
|
||||
return rs
|
||||
}
|
||||
|
||||
|
||||
+66
-1
@@ -3,6 +3,7 @@ package fasthttp
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -102,7 +103,7 @@ aaaaaaaaaa`
|
||||
|
||||
func getChunkedTestEnv(t testing.TB) (*fasthttputil.InmemoryListener, []byte) {
|
||||
body := createFixedBody(128 * 1024)
|
||||
chunkedBody := createChunkedBody(body)
|
||||
chunkedBody := createChunkedBody(body, nil, true)
|
||||
|
||||
testHandler := func(ctx *RequestCtx) {
|
||||
bodyBytes, err := ioutil.ReadAll(ctx.RequestBodyStream())
|
||||
@@ -142,6 +143,70 @@ func getChunkedTestEnv(t testing.TB) (*fasthttputil.InmemoryListener, []byte) {
|
||||
return ln, formattedRequest
|
||||
}
|
||||
|
||||
func TestRequestStreamChunkedWithTrailer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
body := createFixedBody(10)
|
||||
expectedTrailer := map[string]string{
|
||||
"Foo": "footest",
|
||||
"Bar": "bartest",
|
||||
}
|
||||
chunkedBody := createChunkedBody(body, expectedTrailer, true)
|
||||
req := fmt.Sprintf(`POST / HTTP/1.1
|
||||
Host: example.com
|
||||
Transfer-Encoding: chunked
|
||||
Trailer: Foo, Bar
|
||||
|
||||
%s
|
||||
`, chunkedBody)
|
||||
|
||||
ln := fasthttputil.NewInmemoryListener()
|
||||
s := &Server{
|
||||
StreamRequestBody: true,
|
||||
Handler: func(ctx *RequestCtx) {
|
||||
all, err := ioutil.ReadAll(ctx.RequestBodyStream())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
if !bytes.Equal(all, body) {
|
||||
t.Errorf("unexpected body %q. Expecting %q", all, body)
|
||||
}
|
||||
|
||||
for k, v := range expectedTrailer {
|
||||
r := ctx.Request.Header.Peek(k)
|
||||
if string(r) != v {
|
||||
t.Errorf("unexpected trailer %s. Expecting %s. Got %q", k, v, r)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
if err := s.Serve(ln); err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
conn, err := ln.Dial()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if _, err = conn.Write([]byte(req)); err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if err := ln.Close(); err != nil {
|
||||
t.Fatalf("error when closing listener: %s", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ch:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timeout when waiting for the server to stop")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestStream(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
+29
-21
@@ -20,31 +20,39 @@ var (
|
||||
strColon = []byte(":")
|
||||
strColonSlashSlash = []byte("://")
|
||||
strColonSpace = []byte(": ")
|
||||
strCommaSpace = []byte(", ")
|
||||
strGMT = []byte("GMT")
|
||||
|
||||
strResponseContinue = []byte("HTTP/1.1 100 Continue\r\n\r\n")
|
||||
|
||||
strExpect = []byte(HeaderExpect)
|
||||
strConnection = []byte(HeaderConnection)
|
||||
strContentLength = []byte(HeaderContentLength)
|
||||
strContentType = []byte(HeaderContentType)
|
||||
strDate = []byte(HeaderDate)
|
||||
strHost = []byte(HeaderHost)
|
||||
strReferer = []byte(HeaderReferer)
|
||||
strServer = []byte(HeaderServer)
|
||||
strTransferEncoding = []byte(HeaderTransferEncoding)
|
||||
strContentEncoding = []byte(HeaderContentEncoding)
|
||||
strAcceptEncoding = []byte(HeaderAcceptEncoding)
|
||||
strUserAgent = []byte(HeaderUserAgent)
|
||||
strCookie = []byte(HeaderCookie)
|
||||
strSetCookie = []byte(HeaderSetCookie)
|
||||
strLocation = []byte(HeaderLocation)
|
||||
strIfModifiedSince = []byte(HeaderIfModifiedSince)
|
||||
strLastModified = []byte(HeaderLastModified)
|
||||
strAcceptRanges = []byte(HeaderAcceptRanges)
|
||||
strRange = []byte(HeaderRange)
|
||||
strContentRange = []byte(HeaderContentRange)
|
||||
strAuthorization = []byte(HeaderAuthorization)
|
||||
strExpect = []byte(HeaderExpect)
|
||||
strConnection = []byte(HeaderConnection)
|
||||
strContentLength = []byte(HeaderContentLength)
|
||||
strContentType = []byte(HeaderContentType)
|
||||
strDate = []byte(HeaderDate)
|
||||
strHost = []byte(HeaderHost)
|
||||
strReferer = []byte(HeaderReferer)
|
||||
strServer = []byte(HeaderServer)
|
||||
strTransferEncoding = []byte(HeaderTransferEncoding)
|
||||
strContentEncoding = []byte(HeaderContentEncoding)
|
||||
strAcceptEncoding = []byte(HeaderAcceptEncoding)
|
||||
strUserAgent = []byte(HeaderUserAgent)
|
||||
strCookie = []byte(HeaderCookie)
|
||||
strSetCookie = []byte(HeaderSetCookie)
|
||||
strLocation = []byte(HeaderLocation)
|
||||
strIfModifiedSince = []byte(HeaderIfModifiedSince)
|
||||
strLastModified = []byte(HeaderLastModified)
|
||||
strAcceptRanges = []byte(HeaderAcceptRanges)
|
||||
strRange = []byte(HeaderRange)
|
||||
strContentRange = []byte(HeaderContentRange)
|
||||
strAuthorization = []byte(HeaderAuthorization)
|
||||
strTE = []byte(HeaderTE)
|
||||
strTrailer = []byte(HeaderTrailer)
|
||||
strMaxForwards = []byte(HeaderMaxForwards)
|
||||
strProxyConnection = []byte(HeaderProxyConnection)
|
||||
strProxyAuthenticate = []byte(HeaderProxyAuthenticate)
|
||||
strProxyAuthorization = []byte(HeaderProxyAuthorization)
|
||||
strWWWAuthenticate = []byte(HeaderWWWAuthenticate)
|
||||
|
||||
strCookieExpires = []byte("expires")
|
||||
strCookieDomain = []byte("domain")
|
||||
|
||||
Reference in New Issue
Block a user