Repository: valyala/fasthttp
Pull request #415 (2018-09-13 17:14:01)

Support Go modules


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 0acbeac..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.
-//
-// 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/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/fs.go b/fs.go
index 7314062..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,8 @@ func (h *fsHandler) handleRequest(ctx *RequestCtx) {
 			}
 		}
 	}
-	if len(ctx.Response.Header.ContentType()) == 0 {
+	hdr.noDefaultContentType = true
+	if len(hdr.ContentType()) == 0 {
 		ctx.SetContentType(ff.contentType)
 	}
 	ctx.SetStatusCode(statusCode)
@@ -914,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)
@@ -974,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 ddf5add..e00743e 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
@@ -156,12 +157,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.
@@ -282,7 +277,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
@@ -643,6 +638,7 @@ func (h *ResponseHeader) DisableNormalizing() {
 // Reset clears response header.
 func (h *ResponseHeader) Reset() {
 	h.disableNormalizing = false
+	h.noDefaultContentType = false
 	h.resetSkipNormalize()
 }
 
@@ -695,6 +691,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
@@ -1774,7 +1771,8 @@ func (h *ResponseHeader) parseHeaders(buf []byte) (int, error) {
 				if caseInsensitiveCompare(s.key, strContentType) {
 					h.contentType = append(h.contentType[:0], s.value...)
 					continue
-				} else if caseInsensitiveCompare(s.key, strContentLength) {
+				} 
+				if caseInsensitiveCompare(s.key, strContentLength) {
 					if h.contentLength != -1 {
 						if h.contentLength, err = parseContentLength(s.value); err != nil {
 							h.contentLength = -2
@@ -1783,7 +1781,8 @@ func (h *ResponseHeader) parseHeaders(buf []byte) (int, error) {
 						}
 					}
 					continue
-				} else if caseInsensitiveCompare(s.key, strConnection) {
+				}
+				if caseInsensitiveCompare(s.key, strConnection) {
 					if bytes.Equal(s.value, strClose) {
 						h.connectionClose = true
 					} else {
@@ -1796,7 +1795,8 @@ func (h *ResponseHeader) parseHeaders(buf []byte) (int, error) {
 				if caseInsensitiveCompare(s.key, strServer) {
 					h.server = append(h.server[:0], s.value...)
 					continue
-				} else if caseInsensitiveCompare(s.key, strSetCookie) {
+				}
+				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...)
@@ -1859,7 +1859,8 @@ func (h *RequestHeader) parseHeaders(buf []byte) (int, error) {
 				if caseInsensitiveCompare(s.key, strContentType) {
 					h.contentType = append(h.contentType[:0], s.value...)
 					continue
-				} else if caseInsensitiveCompare(s.key, strContentLength) {
+				}
+				if caseInsensitiveCompare(s.key, strContentLength) {
 					if h.contentLength != -1 {
 						if h.contentLength, err = parseContentLength(s.value); err != nil {
 							h.contentLength = -2
@@ -1868,7 +1869,8 @@ func (h *RequestHeader) parseHeaders(buf []byte) (int, error) {
 						}
 					}
 					continue
-				} else if caseInsensitiveCompare(s.key, strConnection) {
+				}
+				if caseInsensitiveCompare(s.key, strConnection) {
 					if bytes.Equal(s.value, strClose) {
 						h.connectionClose = true
 					} else {
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/server.go b/server.go
index 3efa32e..3c185cd 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.
 //
@@ -213,6 +219,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,6 +294,13 @@ 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.
@@ -302,7 +327,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
 }
 
@@ -971,7 +997,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)
@@ -993,7 +1025,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)
@@ -1204,15 +1242,46 @@ func (ctx *RequestCtx) TimeoutErrorWithResponse(resp *Response) {
 	ctx.timeoutResponse = respCopy
 }
 
+// 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)
 }
 
@@ -1244,11 +1313,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)
 }
 
@@ -1261,11 +1340,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)
 }
 
@@ -1374,13 +1463,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)
@@ -1402,9 +1494,9 @@ func (s *Server) Serve(ln net.Listener) error {
 			return err
 		}
 		s.setState(c, StateNew)
-		s.wg.Add(1)
+		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()
@@ -1435,16 +1527,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
 }
 
@@ -1549,7 +1659,7 @@ func (s *Server) ServeConn(c net.Conn) error {
 		return ErrConcurrencyLimit
 	}
 
-	s.wg.Add(1)
+	atomic.AddInt32(&s.open, 1)
 
 	err := s.serveConn(c)
 
@@ -1591,7 +1701,7 @@ 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)
 
 	var serverName []byte
 	if !s.NoDefaultServerHeader {
@@ -1648,6 +1758,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 {
@@ -1708,7 +1819,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 {
@@ -1747,9 +1858,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 {


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 1be6edf..614af92 100644
--- a/client_test.go
+++ b/client_test.go
@@ -17,6 +17,38 @@ 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()
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 1195a72..7cc684d 100644
--- a/header_test.go
+++ b/header_test.go
@@ -431,9 +431,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")
 	}
@@ -1854,6 +1851,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 3f62d89..7be1e81 100644
--- a/server_test.go
+++ b/server_test.go
@@ -126,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) {
@@ -1984,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)
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 @@
+