diff options
Diffstat (limited to 'src/pkg/net/http/server.go')
-rw-r--r-- | src/pkg/net/http/server.go | 270 |
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. |