Repository: valyala/fasthttp
Pull request #1169 (2021-12-04 18:44:26)

Allow to set Host header for Client
Allow to change Host header when using Client. Currently only HostClient allows to change Host header.

Tolyar: Comments for failed tests. Test TestRequestUpdateURI do r.Header.SetHost("aaa.bbb") but checking header Host: foobar.com This correct for the old behaviour but not for the new. I do not want touch tests before PR will be checked. 
erikdubbelboer: There was already a discussion about this here: https://github.com/valyala/fasthttp/pull/1164
I think the change I posted there is the only acceptable solution, the change you made now would break for all users who somehow have the Host header set already but depend on it being change to the host of the url that they specify.

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/bytesconv.go b/bytesconv.go
index d54e77f..bf582af 100644
--- a/bytesconv.go
+++ b/bytesconv.go
@@ -98,7 +98,7 @@ func ParseIPv4(dst net.IP, ipStr []byte) (net.IP, error) {
 		}
 		v, err := ParseUint(b[:n])
 		if err != nil {
-			return dst, fmt.Errorf("cannot parse ipStr %q: %s", ipStr, err)
+			return dst, fmt.Errorf("cannot parse ipStr %q: %w", ipStr, err)
 		}
 		if v > 255 {
 			return dst, fmt.Errorf("cannot parse ipStr %q: ip part cannot exceed 255: parsed %d", ipStr, v)
@@ -108,7 +108,7 @@ func ParseIPv4(dst net.IP, ipStr []byte) (net.IP, error) {
 	}
 	v, err := ParseUint(b)
 	if err != nil {
-		return dst, fmt.Errorf("cannot parse ipStr %q: %s", ipStr, err)
+		return dst, fmt.Errorf("cannot parse ipStr %q: %w", ipStr, err)
 	}
 	if v > 255 {
 		return dst, fmt.Errorf("cannot parse ipStr %q: ip part cannot exceed 255: parsed %d", ipStr, v)
diff --git a/expvarhandler/expvar.go b/expvarhandler/expvar.go
index 6254baa..84b8614 100644
--- a/expvarhandler/expvar.go
+++ b/expvarhandler/expvar.go
@@ -58,7 +58,7 @@ func getExpvarRegexp(ctx *fasthttp.RequestCtx) (*regexp.Regexp, error) {
 	}
 	rr, err := regexp.Compile(r)
 	if err != nil {
-		return nil, fmt.Errorf("cannot parse r=%q: %s", r, err)
+		return nil, fmt.Errorf("cannot parse r=%q: %w", r, err)
 	}
 	return rr, nil
 }
diff --git a/fasthttpproxy/proxy_env.go b/fasthttpproxy/proxy_env.go
index 2457ad5..8a95c70 100644
--- a/fasthttpproxy/proxy_env.go
+++ b/fasthttpproxy/proxy_env.go
@@ -49,7 +49,7 @@ func FasthttpProxyHTTPDialerTimeout(timeout time.Duration) fasthttp.DialFunc {
 
 		port, _, err := net.SplitHostPort(addr)
 		if err != nil {
-			return nil, fmt.Errorf("unexpected addr format: %v", err)
+			return nil, fmt.Errorf("unexpected addr format: %w", err)
 		}
 
 		reqURL := &url.URL{Host: addr, Scheme: httpScheme}
@@ -108,14 +108,14 @@ func FasthttpProxyHTTPDialerTimeout(timeout time.Duration) fasthttp.DialFunc {
 
 		if err := res.Read(bufio.NewReader(conn)); err != nil {
 			if connErr := conn.Close(); connErr != nil {
-				return nil, fmt.Errorf("conn close err %v followed by read conn err %v", connErr, err)
+				return nil, fmt.Errorf("conn close err %v precede by read conn err %w", connErr, err)
 			}
 			return nil, err
 		}
 		if res.Header.StatusCode() != 200 {
 			if connErr := conn.Close(); connErr != nil {
 				return nil, fmt.Errorf(
-					"conn close err %v followed by connect to proxy: code: %d body %s",
+					"conn close err %w precede by connect to proxy: code: %d body %s",
 					connErr, res.StatusCode(), string(res.Body()))
 			}
 			return nil, fmt.Errorf("could not connect to proxy: code: %d body %s", res.StatusCode(), string(res.Body()))
diff --git a/fs.go b/fs.go
index c3be8c0..257f066 100644
--- a/fs.go
+++ b/fs.go
@@ -524,7 +524,7 @@ func (ff *fsFile) bigFileReader() (io.Reader, error) {
 
 	f, err := os.Open(ff.f.Name())
 	if err != nil {
-		return nil, fmt.Errorf("cannot open already opened file: %s", err)
+		return nil, fmt.Errorf("cannot open already opened file: %w", err)
 	}
 	return &bigFileReader{
 		f:  f,
@@ -981,7 +981,7 @@ func (h *fsHandler) openIndexFile(ctx *RequestCtx, dirPath string, mustCompress
 			return ff, nil
 		}
 		if !os.IsNotExist(err) {
-			return nil, fmt.Errorf("cannot open file %q: %s", indexFilePath, err)
+			return nil, fmt.Errorf("cannot open file %q: %w", indexFilePath, err)
 		}
 	}
 
@@ -1100,7 +1100,7 @@ func (h *fsHandler) compressAndOpenFSFile(filePath string, fileEncoding string)
 	fileInfo, err := f.Stat()
 	if err != nil {
 		f.Close()
-		return nil, fmt.Errorf("cannot obtain info for file %q: %s", filePath, err)
+		return nil, fmt.Errorf("cannot obtain info for file %q: %w", filePath, err)
 	}
 
 	if fileInfo.IsDir() {
@@ -1146,7 +1146,7 @@ func (h *fsHandler) compressFileNolock(f *os.File, fileInfo os.FileInfo, filePat
 	if err != nil {
 		f.Close()
 		if !os.IsPermission(err) {
-			return nil, fmt.Errorf("cannot create temporary file %q: %s", tmpFilePath, err)
+			return nil, fmt.Errorf("cannot create temporary file %q: %w", tmpFilePath, err)
 		}
 		return nil, errNoCreatePermission
 	}
@@ -1168,14 +1168,14 @@ func (h *fsHandler) compressFileNolock(f *os.File, fileInfo os.FileInfo, filePat
 	zf.Close()
 	f.Close()
 	if err != nil {
-		return nil, fmt.Errorf("error when compressing file %q to %q: %s", filePath, tmpFilePath, err)
+		return nil, fmt.Errorf("error when compressing file %q to %q: %w", filePath, tmpFilePath, err)
 	}
 	if err = os.Chtimes(tmpFilePath, time.Now(), fileInfo.ModTime()); err != nil {
 		return nil, fmt.Errorf("cannot change modification time to %s for tmp file %q: %s",
 			fileInfo.ModTime(), tmpFilePath, err)
 	}
 	if err = os.Rename(tmpFilePath, compressedFilePath); err != nil {
-		return nil, fmt.Errorf("cannot move compressed file from %q to %q: %s", tmpFilePath, compressedFilePath, err)
+		return nil, fmt.Errorf("cannot move compressed file from %q to %q: %w", tmpFilePath, compressedFilePath, err)
 	}
 	return h.newCompressedFSFile(compressedFilePath, fileEncoding)
 }
@@ -1183,12 +1183,12 @@ func (h *fsHandler) compressFileNolock(f *os.File, fileInfo os.FileInfo, filePat
 func (h *fsHandler) newCompressedFSFile(filePath string, fileEncoding string) (*fsFile, error) {
 	f, err := os.Open(filePath)
 	if err != nil {
-		return nil, fmt.Errorf("cannot open compressed file %q: %s", filePath, err)
+		return nil, fmt.Errorf("cannot open compressed file %q: %w", filePath, err)
 	}
 	fileInfo, err := f.Stat()
 	if err != nil {
 		f.Close()
-		return nil, fmt.Errorf("cannot obtain info for compressed file %q: %s", filePath, err)
+		return nil, fmt.Errorf("cannot obtain info for compressed file %q: %w", filePath, err)
 	}
 	return h.newFSFile(f, fileInfo, true, fileEncoding)
 }
@@ -1210,7 +1210,7 @@ func (h *fsHandler) openFSFile(filePath string, mustCompress bool, fileEncoding
 	fileInfo, err := f.Stat()
 	if err != nil {
 		f.Close()
-		return nil, fmt.Errorf("cannot obtain info for file %q: %s", filePath, err)
+		return nil, fmt.Errorf("cannot obtain info for file %q: %w", filePath, err)
 	}
 
 	if fileInfo.IsDir() {
@@ -1226,7 +1226,7 @@ func (h *fsHandler) openFSFile(filePath string, mustCompress bool, fileEncoding
 		fileInfoOriginal, err := os.Stat(filePathOriginal)
 		if err != nil {
 			f.Close()
-			return nil, fmt.Errorf("cannot obtain info for original file %q: %s", filePathOriginal, err)
+			return nil, fmt.Errorf("cannot obtain info for original file %q: %w", filePathOriginal, err)
 		}
 
 		// Only re-create the compressed file if there was more than a second between the mod times.
@@ -1257,7 +1257,7 @@ func (h *fsHandler) newFSFile(f *os.File, fileInfo os.FileInfo, compressed bool,
 	if len(contentType) == 0 {
 		data, err := readFileHeader(f, compressed, fileEncoding)
 		if err != nil {
-			return nil, fmt.Errorf("cannot read header of the file %q: %s", f.Name(), err)
+			return nil, fmt.Errorf("cannot read header of the file %q: %w", f.Name(), err)
 		}
 		contentType = http.DetectContentType(data)
 	}
diff --git a/header.go b/header.go
index c14532c..14cda98 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)
 	}
@@ -1483,11 +1797,11 @@ func (h *ResponseHeader) tryRead(r *bufio.Reader, n int) error {
 				}
 			}
 			return &ErrSmallBuffer{
-				error: fmt.Errorf("error when reading response headers: %s", errSmallBuffer),
+				error: fmt.Errorf("error when reading response headers: %w", errSmallBuffer),
 			}
 		}
 
-		return fmt.Errorf("error when reading response headers: %s", err)
+		return fmt.Errorf("error when reading response headers: %w", err)
 	}
 	b = mustPeekBuffered(r)
 	headersLen, errParse := h.parse(b)
@@ -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: %w", errSmallBuffer),
+			}
+		}
+
+		return fmt.Errorf("error when reading response trailer: %w", 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)
@@ -1522,9 +1891,9 @@ func headerError(typ string, err, errParse error, b []byte, secureErrorLogMessag
 
 func headerErrorMsg(typ string, err error, b []byte, secureErrorLogMessage bool) error {
 	if secureErrorLogMessage {
-		return fmt.Errorf("error when reading %s headers: %s. Buffer size=%d", typ, err, len(b))
+		return fmt.Errorf("error when reading %s headers: %w. Buffer size=%d", typ, err, len(b))
 	}
-	return fmt.Errorf("error when reading %s headers: %s. Buffer size=%d, contents: %s", typ, err, len(b), bufferSnippet(b))
+	return fmt.Errorf("error when reading %s headers: %w. Buffer size=%d, contents: %s", typ, err, len(b), bufferSnippet(b))
 }
 
 // Read reads request header from r.
@@ -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: %w", errSmallBuffer),
+			}
+		}
+
+		return fmt.Errorf("error when reading request trailer: %w", 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)
@@ -1567,7 +1991,7 @@ func (h *RequestHeader) tryRead(r *bufio.Reader, n int) error {
 		// This is for go 1.6 bug. See https://github.com/golang/go/issues/14121 .
 		if err == bufio.ErrBufferFull {
 			return &ErrSmallBuffer{
-				error: fmt.Errorf("error when reading request headers: %s", errSmallBuffer),
+				error: fmt.Errorf("error when reading request headers: %w", errSmallBuffer),
 			}
 		}
 
@@ -1577,7 +2001,7 @@ func (h *RequestHeader) tryRead(r *bufio.Reader, n int) error {
 			return ErrNothingRead{err}
 		}
 
-		return fmt.Errorf("error when reading request headers: %s", err)
+		return fmt.Errorf("error when reading request headers: %w", err)
 	}
 	b = mustPeekBuffered(r)
 	headersLen, errParse := h.parse(b)
@@ -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,43 @@ func (h *ResponseHeader) parse(buf []byte) (int, error) {
 	return m + n, nil
 }
 
+func (h *ResponseHeader) parseTrailer(buf []byte) (int, error) {
+	// Skip any 0 length chunk.
+	if buf[0] == '0' {
+		skip := len(strCRLF) + 1
+		if len(buf) < skip {
+			return 0, io.EOF
+		}
+		buf = buf[skip:]
+	}
+
+	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 +2397,83 @@ func (h *RequestHeader) parse(buf []byte) (int, error) {
 	return m + n, nil
 }
 
+func (h *RequestHeader) parseTrailer(buf []byte) (int, error) {
+	// Skip any 0 length chunk.
+	if buf[0] == '0' {
+		skip := len(strCRLF) + 1
+		if len(buf) < skip {
+			return 0, io.EOF
+		}
+		buf = buf[skip:]
+	}
+
+	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
@@ -1884,9 +2499,9 @@ func (h *ResponseHeader) parseFirstLine(buf []byte) (int, error) {
 	h.statusCode, n, err = parseUintBuf(b)
 	if err != nil {
 		if h.secureErrorLogMessage {
-			return 0, fmt.Errorf("cannot parse response status code: %s", err)
+			return 0, fmt.Errorf("cannot parse response status code: %w", err)
 		}
-		return 0, fmt.Errorf("cannot parse response status code: %s. Response %q", err, buf)
+		return 0, fmt.Errorf("cannot parse response status code: %w. Response %q", err, buf)
 	}
 	if len(b) > n && b[n] != ' ' {
 		if h.secureErrorLogMessage {
@@ -2028,6 +2643,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 +2669,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 +2735,14 @@ func (h *RequestHeader) parseHeaders(buf []byte) (int, error) {
 					}
 					continue
 				}
+				if caseInsensitiveCompare(s.key, strTrailer) {
+					if nerr := h.SetTrailerBytes(s.value); nerr != nil {
+						if err == nil {
+							err = nerr
+						}
+					}
+					continue
+				}
 			}
 		}
 		h.h = appendArgBytes(h.h, s.key, s.value, argsHasValue)
@@ -2159,13 +2786,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 +3128,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..81b2496 100644
--- a/http.go
+++ b/http.go
@@ -56,6 +56,9 @@ type Request struct {
 	// Request timeout. Usually set by DoDeadline or DoTimeout
 	// if <= 0, means not set
 	timeout time.Duration
+
+	// Use Host header (request.Header.SetHost) instead of the host from SetRequestURI, SetHost, or URI().SetHost
+	UseHostHeader bool
 }
 
 // Response represents HTTP response.
@@ -844,7 +847,7 @@ func (req *Request) MultipartForm() (*multipart.Form, error) {
 		if bytes.Equal(ce, strGzip) {
 			// Do not care about memory usage here.
 			if bodyStream, err = gzip.NewReader(bodyStream); err != nil {
-				return nil, fmt.Errorf("cannot gunzip request body: %s", err)
+				return nil, fmt.Errorf("cannot gunzip request body: %w", err)
 			}
 		} else if len(ce) > 0 {
 			return nil, fmt.Errorf("unsupported Content-Encoding: %q", ce)
@@ -853,14 +856,14 @@ func (req *Request) MultipartForm() (*multipart.Form, error) {
 		mr := multipart.NewReader(bodyStream, req.multipartFormBoundary)
 		req.multipartForm, err = mr.ReadForm(8 * 1024)
 		if err != nil {
-			return nil, fmt.Errorf("cannot read multipart/form-data body: %s", err)
+			return nil, fmt.Errorf("cannot read multipart/form-data body: %w", err)
 		}
 	} else {
 		body := req.bodyBytes()
 		if bytes.Equal(ce, strGzip) {
 			// Do not care about memory usage here.
 			if body, err = AppendGunzipBytes(nil, body); err != nil {
-				return nil, fmt.Errorf("cannot gunzip request body: %s", err)
+				return nil, fmt.Errorf("cannot gunzip request body: %w", err)
 			}
 		} else if len(ce) > 0 {
 			return nil, fmt.Errorf("unsupported Content-Encoding: %q", ce)
@@ -894,14 +897,14 @@ func WriteMultipartForm(w io.Writer, f *multipart.Form, boundary string) error {
 
 	mw := multipart.NewWriter(w)
 	if err := mw.SetBoundary(boundary); err != nil {
-		return fmt.Errorf("cannot use form boundary %q: %s", boundary, err)
+		return fmt.Errorf("cannot use form boundary %q: %w", boundary, err)
 	}
 
 	// marshal values
 	for k, vv := range f.Value {
 		for _, v := range vv {
 			if err := mw.WriteField(k, v); err != nil {
-				return fmt.Errorf("cannot write form field %q value %q: %s", k, v, err)
+				return fmt.Errorf("cannot write form field %q value %q: %w", k, v, err)
 			}
 		}
 	}
@@ -911,23 +914,23 @@ func WriteMultipartForm(w io.Writer, f *multipart.Form, boundary string) error {
 		for _, fv := range fvv {
 			vw, err := mw.CreatePart(fv.Header)
 			if err != nil {
-				return fmt.Errorf("cannot create form file %q (%q): %s", k, fv.Filename, err)
+				return fmt.Errorf("cannot create form file %q (%q): %w", k, fv.Filename, err)
 			}
 			fh, err := fv.Open()
 			if err != nil {
 				return fmt.Errorf("cannot open form file %q (%q): %s", k, fv.Filename, err)
 			}
 			if _, err = copyZeroAlloc(vw, fh); err != nil {
-				return fmt.Errorf("error when copying form file %q (%q): %s", k, fv.Filename, err)
+				return fmt.Errorf("error when copying form file %q (%q): %w", k, fv.Filename, err)
 			}
 			if err = fh.Close(); err != nil {
-				return fmt.Errorf("cannot close form file %q (%q): %s", k, fv.Filename, err)
+				return fmt.Errorf("cannot close form file %q (%q): %w", k, fv.Filename, err)
 			}
 		}
 	}
 
 	if err := mw.Close(); err != nil {
-		return fmt.Errorf("error when closing multipart form writer: %s", err)
+		return fmt.Errorf("error when closing multipart form writer: %w", err)
 	}
 
 	return nil
@@ -945,7 +948,7 @@ func readMultipartForm(r io.Reader, boundary string, size, maxInMemoryFileSize i
 	mr := multipart.NewReader(lr, boundary)
 	f, err := mr.ReadForm(int64(maxInMemoryFileSize))
 	if err != nil {
-		return nil, fmt.Errorf("cannot read multipart/form-data body: %s", err)
+		return nil, fmt.Errorf("cannot read multipart/form-data body: %w", err)
 	}
 	return f, nil
 }
@@ -1136,7 +1139,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 +1159,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 +1220,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 +1233,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 +1268,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 +1290,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 {
@@ -1357,10 +1397,15 @@ func (req *Request) Write(w *bufio.Writer) error {
 	if len(req.Header.Host()) == 0 || req.parsedURI {
 		uri := req.URI()
 		host := uri.Host()
-		if len(host) == 0 {
-			return errRequestHostRequired
+		if len(req.Header.Host()) == 0 {
+			if len(host) == 0 {
+				return errRequestHostRequired
+			} else {
+				req.Header.SetHostBytes(host)
+			}
+		} else if !req.UseHostHeader {
+			req.Header.SetHostBytes(host)
 		}
-		req.Header.SetHostBytes(host)
 		req.Header.SetRequestURIBytes(uri.RequestURI())
 
 		if len(uri.username) > 0 {
@@ -1392,7 +1437,7 @@ func (req *Request) Write(w *bufio.Writer) error {
 	if req.onlyMultipartForm() {
 		body, err = marshalMultipartForm(req.multipartForm, req.multipartFormBoundary)
 		if err != nil {
-			return fmt.Errorf("error when marshaling multipart form: %s", err)
+			return fmt.Errorf("error when marshaling multipart form: %w", err)
 		}
 		req.Header.SetMultipartFormBoundary(req.multipartFormBoundary)
 	}
@@ -1723,9 +1768,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 +1828,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 +1979,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 +1993,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 +2113,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 +2129,6 @@ func readBodyChunked(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, erro
 			}
 		}
 		dst = dst[:len(dst)-strCRLFLen]
-		if chunkSize == 0 {
-			return dst, nil
-		}
 	}
 }
 
@@ -2098,13 +2144,14 @@ 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 {
 			return -1, ErrBrokenChunk{
-				error: fmt.Errorf("cannot unread '\r' char at the end of chunk size: %s", err),
+				error: fmt.Errorf("cannot unread '\r' char at the end of chunk size: %w", err),
 			}
 		}
 		break
@@ -2121,7 +2168,7 @@ func readCrLf(r *bufio.Reader) error {
 		c, err := r.ReadByte()
 		if err != nil {
 			return ErrBrokenChunk{
-				error: fmt.Errorf("cannot read %q char at the end of chunk size: %s", exp, err),
+				error: fmt.Errorf("cannot read %q char at the end of chunk size: %w", exp, err),
 			}
 		}
 		if c != exp {
diff --git a/server.go b/server.go
index 5ffc891..edbd699 100644
--- a/server.go
+++ b/server.go
@@ -1561,14 +1561,14 @@ func (s *Server) ListenAndServe(addr string) error {
 // The server sets the given file mode for the UNIX addr.
 func (s *Server) ListenAndServeUNIX(addr string, mode os.FileMode) error {
 	if err := os.Remove(addr); err != nil && !os.IsNotExist(err) {
-		return fmt.Errorf("unexpected error when trying to remove unix socket file %q: %s", addr, err)
+		return fmt.Errorf("unexpected error when trying to remove unix socket file %q: %w", addr, err)
 	}
 	ln, err := net.Listen("unix", addr)
 	if err != nil {
 		return err
 	}
 	if err = os.Chmod(addr, mode); err != nil {
-		return fmt.Errorf("cannot chmod %#o for %q: %s", mode, addr, err)
+		return fmt.Errorf("cannot chmod %#o for %q: %w", mode, addr, err)
 	}
 	return s.Serve(ln)
 }
@@ -1695,7 +1695,7 @@ func (s *Server) AppendCert(certFile, keyFile string) error {
 
 	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
 	if err != nil {
-		return fmt.Errorf("cannot load TLS key pair from certFile=%q and keyFile=%q: %s", certFile, keyFile, err)
+		return fmt.Errorf("cannot load TLS key pair from certFile=%q and keyFile=%q: %w", certFile, keyFile, err)
 	}
 
 	s.configTLS()
@@ -2079,7 +2079,7 @@ func (s *Server) serveConn(c net.Conn) (err error) {
 		connectionClose bool
 		isHTTP11        bool
 
-		reqReset               bool
+		reqReset, respReset    bool
 		continueReadingRequest bool = true
 	)
 	for {
@@ -2118,7 +2118,7 @@ func (s *Server) serveConn(c net.Conn) (err error) {
 			br, err = acquireByteReader(&ctx)
 		}
 
-		reqReset = false
+		reqReset, respReset = false, false
 		ctx.Request.isTLS = isTLS
 		ctx.Response.Header.noDefaultContentType = s.NoDefaultContentType
 		ctx.Response.Header.noDefaultDate = s.NoDefaultDate
@@ -2403,8 +2403,9 @@ func (s *Server) serveConn(c net.Conn) (err error) {
 		s.setState(c, StateIdle)
 		ctx.userValues.Reset()
 
-		reqReset = true
+		reqReset, respReset = true, true
 		ctx.Request.Reset()
+		ctx.Response.Reset()
 
 		if atomic.LoadInt32(&s.stop) == 1 {
 			err = nil
@@ -2420,11 +2421,14 @@ func (s *Server) serveConn(c net.Conn) (err error) {
 	}
 	if ctx != nil {
 		// in unexpected cases the for loop will break
-		// before request reset call. in such cases, call it before
+		// before request/response reset call. in such cases, call it before
 		// release to fix #548
 		if !reqReset {
 			ctx.Request.Reset()
 		}
+		if !respReset {
+			ctx.Response.Reset()
+		}
 		s.releaseCtx(ctx)
 	}
 	return
@@ -2511,7 +2515,7 @@ func writeResponse(ctx *RequestCtx, w *bufio.Writer) error {
 		panic("BUG: cannot write timed out response")
 	}
 	err := ctx.Response.Write(w)
-	ctx.Response.Reset()
+
 	return err
 }
 
@@ -2796,8 +2800,11 @@ func (s *Server) writeErrorResponse(bw *bufio.Writer, ctx *RequestCtx, serverNam
 	if bw == nil {
 		bw = acquireWriter(ctx)
 	}
+
 	writeResponse(ctx, bw) //nolint:errcheck
+	ctx.Response.Reset()
 	bw.Flush()
+
 	return bw
 }
 
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/brotli_test.go b/brotli_test.go
index 1ac94dd..a4efa05 100644
--- a/brotli_test.go
+++ b/brotli_test.go
@@ -42,7 +42,7 @@ func testBrotliBytesSingleCase(s string) error {
 
 	unbrotliedS, err := AppendUnbrotliBytes(prefix, brotlipedS[len(prefix):])
 	if err != nil {
-		return fmt.Errorf("unexpected error when uncompressing %q: %s", s, err)
+		return fmt.Errorf("unexpected error when uncompressing %q: %w", s, err)
 	}
 	if !bytes.Equal(unbrotliedS[:len(prefix)], prefix) {
 		return fmt.Errorf("unexpected prefix when uncompressing %q: %q. Expecting %q", s, unbrotliedS[:len(prefix)], prefix)
@@ -83,17 +83,17 @@ func testBrotliCompressSingleCase(s string) error {
 	var buf bytes.Buffer
 	zw := acquireStacklessBrotliWriter(&buf, CompressDefaultCompression)
 	if _, err := zw.Write([]byte(s)); err != nil {
-		return fmt.Errorf("unexpected error: %s. s=%q", err, s)
+		return fmt.Errorf("unexpected error: %w. s=%q", err, s)
 	}
 	releaseStacklessBrotliWriter(zw, CompressDefaultCompression)
 
 	zr, err := acquireBrotliReader(&buf)
 	if err != nil {
-		return fmt.Errorf("unexpected error: %s. s=%q", err, s)
+		return fmt.Errorf("unexpected error: %w. s=%q", err, s)
 	}
 	body, err := ioutil.ReadAll(zr)
 	if err != nil {
-		return fmt.Errorf("unexpected error: %s. s=%q", err, s)
+		return fmt.Errorf("unexpected error: %w. s=%q", err, s)
 	}
 	if string(body) != s {
 		return fmt.Errorf("unexpected string after decompression: %q. Expecting %q", body, s)
diff --git a/client_test.go b/client_test.go
index e960745..033284d 100644
--- a/client_test.go
+++ b/client_test.go
@@ -626,6 +626,10 @@ func TestClientHeaderCase(t *testing.T) {
 }
 
 func TestClientReadTimeout(t *testing.T) {
+	if runtime.GOOS == "windows" {
+		t.SkipNow()
+	}
+
 	t.Parallel()
 
 	ln := fasthttputil.NewInmemoryListener()
@@ -814,14 +818,14 @@ func TestClientDoWithCustomHeaders(t *testing.T) {
 	go func() {
 		conn, err := ln.Accept()
 		if err != nil {
-			ch <- fmt.Errorf("cannot accept client connection: %s", err)
+			ch <- fmt.Errorf("cannot accept client connection: %w", err)
 			return
 		}
 		br := bufio.NewReader(conn)
 
 		var req Request
 		if err = req.Read(br); err != nil {
-			ch <- fmt.Errorf("cannot read client request: %s", err)
+			ch <- fmt.Errorf("cannot read client request: %w", err)
 			return
 		}
 		if string(req.Header.Method()) != MethodPost {
@@ -854,11 +858,11 @@ func TestClientDoWithCustomHeaders(t *testing.T) {
 		var resp Response
 		bw := bufio.NewWriter(conn)
 		if err = resp.Write(bw); err != nil {
-			ch <- fmt.Errorf("cannot send response: %s", err)
+			ch <- fmt.Errorf("cannot send response: %w", err)
 			return
 		}
 		if err = bw.Flush(); err != nil {
-			ch <- fmt.Errorf("cannot flush response: %s", err)
+			ch <- fmt.Errorf("cannot flush response: %w", err)
 			return
 		}
 
@@ -1221,7 +1225,7 @@ func TestHostClientPendingRequests(t *testing.T) {
 			resp := AcquireResponse()
 
 			if err := c.DoTimeout(req, resp, 10*time.Second); err != nil {
-				resultCh <- fmt.Errorf("unexpected error: %s", err)
+				resultCh <- fmt.Errorf("unexpected error: %w", err)
 				return
 			}
 
diff --git a/compress_test.go b/compress_test.go
index 572641a..111c93c 100644
--- a/compress_test.go
+++ b/compress_test.go
@@ -78,7 +78,7 @@ func testGzipBytesSingleCase(s string) error {
 
 	gunzippedS, err := AppendGunzipBytes(prefix, gzippedS[len(prefix):])
 	if err != nil {
-		return fmt.Errorf("unexpected error when uncompressing %q: %s", s, err)
+		return fmt.Errorf("unexpected error when uncompressing %q: %w", s, err)
 	}
 	if !bytes.Equal(gunzippedS[:len(prefix)], prefix) {
 		return fmt.Errorf("unexpected prefix when uncompressing %q: %q. Expecting %q", s, gunzippedS[:len(prefix)], prefix)
@@ -99,7 +99,7 @@ func testDeflateBytesSingleCase(s string) error {
 
 	inflatedS, err := AppendInflateBytes(prefix, deflatedS[len(prefix):])
 	if err != nil {
-		return fmt.Errorf("unexpected error when uncompressing %q: %s", s, err)
+		return fmt.Errorf("unexpected error when uncompressing %q: %w", s, err)
 	}
 	if !bytes.Equal(inflatedS[:len(prefix)], prefix) {
 		return fmt.Errorf("unexpected prefix when uncompressing %q: %q. Expecting %q", s, inflatedS[:len(prefix)], prefix)
@@ -165,17 +165,17 @@ func testGzipCompressSingleCase(s string) error {
 	var buf bytes.Buffer
 	zw := acquireStacklessGzipWriter(&buf, CompressDefaultCompression)
 	if _, err := zw.Write([]byte(s)); err != nil {
-		return fmt.Errorf("unexpected error: %s. s=%q", err, s)
+		return fmt.Errorf("unexpected error: %w. s=%q", err, s)
 	}
 	releaseStacklessGzipWriter(zw, CompressDefaultCompression)
 
 	zr, err := acquireGzipReader(&buf)
 	if err != nil {
-		return fmt.Errorf("unexpected error: %s. s=%q", err, s)
+		return fmt.Errorf("unexpected error: %w. s=%q", err, s)
 	}
 	body, err := ioutil.ReadAll(zr)
 	if err != nil {
-		return fmt.Errorf("unexpected error: %s. s=%q", err, s)
+		return fmt.Errorf("unexpected error: %w. s=%q", err, s)
 	}
 	if string(body) != s {
 		return fmt.Errorf("unexpected string after decompression: %q. Expecting %q", body, s)
@@ -188,17 +188,17 @@ func testFlateCompressSingleCase(s string) error {
 	var buf bytes.Buffer
 	zw := acquireStacklessDeflateWriter(&buf, CompressDefaultCompression)
 	if _, err := zw.Write([]byte(s)); err != nil {
-		return fmt.Errorf("unexpected error: %s. s=%q", err, s)
+		return fmt.Errorf("unexpected error: %w. s=%q", err, s)
 	}
 	releaseStacklessDeflateWriter(zw, CompressDefaultCompression)
 
 	zr, err := acquireFlateReader(&buf)
 	if err != nil {
-		return fmt.Errorf("unexpected error: %s. s=%q", err, s)
+		return fmt.Errorf("unexpected error: %w. s=%q", err, s)
 	}
 	body, err := ioutil.ReadAll(zr)
 	if err != nil {
-		return fmt.Errorf("unexpected error: %s. s=%q", err, s)
+		return fmt.Errorf("unexpected error: %w. s=%q", err, s)
 	}
 	if string(body) != s {
 		return fmt.Errorf("unexpected string after decompression: %q. Expecting %q", body, s)
@@ -213,7 +213,7 @@ func testConcurrent(concurrency int, f func() error) error {
 		go func(idx int) {
 			err := f()
 			if err != nil {
-				ch <- fmt.Errorf("error in goroutine %d: %s", idx, err)
+				ch <- fmt.Errorf("error in goroutine %d: %w", idx, err)
 			}
 			ch <- nil
 		}(i)
diff --git a/fasthttputil/pipeconns_test.go b/fasthttputil/pipeconns_test.go
index d61e5e5..709c994 100644
--- a/fasthttputil/pipeconns_test.go
+++ b/fasthttputil/pipeconns_test.go
@@ -164,7 +164,7 @@ func testPipeConnsCloseWhileReadWrite(t *testing.T) {
 		var err error
 		if _, err = io.Copy(ioutil.Discard, c1); err != nil {
 			if err != errConnectionClosed {
-				err = fmt.Errorf("unexpected error: %s", err)
+				err = fmt.Errorf("unexpected error: %w", err)
 			} else {
 				err = nil
 			}
@@ -178,7 +178,7 @@ func testPipeConnsCloseWhileReadWrite(t *testing.T) {
 		for {
 			if _, err = c2.Write([]byte("foobar")); err != nil {
 				if err != errConnectionClosed {
-					err = fmt.Errorf("unexpected error: %s", err)
+					err = fmt.Errorf("unexpected error: %w", err)
 				} else {
 					err = nil
 				}
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..ac71903 100644
--- a/http_test.go
+++ b/http_test.go
@@ -3,6 +3,8 @@ package fasthttp
 import (
 	"bufio"
 	"bytes"
+	"encoding/base64"
+	"errors"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -16,6 +18,25 @@ import (
 	"github.com/valyala/bytebufferpool"
 )
 
+func TestInvalidTrailers(t *testing.T) {
+	t.Parallel()
+
+	if err := (&Response{}).Read(bufio.NewReader(bytes.NewReader([]byte{0x20, 0x30, 0x0a, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0xff, 0x0a, 0x0a, 0x30, 0x0d, 0x0a, 0x30}))); !errors.Is(err, io.EOF) {
+		t.Fatalf("%#v", err)
+	}
+	if err := (&Response{}).Read(bufio.NewReader(bytes.NewReader([]byte{0xff, 0x20, 0x0a, 0x54, 0x52, 0x61, 0x49, 0x4c, 0x65, 0x52, 0x3a, 0x2c, 0x0a, 0x0a}))); !errors.Is(err, errEmptyInt) {
+		t.Fatal(err)
+	}
+	if err := (&Response{}).Read(bufio.NewReader(bytes.NewReader([]byte{0x54, 0x52, 0x61, 0x49, 0x4c, 0x65, 0x52, 0x3a, 0x2c, 0x0a, 0x0a}))); !strings.Contains(err.Error(), "cannot find whitespace in the first line of response") {
+		t.Fatal(err)
+	}
+
+	b, _ := base64.StdEncoding.DecodeString("tCAKIDoKCToKICAKCToKICAKCToKIAogOgoJOgogIAoJOgovIC8vOi4KOh0KVFJhSUxlUjo9HT09HQpUUmFJTGVSOicQAApUUmFJTGVSOj0gHSAKCT09HQoKOgoKCgo=")
+	if err := (&Request{}).Read(bufio.NewReader(bytes.NewReader(b))); !strings.Contains(err.Error(), "error when reading request headers: invalid header key") {
+		t.Fatalf("%#v", err)
+	}
+}
+
 func TestResponseEmptyTransferEncoding(t *testing.T) {
 	t.Parallel()
 
@@ -58,7 +79,7 @@ func TestIssue875(t *testing.T) {
 		expectedLocation string
 	}
 
-	var testcases = []testcase{
+	testcases := []testcase{
 		{
 			uri:              `http://localhost:3000/?redirect=foo%0d%0aSet-Cookie:%20SESSIONID=MaliciousValue%0d%0a`,
 			expectedRedirect: "foo\r\nSet-Cookie: SESSIONID=MaliciousValue\r\n",
@@ -117,7 +138,6 @@ func TestRequestCopyTo(t *testing.T) {
 		t.Fatalf("unexpected error: %s", err)
 	}
 	testRequestCopyTo(t, &req)
-
 }
 
 func TestResponseCopyTo(t *testing.T) {
@@ -134,7 +154,6 @@ func TestResponseCopyTo(t *testing.T) {
 	resp.Header.SetStatusCode(200)
 	resp.SetBodyString("test")
 	testResponseCopyTo(t, &resp)
-
 }
 
 func testRequestCopyTo(t *testing.T, src *Request) {
@@ -155,6 +174,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()
 
@@ -594,6 +729,31 @@ func TestRequestUpdateURI(t *testing.T) {
 	}
 }
 
+func TestUseHostHeader(t *testing.T) {
+	t.Parallel()
+
+	var r Request
+	r.UseHostHeader = true
+	r.Header.SetHost("aaa.bbb")
+	r.SetRequestURI("/lkjkl/kjl")
+
+	// Modify request uri and host via URI() object and make sure
+	// the requestURI and Host header are properly updated
+	u := r.URI()
+	u.SetPath("/123/432.html")
+	u.SetHost("foobar.com")
+	a := u.QueryArgs()
+	a.Set("aaa", "bcse")
+
+	s := r.String()
+	if !strings.HasPrefix(s, "GET /123/432.html?aaa=bcse") {
+		t.Fatalf("cannot find %q in %q", "GET /123/432.html?aaa=bcse", s)
+	}
+	if !strings.Contains(s, "\r\nHost: aaa.bbb\r\n") {
+		t.Fatalf("cannot find %q in %q", "\r\nHost: aaa.bbb\r\n", s)
+	}
+}
+
 func TestRequestBodyStreamMultipleBodyCalls(t *testing.T) {
 	t.Parallel()
 
@@ -1053,7 +1213,6 @@ func TestRequestContinueReadBodyDisablePrereadMultipartForm(t *testing.T) {
 	if string(formData) != string(r.Body()) {
 		t.Fatalf("The body given must equal the body in the Request")
 	}
-
 }
 
 func TestRequestMayContinue(t *testing.T) {
@@ -1344,17 +1503,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 +1524,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 +1542,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 +1556,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 +1564,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 +1653,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 +1723,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 +1800,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 +1870,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 +1881,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 +1909,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 +1939,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 +2120,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"})
+
+	// 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 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", "")
+	// 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 +2184,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 +2201,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 +2215,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 +2376,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 +2402,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 +2413,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 +2426,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) {
@@ -2183,7 +2456,6 @@ Content-Type: application/json
 `, "\n", "\r\n", -1)
 	mr := multipart.NewReader(strings.NewReader(s), "foo")
 	form, err := mr.ReadForm(1024)
-
 	if err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
diff --git a/server_test.go b/server_test.go
index b42197c..0a199b8 100644
--- a/server_test.go
+++ b/server_test.go
@@ -14,6 +14,7 @@ import (
 	"net"
 	"os"
 	"reflect"
+	"regexp"
 	"strings"
 	"sync"
 	"testing"
@@ -2041,23 +2042,28 @@ func TestRequestCtxWriteString(t *testing.T) {
 	}
 }
 
-func TestServeConnKeepRequestValuesUntilResetUserValues(t *testing.T) {
+func TestServeConnKeepRequestAndResponseUntilResetUserValues(t *testing.T) {
 	t.Parallel()
 
 	reqStr := "POST /foo HTTP/1.0\r\nHost: google.com\r\nContent-Type: application/octet-stream\r\nContent-Length: 0\r\nConnection: keep-alive\r\n\r\n"
+	respRegex := regexp.MustCompile("HTTP/1.1 308 Permanent Redirect\r\nServer: fasthttp\r\nDate: (.*)\r\nContent-Length: 0\r\nConnection: keep-alive\r\n\r\n")
 
 	rw := &readWriter{}
 	rw.r.WriteString(reqStr)
 
-	var resultReqStr string
+	var resultReqStr, resultRespStr string
 
 	ch := make(chan struct{})
 	go func() {
 		err := ServeConn(rw, func(ctx *RequestCtx) {
+			ctx.Response.SetStatusCode(StatusPermanentRedirect)
+
 			ctx.SetUserValue("myKey", &closerWithRequestCtx{
 				ctx: ctx,
 				closeFunc: func(closerCtx *RequestCtx) error {
 					resultReqStr = closerCtx.Request.String()
+					resultRespStr = closerCtx.Response.String()
+
 					return nil
 				}})
 		})
@@ -2076,6 +2082,10 @@ func TestServeConnKeepRequestValuesUntilResetUserValues(t *testing.T) {
 	if resultReqStr != reqStr {
 		t.Errorf("Request == %s, want %s", resultReqStr, reqStr)
 	}
+
+	if !respRegex.MatchString(resultRespStr) {
+		t.Errorf("Response == %s, want regex %s", resultRespStr, respRegex)
+	}
 }
 
 func TestServeConnNonHTTP11KeepAlive(t *testing.T) {
@@ -3609,7 +3619,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 +3794,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/stackless/writer_test.go b/stackless/writer_test.go
index 9c4748e..35a4bdc 100644
--- a/stackless/writer_test.go
+++ b/stackless/writer_test.go
@@ -73,7 +73,7 @@ func testWriter(newWriter NewWriterFunc, newReader func(io.Reader) io.Reader) er
 
 	for i := 0; i < 5; i++ {
 		if err := testWriterReuse(w, dstW, newReader); err != nil {
-			return fmt.Errorf("unexpected error when re-using writer on iteration %d: %s", i, err)
+			return fmt.Errorf("unexpected error when re-using writer on iteration %d: %w", i, err)
 		}
 		dstW = &bytes.Buffer{}
 		w.Reset(dstW)
@@ -89,7 +89,7 @@ func testWriterReuse(w Writer, r io.Reader, newReader func(io.Reader) io.Reader)
 		fmt.Fprintf(mw, "foobar %d\n", i)
 		if i%13 == 0 {
 			if err := w.Flush(); err != nil {
-				return fmt.Errorf("error on flush: %s", err)
+				return fmt.Errorf("error on flush: %w", err)
 			}
 		}
 	}
@@ -98,7 +98,7 @@ func testWriterReuse(w Writer, r io.Reader, newReader func(io.Reader) io.Reader)
 	zr := newReader(r)
 	data, err := ioutil.ReadAll(zr)
 	if err != nil {
-		return fmt.Errorf("unexpected error: %s, data=%q", err, data)
+		return fmt.Errorf("unexpected error: %w, data=%q", err, data)
 	}
 
 	wantData := wantW.Bytes()
@@ -120,7 +120,7 @@ func testConcurrent(testFunc func() error, concurrency int) error {
 		select {
 		case err := <-ch:
 			if err != nil {
-				return fmt.Errorf("unexpected error on goroutine %d: %s", i, err)
+				return fmt.Errorf("unexpected error on goroutine %d: %w", i, err)
 			}
 		case <-time.After(time.Second):
 			return fmt.Errorf("timeout on goroutine %d", i)
diff --git a/stream_test.go b/stream_test.go
index c5ce292..9c287ad 100644
--- a/stream_test.go
+++ b/stream_test.go
@@ -47,7 +47,7 @@ func TestStreamReaderClose(t *testing.T) {
 	r := NewStreamReader(func(w *bufio.Writer) {
 		fmt.Fprintf(w, "%s", firstLine)
 		if err := w.Flush(); err != nil {
-			ch <- fmt.Errorf("unexpected error on first flush: %s", err)
+			ch <- fmt.Errorf("unexpected error on first flush: %w", err)
 			return
 		}
 
@@ -89,7 +89,7 @@ func TestStreamReaderClose(t *testing.T) {
 	// read trailing data
 	go func() {
 		if _, err := ioutil.ReadAll(r); err != nil {
-			ch <- fmt.Errorf("unexpected error when reading trailing data: %s", err)
+			ch <- fmt.Errorf("unexpected error when reading trailing data: %w", err)
 			return
 		}
 		ch <- 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()