Repository: valyala/fasthttp
Pull request #378 (2018-08-17 08:57:05)

Add RequestHeader.RawHeaders()
I'm not sure about this one. At first it was ok but because of the messy header parsing and returning this turned out to be buggy and required some fixes to get working properly. I also don't like the test, it seems to test a lot more than is required for the code changes.

See: https://github.com/erikdubbelboer/fasthttp/issues/18, https://github.com/erikdubbelboer/fasthttp/pull/27, https://github.com/erikdubbelboer/fasthttp/pull/29 and https://github.com/erikdubbelboer/fasthttp/pull/40


Patch:
diff --git a/.travis.yml b/.travis.yml
index ab6557c..fca54a8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -31,3 +31,6 @@ script:
 
   # run tests on a standard platform
   - go test -v ./...
+
+  # run tests with the race detector as well
+  - go test -race -v ./...
diff --git a/README.md b/README.md
index f7d0891..1a8bff8 100644
--- a/README.md
+++ b/README.md
@@ -550,7 +550,7 @@ uintBuf := fasthttp.AppendUint(nil, 1234)
   
   **NOTE**: Go 1.9.7 is the oldest tested version. We recommend you to update as soon as you can. As of 1.11.3 we will drop 1.9.x support.
 
-* *Please provide real benchmark data and sever information*
+* *Please provide real benchmark data and server information*
 
   See [this issue](https://github.com/valyala/fasthttp/issues/4).
 
diff --git a/args.go b/args.go
index 2781593..4d9b35e 100644
--- a/args.go
+++ b/args.go
@@ -5,6 +5,8 @@ import (
 	"errors"
 	"io"
 	"sync"
+
+	"github.com/valyala/bytebufferpool"
 )
 
 // AcquireArgs returns an empty Args object from the pool.
@@ -243,10 +245,10 @@ func (a *Args) GetUint(key string) (int, error) {
 
 // SetUint sets uint value for the given key.
 func (a *Args) SetUint(key string, value int) {
-	bb := AcquireByteBuffer()
+	bb := bytebufferpool.Get()
 	bb.B = AppendUint(bb.B[:0], value)
 	a.SetBytesV(key, bb.B)
-	ReleaseByteBuffer(bb)
+	bytebufferpool.Put(bb)
 }
 
 // SetUintBytes sets uint value for the given key.
diff --git a/bytebuffer.go b/bytebuffer.go
deleted file mode 100644
index f965172..0000000
--- a/bytebuffer.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package fasthttp
-
-import (
-	"github.com/valyala/bytebufferpool"
-)
-
-// ByteBuffer provides byte buffer, which can be used with fasthttp API
-// in order to minimize memory allocations.
-//
-// ByteBuffer may be used with functions appending data to the given []byte
-// slice. See example code for details.
-//
-// Use AcquireByteBuffer for obtaining an empty byte buffer.
-//
-// ByteBuffer is deprecated. Use github.com/valyala/bytebufferpool instead.
-type ByteBuffer bytebufferpool.ByteBuffer
-
-// Write implements io.Writer - it appends p to ByteBuffer.B
-func (b *ByteBuffer) Write(p []byte) (int, error) {
-	return bb(b).Write(p)
-}
-
-// WriteString appends s to ByteBuffer.B
-func (b *ByteBuffer) WriteString(s string) (int, error) {
-	return bb(b).WriteString(s)
-}
-
-// Set sets ByteBuffer.B to p
-func (b *ByteBuffer) Set(p []byte) {
-	bb(b).Set(p)
-}
-
-// SetString sets ByteBuffer.B to s
-func (b *ByteBuffer) SetString(s string) {
-	bb(b).SetString(s)
-}
-
-// Reset makes ByteBuffer.B empty.
-func (b *ByteBuffer) Reset() {
-	bb(b).Reset()
-}
-
-// AcquireByteBuffer returns an empty byte buffer from the pool.
-//
-// Acquired byte buffer may be returned to the pool via ReleaseByteBuffer call.
-// This reduces the number of memory allocations required for byte buffer
-// management.
-func AcquireByteBuffer() *ByteBuffer {
-	return (*ByteBuffer)(defaultByteBufferPool.Get())
-}
-
-// ReleaseByteBuffer returns byte buffer to the pool.
-//
-// ByteBuffer.B mustn't be touched after returning it to the pool.
-// Otherwise data races occur.
-func ReleaseByteBuffer(b *ByteBuffer) {
-	defaultByteBufferPool.Put(bb(b))
-}
-
-func bb(b *ByteBuffer) *bytebufferpool.ByteBuffer {
-	return (*bytebufferpool.ByteBuffer)(b)
-}
-
-var defaultByteBufferPool bytebufferpool.Pool
diff --git a/bytesconv.go b/bytesconv.go
index bbb4dbc..6292957 100644
--- a/bytesconv.go
+++ b/bytesconv.go
@@ -416,8 +416,17 @@ func AppendQuotedArg(dst, src []byte) []byte {
 
 func appendQuotedPath(dst, src []byte) []byte {
 	for _, c := range src {
+		// From the spec: http://tools.ietf.org/html/rfc3986#section-3.3
+		// an path can contain zero or more of pchar that is defined as follows:
+		// pchar       = unreserved / pct-encoded / sub-delims / ":" / "@"
+		// pct-encoded = "%" HEXDIG HEXDIG
+		// unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
+		// sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
+		//             / "*" / "+" / "," / ";" / "="
 		if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' ||
-			c == '/' || c == '.' || c == ',' || c == '=' || c == ':' || c == '&' || c == '~' || c == '-' || c == '_' {
+			c == '-' || c == '.' || c == '_' || c == '~' || c == '!' || c == '$' ||
+			c == '&' || c == '\'' || c == '(' || c == ')' || c == '*' || c == '+' ||
+			c == ',' || c == ';' || c == '=' || c == ':' || c == '@' || c == '/' {
 			dst = append(dst, c)
 		} else {
 			dst = append(dst, '%', hexCharUpper(c>>4), hexCharUpper(c&15))
@@ -431,7 +440,7 @@ func appendQuotedPath(dst, src []byte) []byte {
 // This function has no performance benefits comparing to string(b) == s.
 // It is left here for backwards compatibility only.
 //
-// This function is deprecated and may be deleted soon.
+// Deprecated: may be deleted soon.
 func EqualBytesStr(b []byte, s string) bool {
 	return string(b) == s
 }
@@ -441,7 +450,7 @@ func EqualBytesStr(b []byte, s string) bool {
 // This function has no performance benefits comparing to append(dst, src...).
 // It is left here for backwards compatibility only.
 //
-// This function is deprecated and may be deleted soon.
+// Deprecated: may be deleted soon.
 func AppendBytesStr(dst []byte, src string) []byte {
 	return append(dst, src...)
 }
diff --git a/client.go b/client.go
index 4ff7fd4..64be717 100644
--- a/client.go
+++ b/client.go
@@ -447,6 +447,9 @@ const DefaultMaxConnsPerHost = 512
 // connection is closed.
 const DefaultMaxIdleConnDuration = 10 * time.Second
 
+// DefaultMaxIdemponentCallAttempts is the default idempotent calls attempts count.
+const DefaultMaxIdemponentCallAttempts = 5
+
 // DialFunc must establish connection to addr.
 //
 // There is no need in establishing TLS (SSL) connection for https.
@@ -526,6 +529,11 @@ type HostClient struct {
 	// after DefaultMaxIdleConnDuration.
 	MaxIdleConnDuration time.Duration
 
+	// Maximum number of attempts for idempotent calls
+	//
+	// DefaultMaxIdemponentCallAttempts is used if not set.
+	MaxIdemponentCallAttempts int
+
 	// Per-connection buffer size for responses' reading.
 	// This also limits the maximum header size.
 	//
@@ -979,7 +987,10 @@ var errorChPool sync.Pool
 func (c *HostClient) Do(req *Request, resp *Response) error {
 	var err error
 	var retry bool
-	const maxAttempts = 5
+	maxAttempts := c.MaxIdemponentCallAttempts
+	if maxAttempts <= 0 {
+		maxAttempts = DefaultMaxIdemponentCallAttempts
+	}
 	attempts := 0
 
 	atomic.AddUint64(&c.pendingRequests, 1)
@@ -1294,7 +1305,8 @@ func acquireClientConn(conn net.Conn) *clientConn {
 }
 
 func releaseClientConn(cc *clientConn) {
-	cc.c = nil
+	// Reset all fields.
+	*cc = clientConn{}
 	clientConnPool.Put(cc)
 }
 
diff --git a/compress.go b/compress.go
index 221472a..73a40d3 100644
--- a/compress.go
+++ b/compress.go
@@ -152,7 +152,6 @@ func WriteGzipLevel(w io.Writer, p []byte, level int) (int, error) {
 	switch w.(type) {
 	case *byteSliceWriter,
 		*bytes.Buffer,
-		*ByteBuffer,
 		*bytebufferpool.ByteBuffer:
 		// These writers don't block, so we can just use stacklessWriteGzip
 		ctx := &compressCtx{
@@ -249,7 +248,6 @@ func WriteDeflateLevel(w io.Writer, p []byte, level int) (int, error) {
 	switch w.(type) {
 	case *byteSliceWriter,
 		*bytes.Buffer,
-		*ByteBuffer,
 		*bytebufferpool.ByteBuffer:
 		// These writers don't block, so we can just use stacklessWriteDeflate
 		ctx := &compressCtx{
@@ -409,7 +407,7 @@ func isFileCompressible(f *os.File, minCompressRatio float64) bool {
 	// Try compressing the first 4kb of of the file
 	// and see if it can be compressed by more than
 	// the given minCompressRatio.
-	b := AcquireByteBuffer()
+	b := bytebufferpool.Get()
 	zw := acquireStacklessGzipWriter(b, CompressDefaultCompression)
 	lr := &io.LimitedReader{
 		R: f,
@@ -424,7 +422,7 @@ func isFileCompressible(f *os.File, minCompressRatio float64) bool {
 
 	n := 4096 - lr.N
 	zn := len(b.B)
-	ReleaseByteBuffer(b)
+	bytebufferpool.Put(b)
 	return float64(zn) < float64(n)*minCompressRatio
 }
 
diff --git a/cookie.go b/cookie.go
index c383d0b..d5abef2 100644
--- a/cookie.go
+++ b/cookie.go
@@ -52,6 +52,7 @@ type Cookie struct {
 	key    []byte
 	value  []byte
 	expire time.Time
+	maxAge int
 	domain []byte
 	path   []byte
 
@@ -68,6 +69,7 @@ func (c *Cookie) CopyTo(src *Cookie) {
 	c.key = append(c.key[:0], src.key...)
 	c.value = append(c.value[:0], src.value...)
 	c.expire = src.expire
+	c.maxAge = src.maxAge
 	c.domain = append(c.domain[:0], src.domain...)
 	c.path = append(c.path[:0], src.path...)
 	c.httpOnly = src.httpOnly
@@ -128,6 +130,20 @@ func (c *Cookie) SetDomainBytes(domain []byte) {
 	c.domain = append(c.domain[:0], domain...)
 }
 
+// MaxAge returns the seconds until the cookie is meant to expire or 0
+// if no max age.
+func (c *Cookie) MaxAge() int {
+	return c.maxAge
+}
+
+// SetMaxAge sets cookie expiration time based on seconds. This takes precedence
+// over any absolute expiry set on the cookie
+//
+// Set max age to 0 to unset
+func (c *Cookie) SetMaxAge(seconds int) {
+	c.maxAge = seconds
+}
+
 // Expire returns cookie expiration time.
 //
 // CookieExpireUnlimited is returned if cookie doesn't expire
@@ -188,6 +204,7 @@ func (c *Cookie) Reset() {
 	c.key = c.key[:0]
 	c.value = c.value[:0]
 	c.expire = zeroTime
+	c.maxAge = 0
 	c.domain = c.domain[:0]
 	c.path = c.path[:0]
 	c.httpOnly = false
@@ -203,7 +220,12 @@ func (c *Cookie) AppendBytes(dst []byte) []byte {
 	}
 	dst = append(dst, c.value...)
 
-	if !c.expire.IsZero() {
+	if c.maxAge > 0 {
+		dst = append(dst, ';', ' ')
+		dst = append(dst, strCookieMaxAge...)
+		dst = append(dst, '=')
+		dst = AppendUint(dst, c.maxAge)
+	} else if !c.expire.IsZero() {
 		c.bufKV.value = AppendHTTPDate(c.bufKV.value[:0], c.expire)
 		dst = append(dst, ';', ' ')
 		dst = append(dst, strCookieExpires...)
@@ -272,29 +294,58 @@ func (c *Cookie) ParseBytes(src []byte) error {
 	c.value = append(c.value[:0], kv.value...)
 
 	for s.next(kv) {
-		if len(kv.key) == 0 && len(kv.value) == 0 {
-			continue
-		}
-		switch string(kv.key) {
-		case "expires":
-			v := b2s(kv.value)
-			exptime, err := time.ParseInLocation(time.RFC1123, v, time.UTC)
-			if err != nil {
-				return err
+		if len(kv.key) != 0 {
+			// Case insensitive switch on first char
+			switch kv.key[0] | 0x20 {
+			case 'm':
+				if caseInsensitiveCompare(strCookieMaxAge, kv.key) {
+					maxAge, err := ParseUint(kv.value)
+					if err != nil {
+						return err
+					}
+					c.maxAge = maxAge
+				}
+
+			case 'e': // "expires"
+				if caseInsensitiveCompare(strCookieExpires, kv.key) {
+					v := b2s(kv.value)
+					// Try the same two formats as net/http
+					// See: https://github.com/golang/go/blob/00379be17e63a5b75b3237819392d2dc3b313a27/src/net/http/cookie.go#L133-L135
+					exptime, err := time.ParseInLocation(time.RFC1123, v, time.UTC)
+					if err != nil {
+						exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", v)
+						if err != nil {
+							return err
+						}
+					}
+					c.expire = exptime
+				}
+
+			case 'd': // "domain"
+				if caseInsensitiveCompare(strCookieDomain, kv.key) {
+					c.domain = append(c.domain[:0], kv.value...)
+				}
+
+			case 'p': // "path"
+				if caseInsensitiveCompare(strCookiePath, kv.key) {
+					c.path = append(c.path[:0], kv.value...)
+				}
 			}
-			c.expire = exptime
-		case "domain":
-			c.domain = append(c.domain[:0], kv.value...)
-		case "path":
-			c.path = append(c.path[:0], kv.value...)
-		case "":
-			switch string(kv.value) {
-			case "HttpOnly":
-				c.httpOnly = true
-			case "secure":
-				c.secure = true
+
+		} else if len(kv.value) != 0 {
+			// Case insensitive switch on first char
+			switch kv.value[0] | 0x20 {
+			case 'h': // "httponly"
+				if caseInsensitiveCompare(strCookieHTTPOnly, kv.value) {
+					c.httpOnly = true
+				}
+
+			case 's': // "secure"
+				if caseInsensitiveCompare(strCookieSecure, kv.value) {
+					c.secure = true
+				}
 			}
-		}
+		} // else empty or no match
 	}
 	return nil
 }
@@ -407,3 +458,17 @@ func decodeCookieArg(dst, src []byte, skipQuotes bool) []byte {
 	}
 	return append(dst[:0], src...)
 }
+
+// caseInsensitiveCompare does a case insensitive equality comparison of
+// two []byte. Assumes only letters need to be matched.
+func caseInsensitiveCompare(a, b []byte) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	for i := 0; i < len(a); i++ {
+		if a[i]|0x20 != b[i]|0x20 {
+			return false
+		}
+	}
+	return true
+}
diff --git a/doc.go b/doc.go
index 501eff6..efcd4a0 100644
--- a/doc.go
+++ b/doc.go
@@ -7,9 +7,6 @@ Fasthttp provides the following features:
       concurrent keep-alive connections on modern hardware.
     * Optimized for low memory usage.
     * Easy 'Connection: Upgrade' support via RequestCtx.Hijack.
-    * Server supports requests' pipelining. Multiple requests may be read from
-      a single network packet and multiple responses may be sent in a single
-      network packet. This may be useful for highly loaded REST services.
     * Server provides the following anti-DoS limits:
 
         * The number of concurrent connections.
diff --git a/fasthttpproxy/socks5.go b/fasthttpproxy/socks5.go
new file mode 100644
index 0000000..77d965d
--- /dev/null
+++ b/fasthttpproxy/socks5.go
@@ -0,0 +1,25 @@
+package fasthttpproxy
+
+import (
+	"net"
+
+	"github.com/valyala/fasthttp"
+	"golang.org/x/net/proxy"
+)
+
+// FasthttpSocksDialer returns a fasthttp.DialFunc that dials using
+// the provided SOCKS5 proxy.
+//
+// Example usage:
+// c := &fasthttp.Client{
+//   Dial: fasthttpproxy.FasthttpSocksDialer("localhost:9050"),
+// }
+func FasthttpSocksDialer(proxyAddr string) fasthttp.DialFunc {
+	return func(addr string) (net.Conn, error) {
+		dialer, err := proxy.SOCKS5("tcp", proxyAddr, nil, proxy.Direct)
+		if err != nil {
+			return nil, err
+		}
+		return dialer.Dial("tcp", addr)
+	}
+}
diff --git a/fs.go b/fs.go
index ff4d5ac..7a73a79 100644
--- a/fs.go
+++ b/fs.go
@@ -17,6 +17,7 @@ import (
 	"time"
 
 	"github.com/klauspost/compress/gzip"
+	"github.com/valyala/bytebufferpool"
 )
 
 // ServeFileBytesUncompressed returns HTTP response containing file contents
@@ -139,12 +140,12 @@ func NewVHostPathRewriter(slashesCount int) PathRewriteFunc {
 		if len(host) == 0 {
 			host = strInvalidHost
 		}
-		b := AcquireByteBuffer()
+		b := bytebufferpool.Get()
 		b.B = append(b.B, '/')
 		b.B = append(b.B, host...)
 		b.B = append(b.B, path...)
 		ctx.URI().SetPathBytes(b.B)
-		ReleaseByteBuffer(b)
+		bytebufferpool.Put(b)
 
 		return ctx.Path()
 	}
@@ -823,7 +824,10 @@ func (h *fsHandler) handleRequest(ctx *RequestCtx) {
 			}
 		}
 	}
-	ctx.SetContentType(ff.contentType)
+	hdr.noDefaultContentType = true
+	if len(hdr.ContentType()) == 0 {
+		ctx.SetContentType(ff.contentType)
+	}
 	ctx.SetStatusCode(statusCode)
 }
 
@@ -912,7 +916,7 @@ var (
 )
 
 func (h *fsHandler) createDirIndex(base *URI, dirPath string, mustCompress bool) (*fsFile, error) {
-	w := &ByteBuffer{}
+	w := &bytebufferpool.ByteBuffer{}
 
 	basePathEscaped := html.EscapeString(string(base.Path()))
 	fmt.Fprintf(w, "<html><head><title>%s</title><style>.dir { font-weight: bold }</style></head><body>", basePathEscaped)
@@ -972,7 +976,7 @@ func (h *fsHandler) createDirIndex(base *URI, dirPath string, mustCompress bool)
 	fmt.Fprintf(w, "</ul></body></html>")
 
 	if mustCompress {
-		var zbuf ByteBuffer
+		var zbuf bytebufferpool.ByteBuffer
 		zbuf.B = AppendGzipBytesLevel(zbuf.B, w.B, CompressDefaultCompression)
 		w = &zbuf
 	}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..8434ca1
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,9 @@
+module github.com/valyala/fasthttp
+
+require (
+	github.com/klauspost/compress v1.4.0
+	github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e // indirect
+	github.com/valyala/bytebufferpool v1.0.0
+	github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a
+	golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..93f38fc
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,10 @@
+github.com/klauspost/compress v1.4.0 h1:8nsMz3tWa9SWWPL60G1V6CUsf4lLjWLTNEtibhe8gh8=
+github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e h1:+lIPJOWl+jSiJOc70QXJ07+2eg2Jy2EC7Mi11BWujeM=
+github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc=
+github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
+golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3 h1:czFLhve3vsQetD6JOJ8NZZvGQIXlnN3/yXxbT6/awxI=
+golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
diff --git a/header.go b/header.go
index 764f7ee..bbcd9df 100644
--- a/header.go
+++ b/header.go
@@ -20,9 +20,10 @@ import (
 type ResponseHeader struct {
 	noCopy noCopy
 
-	disableNormalizing bool
-	noHTTP11           bool
-	connectionClose    bool
+	disableNormalizing   bool
+	noHTTP11             bool
+	connectionClose      bool
+	noDefaultContentType bool
 
 	statusCode         int
 	contentLength      int
@@ -50,7 +51,6 @@ type RequestHeader struct {
 	disableNormalizing bool
 	noHTTP11           bool
 	connectionClose    bool
-	isGet              bool
 
 	// These two fields have been moved close to other bool fields
 	// for reducing RequestHeader object size.
@@ -72,6 +72,9 @@ type RequestHeader struct {
 	cookies []argsKV
 
 	rawHeaders []byte
+
+	// stores an immutable copy of headers as they were recieved from the wire.
+	rawHeadersCopy []byte
 }
 
 // SetContentRange sets 'Content-Range: bytes startPos-endPos/contentLength'
@@ -157,12 +160,6 @@ func (h *RequestHeader) ConnectionClose() bool {
 	return h.connectionClose
 }
 
-func (h *RequestHeader) connectionCloseFast() bool {
-	// h.parseRawHeaders() isn't called for performance reasons.
-	// Use ConnectionClose for triggering raw headers parsing.
-	return h.connectionClose
-}
-
 // SetConnectionClose sets 'Connection: close' header.
 func (h *RequestHeader) SetConnectionClose() {
 	// h.parseRawHeaders() isn't called for performance reasons.
@@ -283,7 +280,7 @@ func (h *ResponseHeader) isCompressibleContentType() bool {
 // ContentType returns Content-Type header value.
 func (h *ResponseHeader) ContentType() []byte {
 	contentType := h.contentType
-	if len(h.contentType) == 0 {
+	if !h.noDefaultContentType && len(h.contentType) == 0 {
 		contentType = defaultContentType
 	}
 	return contentType
@@ -515,11 +512,7 @@ func (h *RequestHeader) SetRequestURIBytes(requestURI []byte) {
 
 // IsGet returns true if request method is GET.
 func (h *RequestHeader) IsGet() bool {
-	// Optimize fast path for GET requests.
-	if !h.isGet {
-		h.isGet = bytes.Equal(h.Method(), strGet)
-	}
-	return h.isGet
+	return bytes.Equal(h.Method(), strGet)
 }
 
 // IsPost returns true if request method is POST.
@@ -534,10 +527,6 @@ func (h *RequestHeader) IsPut() bool {
 
 // IsHead returns true if request method is HEAD.
 func (h *RequestHeader) IsHead() bool {
-	// Fast path
-	if h.isGet {
-		return false
-	}
 	return bytes.Equal(h.Method(), strHead)
 }
 
@@ -652,6 +641,7 @@ func (h *ResponseHeader) DisableNormalizing() {
 // Reset clears response header.
 func (h *ResponseHeader) Reset() {
 	h.disableNormalizing = false
+	h.noDefaultContentType = false
 	h.resetSkipNormalize()
 }
 
@@ -679,7 +669,6 @@ func (h *RequestHeader) Reset() {
 func (h *RequestHeader) resetSkipNormalize() {
 	h.noHTTP11 = false
 	h.connectionClose = false
-	h.isGet = false
 
 	h.contentLength = 0
 	h.contentLengthBytes = h.contentLengthBytes[:0]
@@ -705,6 +694,7 @@ func (h *ResponseHeader) CopyTo(dst *ResponseHeader) {
 	dst.disableNormalizing = h.disableNormalizing
 	dst.noHTTP11 = h.noHTTP11
 	dst.connectionClose = h.connectionClose
+	dst.noDefaultContentType = h.noDefaultContentType
 
 	dst.statusCode = h.statusCode
 	dst.contentLength = h.contentLength
@@ -722,7 +712,6 @@ func (h *RequestHeader) CopyTo(dst *RequestHeader) {
 	dst.disableNormalizing = h.disableNormalizing
 	dst.noHTTP11 = h.noHTTP11
 	dst.connectionClose = h.connectionClose
-	dst.isGet = h.isGet
 
 	dst.contentLength = h.contentLength
 	dst.contentLengthBytes = append(dst.contentLengthBytes[:0], h.contentLengthBytes...)
@@ -1239,6 +1228,12 @@ func (h *RequestHeader) peek(key []byte) []byte {
 		return peekArgBytes(h.h, key)
 	case "Content-Length":
 		return h.contentLengthBytes
+	case "Cookie":
+		if h.cookiesCollected {
+			return appendRequestCookieBytes(nil, h.cookies)
+		} else {
+			return peekArgBytes(h.h, key)
+		}
 	default:
 		return peekArgBytes(h.h, key)
 	}
@@ -1363,9 +1358,12 @@ func (h *RequestHeader) tryRead(r *bufio.Reader, n int) error {
 	h.resetSkipNormalize()
 	b, err := r.Peek(n)
 	if len(b) == 0 {
-		// treat all errors on the first byte read as EOF
-		if n == 1 || err == io.EOF {
-			return io.EOF
+		if err == io.EOF {
+			return err
+		}
+
+		if err == nil {
+			panic("bufio.Reader.Peek() returned nil, nil")
 		}
 
 		// This is for go 1.6 bug. See https://github.com/golang/go/issues/14121 .
@@ -1524,6 +1522,20 @@ func (h *RequestHeader) Header() []byte {
 	return h.bufKV.value
 }
 
+// RawHeaders returns raw header key/value bytes.
+//
+// Depending on server configuration, header keys may be normalized to
+// capital-case in place.
+//
+// This copy is set aside during parsing, so empty slice is returned for all
+// cases where parsing did not happen. Similarly, request line is not stored
+// during parsing and can not be returned.
+//
+// The slice is not safe to use after the handler returns.
+func (h *RequestHeader) RawHeaders() []byte {
+	return h.rawHeadersCopy
+}
+
 // String returns request header representation.
 func (h *RequestHeader) String() string {
 	return string(h.Header())
@@ -1621,18 +1633,20 @@ func (h *RequestHeader) parse(buf []byte) (int, error) {
 	}
 
 	var n int
+	var rawHeaders []byte
+	rawHeaders, n, err = readRawHeaders(h.rawHeaders[:0], buf[m:])
+	if err != nil {
+		return 0, err
+	}
+	h.rawHeadersCopy = append(h.rawHeadersCopy[:0], rawHeaders...)
 	if !h.ignoreBody() || h.noHTTP11 {
 		n, err = h.parseHeaders(buf[m:])
 		if err != nil {
 			return 0, err
 		}
+		h.rawHeaders = append(h.rawHeaders[:0], buf[m:m+n]...)
 		h.rawHeadersParsed = true
 	} else {
-		var rawHeaders []byte
-		rawHeaders, n, err = readRawHeaders(h.rawHeaders[:0], buf[m:])
-		if err != nil {
-			return 0, err
-		}
 		h.rawHeaders = rawHeaders
 	}
 	return m + n, nil
@@ -1770,36 +1784,52 @@ func (h *ResponseHeader) parseHeaders(buf []byte) (int, error) {
 	var err error
 	var kv *argsKV
 	for s.next() {
-		switch string(s.key) {
-		case "Content-Type":
-			h.contentType = append(h.contentType[:0], s.value...)
-		case "Server":
-			h.server = append(h.server[:0], s.value...)
-		case "Content-Length":
-			if h.contentLength != -1 {
-				if h.contentLength, err = parseContentLength(s.value); err != nil {
-					h.contentLength = -2
-				} else {
-					h.contentLengthBytes = append(h.contentLengthBytes[:0], s.value...)
+		if len(s.key) > 0 {
+			switch s.key[0] | 0x20 {
+			case 'c':
+				if caseInsensitiveCompare(s.key, strContentType) {
+					h.contentType = append(h.contentType[:0], s.value...)
+					continue
+				}
+				if caseInsensitiveCompare(s.key, strContentLength) {
+					if h.contentLength != -1 {
+						if h.contentLength, err = parseContentLength(s.value); err != nil {
+							h.contentLength = -2
+						} else {
+							h.contentLengthBytes = append(h.contentLengthBytes[:0], s.value...)
+						}
+					}
+					continue
+				}
+				if caseInsensitiveCompare(s.key, strConnection) {
+					if bytes.Equal(s.value, strClose) {
+						h.connectionClose = true
+					} else {
+						h.connectionClose = false
+						h.h = appendArgBytes(h.h, s.key, s.value)
+					}
+					continue
+				}
+			case 's':
+				if caseInsensitiveCompare(s.key, strServer) {
+					h.server = append(h.server[:0], s.value...)
+					continue
+				}
+				if caseInsensitiveCompare(s.key, strSetCookie) {
+					h.cookies, kv = allocArg(h.cookies)
+					kv.key = getCookieKey(kv.key, s.value)
+					kv.value = append(kv.value[:0], s.value...)
+					continue
+				}
+			case 't':
+				if caseInsensitiveCompare(s.key, strTransferEncoding) {
+					if !bytes.Equal(s.value, strIdentity) {
+						h.contentLength = -1
+						h.h = setArgBytes(h.h, strTransferEncoding, strChunked)
+					}
+					continue
 				}
 			}
-		case "Transfer-Encoding":
-			if !bytes.Equal(s.value, strIdentity) {
-				h.contentLength = -1
-				h.h = setArgBytes(h.h, strTransferEncoding, strChunked)
-			}
-		case "Set-Cookie":
-			h.cookies, kv = allocArg(h.cookies)
-			kv.key = getCookieKey(kv.key, s.value)
-			kv.value = append(kv.value[:0], s.value...)
-		case "Connection":
-			if bytes.Equal(s.value, strClose) {
-				h.connectionClose = true
-			} else {
-				h.connectionClose = false
-				h.h = appendArgBytes(h.h, s.key, s.value)
-			}
-		default:
 			h.h = appendArgBytes(h.h, s.key, s.value)
 		}
 	}
@@ -1832,36 +1862,53 @@ func (h *RequestHeader) parseHeaders(buf []byte) (int, error) {
 	s.disableNormalizing = h.disableNormalizing
 	var err error
 	for s.next() {
-		switch string(s.key) {
-		case "Host":
-			h.host = append(h.host[:0], s.value...)
-		case "User-Agent":
-			h.userAgent = append(h.userAgent[:0], s.value...)
-		case "Content-Type":
-			h.contentType = append(h.contentType[:0], s.value...)
-		case "Content-Length":
-			if h.contentLength != -1 {
-				if h.contentLength, err = parseContentLength(s.value); err != nil {
-					h.contentLength = -2
-				} else {
-					h.contentLengthBytes = append(h.contentLengthBytes[:0], s.value...)
+		if len(s.key) > 0 {
+			switch s.key[0] | 0x20 {
+			case 'h':
+				if caseInsensitiveCompare(s.key, strHost) {
+					h.host = append(h.host[:0], s.value...)
+					continue
+				}
+			case 'u':
+				if caseInsensitiveCompare(s.key, strUserAgent) {
+					h.userAgent = append(h.userAgent[:0], s.value...)
+					continue
+				}
+			case 'c':
+				if caseInsensitiveCompare(s.key, strContentType) {
+					h.contentType = append(h.contentType[:0], s.value...)
+					continue
+				}
+				if caseInsensitiveCompare(s.key, strContentLength) {
+					if h.contentLength != -1 {
+						if h.contentLength, err = parseContentLength(s.value); err != nil {
+							h.contentLength = -2
+						} else {
+							h.contentLengthBytes = append(h.contentLengthBytes[:0], s.value...)
+						}
+					}
+					continue
+				}
+				if caseInsensitiveCompare(s.key, strConnection) {
+					if bytes.Equal(s.value, strClose) {
+						h.connectionClose = true
+					} else {
+						h.connectionClose = false
+						h.h = appendArgBytes(h.h, s.key, s.value)
+					}
+					continue
+				}
+			case 't':
+				if caseInsensitiveCompare(s.key, strTransferEncoding) {
+					if !bytes.Equal(s.value, strIdentity) {
+						h.contentLength = -1
+						h.h = setArgBytes(h.h, strTransferEncoding, strChunked)
+					}
+					continue
 				}
 			}
-		case "Transfer-Encoding":
-			if !bytes.Equal(s.value, strIdentity) {
-				h.contentLength = -1
-				h.h = setArgBytes(h.h, strTransferEncoding, strChunked)
-			}
-		case "Connection":
-			if bytes.Equal(s.value, strClose) {
-				h.connectionClose = true
-			} else {
-				h.connectionClose = false
-				h.h = appendArgBytes(h.h, s.key, s.value)
-			}
-		default:
-			h.h = appendArgBytes(h.h, s.key, s.value)
 		}
+		h.h = appendArgBytes(h.h, s.key, s.value)
 	}
 	if s.err != nil {
 		h.connectionClose = true
@@ -1876,8 +1923,7 @@ func (h *RequestHeader) parseHeaders(buf []byte) (int, error) {
 		v := peekArgBytes(h.h, strConnection)
 		h.connectionClose = !hasHeaderValue(v, strKeepAlive) && !hasHeaderValue(v, strKeepAliveCamelCase)
 	}
-
-	return len(buf) - len(s.b), nil
+	return s.hLen, nil
 }
 
 func (h *RequestHeader) parseRawHeaders() {
@@ -1928,6 +1974,9 @@ type headerScanner struct {
 	value []byte
 	err   error
 
+	// hLen stores header subslice len
+	hLen int
+
 	disableNormalizing bool
 }
 
@@ -1935,10 +1984,12 @@ func (s *headerScanner) next() bool {
 	bLen := len(s.b)
 	if bLen >= 2 && s.b[0] == '\r' && s.b[1] == '\n' {
 		s.b = s.b[2:]
+		s.hLen += 2
 		return false
 	}
 	if bLen >= 1 && s.b[0] == '\n' {
 		s.b = s.b[1:]
+		s.hLen++
 		return false
 	}
 	n := bytes.IndexByte(s.b, ':')
@@ -1952,6 +2003,7 @@ func (s *headerScanner) next() bool {
 	for len(s.b) > n && s.b[n] == ' ' {
 		n++
 	}
+	s.hLen += n
 	s.b = s.b[n:]
 	n = bytes.IndexByte(s.b, '\n')
 	if n < 0 {
@@ -1959,6 +2011,7 @@ func (s *headerScanner) next() bool {
 		return false
 	}
 	s.value = s.b[:n]
+	s.hLen += n + 1
 	s.b = s.b[n+1:]
 
 	if n > 0 && s.value[n-1] == '\r' {
diff --git a/http.go b/http.go
index 1a618c8..f8c38fc 100644
--- a/http.go
+++ b/http.go
@@ -346,7 +346,7 @@ func (resp *Response) BodyGunzip() ([]byte, error) {
 }
 
 func gunzipData(p []byte) ([]byte, error) {
-	var bb ByteBuffer
+	var bb bytebufferpool.ByteBuffer
 	_, err := WriteGunzip(&bb, p)
 	if err != nil {
 		return nil, err
@@ -373,7 +373,7 @@ func (resp *Response) BodyInflate() ([]byte, error) {
 }
 
 func inflateData(p []byte) ([]byte, error) {
-	var bb ByteBuffer
+	var bb bytebufferpool.ByteBuffer
 	_, err := WriteInflate(&bb, p)
 	if err != nil {
 		return nil, err
@@ -711,7 +711,7 @@ func (req *Request) MultipartForm() (*multipart.Form, error) {
 }
 
 func marshalMultipartForm(f *multipart.Form, boundary string) ([]byte, error) {
-	var buf ByteBuffer
+	var buf bytebufferpool.ByteBuffer
 	if err := WriteMultipartForm(&buf, f, boundary); err != nil {
 		return nil, err
 	}
@@ -1107,6 +1107,9 @@ func (req *Request) Write(w *bufio.Writer) error {
 
 	hasBody := !req.Header.ignoreBody()
 	if hasBody {
+		if len(body) == 0 {
+			body = req.postArgs.QueryString()
+		}
 		req.Header.SetContentLength(len(body))
 	}
 	if err = req.Header.Write(w); err != nil {
@@ -1454,7 +1457,7 @@ func (resp *Response) String() string {
 }
 
 func getHTTPString(hw httpWriter) string {
-	w := AcquireByteBuffer()
+	w := bytebufferpool.Get()
 	bw := bufio.NewWriter(w)
 	if err := hw.Write(bw); err != nil {
 		return err.Error()
@@ -1463,7 +1466,7 @@ func getHTTPString(hw httpWriter) string {
 		return err.Error()
 	}
 	s := string(w.B)
-	ReleaseByteBuffer(w)
+	bytebufferpool.Put(w)
 	return s
 }
 
diff --git a/pprofhandler/pprof.go b/pprofhandler/pprof.go
index 200ae51..c94d2c1 100644
--- a/pprofhandler/pprof.go
+++ b/pprofhandler/pprof.go
@@ -2,6 +2,7 @@ package pprofhandler
 
 import (
 	"net/http/pprof"
+	rtp "runtime/pprof"
 	"strings"
 
 	"github.com/valyala/fasthttp"
@@ -30,6 +31,14 @@ func PprofHandler(ctx *fasthttp.RequestCtx) {
 	} else if strings.HasPrefix(string(ctx.Path()), "/debug/pprof/trace") {
 		trace(ctx)
 	} else {
+		for _, v := range rtp.Profiles() {
+			ppName := v.Name()
+			if strings.HasPrefix(string(ctx.Path()), "/debug/pprof/"+ppName) {
+				namedHandler := fasthttpadaptor.NewFastHTTPHandlerFunc(pprof.Handler(ppName).ServeHTTP)
+				namedHandler(ctx)
+				return
+			}
+		}
 		index(ctx)
 	}
 }
diff --git a/server.go b/server.go
index 20f289d..d0ef421 100644
--- a/server.go
+++ b/server.go
@@ -18,6 +18,12 @@ import (
 
 var errNoCertOrKeyProvided = errors.New("Cert or key has not provided")
 
+var (
+	// ErrAlreadyServing is returned when calling Serve on a Server
+	// that is already serving connections.
+	ErrAlreadyServing = errors.New("Server is already serving connections")
+)
+
 // ServeConn serves HTTP requests from the given connection
 // using the given handler.
 //
@@ -128,6 +134,9 @@ func ListenAndServeTLSEmbed(addr string, certData, keyData []byte, handler Reque
 // must be limited.
 type RequestHandler func(ctx *RequestCtx)
 
+// ServeHandler must process tls.Config.NextProto negotiated requests.
+type ServeHandler func(c net.Conn) error
+
 // Server implements HTTP server.
 //
 // Default Server settings should satisfy the majority of Server users.
@@ -213,6 +222,18 @@ type Server struct {
 	// By default keep-alive connection lifetime is unlimited.
 	MaxKeepaliveDuration time.Duration
 
+	// Whether to enable tcp keep-alive connections.
+	//
+	// Whether the operating system should send tcp keep-alive messages on the tcp connection.
+	//
+	// By default tcp keep-alive connections are disabled.
+	TCPKeepalive bool
+
+	// Period between tcp keep-alive messages.
+	//
+	// TCP keep-alive period is determined by operation system by default.
+	TCPKeepalivePeriod time.Duration
+
 	// Maximum request body size.
 	//
 	// The server rejects requests with bodies exceeding this limit.
@@ -276,12 +297,25 @@ type Server struct {
 	// value is explicitly provided during a request.
 	NoDefaultServerHeader bool
 
+	// NoDefaultContentType, when set to true, causes the default Content-Type
+	// header to be excluded from the Response.
+	//
+	// The default Content-Type header value is the internal default value. When
+	// set to true, the Content-Type will not be present.
+	NoDefaultContentType bool
+
+	// ConnState specifies an optional callback function that is
+	// called when a client connection changes state. See the
+	// ConnState type and associated constants for details.
+	ConnState func(net.Conn, ConnState)
+
 	// Logger, which is used by RequestCtx.Logger().
 	//
 	// By default standard logger from log package is used.
 	Logger Logger
 
-	tlsConfig *tls.Config
+	tlsConfig  *tls.Config
+	nextProtos map[string]ServeHandler
 
 	concurrency      uint32
 	concurrencyCh    chan struct{}
@@ -297,7 +331,8 @@ type Server struct {
 	// We need to know our listener so we can close it in Shutdown().
 	ln net.Listener
 
-	wg   sync.WaitGroup
+	mu   sync.Mutex
+	open int32
 	stop int32
 }
 
@@ -766,16 +801,28 @@ func SaveMultipartFile(fh *multipart.FileHeader, path string) error {
 	if err != nil {
 		return err
 	}
-	defer f.Close()
 
 	if ff, ok := f.(*os.File); ok {
+		// Windows can't rename files that are opened.
+		if err := f.Close(); err != nil {
+			return err
+		}
+
 		// If renaming fails we try the normal copying method.
 		// Renaming could fail if the files are on different devices.
 		if os.Rename(ff.Name(), path) == nil {
 			return nil
 		}
+
+		// Reopen f for the code below.
+		f, err = fh.Open()
+		if err != nil {
+			return err
+		}
 	}
 
+	defer f.Close()
+
 	ff, err := os.Create(path)
 	if err != nil {
 		return err
@@ -954,7 +1001,13 @@ func (ctx *RequestCtx) SuccessString(contentType, body string) {
 // All other statusCode values are replaced by StatusFound (302).
 //
 // The redirect uri may be either absolute or relative to the current
-// request uri.
+// request uri. Fasthttp will always send an absolute uri back to the client.
+// To send a relative uri you can use the following code:
+//
+//   strLocation = []byte("Location") // Put this with your top level var () declarations.
+//   ctx.Response.Header.SetCanonical(strLocation, "/relative?uri")
+//   ctx.Response.SetStatusCode(fasthttp.StatusMovedPermanently)
+//
 func (ctx *RequestCtx) Redirect(uri string, statusCode int) {
 	u := AcquireURI()
 	ctx.URI().CopyTo(u)
@@ -976,7 +1029,13 @@ func (ctx *RequestCtx) Redirect(uri string, statusCode int) {
 // All other statusCode values are replaced by StatusFound (302).
 //
 // The redirect uri may be either absolute or relative to the current
-// request uri.
+// request uri. Fasthttp will always send an absolute uri back to the client.
+// To send a relative uri you can use the following code:
+//
+//   strLocation = []byte("Location") // Put this with your top level var () declarations.
+//   ctx.Response.Header.SetCanonical(strLocation, "/relative?uri")
+//   ctx.Response.SetStatusCode(fasthttp.StatusMovedPermanently)
+//
 func (ctx *RequestCtx) RedirectBytes(uri []byte, statusCode int) {
 	s := b2s(uri)
 	ctx.Redirect(s, statusCode)
@@ -1187,15 +1246,66 @@ func (ctx *RequestCtx) TimeoutErrorWithResponse(resp *Response) {
 	ctx.timeoutResponse = respCopy
 }
 
+// NextProto adds nph to be processed when key is negotiated when TLS conection is established.
+//
+// This function can only be called before the server is started.
+func (s *Server) NextProto(key string, nph ServeHandler) {
+	if s.nextProtos == nil {
+		s.nextProtos = make(map[string]ServeHandler)
+	}
+	s.configTLS()
+	s.tlsConfig.NextProtos = append(s.tlsConfig.NextProtos, key)
+	s.nextProtos[key] = nph
+}
+
+func (s *Server) hasNextProto(c net.Conn) (proto string) {
+	tlsConn, ok := c.(connTLSer)
+	if ok {
+		proto = tlsConn.ConnectionState().NegotiatedProtocol
+	}
+	return
+}
+
+// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
+// connections. It's used by ListenAndServe, ListenAndServeTLS and
+// ListenAndServeTLSEmbed so dead TCP connections (e.g. closing laptop mid-download)
+// eventually go away.
+type tcpKeepaliveListener struct {
+	*net.TCPListener
+	keepalivePeriod time.Duration
+}
+
+func (ln tcpKeepaliveListener) Accept() (net.Conn, error) {
+	tc, err := ln.AcceptTCP()
+	if err != nil {
+		return nil, err
+	}
+	tc.SetKeepAlive(true)
+	if ln.keepalivePeriod > 0 {
+		tc.SetKeepAlivePeriod(ln.keepalivePeriod)
+	}
+	return tc, nil
+}
+
 // ListenAndServe serves HTTP requests from the given TCP4 addr.
 //
 // Pass custom listener to Serve if you need listening on non-TCP4 media
 // such as IPv6.
+//
+// Accepted connections are configured to enable TCP keep-alives.
 func (s *Server) ListenAndServe(addr string) error {
 	ln, err := net.Listen("tcp4", addr)
 	if err != nil {
 		return err
 	}
+	if s.TCPKeepalive {
+		if tcpln, ok := ln.(*net.TCPListener); ok {
+			return s.Serve(tcpKeepaliveListener{
+				TCPListener:     tcpln,
+				keepalivePeriod: s.TCPKeepalivePeriod,
+			})
+		}
+	}
 	return s.Serve(ln)
 }
 
@@ -1227,11 +1337,21 @@ func (s *Server) ListenAndServeUNIX(addr string, mode os.FileMode) error {
 //
 // If the certFile or keyFile has not been provided to the server structure,
 // the function will use the previously added TLS configuration.
+//
+// Accepted connections are configured to enable TCP keep-alives.
 func (s *Server) ListenAndServeTLS(addr, certFile, keyFile string) error {
 	ln, err := net.Listen("tcp4", addr)
 	if err != nil {
 		return err
 	}
+	if s.TCPKeepalive {
+		if tcpln, ok := ln.(*net.TCPListener); ok {
+			return s.ServeTLS(tcpKeepaliveListener{
+				TCPListener:     tcpln,
+				keepalivePeriod: s.TCPKeepalivePeriod,
+			}, certFile, keyFile)
+		}
+	}
 	return s.ServeTLS(ln, certFile, keyFile)
 }
 
@@ -1244,11 +1364,21 @@ func (s *Server) ListenAndServeTLS(addr, certFile, keyFile string) error {
 //
 // If the certFile or keyFile has not been provided the server structure,
 // the function will use previously added TLS configuration.
+//
+// Accepted connections are configured to enable TCP keep-alives.
 func (s *Server) ListenAndServeTLSEmbed(addr string, certData, keyData []byte) error {
 	ln, err := net.Listen("tcp4", addr)
 	if err != nil {
 		return err
 	}
+	if s.TCPKeepalive {
+		if tcpln, ok := ln.(*net.TCPListener); ok {
+			return s.ServeTLSEmbed(tcpKeepaliveListener{
+				TCPListener:     tcpln,
+				keepalivePeriod: s.TCPKeepalivePeriod,
+			}, certData, keyData)
+		}
+	}
 	return s.ServeTLSEmbed(ln, certData, keyData)
 }
 
@@ -1308,13 +1438,7 @@ func (s *Server) AppendCert(certFile, keyFile string) error {
 		return fmt.Errorf("cannot load TLS key pair from certFile=%q and keyFile=%q: %s", certFile, keyFile, err)
 	}
 
-	if s.tlsConfig == nil {
-		s.tlsConfig = &tls.Config{
-			Certificates:             []tls.Certificate{cert},
-			PreferServerCipherSuites: true,
-		}
-		return nil
-	}
+	s.configTLS()
 
 	s.tlsConfig.Certificates = append(s.tlsConfig.Certificates, cert)
 	return nil
@@ -1332,16 +1456,18 @@ func (s *Server) AppendCertEmbed(certData, keyData []byte) error {
 			len(certData), len(keyData), err)
 	}
 
+	s.configTLS()
+
+	s.tlsConfig.Certificates = append(s.tlsConfig.Certificates, cert)
+	return nil
+}
+
+func (s *Server) configTLS() {
 	if s.tlsConfig == nil {
 		s.tlsConfig = &tls.Config{
-			Certificates:             []tls.Certificate{cert},
 			PreferServerCipherSuites: true,
 		}
-		return nil
 	}
-
-	s.tlsConfig.Certificates = append(s.tlsConfig.Certificates, cert)
-	return nil
 }
 
 // DefaultConcurrency is the maximum number of concurrent connections
@@ -1357,13 +1483,16 @@ func (s *Server) Serve(ln net.Listener) error {
 	var c net.Conn
 	var err error
 
-	s.ln = ln
-	defer func() {
-		// Don't keep a reference to the listener if we don't need to.
-		s.ln = nil
-	}()
+	s.mu.Lock()
+	{
+		if s.ln != nil {
+			s.mu.Unlock()
+			return ErrAlreadyServing
+		}
 
-	atomic.StoreInt32(&s.stop, 0)
+		s.ln = ln
+	}
+	s.mu.Unlock()
 
 	maxWorkersCount := s.getConcurrency()
 	s.concurrencyCh = make(chan struct{}, maxWorkersCount)
@@ -1372,6 +1501,7 @@ func (s *Server) Serve(ln net.Listener) error {
 		MaxWorkersCount: maxWorkersCount,
 		LogAllErrors:    s.LogAllErrors,
 		Logger:          s.logger(),
+		connState:       s.setState,
 	}
 	wp.Start()
 
@@ -1383,12 +1513,14 @@ func (s *Server) Serve(ln net.Listener) error {
 			}
 			return err
 		}
-		s.wg.Add(1)
+		s.setState(c, StateNew)
+		atomic.AddInt32(&s.open, 1)
 		if !wp.Serve(c) {
-			s.wg.Done()
+			atomic.AddInt32(&s.open, -1)
 			s.writeFastError(c, StatusServiceUnavailable,
 				"The connection cannot be served because Server.Concurrency limit exceeded")
 			c.Close()
+			s.setState(c, StateClosed)
 			if time.Since(lastOverflowErrorTime) > time.Minute {
 				s.logger().Printf("The incoming connection cannot be served, because %d concurrent connections are served. "+
 					"Try increasing Server.Concurrency", maxWorkersCount)
@@ -1415,16 +1547,34 @@ func (s *Server) Serve(ln net.Listener) error {
 //
 // Shutdown does not close keepalive connections so its recommended to set ReadTimeout to something else than 0.
 func (s *Server) Shutdown() error {
-	if s.ln != nil {
-		if err := s.ln.Close(); err != nil {
-			return err
-		}
-	}
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
 	atomic.StoreInt32(&s.stop, 1)
+	defer atomic.StoreInt32(&s.stop, 0)
+
+	if s.ln == nil {
+		return nil
+	}
+
+	if err := s.ln.Close(); err != nil {
+		return err
+	}
+
 	// Closing the listener will make Serve() call Stop on the worker pool.
 	// Setting .stop to 1 will make serveConn() break out of its loop.
 	// Now we just have to wait until all workers are done.
-	s.wg.Wait()
+	for {
+		if open := atomic.LoadInt32(&s.open); open == 0 {
+			break
+		}
+		// This is not an optimal solution but using a sync.WaitGroup
+		// here causes data races as it's hard to prevent Add() to be called
+		// while Wait() is waiting.
+		time.Sleep(time.Millisecond * 100)
+	}
+
+	s.ln = nil
 	return nil
 }
 
@@ -1529,7 +1679,7 @@ func (s *Server) ServeConn(c net.Conn) error {
 		return ErrConcurrencyLimit
 	}
 
-	s.wg.Add(1)
+	atomic.AddInt32(&s.open, 1)
 
 	err := s.serveConn(c)
 
@@ -1537,11 +1687,13 @@ func (s *Server) ServeConn(c net.Conn) error {
 
 	if err != errHijacked {
 		err1 := c.Close()
+		s.setState(c, StateClosed)
 		if err == nil {
 			err = err1
 		}
 	} else {
 		err = nil
+		s.setState(c, StateHijacked)
 	}
 	return err
 }
@@ -1569,7 +1721,14 @@ func nextConnID() uint64 {
 const DefaultMaxRequestBodySize = 4 * 1024 * 1024
 
 func (s *Server) serveConn(c net.Conn) error {
-	defer s.wg.Done()
+	defer atomic.AddInt32(&s.open, -1)
+
+	if proto := s.hasNextProto(c); proto != "" {
+		handler, ok := s.nextProtos[proto]
+		if ok {
+			return handler(c)
+		}
+	}
 
 	var serverName []byte
 	if !s.NoDefaultServerHeader {
@@ -1626,6 +1785,7 @@ func (s *Server) serveConn(c net.Conn) error {
 			br, err = acquireByteReader(&ctx)
 		}
 		ctx.Request.isTLS = isTLS
+		ctx.Response.Header.noDefaultContentType = s.NoDefaultContentType
 
 		if err == nil {
 			if s.DisableHeaderNamesNormalizing {
@@ -1634,6 +1794,10 @@ func (s *Server) serveConn(c net.Conn) error {
 			}
 			// reading Headers and Body
 			err = ctx.Request.readLimitBody(br, maxRequestBodySize, s.GetOnly)
+			if br.Buffered() > 0 {
+				// If we read any bytes off the wire, we're active.
+				s.setState(c, StateActive)
+			}
 			if br.Buffered() == 0 || err != nil {
 				releaseReader(s, br)
 				br = nil
@@ -1682,7 +1846,7 @@ func (s *Server) serveConn(c net.Conn) error {
 			}
 		}
 
-		connectionClose = s.DisableKeepalive || ctx.Request.Header.connectionCloseFast()
+		connectionClose = s.DisableKeepalive || ctx.Request.Header.ConnectionClose()
 		isHTTP11 = ctx.Request.Header.IsHTTP11()
 
 		if serverName != nil {
@@ -1721,9 +1885,7 @@ func (s *Server) serveConn(c net.Conn) error {
 			lastWriteDeadlineTime = s.updateWriteDeadline(c, ctx, lastWriteDeadlineTime)
 		}
 
-		// Verify Request.Header.connectionCloseFast() again,
-		// since request handler might trigger full headers' parsing.
-		connectionClose = connectionClose || ctx.Request.Header.connectionCloseFast() || ctx.Response.ConnectionClose()
+		connectionClose = connectionClose || ctx.Response.ConnectionClose()
 		if connectionClose {
 			ctx.Response.Header.SetCanonical(strConnection, strClose)
 		} else if !isHTTP11 {
@@ -1783,6 +1945,7 @@ func (s *Server) serveConn(c net.Conn) error {
 		}
 
 		currentTime = time.Now()
+		s.setState(c, StateIdle)
 	}
 
 	if br != nil {
@@ -1795,6 +1958,12 @@ func (s *Server) serveConn(c net.Conn) error {
 	return err
 }
 
+func (s *Server) setState(nc net.Conn, state ConnState) {
+	if hook := s.ConnState; hook != nil {
+		hook(nc, state)
+	}
+}
+
 func (s *Server) updateReadDeadline(c net.Conn, ctx *RequestCtx, lastDeadlineTime time.Time) time.Time {
 	readTimeout := s.ReadTimeout
 	currentTime := ctx.time
@@ -2138,3 +2307,55 @@ func writeErrorResponse(bw *bufio.Writer, ctx *RequestCtx, serverName []byte, er
 	bw.Flush()
 	return bw
 }
+
+// A ConnState represents the state of a client connection to a server.
+// It's used by the optional Server.ConnState hook.
+type ConnState int
+
+const (
+	// StateNew represents a new connection that is expected to
+	// send a request immediately. Connections begin at this
+	// state and then transition to either StateActive or
+	// StateClosed.
+	StateNew ConnState = iota
+
+	// StateActive represents a connection that has read 1 or more
+	// bytes of a request. The Server.ConnState hook for
+	// StateActive fires before the request has entered a handler
+	// and doesn't fire again until the request has been
+	// handled. After the request is handled, the state
+	// transitions to StateClosed, StateHijacked, or StateIdle.
+	// For HTTP/2, StateActive fires on the transition from zero
+	// to one active request, and only transitions away once all
+	// active requests are complete. That means that ConnState
+	// cannot be used to do per-request work; ConnState only notes
+	// the overall state of the connection.
+	StateActive
+
+	// StateIdle represents a connection that has finished
+	// handling a request and is in the keep-alive state, waiting
+	// for a new request. Connections transition from StateIdle
+	// to either StateActive or StateClosed.
+	StateIdle
+
+	// StateHijacked represents a hijacked connection.
+	// This is a terminal state. It does not transition to StateClosed.
+	StateHijacked
+
+	// StateClosed represents a closed connection.
+	// This is a terminal state. Hijacked connections do not
+	// transition to StateClosed.
+	StateClosed
+)
+
+var stateName = map[ConnState]string{
+	StateNew:      "new",
+	StateActive:   "active",
+	StateIdle:     "idle",
+	StateHijacked: "hijacked",
+	StateClosed:   "closed",
+}
+
+func (c ConnState) String() string {
+	return stateName[c]
+}
diff --git a/strings.go b/strings.go
index 7b7e53e..afd70b4 100644
--- a/strings.go
+++ b/strings.go
@@ -58,6 +58,7 @@ var (
 	strCookiePath     = []byte("path")
 	strCookieHTTPOnly = []byte("HttpOnly")
 	strCookieSecure   = []byte("secure")
+	strCookieMaxAge   = []byte("max-age")
 
 	strClose               = []byte("close")
 	strGzip                = []byte("gzip")
diff --git a/workerpool.go b/workerpool.go
index 6ce2778..bfd297c 100644
--- a/workerpool.go
+++ b/workerpool.go
@@ -16,7 +16,7 @@ import (
 type workerPool struct {
 	// Function for serving server connections.
 	// It must leave c unclosed.
-	WorkerFunc func(c net.Conn) error
+	WorkerFunc ServeHandler
 
 	MaxWorkersCount int
 
@@ -35,6 +35,8 @@ type workerPool struct {
 	stopCh chan struct{}
 
 	workerChanPool sync.Pool
+
+	connState func(net.Conn, ConnState)
 }
 
 type workerChan struct {
@@ -216,8 +218,11 @@ func (wp *workerPool) workerFunc(ch *workerChan) {
 				wp.Logger.Printf("error when serving connection %q<->%q: %s", c.LocalAddr(), c.RemoteAddr(), err)
 			}
 		}
-		if err != errHijacked {
+		if err == errHijacked {
+			wp.connState(c, StateHijacked)
+		} else {
 			c.Close()
+			wp.connState(c, StateClosed)
 		}
 		c = nil
 


Test patch:
diff --git a/args_test.go b/args_test.go
index d5b768f..217952a 100644
--- a/args_test.go
+++ b/args_test.go
@@ -6,6 +6,8 @@ import (
 	"strings"
 	"testing"
 	"time"
+
+	"github.com/valyala/bytebufferpool"
 )
 
 func TestDecodeArgAppend(t *testing.T) {
@@ -171,7 +173,7 @@ func TestArgsWriteTo(t *testing.T) {
 	var a Args
 	a.Parse(s)
 
-	var w ByteBuffer
+	var w bytebufferpool.ByteBuffer
 	n, err := a.WriteTo(&w)
 	if err != nil {
 		t.Fatalf("unexpected error: %s", err)
diff --git a/bytebuffer_example_test.go b/bytebuffer_example_test.go
deleted file mode 100644
index b89542b..0000000
--- a/bytebuffer_example_test.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package fasthttp_test
-
-import (
-	"fmt"
-
-	"github.com/valyala/fasthttp"
-)
-
-func ExampleByteBuffer() {
-	// This request handler sets 'Your-IP' response header
-	// to 'Your IP is <ip>'. It uses ByteBuffer for constructing response
-	// header value with zero memory allocations.
-	yourIPRequestHandler := func(ctx *fasthttp.RequestCtx) {
-		b := fasthttp.AcquireByteBuffer()
-		b.B = append(b.B, "Your IP is <"...)
-		b.B = fasthttp.AppendIPv4(b.B, ctx.RemoteIP())
-		b.B = append(b.B, ">"...)
-		ctx.Response.Header.SetBytesV("Your-IP", b.B)
-
-		fmt.Fprintf(ctx, "Check response headers - they must contain 'Your-IP: %s'", b.B)
-
-		// It is safe to release byte buffer now, since it is
-		// no longer used.
-		fasthttp.ReleaseByteBuffer(b)
-	}
-
-	// Start fasthttp server returning your ip in response headers.
-	fasthttp.ListenAndServe(":8080", yourIPRequestHandler)
-}
diff --git a/bytebuffer_test.go b/bytebuffer_test.go
deleted file mode 100644
index 2ecbd77..0000000
--- a/bytebuffer_test.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package fasthttp
-
-import (
-	"fmt"
-	"testing"
-	"time"
-)
-
-func TestByteBufferAcquireReleaseSerial(t *testing.T) {
-	testByteBufferAcquireRelease(t)
-}
-
-func TestByteBufferAcquireReleaseConcurrent(t *testing.T) {
-	concurrency := 10
-	ch := make(chan struct{}, concurrency)
-	for i := 0; i < concurrency; i++ {
-		go func() {
-			testByteBufferAcquireRelease(t)
-			ch <- struct{}{}
-		}()
-	}
-
-	for i := 0; i < concurrency; i++ {
-		select {
-		case <-ch:
-		case <-time.After(time.Second):
-			t.Fatalf("timeout!")
-		}
-	}
-}
-
-func testByteBufferAcquireRelease(t *testing.T) {
-	for i := 0; i < 10; i++ {
-		b := AcquireByteBuffer()
-		b.B = append(b.B, "num "...)
-		b.B = AppendUint(b.B, i)
-		expectedS := fmt.Sprintf("num %d", i)
-		if string(b.B) != expectedS {
-			t.Fatalf("unexpected result: %q. Expecting %q", b.B, expectedS)
-		}
-		ReleaseByteBuffer(b)
-	}
-}
diff --git a/bytebuffer_timing_test.go b/bytebuffer_timing_test.go
deleted file mode 100644
index 92bbafa..0000000
--- a/bytebuffer_timing_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package fasthttp
-
-import (
-	"bytes"
-	"testing"
-)
-
-func BenchmarkByteBufferWrite(b *testing.B) {
-	s := []byte("foobarbaz")
-	b.RunParallel(func(pb *testing.PB) {
-		var buf ByteBuffer
-		for pb.Next() {
-			for i := 0; i < 100; i++ {
-				buf.Write(s)
-			}
-			buf.Reset()
-		}
-	})
-}
-
-func BenchmarkBytesBufferWrite(b *testing.B) {
-	s := []byte("foobarbaz")
-	b.RunParallel(func(pb *testing.PB) {
-		var buf bytes.Buffer
-		for pb.Next() {
-			for i := 0; i < 100; i++ {
-				buf.Write(s)
-			}
-			buf.Reset()
-		}
-	})
-}
diff --git a/bytesconv_test.go b/bytesconv_test.go
index b5da44d..ea31aab 100644
--- a/bytesconv_test.go
+++ b/bytesconv_test.go
@@ -7,6 +7,8 @@ import (
 	"net"
 	"testing"
 	"time"
+
+	"github.com/valyala/bytebufferpool"
 )
 
 func TestAppendHTMLEscape(t *testing.T) {
@@ -92,7 +94,7 @@ func testAppendUint(t *testing.T, n int) {
 }
 
 func testWriteHexInt(t *testing.T, n int, expectedS string) {
-	var w ByteBuffer
+	var w bytebufferpool.ByteBuffer
 	bw := bufio.NewWriter(&w)
 	if err := writeHexInt(bw, n); err != nil {
 		t.Fatalf("unexpected error when writing hex %x: %s", n, err)
diff --git a/bytesconv_timing_test.go b/bytesconv_timing_test.go
index 24b6a49..f817cf4 100644
--- a/bytesconv_timing_test.go
+++ b/bytesconv_timing_test.go
@@ -5,6 +5,8 @@ import (
 	"html"
 	"net"
 	"testing"
+
+	"github.com/valyala/bytebufferpool"
 )
 
 func BenchmarkAppendHTMLEscape(b *testing.B) {
@@ -77,7 +79,7 @@ func BenchmarkInt2HexByte(b *testing.B) {
 
 func BenchmarkWriteHexInt(b *testing.B) {
 	b.RunParallel(func(pb *testing.PB) {
-		var w ByteBuffer
+		var w bytebufferpool.ByteBuffer
 		bw := bufio.NewWriter(&w)
 		i := 0
 		for pb.Next() {
diff --git a/client_test.go b/client_test.go
index a754b81..614af92 100644
--- a/client_test.go
+++ b/client_test.go
@@ -17,6 +17,146 @@ import (
 	"github.com/valyala/fasthttp/fasthttputil"
 )
 
+func TestClientPostArgs(t *testing.T) {
+	ln := fasthttputil.NewInmemoryListener()
+	s := &Server{
+		Handler: func(ctx *RequestCtx) {
+			body := ctx.Request.Body()
+			if len(body) == 0 {
+				return
+			}
+			ctx.Write(body)
+		},
+	}
+	go s.Serve(ln)
+	c := &Client{
+		Dial: func(addr string) (net.Conn, error) {
+			return ln.Dial()
+		},
+	}
+	req, res := AcquireRequest(), AcquireResponse()
+	args := req.PostArgs()
+	args.Add("addhttp2", "support")
+	args.Add("fast", "http")
+	req.Header.SetMethod("POST")
+	req.SetRequestURI("http://make.fasthttp.great?again")
+	err := c.Do(req, res)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(res.Body()) == 0 {
+		t.Fatal("cannot set args as body")
+	}
+}
+
+func TestClientHeaderCase(t *testing.T) {
+	ln := fasthttputil.NewInmemoryListener()
+	defer ln.Close()
+
+	go func() {
+		c, err := ln.Accept()
+		if err != nil {
+			t.Fatal(err)
+		}
+		c.Write([]byte("HTTP/1.1 200 OK\r\n" +
+			"content-type: text/plain\r\n" +
+			"transfer-encoding: chunked\r\n\r\n" +
+			"24\r\nThis is the data in the first chunk \r\n" +
+			"1B\r\nand this is the second one \r\n" +
+			"0\r\n\r\n",
+		))
+	}()
+
+	c := &Client{
+		Dial: func(addr string) (net.Conn, error) {
+			return ln.Dial()
+		},
+		ReadTimeout: time.Millisecond * 10,
+
+		// Even without name normalizing we should parse headers correctly.
+		DisableHeaderNamesNormalizing: true,
+	}
+
+	code, body, err := c.Get(nil, "http://example.com")
+	if err != nil {
+		t.Error(err)
+	} else if code != 200 {
+		t.Errorf("expected status code 200 got %d", code)
+	} else if string(body) != "This is the data in the first chunk and this is the second one " {
+		t.Errorf("wrong body: %q", body)
+	}
+}
+
+func TestClientReadTimeout(t *testing.T) {
+	// This test is rather slow and increase the total test time
+	// from 2.5 seconds to 6.5 seconds.
+	if testing.Short() {
+		t.Skip("skipping test in short mode")
+	}
+
+	ln := fasthttputil.NewInmemoryListener()
+
+	timeout := false
+	s := &Server{
+		Handler: func(ctx *RequestCtx) {
+			if timeout {
+				time.Sleep(time.Minute)
+			} else {
+				timeout = true
+			}
+		},
+		Logger: &customLogger{}, // Don't print closed pipe errors.
+	}
+	go s.Serve(ln)
+
+	c := &HostClient{
+		ReadTimeout:               time.Second * 4,
+		MaxIdemponentCallAttempts: 1,
+		Dial: func(addr string) (net.Conn, error) {
+			return ln.Dial()
+		},
+	}
+
+	req := AcquireRequest()
+	res := AcquireResponse()
+
+	req.SetRequestURI("http://localhost")
+
+	// Setting Connection: Close will make the connection be
+	// returned to the pool.
+	req.SetConnectionClose()
+
+	if err := c.Do(req, res); err != nil {
+		t.Fatal(err)
+	}
+
+	ReleaseRequest(req)
+	ReleaseResponse(res)
+
+	done := make(chan struct{})
+	go func() {
+		req := AcquireRequest()
+		res := AcquireResponse()
+
+		req.SetRequestURI("http://localhost")
+		req.SetConnectionClose()
+
+		c.Do(req, res)
+
+		ReleaseRequest(req)
+		ReleaseResponse(res)
+		close(done)
+	}()
+
+	select {
+	case <-done:
+		// This shouldn't take longer than the timeout times the number of requests it is going to try to do.
+		// Give it 2 seconds extra seconds just to be sure.
+	case <-time.After(c.ReadTimeout*time.Duration(c.MaxIdemponentCallAttempts) + time.Second*2):
+		t.Fatal("Client.ReadTimeout didn't work")
+	}
+}
+
 func TestClientUserAgent(t *testing.T) {
 	ln := fasthttputil.NewInmemoryListener()
 
@@ -1276,6 +1416,7 @@ func startEchoServerExt(t *testing.T, network, addr string, isTLS bool) *testEch
 				ctx.PostArgs().WriteTo(ctx)
 			}
 		},
+		Logger: &customLogger{}, // Ignore log output.
 	}
 	ch := make(chan struct{})
 	go func() {
diff --git a/cookie_test.go b/cookie_test.go
index 85f1569..cec8edd 100644
--- a/cookie_test.go
+++ b/cookie_test.go
@@ -85,6 +85,46 @@ func TestCookieSecure(t *testing.T) {
 	}
 }
 
+func TestCookieMaxAge(t *testing.T) {
+	var c Cookie
+
+	maxAge := 100
+	if err := c.Parse("foo=bar; max-age=100"); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if maxAge != c.MaxAge() {
+		t.Fatalf("max-age must be set")
+	}
+	s := c.String()
+	if !strings.Contains(s, "; max-age=100") {
+		t.Fatalf("missing max-age flag in cookie %q", s)
+	}
+
+	if err := c.Parse("foo=bar; expires=Tue, 10 Nov 2009 23:00:00 GMT; max-age=100;"); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if maxAge != c.MaxAge() {
+		t.Fatalf("max-age ignored")
+	}
+	s = c.String()
+	if s != "foo=bar; max-age=100" {
+		t.Fatalf("missing max-age in cookie %q", s)
+	}
+
+	expires := time.Unix(100, 0)
+	c.SetExpire(expires)
+	s = c.String()
+	if s != "foo=bar; max-age=100" {
+		t.Fatalf("expires should be ignored due to max-age: %q", s)
+	}
+
+	c.SetMaxAge(0)
+	s = c.String()
+	if s != "foo=bar; expires=Thu, 01 Jan 1970 00:01:40 GMT" {
+		t.Fatalf("missing expires %q", s)
+	}
+}
+
 func TestCookieHttpOnly(t *testing.T) {
 	var c Cookie
 
@@ -175,7 +215,8 @@ func TestCookieParse(t *testing.T) {
 	testCookieParse(t, "foo=", "foo=")
 	testCookieParse(t, `foo="bar"`, "foo=bar")
 	testCookieParse(t, `"foo"=bar`, `"foo"=bar`)
-	testCookieParse(t, "foo=bar; domain=aaa.com; path=/foo/bar", "foo=bar; domain=aaa.com; path=/foo/bar")
+	testCookieParse(t, "foo=bar; Domain=aaa.com; PATH=/foo/bar", "foo=bar; domain=aaa.com; path=/foo/bar")
+	testCookieParse(t, "foo=bar; max-age= 101 ; expires= Tue, 10 Nov 2009 23:00:00 GMT", "foo=bar; max-age=101")
 	testCookieParse(t, " xxx = yyy  ; path=/a/b;;;domain=foobar.com ; expires= Tue, 10 Nov 2009 23:00:00 GMT ; ;;",
 		"xxx=yyy; expires=Tue, 10 Nov 2009 23:00:00 GMT; domain=foobar.com; path=/a/b")
 }
diff --git a/fs_test.go b/fs_test.go
index 78b1e13..92e5ac9 100644
--- a/fs_test.go
+++ b/fs_test.go
@@ -682,3 +682,25 @@ func testFileExtension(t *testing.T, path string, compressed bool, compressedFil
 		t.Fatalf("unexpected file extension for file %q: %q. Expecting %q", path, ext, expectedExt)
 	}
 }
+
+func TestServeFileContentType(t *testing.T) {
+	var ctx RequestCtx
+	var req Request
+	req.Header.SetMethod("GET")
+	req.SetRequestURI("http://foobar.com/baz")
+	ctx.Init(&req, nil, nil)
+
+	ServeFile(&ctx, "testdata/test.png")
+
+	var resp Response
+	s := ctx.Response.String()
+	br := bufio.NewReader(bytes.NewBufferString(s))
+	if err := resp.Read(br); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+
+	expected := []byte("image/png")
+	if !bytes.Equal(resp.Header.ContentType(), expected) {
+		t.Fatalf("Unexpected Content-Type, expected: %q got %q", expected, resp.Header.ContentType())
+	}
+}
diff --git a/header_test.go b/header_test.go
index 47956eb..096c856 100644
--- a/header_test.go
+++ b/header_test.go
@@ -110,6 +110,117 @@ func TestRequestHeaderEmptyValueFromString(t *testing.T) {
 	}
 }
 
+func TestRequestRawHeaders(t *testing.T) {
+	kvs := "host: foobar\r\n" +
+		"value: b\r\n" +
+		"\r\n"
+	t.Run("normalized", func(t *testing.T) {
+		s := "GET / HTTP/1.1\r\n" + kvs
+		exp := kvs
+		var h RequestHeader
+		br := bufio.NewReader(bytes.NewBufferString(s))
+		if err := h.Read(br); err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+		if string(h.Host()) != "foobar" {
+			t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar")
+		}
+		v2 := h.Peek("Value")
+		if !bytes.Equal(v2, []byte{'b'}) {
+			t.Fatalf("expecting non empty value. Got %q", v2)
+		}
+		if raw := h.RawHeaders(); string(raw) != exp {
+			t.Fatalf("expected header %q, got %q", exp, raw)
+		}
+	})
+	for _, n := range []int{0, 1, 4, 8} {
+		t.Run(fmt.Sprintf("post-%dk", n), func(t *testing.T) {
+			l := 1024 * n
+			body := make([]byte, l)
+			for i := range body {
+				body[i] = 'a'
+			}
+			cl := fmt.Sprintf("Content-Length: %d\r\n", l)
+			s := "POST / HTTP/1.1\r\n" + cl + kvs + string(body)
+			exp := cl + kvs
+			var h RequestHeader
+			br := bufio.NewReader(bytes.NewBufferString(s))
+			if err := h.Read(br); err != nil {
+				t.Fatalf("unexpected error: %s", err)
+			}
+			if string(h.Host()) != "foobar" {
+				t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar")
+			}
+			v2 := h.Peek("Value")
+			if !bytes.Equal(v2, []byte{'b'}) {
+				t.Fatalf("expecting non empty value. Got %q", v2)
+			}
+			if raw := h.RawHeaders(); string(raw) != exp {
+				t.Fatalf("expected header %q, got %q", exp, raw)
+			}
+		})
+	}
+	t.Run("http10", func(t *testing.T) {
+		s := "GET / HTTP/1.0\r\n" + kvs
+		exp := kvs
+		var h RequestHeader
+		br := bufio.NewReader(bytes.NewBufferString(s))
+		if err := h.Read(br); err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+		if string(h.Host()) != "foobar" {
+			t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "foobar")
+		}
+		v2 := h.Peek("Value")
+		if !bytes.Equal(v2, []byte{'b'}) {
+			t.Fatalf("expecting non empty value. Got %q", v2)
+		}
+		if raw := h.RawHeaders(); string(raw) != exp {
+			t.Fatalf("expected header %q, got %q", exp, raw)
+		}
+	})
+	t.Run("non-normalized", func(t *testing.T) {
+		s := "GET / HTTP/1.1\r\n" + kvs
+		exp := kvs
+		var h RequestHeader
+		h.DisableNormalizing()
+		br := bufio.NewReader(bytes.NewBufferString(s))
+		if err := h.Read(br); err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+		if string(h.Host()) != "" {
+			t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "")
+		}
+		v2 := h.Peek("value")
+		if !bytes.Equal(v2, []byte{'b'}) {
+			t.Fatalf("expecting non empty value. Got %q", v2)
+		}
+		if raw := h.RawHeaders(); string(raw) != exp {
+			t.Fatalf("expected header %q, got %q", exp, raw)
+		}
+	})
+	t.Run("no-kvs", func(t *testing.T) {
+		s := "GET / HTTP/1.1\r\n\r\n"
+		exp := ""
+		var h RequestHeader
+		h.DisableNormalizing()
+		br := bufio.NewReader(bytes.NewBufferString(s))
+		if err := h.Read(br); err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+		if string(h.Host()) != "" {
+			t.Fatalf("unexpected host: %q. Expecting %q", h.Host(), "")
+		}
+		v1 := h.Peek("NoKey")
+		if len(v1) > 0 {
+			t.Fatalf("expecting empty value. Got %q", v1)
+		}
+		if raw := h.RawHeaders(); string(raw) != exp {
+			t.Fatalf("expected header %q, got %q", exp, raw)
+		}
+	})
+}
+
 func TestRequestHeaderSetCookieWithSpecialChars(t *testing.T) {
 	var h RequestHeader
 	h.Set("Cookie", "ID&14")
@@ -431,9 +542,6 @@ func TestRequestHeaderHTTP10ConnectionClose(t *testing.T) {
 		t.Fatalf("unexpected error: %s", err)
 	}
 
-	if !h.connectionCloseFast() {
-		t.Fatalf("expecting 'Connection: close' request header")
-	}
 	if !h.ConnectionClose() {
 		t.Fatalf("expecting 'Connection: close' request header")
 	}
@@ -896,20 +1004,6 @@ func TestResponseHeaderFirstByteReadEOF(t *testing.T) {
 	}
 }
 
-func TestRequestHeaderFirstByteReadEOF(t *testing.T) {
-	var h RequestHeader
-
-	r := &errorReader{fmt.Errorf("non-eof error")}
-	br := bufio.NewReader(r)
-	err := h.Read(br)
-	if err == nil {
-		t.Fatalf("expecting error")
-	}
-	if err != io.EOF {
-		t.Fatalf("unexpected error %s. Expecting %s", err, io.EOF)
-	}
-}
-
 type errorReader struct {
 	err error
 }
@@ -1428,6 +1522,46 @@ func TestResponseHeaderCookieIssue4(t *testing.T) {
 	}
 }
 
+func TestRequestHeaderCookieIssue313(t *testing.T) {
+	var h RequestHeader
+	h.SetRequestURI("/")
+	h.Set("Host", "foobar.com")
+
+	h.SetCookie("foo", "bar")
+
+	if string(h.Peek("Cookie")) != "foo=bar" {
+		t.Fatalf("Unexpected Cookie header %q. Expected %q", h.Peek("Cookie"), "foo=bar")
+	}
+	cookieSeen := false
+	h.VisitAll(func(key, value []byte) {
+		switch string(key) {
+		case "Cookie":
+			cookieSeen = true
+		}
+	})
+	if !cookieSeen {
+		t.Fatalf("Cookie not present in VisitAll")
+	}
+
+	if string(h.Cookie("foo")) != "bar" {
+		t.Fatalf("Unexpected cookie value %q. Exepcted %q", h.Cookie("foo"), "bar")
+	}
+
+	if string(h.Peek("Cookie")) != "foo=bar" {
+		t.Fatalf("Unexpected Cookie header %q. Expected %q", h.Peek("Cookie"), "foo=bar")
+	}
+	cookieSeen = false
+	h.VisitAll(func(key, value []byte) {
+		switch string(key) {
+		case "Cookie":
+			cookieSeen = true
+		}
+	})
+	if !cookieSeen {
+		t.Fatalf("Cookie not present in VisitAll")
+	}
+}
+
 func TestRequestHeaderMethod(t *testing.T) {
 	// common http methods
 	testRequestHeaderMethod(t, "GET")
@@ -1828,6 +1962,12 @@ func TestResponseHeaderReadSuccess(t *testing.T) {
 	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\r\nContent-Length: 123\r\n\r\nfoiaaa",
 		400, 123, string(defaultContentType), "foiaaa")
 
+	// no content-type and no default
+	h.noDefaultContentType = true
+	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 400 OK\r\nContent-Length: 123\r\n\r\nfoiaaa",
+		400, 123, "", "foiaaa")
+	h.noDefaultContentType = false
+
 	// no headers
 	testResponseHeaderReadSuccess(t, h, "HTTP/1.1 200 OK\r\n\r\naaaabbb",
 		200, -2, string(defaultContentType), "aaaabbb")
diff --git a/header_timing_test.go b/header_timing_test.go
index a6f7a11..55f5df9 100644
--- a/header_timing_test.go
+++ b/header_timing_test.go
@@ -5,6 +5,8 @@ import (
 	"bytes"
 	"io"
 	"testing"
+
+	"github.com/valyala/bytebufferpool"
 )
 
 var strFoobar = []byte("foobar.com")
@@ -65,7 +67,7 @@ func BenchmarkRequestHeaderWrite(b *testing.B) {
 		h.SetHost("foobar.com")
 		h.SetUserAgent("aaa.bbb")
 		h.SetReferer("http://google.com/aaa/bbb")
-		var w ByteBuffer
+		var w bytebufferpool.ByteBuffer
 		for pb.Next() {
 			if _, err := h.WriteTo(&w); err != nil {
 				b.Fatalf("unexpected error when writing header: %s", err)
@@ -83,7 +85,7 @@ func BenchmarkResponseHeaderWrite(b *testing.B) {
 		h.SetContentLength(1256)
 		h.SetServer("aaa 1/2.3")
 		h.Set("Test", "1.2.3")
-		var w ByteBuffer
+		var w bytebufferpool.ByteBuffer
 		for pb.Next() {
 			if _, err := h.WriteTo(&w); err != nil {
 				b.Fatalf("unexpected error when writing header: %s", err)
diff --git a/http_test.go b/http_test.go
index 4ecab5b..4052c61 100644
--- a/http_test.go
+++ b/http_test.go
@@ -10,6 +10,8 @@ import (
 	"strings"
 	"testing"
 	"time"
+
+	"github.com/valyala/bytebufferpool"
 )
 
 func TestResponseBodyStreamDeflate(t *testing.T) {
@@ -490,7 +492,7 @@ type bodyWriterTo interface {
 }
 
 func testBodyWriteTo(t *testing.T, bw bodyWriterTo, expectedS string, isRetainedBody bool) {
-	var buf ByteBuffer
+	var buf bytebufferpool.ByteBuffer
 	if err := bw.BodyWriteTo(&buf); err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
@@ -564,7 +566,7 @@ func TestResponseWriteTo(t *testing.T) {
 	r.SetBodyString("foobar")
 
 	s := r.String()
-	var buf ByteBuffer
+	var buf bytebufferpool.ByteBuffer
 	n, err := r.WriteTo(&buf)
 	if err != nil {
 		t.Fatalf("unexpected error: %s", err)
@@ -583,7 +585,7 @@ func TestRequestWriteTo(t *testing.T) {
 	r.SetRequestURI("http://foobar.com/aaa/bbb")
 
 	s := r.String()
-	var buf ByteBuffer
+	var buf bytebufferpool.ByteBuffer
 	n, err := r.WriteTo(&buf)
 	if err != nil {
 		t.Fatalf("unexpected error: %s", err)
@@ -1457,7 +1459,7 @@ func testRequestWriteError(t *testing.T, method, requestURI, host, userAgent, bo
 	req.Header.Set("User-Agent", userAgent)
 	req.SetBody([]byte(body))
 
-	w := &ByteBuffer{}
+	w := &bytebufferpool.ByteBuffer{}
 	bw := bufio.NewWriter(w)
 	err := req.Write(bw)
 	if err == nil {
diff --git a/reuseport/reuseport_test.go b/reuseport/reuseport_test.go
index 0c70e83..e781ff1 100644
--- a/reuseport/reuseport_test.go
+++ b/reuseport/reuseport_test.go
@@ -73,7 +73,7 @@ func testNewListener(t *testing.T, network, addr string, serversCount, requestsC
 		select {
 		case <-ch:
 		case <-time.After(200 * time.Millisecond):
-			t.Fatalf("%d. timeout when waiting for response: %s", i, err)
+			t.Fatalf("%d. timeout when waiting for response", i)
 		}
 
 		if string(resp) != req {
diff --git a/server_test.go b/server_test.go
index 5d85495..7be1e81 100644
--- a/server_test.go
+++ b/server_test.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"io"
 	"io/ioutil"
+	"mime/multipart"
 	"net"
 	"os"
 	"strings"
@@ -17,6 +18,61 @@ import (
 	"github.com/valyala/fasthttp/fasthttputil"
 )
 
+func TestSaveMultipartFile(t *testing.T) {
+	filea := "This is a test file."
+	fileb := strings.Repeat("test", 64)
+
+	mr := multipart.NewReader(strings.NewReader(""+
+		"--foo\r\n"+
+		"Content-Disposition: form-data; name=\"filea\"; filename=\"filea.txt\"\r\n"+
+		"Content-Type: text/plain\r\n"+
+		"\r\n"+
+		filea+"\r\n"+
+		"--foo\r\n"+
+		"Content-Disposition: form-data; name=\"fileb\"; filename=\"fileb.txt\"\r\n"+
+		"Content-Type: text/plain\r\n"+
+		"\r\n"+
+		fileb+"\r\n"+
+		"--foo--\r\n",
+	), "foo")
+
+	f, err := mr.ReadForm(64)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := SaveMultipartFile(f.File["filea"][0], "filea.txt"); err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove("filea.txt")
+
+	if c, err := ioutil.ReadFile("filea.txt"); err != nil {
+		t.Fatal(err)
+	} else if string(c) != filea {
+		t.Fatalf("filea changed expeced %q got %q", filea, c)
+	}
+
+	// Make sure fileb was saved to a file.
+	if ff, err := f.File["fileb"][0].Open(); err != nil {
+		t.Fatalf("expected FileHeader.Open to work")
+	} else if _, ok := ff.(*os.File); !ok {
+		t.Fatalf("expected fileb to be an os.File")
+	} else {
+		ff.Close()
+	}
+
+	if err := SaveMultipartFile(f.File["fileb"][0], "fileb.txt"); err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove("fileb.txt")
+
+	if c, err := ioutil.ReadFile("fileb.txt"); err != nil {
+		t.Fatal(err)
+	} else if string(c) != fileb {
+		t.Fatalf("fileb changed expeced %q got %q", fileb, c)
+	}
+}
+
 func TestServerName(t *testing.T) {
 	s := &Server{
 		Handler: func(ctx *RequestCtx) {
@@ -70,12 +126,17 @@ func TestServerName(t *testing.T) {
 		Handler: func(ctx *RequestCtx) {
 		},
 		NoDefaultServerHeader: true,
+		NoDefaultContentType:  true,
 	}
 
 	resp = getReponse()
 	if bytes.Contains(resp, []byte("\r\nServer: ")) {
 		t.Fatalf("Unexpected response %q expected no Server header", resp)
 	}
+
+	if bytes.Contains(resp, []byte("\r\nContent-Type: ")) {
+		t.Fatalf("Unexpected response %q expected no Content-Type header", resp)
+	}
 }
 
 func TestRequestCtxString(t *testing.T) {
@@ -1928,7 +1989,7 @@ func TestTimeoutHandlerTimeout(t *testing.T) {
 		go func() {
 			conn, err := ln.Dial()
 			if err != nil {
-				t.Fatalf("unexepcted error: %s", err)
+				t.Fatalf("unexpected error: %s", err)
 			}
 			if _, err = conn.Write([]byte("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")); err != nil {
 				t.Fatalf("unexpected error: %s", err)
@@ -2649,6 +2710,7 @@ func TestShutdownReuse(t *testing.T) {
 	s := &Server{
 		Handler:              h,
 		MaxKeepaliveDuration: time.Millisecond * 100,
+		Logger:               &customLogger{}, // Ignore log output.
 	}
 	go func() {
 		if err := s.Serve(ln); err != nil {
diff --git a/testdata/test.png b/testdata/test.png
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/testdata/test.png
@@ -0,0 +1,1 @@
+
diff --git a/uri_test.go b/uri_test.go
index beb4f7d..dcba56e 100644
--- a/uri_test.go
+++ b/uri_test.go
@@ -275,7 +275,7 @@ func TestURIParse(t *testing.T) {
 
 	// encoded path
 	testURIParse(t, &u, "aa.com", "/Test%20+%20%D0%BF%D1%80%D0%B8?asdf=%20%20&s=12#sdf",
-		"http://aa.com/Test%20%2B%20%D0%BF%D1%80%D0%B8?asdf=%20%20&s=12#sdf", "aa.com", "/Test + при", "/Test%20+%20%D0%BF%D1%80%D0%B8", "asdf=%20%20&s=12", "sdf")
+		"http://aa.com/Test%20+%20%D0%BF%D1%80%D0%B8?asdf=%20%20&s=12#sdf", "aa.com", "/Test + при", "/Test%20+%20%D0%BF%D1%80%D0%B8", "asdf=%20%20&s=12", "sdf")
 
 	// host in uppercase
 	testURIParse(t, &u, "FOObar.COM", "/bC?De=F#Gh",
diff --git a/workerpool_test.go b/workerpool_test.go
index 6f63f2c..05e1be0 100644
--- a/workerpool_test.go
+++ b/workerpool_test.go
@@ -94,6 +94,7 @@ func testWorkerPoolMaxWorkersCount(t *testing.T) {
 		},
 		MaxWorkersCount: 10,
 		Logger:          defaultLogger,
+		connState:       func(net.Conn, ConnState) {},
 	}
 	wp.Start()