Skip to content

Commit 67b8bf3

Browse files
committed
net/http: add optional Server.ConnState callback
Update #4674 This allows for all sorts of graceful shutdown policies, without picking a policy (e.g. lameduck period) and without adding lots of locking to the server core. That policy and locking can be implemented outside of net/http now. LGTM=adg R=golang-codereviews, josharian, r, adg, dvyukov CC=golang-codereviews https://golang.org/cl/69260044
1 parent d9c6ae6 commit 67b8bf3

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed

src/pkg/net/http/serve_test.go

+115
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"runtime"
2727
"strconv"
2828
"strings"
29+
"sync"
2930
"sync/atomic"
3031
"syscall"
3132
"testing"
@@ -2243,6 +2244,120 @@ func TestAppendTime(t *testing.T) {
22432244
}
22442245
}
22452246

2247+
func TestServerConnState(t *testing.T) {
2248+
defer afterTest(t)
2249+
handler := map[string]func(w ResponseWriter, r *Request){
2250+
"/": func(w ResponseWriter, r *Request) {
2251+
fmt.Fprintf(w, "Hello.")
2252+
},
2253+
"/close": func(w ResponseWriter, r *Request) {
2254+
w.Header().Set("Connection", "close")
2255+
fmt.Fprintf(w, "Hello.")
2256+
},
2257+
"/hijack": func(w ResponseWriter, r *Request) {
2258+
c, _, _ := w.(Hijacker).Hijack()
2259+
c.Write([]byte("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nHello."))
2260+
c.Close()
2261+
},
2262+
}
2263+
ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) {
2264+
handler[r.URL.Path](w, r)
2265+
}))
2266+
defer ts.Close()
2267+
2268+
type connIDAndState struct {
2269+
connID int
2270+
state ConnState
2271+
}
2272+
var mu sync.Mutex // guard stateLog and connID
2273+
var stateLog []connIDAndState
2274+
var connID = map[net.Conn]int{}
2275+
2276+
ts.Config.ConnState = func(c net.Conn, state ConnState) {
2277+
if c == nil {
2278+
t.Error("nil conn seen in state %s", state)
2279+
return
2280+
}
2281+
mu.Lock()
2282+
defer mu.Unlock()
2283+
id, ok := connID[c]
2284+
if !ok {
2285+
id = len(connID) + 1
2286+
connID[c] = id
2287+
}
2288+
stateLog = append(stateLog, connIDAndState{id, state})
2289+
}
2290+
ts.Start()
2291+
2292+
mustGet(t, ts.URL+"/")
2293+
mustGet(t, ts.URL+"/close")
2294+
2295+
mustGet(t, ts.URL+"/")
2296+
mustGet(t, ts.URL+"/", "Connection", "close")
2297+
2298+
mustGet(t, ts.URL+"/hijack")
2299+
2300+
want := []connIDAndState{
2301+
{1, StateNew},
2302+
{1, StateActive},
2303+
{1, StateIdle},
2304+
{1, StateActive},
2305+
{1, StateClosed},
2306+
2307+
{2, StateNew},
2308+
{2, StateActive},
2309+
{2, StateIdle},
2310+
{2, StateActive},
2311+
{2, StateClosed},
2312+
2313+
{3, StateNew},
2314+
{3, StateActive},
2315+
{3, StateHijacked},
2316+
}
2317+
logString := func(l []connIDAndState) string {
2318+
var b bytes.Buffer
2319+
for _, cs := range l {
2320+
fmt.Fprintf(&b, "[%d %s] ", cs.connID, cs.state)
2321+
}
2322+
return b.String()
2323+
}
2324+
2325+
for i := 0; i < 5; i++ {
2326+
time.Sleep(time.Duration(i) * 50 * time.Millisecond)
2327+
mu.Lock()
2328+
match := reflect.DeepEqual(stateLog, want)
2329+
mu.Unlock()
2330+
if match {
2331+
return
2332+
}
2333+
}
2334+
2335+
mu.Lock()
2336+
t.Errorf("Unexpected events.\nGot log: %s\n Want: %s\n", logString(stateLog), logString(want))
2337+
mu.Unlock()
2338+
}
2339+
2340+
func mustGet(t *testing.T, url string, headers ...string) {
2341+
req, err := NewRequest("GET", url, nil)
2342+
if err != nil {
2343+
t.Fatal(err)
2344+
}
2345+
for len(headers) > 0 {
2346+
req.Header.Add(headers[0], headers[1])
2347+
headers = headers[2:]
2348+
}
2349+
res, err := DefaultClient.Do(req)
2350+
if err != nil {
2351+
t.Errorf("Error fetching %s: %v", url, err)
2352+
return
2353+
}
2354+
_, err = ioutil.ReadAll(res.Body)
2355+
defer res.Body.Close()
2356+
if err != nil {
2357+
t.Errorf("Error reading %s: %v", url, err)
2358+
}
2359+
}
2360+
22462361
func BenchmarkClientServer(b *testing.B) {
22472362
b.ReportAllocs()
22482363
b.StopTimer()

src/pkg/net/http/server.go

+67
Original file line numberDiff line numberDiff line change
@@ -1079,8 +1079,16 @@ func validNPN(proto string) bool {
10791079
return true
10801080
}
10811081

1082+
func (c *conn) setState(nc net.Conn, state ConnState) {
1083+
if hook := c.server.ConnState; hook != nil {
1084+
hook(nc, state)
1085+
}
1086+
}
1087+
10821088
// Serve a new connection.
10831089
func (c *conn) serve() {
1090+
origConn := c.rwc // copy it before it's set nil on Close or Hijack
1091+
c.setState(origConn, StateNew)
10841092
defer func() {
10851093
if err := recover(); err != nil {
10861094
const size = 64 << 10
@@ -1090,6 +1098,7 @@ func (c *conn) serve() {
10901098
}
10911099
if !c.hijacked() {
10921100
c.close()
1101+
c.setState(origConn, StateClosed)
10931102
}
10941103
}()
10951104

@@ -1116,6 +1125,10 @@ func (c *conn) serve() {
11161125

11171126
for {
11181127
w, err := c.readRequest()
1128+
// TODO(bradfitz): could push this StateActive
1129+
// earlier, but in practice header will be all in one
1130+
// packet/Read:
1131+
c.setState(c.rwc, StateActive)
11191132
if err != nil {
11201133
if err == errTooLarge {
11211134
// Their HTTP client may or may not be
@@ -1161,6 +1174,7 @@ func (c *conn) serve() {
11611174
// in parallel even if their responses need to be serialized.
11621175
serverHandler{c.server}.ServeHTTP(w, w.req)
11631176
if c.hijacked() {
1177+
c.setState(origConn, StateHijacked)
11641178
return
11651179
}
11661180
w.finishRequest()
@@ -1170,6 +1184,7 @@ func (c *conn) serve() {
11701184
}
11711185
break
11721186
}
1187+
c.setState(c.rwc, StateIdle)
11731188
}
11741189
}
11751190

@@ -1580,6 +1595,58 @@ type Server struct {
15801595
// and RemoteAddr if not already set. The connection is
15811596
// automatically closed when the function returns.
15821597
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
1598+
1599+
// ConnState specifies an optional callback function that is
1600+
// called when a client connection changes state. See the
1601+
// ConnState type and associated constants for details.
1602+
ConnState func(net.Conn, ConnState)
1603+
}
1604+
1605+
// A ConnState represents the state of a client connection to a server.
1606+
// It's used by the optional Server.ConnState hook.
1607+
type ConnState int
1608+
1609+
const (
1610+
// StateNew represents a new connection that is expected to
1611+
// send a request immediately. Connections begin at this
1612+
// state and then transition to either StateActive or
1613+
// StateClosed.
1614+
StateNew ConnState = iota
1615+
1616+
// StateActive represents a connection that has read 1 or more
1617+
// bytes of a request. The Server.ConnState hook for
1618+
// StateActive fires before the request has entered a handler
1619+
// and doesn't fire again until the request has been
1620+
// handled. After the request is handled, the state
1621+
// transitions to StateClosed, StateHijacked, or StateIdle.
1622+
StateActive
1623+
1624+
// StateIdle represents a connection that has finished
1625+
// handling a request and is in the keep-alive state, waiting
1626+
// for a new request. Connections transition from StateIdle
1627+
// to either StateActive or StateClosed.
1628+
StateIdle
1629+
1630+
// StateHijacked represents a hijacked connection.
1631+
// This is a terminal state. It does not transition to StateClosed.
1632+
StateHijacked
1633+
1634+
// StateClosed represents a closed connection.
1635+
// This is a terminal state. Hijacked connections do not
1636+
// transition to StateClosed.
1637+
StateClosed
1638+
)
1639+
1640+
var stateName = map[ConnState]string{
1641+
StateNew: "new",
1642+
StateActive: "active",
1643+
StateIdle: "idle",
1644+
StateHijacked: "hijacked",
1645+
StateClosed: "closed",
1646+
}
1647+
1648+
func (c ConnState) String() string {
1649+
return stateName[c]
15831650
}
15841651

15851652
// serverHandler delegates to either the server's Handler or

0 commit comments

Comments
 (0)