Skip to content

Commit 1faa886

Browse files
committed
net/http: set the Request context for incoming server requests
Updates #13021 Updates #15224 Change-Id: Ia3cd608bb887fcfd8d81b035fa57bd5eb8edf09b Reviewed-on: https://go-review.googlesource.com/21810 Reviewed-by: Andrew Gerrand <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> Reviewed-by: Emmanuel Odeke <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent bd72497 commit 1faa886

File tree

3 files changed

+95
-8
lines changed

3 files changed

+95
-8
lines changed

src/net/http/request.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,13 @@ type Request struct {
266266
//
267267
// The returned context is always non-nil; it defaults to the
268268
// background context.
269+
//
270+
// For outgoing client requests, the context controls cancelation.
271+
//
272+
// For incoming server requests, the context is canceled when either
273+
// the client's connection closes, or when the ServeHTTP method
274+
// returns.
269275
func (r *Request) Context() context.Context {
270-
// TODO(bradfitz): document above what Context means for server and client
271-
// requests, once implemented.
272276
if r.ctx != nil {
273277
return r.ctx
274278
}

src/net/http/serve_test.go

+67
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package http_test
99
import (
1010
"bufio"
1111
"bytes"
12+
"context"
1213
"crypto/tls"
1314
"errors"
1415
"fmt"
@@ -3989,6 +3990,72 @@ func TestServerValidatesHeaders(t *testing.T) {
39893990
}
39903991
}
39913992

3993+
func TestServerRequestContextCancel_ServeHTTPDone(t *testing.T) {
3994+
defer afterTest(t)
3995+
ctxc := make(chan context.Context, 1)
3996+
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
3997+
ctx := r.Context()
3998+
select {
3999+
case <-ctx.Done():
4000+
t.Error("should not be Done in ServeHTTP")
4001+
default:
4002+
}
4003+
ctxc <- ctx
4004+
}))
4005+
defer ts.Close()
4006+
res, err := Get(ts.URL)
4007+
if err != nil {
4008+
t.Fatal(err)
4009+
}
4010+
res.Body.Close()
4011+
ctx := <-ctxc
4012+
select {
4013+
case <-ctx.Done():
4014+
default:
4015+
t.Error("context should be done after ServeHTTP completes")
4016+
}
4017+
}
4018+
4019+
func TestServerRequestContextCancel_ConnClose(t *testing.T) {
4020+
// Currently the context is not canceled when the connection
4021+
// is closed because we're not reading from the connection
4022+
// until after ServeHTTP for the previous handler is done.
4023+
// Until the server code is modified to always be in a read
4024+
// (Issue 15224), this test doesn't work yet.
4025+
t.Skip("TODO(bradfitz): this test doesn't yet work; golang.org/issue/15224")
4026+
defer afterTest(t)
4027+
inHandler := make(chan struct{})
4028+
handlerDone := make(chan struct{})
4029+
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
4030+
close(inHandler)
4031+
select {
4032+
case <-r.Context().Done():
4033+
case <-time.After(3 * time.Second):
4034+
t.Errorf("timeout waiting for context to be done")
4035+
}
4036+
close(handlerDone)
4037+
}))
4038+
defer ts.Close()
4039+
c, err := net.Dial("tcp", ts.Listener.Addr().String())
4040+
if err != nil {
4041+
t.Fatal(err)
4042+
}
4043+
defer c.Close()
4044+
io.WriteString(c, "GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
4045+
select {
4046+
case <-inHandler:
4047+
case <-time.After(3 * time.Second):
4048+
t.Fatalf("timeout waiting to see ServeHTTP get called")
4049+
}
4050+
c.Close() // this should trigger the context being done
4051+
4052+
select {
4053+
case <-handlerDone:
4054+
case <-time.After(3 * time.Second):
4055+
t.Fatalf("timeout waiting to see ServeHTTP exit")
4056+
}
4057+
}
4058+
39924059
func BenchmarkClientServer(b *testing.B) {
39934060
b.ReportAllocs()
39944061
b.StopTimer()

src/net/http/server.go

+22-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package http
99
import (
1010
"bufio"
1111
"bytes"
12+
"context"
1213
"crypto/tls"
1314
"errors"
1415
"fmt"
@@ -312,10 +313,11 @@ type response struct {
312313
conn *conn
313314
req *Request // request for this response
314315
reqBody io.ReadCloser
315-
wroteHeader bool // reply header has been (logically) written
316-
wroteContinue bool // 100 Continue response was written
317-
wants10KeepAlive bool // HTTP/1.0 w/ Connection "keep-alive"
318-
wantsClose bool // HTTP request has Connection "close"
316+
cancelCtx context.CancelFunc // when ServeHTTP exits
317+
wroteHeader bool // reply header has been (logically) written
318+
wroteContinue bool // 100 Continue response was written
319+
wants10KeepAlive bool // HTTP/1.0 w/ Connection "keep-alive"
320+
wantsClose bool // HTTP request has Connection "close"
319321

320322
w *bufio.Writer // buffers output in chunks to chunkWriter
321323
cw chunkWriter
@@ -686,7 +688,7 @@ func appendTime(b []byte, t time.Time) []byte {
686688
var errTooLarge = errors.New("http: request too large")
687689

688690
// Read next request from connection.
689-
func (c *conn) readRequest() (w *response, err error) {
691+
func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
690692
if c.hijacked() {
691693
return nil, ErrHijacked
692694
}
@@ -715,6 +717,10 @@ func (c *conn) readRequest() (w *response, err error) {
715717
}
716718
return nil, err
717719
}
720+
721+
ctx, cancelCtx := context.WithCancel(ctx)
722+
req.ctx = ctx
723+
718724
c.lastMethod = req.Method
719725
c.r.setInfiniteReadLimit()
720726

@@ -749,6 +755,7 @@ func (c *conn) readRequest() (w *response, err error) {
749755

750756
w = &response{
751757
conn: c,
758+
cancelCtx: cancelCtx,
752759
req: req,
753760
reqBody: req.Body,
754761
handlerHeader: make(Header),
@@ -1432,12 +1439,20 @@ func (c *conn) serve() {
14321439
}
14331440
}
14341441

1442+
// HTTP/1.x from here on.
1443+
14351444
c.r = &connReader{r: c.rwc}
14361445
c.bufr = newBufioReader(c.r)
14371446
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
14381447

1448+
// TODO: allow changing base context? can't imagine concrete
1449+
// use cases yet.
1450+
baseCtx := context.Background()
1451+
ctx, cancelCtx := context.WithCancel(baseCtx)
1452+
defer cancelCtx()
1453+
14391454
for {
1440-
w, err := c.readRequest()
1455+
w, err := c.readRequest(ctx)
14411456
if c.r.remain != c.server.initialReadLimitSize() {
14421457
// If we read any bytes off the wire, we're active.
14431458
c.setState(c.rwc, StateActive)
@@ -1485,6 +1500,7 @@ func (c *conn) serve() {
14851500
// [*] Not strictly true: HTTP pipelining. We could let them all process
14861501
// in parallel even if their responses need to be serialized.
14871502
serverHandler{c.server}.ServeHTTP(w, w.req)
1503+
w.cancelCtx()
14881504
if c.hijacked() {
14891505
return
14901506
}

0 commit comments

Comments
 (0)