From fbab1f1bade7bcf6749bfa0d7599ad2c16322821 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 27 Sep 2010 21:55:04 -0400 Subject: [PATCH] http: support HTTP/1.0 Keep-Alive R=rsc, bradfitz1 CC=golang-dev https://golang.org/cl/2261042 --- src/pkg/http/request.go | 11 +++++++++ src/pkg/http/server.go | 49 +++++++++++++++++++++++++++++----------- src/pkg/http/transfer.go | 13 ++++++++++- 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/pkg/http/request.go b/src/pkg/http/request.go index 81d718e97e0..d0de2732d28 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 c7fd942134e..99d5f6b5177 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 50c1b869783..ee463fea104 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.