aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrad Fitzpatrick <brad@danga.com>2010-09-27 21:55:04 -0400
committerRuss Cox <rsc@golang.org>2010-09-27 21:55:04 -0400
commitfbab1f1bade7bcf6749bfa0d7599ad2c16322821 (patch)
tree9ce285b4f9a5026c9dff445d6ced23b8301d6af4
parent5c3827cb9fef53f1f6c4aadc7787e8d3540b0d59 (diff)
downloadgo-fbab1f1bade7bcf6749bfa0d7599ad2c16322821.tar.gz
go-fbab1f1bade7bcf6749bfa0d7599ad2c16322821.zip
http: support HTTP/1.0 Keep-Alive
R=rsc, bradfitz1 CC=golang-dev https://golang.org/cl/2261042
-rw-r--r--src/pkg/http/request.go11
-rw-r--r--src/pkg/http/server.go49
-rw-r--r--src/pkg/http/transfer.go13
3 files changed, 59 insertions, 14 deletions
diff --git a/src/pkg/http/request.go b/src/pkg/http/request.go
index 81d718e97e..d0de2732d2 100644
--- a/src/pkg/http/request.go
+++ b/src/pkg/http/request.go
@@ -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
+}
diff --git a/src/pkg/http/server.go b/src/pkg/http/server.go
index c7fd942134..99d5f6b517 100644
--- a/src/pkg/http/server.go
+++ b/src/pkg/http/server.go
@@ -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
diff --git a/src/pkg/http/transfer.go b/src/pkg/http/transfer.go
index 50c1b86978..ee463fea10 100644
--- a/src/pkg/http/transfer.go
+++ b/src/pkg/http/transfer.go
@@ -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.