]> Cypherpunks repositories - gostls13.git/commitdiff
http: support HTTP/1.0 Keep-Alive
authorBrad Fitzpatrick <brad@danga.com>
Tue, 28 Sep 2010 01:55:04 +0000 (21:55 -0400)
committerRuss Cox <rsc@golang.org>
Tue, 28 Sep 2010 01:55:04 +0000 (21:55 -0400)
R=rsc, bradfitz1
CC=golang-dev
https://golang.org/cl/2261042

src/pkg/http/request.go
src/pkg/http/server.go
src/pkg/http/transfer.go

index 81d718e97e08b9c08c37ad12596b1beef12c90fe..d0de2732d2884e3ea1dd0664b74b32b513eaacb1 100644 (file)
@@ -678,3 +678,14 @@ func (r *Request) expectsContinue() bool {
        expectation, ok := r.Header["Expect"]
        return ok && strings.ToLower(expectation) == "100-continue"
 }
+
+func (r *Request) wantsHttp10KeepAlive() bool {
+       if r.ProtoMajor != 1 || r.ProtoMinor != 0 {
+               return false
+       }
+       value, exists := r.Header["Connection"]
+       if !exists {
+               return false
+       }
+       return strings.Index(strings.ToLower(value), "keep-alive") != -1
+}
index c7fd942134e5dce92d209b5f5bdc01bf411c410c..99d5f6b517770cbb189808bdedce41934f6c9b14 100644 (file)
@@ -56,14 +56,19 @@ type Conn struct {
        hijacked bool               // connection has been hijacked by handler
 
        // state for the current reply
-       closeAfterReply bool              // close connection after this reply
-       chunking        bool              // using chunked transfer encoding for reply body
-       wroteHeader     bool              // reply header has been written
-       wroteContinue   bool              // 100 Continue response was written
-       header          map[string]string // reply header parameters
-       written         int64             // number of bytes written in body
-       status          int               // status code passed to WriteHeader
-       usingTLS        bool              // a flag indicating connection over TLS
+       chunking      bool              // using chunked transfer encoding for reply body
+       wroteHeader   bool              // reply header has been written
+       wroteContinue bool              // 100 Continue response was written
+       header        map[string]string // reply header parameters
+       written       int64             // number of bytes written in body
+       status        int               // status code passed to WriteHeader
+       usingTLS      bool              // a flag indicating connection over TLS
+
+       // close connection after this reply.  set on request and
+       // updated after response from handler if there's a
+       // "Connection: keep-alive" response header and a
+       // Content-Length.
+       closeAfterReply bool
 }
 
 // Create new connection from rwc.
@@ -142,10 +147,9 @@ func (c *Conn) readRequest() (req *Request, err os.Error) {
        } else {
                // HTTP version < 1.1: cannot do chunked transfer
                // encoding, so signal EOF by closing connection.
-               // Could avoid closing the connection if there is
-               // a Content-Length: header in the response,
-               // but everyone who expects persistent connections
-               // does HTTP/1.1 now.
+               // Will be overridden if the HTTP handler ends up
+               // writing a Content-Length and the client requested
+               // "Connection: keep-alive"
                c.closeAfterReply = true
                c.chunking = false
        }
@@ -220,6 +224,15 @@ func (c *Conn) Write(data []byte) (n int, err os.Error) {
                return 0, ErrHijacked
        }
        if !c.wroteHeader {
+               if c.Req.wantsHttp10KeepAlive() {
+                       _, hasLength := c.header["Content-Length"]
+                       if hasLength {
+                               _, connectionHeaderSet := c.header["Connection"]
+                               if !connectionHeaderSet {
+                                       c.header["Connection"] = "keep-alive"
+                               }
+                       }
+               }
                c.WriteHeader(StatusOK)
        }
        if len(data) == 0 {
@@ -302,6 +315,14 @@ func errorKludge(c *Conn, req *Request) {
 }
 
 func (c *Conn) finishRequest() {
+       // 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 c.Req.wantsHttp10KeepAlive() {
+               _, sentLength := c.header["Content-Length"]
+               if sentLength && c.header["Connection"] == "keep-alive" {
+                       c.closeAfterReply = false
+               }
+       }
        if !c.wroteHeader {
                c.WriteHeader(StatusOK)
        }
@@ -341,9 +362,11 @@ func (c *Conn) serve() {
                if err != nil {
                        break
                }
-               // HTTP cannot have multiple simultaneous active requests.
+               // HTTP cannot have multiple simultaneous active requests.[*]
                // Until the server replies to this request, it can't read another,
                // so we might as well run the handler in this goroutine.
+               // [*] Not strictly true: HTTP pipelining.  We could let them all process
+               // in parallel even if their responses need to be serialized.
                c.handler.ServeHTTP(c, req)
                if c.hijacked {
                        return
index 50c1b869783564841cc82d0f500cd33cb0009f66..ee463fea1043958f021e5c500ac26e24be185f25 100644 (file)
@@ -352,9 +352,20 @@ func fixLength(status int, requestMethod string, header map[string]string, te []
 
 // Determine whether to hang up after sending a request and body, or
 // receiving a response and body
+// 'header' is the request headers
 func shouldClose(major, minor int, header map[string]string) bool {
-       if major < 1 || (major == 1 && minor < 1) {
+       if major < 1 {
                return true
+       } else if major == 1 && minor == 0 {
+               v, present := header["Connection"]
+               if !present {
+                       return true
+               }
+               v = strings.ToLower(v)
+               if strings.Index(v, "keep-alive") == -1 {
+                       return true
+               }
+               return false
        } else if v, present := header["Connection"]; present {
                // TODO: Should split on commas, toss surrounding white space,
                // and check each field.