Repository: valyala/fasthttp
Pull request #1165 (2021-11-24 09:34:12)

Add trailer support
This is in reference to #1078 , #210 .
This will allow FastHTTP to support trailer for chunked transfers.

### Write trailer
For example:
```go
resp := &Response{}
resp.Header.Set("Trailer", "Foo")
// write body
resp.Header.Set("Foo", "bar")

req := &Request{}
req.Header.SetTrailer("Foo")
// write body
req.Header.Set("Foo", "bar")
```
Header `Foo` will send after the chunked body.


### Read trailer
For example:
```go
func(ctx *RequestCtx) {
    ioutil.ReadAll(ctx.RequestBodyStream())
    ctx.Request.Header.VisitAllTrailer(func(key []byte) {
        trailer := ctx.Request.Header.PeekBytes(key)
        // do something
    })
}
```
Trailer can be found by inspecting Header after reading all chunked body.

ichxxx: > Lint is failing: https://github.com/valyala/fasthttp/runs/4310069291?check_suite_focus=true
> 
> Can you add some clear documentation that trailers are only supported with chunked bodies?

Sure.


ichxxx: Seems the tests are a bit flaky.
erikdubbelboer: > Seems the tests are a bit flaky.

Yeah they are. You can ignore that.
erikdubbelboer: Sorry for asking for so many changes. I'm super happy that you wrote this code. But it's also quite a lot and important to get right I think.
ichxxx: This is not a problem at all. It's always better to be strict with code. I have also learned a lot from this pr.

Patch:
diff --git a/args.go b/args.go
index a6db223..a8e4394 100644
--- a/args.go
+++ b/args.go
@@ -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))
diff --git a/header.go b/header.go
index c14532c..105f0c3 100644
--- a/header.go
+++ b/header.go
@@ -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")
diff --git a/headers.go b/headers.go
index 378dfec..676a0da 100644
--- a/headers.go
+++ b/headers.go
@@ -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"
diff --git a/http.go b/http.go
index c8c2eb4..4b7a219 100644
--- a/http.go
+++ b/http.go
@@ -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 contentLength == -1 {
-		return readBodyChunked(r, maxBodySize, dst)
+	if maxBodySize > 0 && contentLength > maxBodySize {
+		return dst, ErrBodyTooLarge
 	}
-	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 {
diff --git a/streaming.go b/streaming.go
index 1a3d748..11750a9 100644
--- a/streaming.go
+++ b/streaming.go
@@ -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
 }
 
diff --git a/strings.go b/strings.go
index e28eaac..e4fb08b 100644
--- a/strings.go
+++ b/strings.go
@@ -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")


Test patch:
diff --git a/header_test.go b/header_test.go
index 27a292c..f041577 100644
--- a/header_test.go
+++ b/header_test.go
@@ -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)
 }
diff --git a/http_test.go b/http_test.go
index 8bd050b..978703a 100644
--- a/http_test.go
+++ b/http_test.go
@@ -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) {
diff --git a/server_test.go b/server_test.go
index b42197c..2f7e6ae 100644
--- a/server_test.go
+++ b/server_test.go
@@ -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 {
diff --git a/streaming_test.go b/streaming_test.go
index 6066c39..32f0003 100644
--- a/streaming_test.go
+++ b/streaming_test.go
@@ -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()