Repository: valyala/fasthttp
Pull request #356 (2018-07-07 17:34:51)

Use form headers in WriteMultipartForm
[CreateFormFile](https://golang.org/pkg/mime/multipart/#Writer.CreateFormFile) always set `Content-Type` header to `application/octet-stream`.
Use [CreatePart](https://golang.org/pkg/mime/multipart/#Writer.CreatePart) instead so all headers in the multipart form can be used.

erikdubbelboer: Looks like a good fix to me even though it breaks backwards compatibility. People who didn't provide a correct form the `Content-Disposition` and `Content-Type` headers will run into issues where these aren't automatically set anymore.
erikdubbelboer: Travis CI seems to have had an error there and that's why it failed.

Like I said before, looks good to me 👍 

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..0ee78a2 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
 	}
@@ -744,7 +744,7 @@ func WriteMultipartForm(w io.Writer, f *multipart.Form, boundary string) error {
 	// marshal files
 	for k, fvv := range f.File {
 		for _, fv := range fvv {
-			vw, err := mw.CreateFormFile(k, fv.Filename)
+			vw, err := mw.CreatePart(fv.Header)
 			if err != nil {
 				return fmt.Errorf("cannot create form file %q (%q): %s", k, fv.Filename, 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..7fbd905 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 {
@@ -1809,3 +1811,32 @@ func createChunkedBody(body []byte) []byte {
 	}
 	return append(b, []byte("0\r\n\r\n")...)
 }
+
+func TestWriteMultipartForm(t *testing.T) {
+	var w bytes.Buffer
+	s := strings.Replace(`--foo
+Content-Disposition: form-data; name="key"
+
+value
+--foo
+Content-Disposition: form-data; name="file"; filename="test.json"
+Content-Type: application/json
+
+{"foo": "bar"}
+--foo--
+`, "\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)
+	}
+
+	if err := WriteMultipartForm(&w, form, "foo"); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+
+	if string(w.Bytes()) != s {
+		t.Fatalf("unexpected output %q", w.Bytes())
+	}
+}
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()