diff options
Diffstat (limited to 'libgo/go/net/http/server.go')
-rw-r--r-- | libgo/go/net/http/server.go | 280 |
1 files changed, 184 insertions, 96 deletions
diff --git a/libgo/go/net/http/server.go b/libgo/go/net/http/server.go index b2596070500..0e46863d5ae 100644 --- a/libgo/go/net/http/server.go +++ b/libgo/go/net/http/server.go @@ -16,6 +16,7 @@ import ( "log" "net" "net/url" + "os" "path" "runtime" "strconv" @@ -109,8 +110,6 @@ type conn struct { sr liveSwitchReader // where the LimitReader reads from; usually the rwc lr *io.LimitedReader // io.LimitReader(sr) buf *bufio.ReadWriter // buffered(lr,rwc), reading from bufio->limitReader->sr->rwc - bufswr *switchReader // the *switchReader io.Reader source of buf - bufsww *switchWriter // the *switchWriter io.Writer dest of buf tlsState *tls.ConnectionState // or nil when not using TLS mu sync.Mutex // guards the following @@ -246,6 +245,10 @@ func (cw *chunkWriter) Write(p []byte) (n int, err error) { if !cw.wroteHeader { cw.writeHeader(p) } + if cw.res.req.Method == "HEAD" { + // Eat writes. + return len(p), nil + } if cw.chunking { _, err = fmt.Fprintf(cw.res.conn.buf, "%x\r\n", len(p)) if err != nil { @@ -278,7 +281,7 @@ func (cw *chunkWriter) close() { // zero EOF chunk, trailer key/value pairs (currently // unsupported in Go's server), followed by a blank // line. - io.WriteString(cw.res.conn.buf, "0\r\n\r\n") + cw.res.conn.buf.WriteString("0\r\n\r\n") } } @@ -320,6 +323,10 @@ type response struct { requestBodyLimitHit bool handlerDone bool // set true when the handler exits + + // Buffers for Date and Content-Length + dateBuf [len(TimeFormat)]byte + clenBuf [10]byte } // requestTooLarge is called by maxBytesReader when too much input has @@ -332,16 +339,50 @@ func (w *response) requestTooLarge() { } } -// needsSniff returns whether a Content-Type still needs to be sniffed. +// needsSniff reports whether a Content-Type still needs to be sniffed. func (w *response) needsSniff() bool { - return !w.cw.wroteHeader && w.handlerHeader.Get("Content-Type") == "" && w.written < sniffLen + _, haveType := w.handlerHeader["Content-Type"] + return !w.cw.wroteHeader && !haveType && w.written < sniffLen } +// writerOnly hides an io.Writer value's optional ReadFrom method +// from io.Copy. type writerOnly struct { io.Writer } +func srcIsRegularFile(src io.Reader) (isRegular bool, err error) { + switch v := src.(type) { + case *os.File: + fi, err := v.Stat() + if err != nil { + return false, err + } + return fi.Mode().IsRegular(), nil + case *io.LimitedReader: + return srcIsRegularFile(v.R) + default: + return + } +} + +// ReadFrom is here to optimize copying from an *os.File regular file +// to a *net.TCPConn with sendfile. func (w *response) ReadFrom(src io.Reader) (n int64, err error) { + // Our underlying w.conn.rwc is usually a *TCPConn (with its + // own ReadFrom method). If not, or if our src isn't a regular + // file, just fall back to the normal copy method. + rf, ok := w.conn.rwc.(io.ReaderFrom) + regFile, err := srcIsRegularFile(src) + if err != nil { + return 0, err + } + if !ok || !regFile { + return io.Copy(writerOnly{w}, src) + } + + // sendfile path: + if !w.wroteHeader { w.WriteHeader(StatusOK) } @@ -359,16 +400,12 @@ func (w *response) ReadFrom(src io.Reader) (n int64, err error) { // Now that cw has been flushed, its chunking field is guaranteed initialized. if !w.cw.chunking && w.bodyAllowed() { - if rf, ok := w.conn.rwc.(io.ReaderFrom); ok { - n0, err := rf.ReadFrom(src) - n += n0 - w.written += n0 - return n, err - } + n0, err := rf.ReadFrom(src) + n += n0 + w.written += n0 + return n, err } - // Fall back to default io.Copy implementation. - // Use wrapper to hide w.ReadFrom from io.Copy. n0, err := io.Copy(writerOnly{w}, src) n += n0 return n, err @@ -392,34 +429,20 @@ func (srv *Server) newConn(rwc net.Conn) (c *conn, err error) { } c.sr = liveSwitchReader{r: c.rwc} c.lr = io.LimitReader(&c.sr, noLimit).(*io.LimitedReader) - br, sr := newBufioReader(c.lr) - bw, sw := newBufioWriterSize(c.rwc, 4<<10) + br := newBufioReader(c.lr) + bw := newBufioWriterSize(c.rwc, 4<<10) c.buf = bufio.NewReadWriter(br, bw) - c.bufswr = sr - c.bufsww = sw return c, nil } -// TODO: remove this, if issue 5100 is fixed -type bufioReaderPair struct { - br *bufio.Reader - sr *switchReader // from which the bufio.Reader is reading -} - -// TODO: remove this, if issue 5100 is fixed -type bufioWriterPair struct { - bw *bufio.Writer - sw *switchWriter // to which the bufio.Writer is writing -} - // TODO: use a sync.Cache instead var ( - bufioReaderCache = make(chan bufioReaderPair, 4) - bufioWriterCache2k = make(chan bufioWriterPair, 4) - bufioWriterCache4k = make(chan bufioWriterPair, 4) + bufioReaderCache = make(chan *bufio.Reader, 4) + bufioWriterCache2k = make(chan *bufio.Writer, 4) + bufioWriterCache4k = make(chan *bufio.Writer, 4) ) -func bufioWriterCache(size int) chan bufioWriterPair { +func bufioWriterCache(size int) chan *bufio.Writer { switch size { case 2 << 10: return bufioWriterCache2k @@ -429,55 +452,38 @@ func bufioWriterCache(size int) chan bufioWriterPair { return nil } -func newBufioReader(r io.Reader) (*bufio.Reader, *switchReader) { +func newBufioReader(r io.Reader) *bufio.Reader { select { case p := <-bufioReaderCache: - p.sr.Reader = r - return p.br, p.sr + p.Reset(r) + return p default: - sr := &switchReader{r} - return bufio.NewReader(sr), sr + return bufio.NewReader(r) } } -func putBufioReader(br *bufio.Reader, sr *switchReader) { - if n := br.Buffered(); n > 0 { - io.CopyN(ioutil.Discard, br, int64(n)) - } - br.Read(nil) // clears br.err - sr.Reader = nil +func putBufioReader(br *bufio.Reader) { + br.Reset(nil) select { - case bufioReaderCache <- bufioReaderPair{br, sr}: + case bufioReaderCache <- br: default: } } -func newBufioWriterSize(w io.Writer, size int) (*bufio.Writer, *switchWriter) { +func newBufioWriterSize(w io.Writer, size int) *bufio.Writer { select { case p := <-bufioWriterCache(size): - p.sw.Writer = w - return p.bw, p.sw + p.Reset(w) + return p default: - sw := &switchWriter{w} - return bufio.NewWriterSize(sw, size), sw + return bufio.NewWriterSize(w, size) } } -func putBufioWriter(bw *bufio.Writer, sw *switchWriter) { - if bw.Buffered() > 0 { - // It must have failed to flush to its target - // earlier. We can't reuse this bufio.Writer. - return - } - if err := bw.Flush(); err != nil { - // Its sticky error field is set, which is returned by - // Flush even when there's no data buffered. This - // bufio Writer is dead to us. Don't reuse it. - return - } - sw.Writer = nil +func putBufioWriter(bw *bufio.Writer) { + bw.Reset(nil) select { - case bufioWriterCache(bw.Available()) <- bufioWriterPair{bw, sw}: + case bufioWriterCache(bw.Available()) <- bw: default: } } @@ -508,7 +514,7 @@ func (ecr *expectContinueReader) Read(p []byte) (n int, err error) { } if !ecr.resp.wroteContinue && !ecr.resp.conn.hijacked() { ecr.resp.wroteContinue = true - io.WriteString(ecr.resp.conn.buf, "HTTP/1.1 100 Continue\r\n\r\n") + ecr.resp.conn.buf.WriteString("HTTP/1.1 100 Continue\r\n\r\n") ecr.resp.conn.buf.Flush() } return ecr.readCloser.Read(p) @@ -525,6 +531,28 @@ func (ecr *expectContinueReader) Close() error { // It is like time.RFC1123 but hard codes GMT as the time zone. const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT" +// appendTime is a non-allocating version of []byte(t.UTC().Format(TimeFormat)) +func appendTime(b []byte, t time.Time) []byte { + const days = "SunMonTueWedThuFriSat" + const months = "JanFebMarAprMayJunJulAugSepOctNovDec" + + t = t.UTC() + yy, mm, dd := t.Date() + hh, mn, ss := t.Clock() + day := days[3*t.Weekday():] + mon := months[3*(mm-1):] + + return append(b, + day[0], day[1], day[2], ',', ' ', + byte('0'+dd/10), byte('0'+dd%10), ' ', + mon[0], mon[1], mon[2], ' ', + byte('0'+yy/1000), byte('0'+(yy/100)%10), byte('0'+(yy/10)%10), byte('0'+yy%10), ' ', + byte('0'+hh/10), byte('0'+hh%10), ':', + byte('0'+mn/10), byte('0'+mn%10), ':', + byte('0'+ss/10), byte('0'+ss%10), ' ', + 'G', 'M', 'T') +} + var errTooLarge = errors.New("http: request too large") // Read next request from connection. @@ -562,7 +590,7 @@ func (c *conn) readRequest() (w *response, err error) { contentLength: -1, } w.cw.res = w - w.w, w.sw = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize) + w.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize) return w, nil } @@ -620,27 +648,45 @@ func (w *response) WriteHeader(code int) { // the response Header map and all its 1-element slices. type extraHeader struct { contentType string - contentLength string connection string - date string transferEncoding string + date []byte // written if not nil + contentLength []byte // written if not nil } // Sorted the same as extraHeader.Write's loop. var extraHeaderKeys = [][]byte{ - []byte("Content-Type"), []byte("Content-Length"), - []byte("Connection"), []byte("Date"), []byte("Transfer-Encoding"), + []byte("Content-Type"), + []byte("Connection"), + []byte("Transfer-Encoding"), } -// The value receiver, despite copying 5 strings to the stack, -// prevents an extra allocation. The escape analysis isn't smart -// enough to realize this doesn't mutate h. -func (h extraHeader) Write(w io.Writer) { - for i, v := range []string{h.contentType, h.contentLength, h.connection, h.date, h.transferEncoding} { +var ( + headerContentLength = []byte("Content-Length: ") + headerDate = []byte("Date: ") +) + +// Write writes the headers described in h to w. +// +// This method has a value receiver, despite the somewhat large size +// of h, because it prevents an allocation. The escape analysis isn't +// smart enough to realize this function doesn't mutate h. +func (h extraHeader) Write(w *bufio.Writer) { + if h.date != nil { + w.Write(headerDate) + w.Write(h.date) + w.Write(crlf) + } + if h.contentLength != nil { + w.Write(headerContentLength) + w.Write(h.contentLength) + w.Write(crlf) + } + for i, v := range []string{h.contentType, h.connection, h.transferEncoding} { if v != "" { w.Write(extraHeaderKeys[i]) w.Write(colonSpace) - io.WriteString(w, v) + w.WriteString(v) w.Write(crlf) } } @@ -661,6 +707,7 @@ func (cw *chunkWriter) writeHeader(p []byte) { cw.wroteHeader = true w := cw.res + isHEAD := w.req.Method == "HEAD" // header is written out to w.conn.buf below. Depending on the // state of the handler, we either own the map or not. If we @@ -692,9 +739,17 @@ 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. - if w.handlerDone && header.get("Content-Length") == "" && w.req.Method != "HEAD" { + // Exceptions: 304 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 + // HEAD, the handler should either write the Content-Length or + // 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) { w.contentLength = int64(len(p)) - setHeader.contentLength = strconv.Itoa(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 @@ -709,7 +764,7 @@ func (cw *chunkWriter) writeHeader(p []byte) { // Check for a explicit (and valid) Content-Length header. hasCL := w.contentLength != -1 - if w.req.wantsHttp10KeepAlive() && (w.req.Method == "HEAD" || hasCL) { + if w.req.wantsHttp10KeepAlive() && (isHEAD || hasCL) { _, connectionHeaderSet := header["Connection"] if !connectionHeaderSet { setHeader.connection = "keep-alive" @@ -749,13 +804,14 @@ func (cw *chunkWriter) writeHeader(p []byte) { } } else { // If no content type, apply sniffing algorithm to body. - if header.get("Content-Type") == "" && w.req.Method != "HEAD" { + _, haveType := header["Content-Type"] + if !haveType { setHeader.contentType = DetectContentType(p) } } if _, ok := header["Date"]; !ok { - setHeader.date = time.Now().UTC().Format(TimeFormat) + setHeader.date = appendTime(cw.res.dateBuf[:0], time.Now()) } te := header.get("Transfer-Encoding") @@ -801,12 +857,14 @@ func (cw *chunkWriter) writeHeader(p []byte) { if w.closeAfterReply && !hasToken(cw.header.get("Connection"), "close") { delHeader("Connection") - setHeader.connection = "close" + if w.req.ProtoAtLeast(1, 1) { + setHeader.connection = "close" + } } - io.WriteString(w.conn.buf, statusLine(w.req, code)) + w.conn.buf.WriteString(statusLine(w.req, code)) cw.header.WriteSubset(w.conn.buf, excludeHeader) - setHeader.Write(w.conn.buf) + setHeader.Write(w.conn.buf.Writer) w.conn.buf.Write(crlf) } @@ -861,7 +919,7 @@ func (w *response) bodyAllowed() bool { if !w.wroteHeader { panic("") } - return w.status != StatusNotModified && w.req.Method != "HEAD" + return w.status != StatusNotModified } // The Life Of A Write is like this: @@ -897,6 +955,15 @@ func (w *response) bodyAllowed() bool { // bufferBeforeChunkingSize smaller and having bufio's fast-paths deal // with this instead. func (w *response) Write(data []byte) (n int, err error) { + return w.write(len(data), data, "") +} + +func (w *response) WriteString(data string) (n int, err error) { + return w.write(len(data), nil, data) +} + +// 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") return 0, ErrHijacked @@ -904,18 +971,22 @@ func (w *response) Write(data []byte) (n int, err error) { if !w.wroteHeader { w.WriteHeader(StatusOK) } - if len(data) == 0 { + if lenData == 0 { return 0, nil } if !w.bodyAllowed() { return 0, ErrBodyNotAllowed } - w.written += int64(len(data)) // ignoring errors, for errorKludge + w.written += int64(lenData) // ignoring errors, for errorKludge if w.contentLength != -1 && w.written > w.contentLength { return 0, ErrContentLength } - return w.w.Write(data) + if dataB != nil { + return w.w.Write(dataB) + } else { + return w.w.WriteString(dataS) + } } func (w *response) finishRequest() { @@ -926,7 +997,7 @@ func (w *response) finishRequest() { } w.w.Flush() - putBufioWriter(w.w, w.sw) + putBufioWriter(w.w) w.cw.close() w.conn.buf.Flush() @@ -939,7 +1010,7 @@ func (w *response) finishRequest() { w.req.MultipartForm.RemoveAll() } - if w.contentLength != -1 && w.bodyAllowed() && w.contentLength != w.written { + if w.req.Method != "HEAD" && w.contentLength != -1 && w.bodyAllowed() && w.contentLength != w.written { // Did not write enough. Avoid getting out of sync. w.closeAfterReply = true } @@ -959,11 +1030,11 @@ func (c *conn) finalFlush() { // Steal the bufio.Reader (~4KB worth of memory) and its associated // reader for a future connection. - putBufioReader(c.buf.Reader, c.bufswr) + putBufioReader(c.buf.Reader) // Steal the bufio.Writer (~4KB worth of memory) and its associated // writer for a future connection. - putBufioWriter(c.buf.Writer, c.bufsww) + putBufioWriter(c.buf.Writer) c.buf = nil } @@ -1001,7 +1072,7 @@ func (c *conn) closeWriteAndWait() { time.Sleep(rstAvoidanceDelay) } -// validNPN returns whether the proto is not a blacklisted Next +// validNPN reports whether the proto is not a blacklisted Next // Protocol Negotiation protocol. Empty and built-in protocol types // are blacklisted and can't be overridden with alternate // implementations. @@ -1152,6 +1223,7 @@ func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { // Helper handlers // Error replies to the request with the specified error message and HTTP code. +// The error message should be plain text. func Error(w ResponseWriter, error string, code int) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(code) @@ -1288,6 +1360,10 @@ func RedirectHandler(url string, code int) Handler { // former will receive requests for any other paths in the // "/images/" subtree. // +// Note that since a pattern ending in a slash names a rooted subtree, +// the pattern "/" matches all paths not matched by other registered +// patterns, not just the URL with Path == "/". +// // Patterns may optionally begin with a host name, restricting matches to // URLs on that host only. Host-specific patterns take precedence over // general patterns, so that a handler might register for the two patterns @@ -1378,7 +1454,9 @@ func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { if r.Method != "CONNECT" { if p := cleanPath(r.URL.Path); p != r.URL.Path { _, pattern = mux.handler(r.Host, p) - return RedirectHandler(p, StatusMovedPermanently), pattern + url := *r.URL + url.Path = p + return RedirectHandler(url.String(), StatusMovedPermanently), pattern } } @@ -1408,7 +1486,9 @@ func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { // pattern most closely matches the request URL. func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { if r.RequestURI == "*" { - w.Header().Set("Connection", "close") + if r.ProtoAtLeast(1, 1) { + w.Header().Set("Connection", "close") + } w.WriteHeader(StatusBadRequest) return } @@ -1771,7 +1851,15 @@ func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request) { } // eofReader is a non-nil io.ReadCloser that always returns EOF. -var eofReader = ioutil.NopCloser(strings.NewReader("")) +// It embeds a *strings.Reader so it still has a WriteTo method +// and io.Copy won't need a buffer. +var eofReader = &struct { + *strings.Reader + io.Closer +}{ + strings.NewReader(""), + ioutil.NopCloser(nil), +} // initNPNRequest is an HTTP handler that initializes certain // uninitialized fields in its *Request. Such partially-initialized |