summaryrefslogtreecommitdiff
path: root/src/pkg/net/http/server.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/net/http/server.go')
-rw-r--r--src/pkg/net/http/server.go270
1 files changed, 198 insertions, 72 deletions
diff --git a/src/pkg/net/http/server.go b/src/pkg/net/http/server.go
index 0e46863d5..eae097eb8 100644
--- a/src/pkg/net/http/server.go
+++ b/src/pkg/net/http/server.go
@@ -22,6 +22,7 @@ import (
"strconv"
"strings"
"sync"
+ "sync/atomic"
"time"
)
@@ -138,6 +139,7 @@ func (c *conn) hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error) {
buf = c.buf
c.rwc = nil
c.buf = nil
+ c.setState(rwc, StateHijacked)
return
}
@@ -435,56 +437,52 @@ func (srv *Server) newConn(rwc net.Conn) (c *conn, err error) {
return c, nil
}
-// TODO: use a sync.Cache instead
var (
- bufioReaderCache = make(chan *bufio.Reader, 4)
- bufioWriterCache2k = make(chan *bufio.Writer, 4)
- bufioWriterCache4k = make(chan *bufio.Writer, 4)
+ bufioReaderPool sync.Pool
+ bufioWriter2kPool sync.Pool
+ bufioWriter4kPool sync.Pool
)
-func bufioWriterCache(size int) chan *bufio.Writer {
+func bufioWriterPool(size int) *sync.Pool {
switch size {
case 2 << 10:
- return bufioWriterCache2k
+ return &bufioWriter2kPool
case 4 << 10:
- return bufioWriterCache4k
+ return &bufioWriter4kPool
}
return nil
}
func newBufioReader(r io.Reader) *bufio.Reader {
- select {
- case p := <-bufioReaderCache:
- p.Reset(r)
- return p
- default:
- return bufio.NewReader(r)
+ if v := bufioReaderPool.Get(); v != nil {
+ br := v.(*bufio.Reader)
+ br.Reset(r)
+ return br
}
+ return bufio.NewReader(r)
}
func putBufioReader(br *bufio.Reader) {
br.Reset(nil)
- select {
- case bufioReaderCache <- br:
- default:
- }
+ bufioReaderPool.Put(br)
}
func newBufioWriterSize(w io.Writer, size int) *bufio.Writer {
- select {
- case p := <-bufioWriterCache(size):
- p.Reset(w)
- return p
- default:
- return bufio.NewWriterSize(w, size)
+ pool := bufioWriterPool(size)
+ if pool != nil {
+ if v := pool.Get(); v != nil {
+ bw := v.(*bufio.Writer)
+ bw.Reset(w)
+ return bw
+ }
}
+ return bufio.NewWriterSize(w, size)
}
func putBufioWriter(bw *bufio.Writer) {
bw.Reset(nil)
- select {
- case bufioWriterCache(bw.Available()) <- bw:
- default:
+ if pool := bufioWriterPool(bw.Available()); pool != nil {
+ pool.Put(bw)
}
}
@@ -500,6 +498,10 @@ func (srv *Server) maxHeaderBytes() int {
return DefaultMaxHeaderBytes
}
+func (srv *Server) initialLimitedReaderSize() int64 {
+ return int64(srv.maxHeaderBytes()) + 4096 // bufio slop
+}
+
// wrapper around io.ReaderCloser which on first read, sends an
// HTTP/1.1 100 Continue header
type expectContinueReader struct {
@@ -570,7 +572,7 @@ func (c *conn) readRequest() (w *response, err error) {
}()
}
- c.lr.N = int64(c.server.maxHeaderBytes()) + 4096 /* bufio slop */
+ c.lr.N = c.server.initialLimitedReaderSize()
var req *Request
if req, err = ReadRequest(c.buf.Reader); err != nil {
if c.lr.N == 0 {
@@ -618,11 +620,11 @@ const maxPostHandlerReadBytes = 256 << 10
func (w *response) WriteHeader(code int) {
if w.conn.hijacked() {
- log.Print("http: response.WriteHeader on hijacked connection")
+ w.conn.server.logf("http: response.WriteHeader on hijacked connection")
return
}
if w.wroteHeader {
- log.Print("http: multiple response.WriteHeader calls")
+ w.conn.server.logf("http: multiple response.WriteHeader calls")
return
}
w.wroteHeader = true
@@ -637,7 +639,7 @@ func (w *response) WriteHeader(code int) {
if err == nil && v >= 0 {
w.contentLength = v
} else {
- log.Printf("http: invalid Content-Length of %q", cl)
+ w.conn.server.logf("http: invalid Content-Length of %q", cl)
w.handlerHeader.Del("Content-Length")
}
}
@@ -707,6 +709,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
cw.wroteHeader = true
w := cw.res
+ keepAlivesEnabled := w.conn.server.doKeepAlives()
isHEAD := w.req.Method == "HEAD"
// header is written out to w.conn.buf below. Depending on the
@@ -739,7 +742,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
// response header and this is our first (and last) write, set
// it, even to zero. This helps HTTP/1.0 clients keep their
// "keep-alive" connections alive.
- // Exceptions: 304 responses never get Content-Length, and if
+ // Exceptions: 304/204/1xx responses never get Content-Length, and if
// it was a HEAD request, we don't know the difference between
// 0 actual bytes and 0 bytes because the handler noticed it
// was a HEAD request and chose not to write anything. So for
@@ -747,14 +750,14 @@ func (cw *chunkWriter) writeHeader(p []byte) {
// write non-zero bytes. If it's actually 0 bytes and the
// handler never looked at the Request.Method, we just don't
// send a Content-Length header.
- if w.handlerDone && w.status != StatusNotModified && header.get("Content-Length") == "" && (!isHEAD || len(p) > 0) {
+ if w.handlerDone && bodyAllowedForStatus(w.status) && header.get("Content-Length") == "" && (!isHEAD || len(p) > 0) {
w.contentLength = int64(len(p))
setHeader.contentLength = strconv.AppendInt(cw.res.clenBuf[:0], int64(len(p)), 10)
}
// If this was an HTTP/1.0 request with keep-alive and we sent a
// Content-Length back, we can make this a keep-alive response ...
- if w.req.wantsHttp10KeepAlive() {
+ if w.req.wantsHttp10KeepAlive() && keepAlivesEnabled {
sentLength := header.get("Content-Length") != ""
if sentLength && header.get("Connection") == "keep-alive" {
w.closeAfterReply = false
@@ -773,7 +776,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
w.closeAfterReply = true
}
- if header.get("Connection") == "close" {
+ if header.get("Connection") == "close" || !keepAlivesEnabled {
w.closeAfterReply = true
}
@@ -796,18 +799,16 @@ func (cw *chunkWriter) writeHeader(p []byte) {
}
code := w.status
- if code == StatusNotModified {
- // Must not have body.
- // RFC 2616 section 10.3.5: "the response MUST NOT include other entity-headers"
- for _, k := range []string{"Content-Type", "Content-Length", "Transfer-Encoding"} {
- delHeader(k)
- }
- } else {
+ if bodyAllowedForStatus(code) {
// If no content type, apply sniffing algorithm to body.
_, haveType := header["Content-Type"]
if !haveType {
setHeader.contentType = DetectContentType(p)
}
+ } else {
+ for _, k := range suppressedHeaders(code) {
+ delHeader(k)
+ }
}
if _, ok := header["Date"]; !ok {
@@ -819,13 +820,13 @@ func (cw *chunkWriter) writeHeader(p []byte) {
if hasCL && hasTE && te != "identity" {
// TODO: return an error if WriteHeader gets a return parameter
// For now just ignore the Content-Length.
- log.Printf("http: WriteHeader called with both Transfer-Encoding of %q and a Content-Length of %d",
+ w.conn.server.logf("http: WriteHeader called with both Transfer-Encoding of %q and a Content-Length of %d",
te, w.contentLength)
delHeader("Content-Length")
hasCL = false
}
- if w.req.Method == "HEAD" || code == StatusNotModified {
+ if w.req.Method == "HEAD" || !bodyAllowedForStatus(code) {
// do nothing
} else if code == StatusNoContent {
delHeader("Transfer-Encoding")
@@ -855,7 +856,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
return
}
- if w.closeAfterReply && !hasToken(cw.header.get("Connection"), "close") {
+ if w.closeAfterReply && (!keepAlivesEnabled || !hasToken(cw.header.get("Connection"), "close")) {
delHeader("Connection")
if w.req.ProtoAtLeast(1, 1) {
setHeader.connection = "close"
@@ -919,7 +920,7 @@ func (w *response) bodyAllowed() bool {
if !w.wroteHeader {
panic("")
}
- return w.status != StatusNotModified
+ return bodyAllowedForStatus(w.status)
}
// The Life Of A Write is like this:
@@ -965,7 +966,7 @@ func (w *response) WriteString(data string) (n int, err error) {
// either dataB or dataS is non-zero.
func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err error) {
if w.conn.hijacked() {
- log.Print("http: response.Write on hijacked connection")
+ w.conn.server.logf("http: response.Write on hijacked connection")
return 0, ErrHijacked
}
if !w.wroteHeader {
@@ -1001,11 +1002,10 @@ func (w *response) finishRequest() {
w.cw.close()
w.conn.buf.Flush()
- // Close the body, unless we're about to close the whole TCP connection
- // anyway.
- if !w.closeAfterReply {
- w.req.Body.Close()
- }
+ // Close the body (regardless of w.closeAfterReply) so we can
+ // re-use its bufio.Reader later safely.
+ w.req.Body.Close()
+
if w.req.MultipartForm != nil {
w.req.MultipartForm.RemoveAll()
}
@@ -1084,17 +1084,25 @@ func validNPN(proto string) bool {
return true
}
+func (c *conn) setState(nc net.Conn, state ConnState) {
+ if hook := c.server.ConnState; hook != nil {
+ hook(nc, state)
+ }
+}
+
// Serve a new connection.
func (c *conn) serve() {
+ origConn := c.rwc // copy it before it's set nil on Close or Hijack
defer func() {
if err := recover(); err != nil {
- const size = 4096
+ const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
- log.Printf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
+ c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
}
if !c.hijacked() {
c.close()
+ c.setState(origConn, StateClosed)
}
}()
@@ -1106,6 +1114,7 @@ func (c *conn) serve() {
c.rwc.SetWriteDeadline(time.Now().Add(d))
}
if err := tlsConn.Handshake(); err != nil {
+ c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
return
}
c.tlsState = new(tls.ConnectionState)
@@ -1121,6 +1130,10 @@ func (c *conn) serve() {
for {
w, err := c.readRequest()
+ if c.lr.N != c.server.initialLimitedReaderSize() {
+ // If we read any bytes off the wire, we're active.
+ c.setState(c.rwc, StateActive)
+ }
if err != nil {
if err == errTooLarge {
// Their HTTP client may or may not be
@@ -1143,16 +1156,10 @@ func (c *conn) serve() {
// Expect 100 Continue support
req := w.req
if req.expectsContinue() {
- if req.ProtoAtLeast(1, 1) {
+ if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
// Wrap the Body reader with one that replies on the connection
req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
}
- if req.ContentLength == 0 {
- w.Header().Set("Connection", "close")
- w.WriteHeader(StatusBadRequest)
- w.finishRequest()
- break
- }
req.Header.Del("Expect")
} else if req.Header.get("Expect") != "" {
w.sendExpectationFailed()
@@ -1175,6 +1182,7 @@ func (c *conn) serve() {
}
break
}
+ c.setState(c.rwc, StateIdle)
}
}
@@ -1202,7 +1210,14 @@ func (w *response) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error) {
if w.wroteHeader {
w.cw.flush()
}
- return w.conn.hijack()
+ // Release the bufioWriter that writes to the chunk writer, it is not
+ // used after a connection has been hijacked.
+ rwc, buf, err = w.conn.hijack()
+ if err == nil {
+ putBufioWriter(w.w)
+ w.w = nil
+ }
+ return rwc, buf, err
}
func (w *response) CloseNotify() <-chan bool {
@@ -1562,6 +1577,7 @@ func Serve(l net.Listener, handler Handler) error {
}
// A Server defines parameters for running an HTTP server.
+// The zero value for Server is a valid configuration.
type Server struct {
Addr string // TCP address to listen on, ":http" if empty
Handler Handler // handler to invoke, http.DefaultServeMux if nil
@@ -1578,6 +1594,66 @@ type Server struct {
// and RemoteAddr if not already set. The connection is
// automatically closed when the function returns.
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
+
+ // 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)
+
+ // ErrorLog specifies an optional logger for errors accepting
+ // connections and unexpected behavior from handlers.
+ // If nil, logging goes to os.Stderr via the log package's
+ // standard logger.
+ ErrorLog *log.Logger
+
+ disableKeepAlives int32 // accessed atomically.
+}
+
+// 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.
+ 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]
}
// serverHandler delegates to either the server's Handler or
@@ -1605,11 +1681,11 @@ func (srv *Server) ListenAndServe() error {
if addr == "" {
addr = ":http"
}
- l, e := net.Listen("tcp", addr)
- if e != nil {
- return e
+ ln, err := net.Listen("tcp", addr)
+ if err != nil {
+ return err
}
- return srv.Serve(l)
+ return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
// Serve accepts incoming connections on the Listener l, creating a
@@ -1630,7 +1706,7 @@ func (srv *Server) Serve(l net.Listener) error {
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
- log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
+ srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
@@ -1641,10 +1717,35 @@ func (srv *Server) Serve(l net.Listener) error {
if err != nil {
continue
}
+ c.setState(c.rwc, StateNew) // before Serve can return
go c.serve()
}
}
+func (s *Server) doKeepAlives() bool {
+ return atomic.LoadInt32(&s.disableKeepAlives) == 0
+}
+
+// SetKeepAlivesEnabled controls whether HTTP keep-alives are enabled.
+// By default, keep-alives are always enabled. Only very
+// resource-constrained environments or servers in the process of
+// shutting down should disable them.
+func (s *Server) SetKeepAlivesEnabled(v bool) {
+ if v {
+ atomic.StoreInt32(&s.disableKeepAlives, 0)
+ } else {
+ atomic.StoreInt32(&s.disableKeepAlives, 1)
+ }
+}
+
+func (s *Server) logf(format string, args ...interface{}) {
+ if s.ErrorLog != nil {
+ s.ErrorLog.Printf(format, args...)
+ } else {
+ log.Printf(format, args...)
+ }
+}
+
// ListenAndServe listens on the TCP network address addr
// and then calls Serve with handler to handle requests
// on incoming connections. Handler is typically nil,
@@ -1739,12 +1840,12 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error {
return err
}
- conn, err := net.Listen("tcp", addr)
+ ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
- tlsListener := tls.NewListener(conn, config)
+ tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config)
return srv.Serve(tlsListener)
}
@@ -1834,6 +1935,24 @@ func (tw *timeoutWriter) WriteHeader(code int) {
tw.w.WriteHeader(code)
}
+// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
+// connections. It's used by ListenAndServe and ListenAndServeTLS so
+// dead TCP connections (e.g. closing laptop mid-download) eventually
+// go away.
+type tcpKeepAliveListener struct {
+ *net.TCPListener
+}
+
+func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
+ tc, err := ln.AcceptTCP()
+ if err != nil {
+ return
+ }
+ tc.SetKeepAlive(true)
+ tc.SetKeepAlivePeriod(3 * time.Minute)
+ return tc, nil
+}
+
// globalOptionsHandler responds to "OPTIONS *" requests.
type globalOptionsHandler struct{}
@@ -1850,17 +1969,24 @@ func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request) {
}
}
+type eofReaderWithWriteTo struct{}
+
+func (eofReaderWithWriteTo) WriteTo(io.Writer) (int64, error) { return 0, nil }
+func (eofReaderWithWriteTo) Read([]byte) (int, error) { return 0, io.EOF }
+
// eofReader is a non-nil io.ReadCloser that always returns EOF.
-// It embeds a *strings.Reader so it still has a WriteTo method
-// and io.Copy won't need a buffer.
+// It has a WriteTo method so io.Copy won't need a buffer.
var eofReader = &struct {
- *strings.Reader
+ eofReaderWithWriteTo
io.Closer
}{
- strings.NewReader(""),
+ eofReaderWithWriteTo{},
ioutil.NopCloser(nil),
}
+// Verify that an io.Copy from an eofReader won't require a buffer.
+var _ io.WriterTo = eofReader
+
// initNPNRequest is an HTTP handler that initializes certain
// uninitialized fields in its *Request. Such partially-initialized
// Requests come from NPN protocol handlers.