github.com/sean-/go@v0.0.0-20151219100004-97f854cd7bb6/src/net/http/clientserver_test.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Tests that use both the client & server, in both HTTP/1 and HTTP/2 mode. 6 7 package http_test 8 9 import ( 10 "bytes" 11 "compress/gzip" 12 "crypto/tls" 13 "fmt" 14 "io" 15 "io/ioutil" 16 "log" 17 . "net/http" 18 "net/http/httptest" 19 "os" 20 "reflect" 21 "sort" 22 "strings" 23 "sync" 24 "testing" 25 ) 26 27 type clientServerTest struct { 28 t *testing.T 29 h2 bool 30 h Handler 31 ts *httptest.Server 32 tr *Transport 33 c *Client 34 } 35 36 func (t *clientServerTest) close() { 37 t.tr.CloseIdleConnections() 38 t.ts.Close() 39 } 40 41 const ( 42 h1Mode = false 43 h2Mode = true 44 ) 45 46 func newClientServerTest(t *testing.T, h2 bool, h Handler) *clientServerTest { 47 cst := &clientServerTest{ 48 t: t, 49 h2: h2, 50 h: h, 51 tr: &Transport{}, 52 } 53 cst.c = &Client{Transport: cst.tr} 54 if !h2 { 55 cst.ts = httptest.NewServer(h) 56 return cst 57 } 58 cst.ts = httptest.NewUnstartedServer(h) 59 ExportHttp2ConfigureServer(cst.ts.Config, nil) 60 cst.ts.TLS = cst.ts.Config.TLSConfig 61 cst.ts.StartTLS() 62 63 cst.tr.TLSClientConfig = &tls.Config{ 64 InsecureSkipVerify: true, 65 } 66 if err := ExportHttp2ConfigureTransport(cst.tr); err != nil { 67 t.Fatal(err) 68 } 69 return cst 70 } 71 72 // Testing the newClientServerTest helper itself. 73 func TestNewClientServerTest(t *testing.T) { 74 var got struct { 75 sync.Mutex 76 log []string 77 } 78 h := HandlerFunc(func(w ResponseWriter, r *Request) { 79 got.Lock() 80 defer got.Unlock() 81 got.log = append(got.log, r.Proto) 82 }) 83 for _, v := range [2]bool{false, true} { 84 cst := newClientServerTest(t, v, h) 85 if _, err := cst.c.Head(cst.ts.URL); err != nil { 86 t.Fatal(err) 87 } 88 cst.close() 89 } 90 got.Lock() // no need to unlock 91 if want := []string{"HTTP/1.1", "HTTP/2.0"}; !reflect.DeepEqual(got.log, want) { 92 t.Errorf("got %q; want %q", got.log, want) 93 } 94 } 95 96 func TestChunkedResponseHeaders_h1(t *testing.T) { testChunkedResponseHeaders(t, h1Mode) } 97 func TestChunkedResponseHeaders_h2(t *testing.T) { testChunkedResponseHeaders(t, h2Mode) } 98 99 func testChunkedResponseHeaders(t *testing.T, h2 bool) { 100 defer afterTest(t) 101 log.SetOutput(ioutil.Discard) // is noisy otherwise 102 defer log.SetOutput(os.Stderr) 103 cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) { 104 w.Header().Set("Content-Length", "intentional gibberish") // we check that this is deleted 105 w.(Flusher).Flush() 106 fmt.Fprintf(w, "I am a chunked response.") 107 })) 108 defer cst.close() 109 110 res, err := cst.c.Get(cst.ts.URL) 111 if err != nil { 112 t.Fatalf("Get error: %v", err) 113 } 114 defer res.Body.Close() 115 if g, e := res.ContentLength, int64(-1); g != e { 116 t.Errorf("expected ContentLength of %d; got %d", e, g) 117 } 118 wantTE := []string{"chunked"} 119 if h2 { 120 wantTE = nil 121 } 122 if !reflect.DeepEqual(res.TransferEncoding, wantTE) { 123 t.Errorf("TransferEncoding = %v; want %v", res.TransferEncoding, wantTE) 124 } 125 if got, haveCL := res.Header["Content-Length"]; haveCL { 126 t.Errorf("Unexpected Content-Length: %q", got) 127 } 128 } 129 130 type reqFunc func(c *Client, url string) (*Response, error) 131 132 // h12Compare is a test that compares HTTP/1 and HTTP/2 behavior 133 // against each other. 134 type h12Compare struct { 135 Handler func(ResponseWriter, *Request) // required 136 ReqFunc reqFunc // optional 137 CheckResponse func(proto string, res *Response) // optional 138 } 139 140 func (tt h12Compare) reqFunc() reqFunc { 141 if tt.ReqFunc == nil { 142 return (*Client).Get 143 } 144 return tt.ReqFunc 145 } 146 147 func (tt h12Compare) run(t *testing.T) { 148 cst1 := newClientServerTest(t, false, HandlerFunc(tt.Handler)) 149 defer cst1.close() 150 cst2 := newClientServerTest(t, true, HandlerFunc(tt.Handler)) 151 defer cst2.close() 152 153 res1, err := tt.reqFunc()(cst1.c, cst1.ts.URL) 154 if err != nil { 155 t.Errorf("HTTP/1 request: %v", err) 156 return 157 } 158 res2, err := tt.reqFunc()(cst2.c, cst2.ts.URL) 159 if err != nil { 160 t.Errorf("HTTP/2 request: %v", err) 161 return 162 } 163 tt.normalizeRes(t, res1, "HTTP/1.1") 164 tt.normalizeRes(t, res2, "HTTP/2.0") 165 res1body, res2body := res1.Body, res2.Body 166 167 eres1 := mostlyCopy(res1) 168 eres2 := mostlyCopy(res2) 169 if !reflect.DeepEqual(eres1, eres2) { 170 t.Errorf("Response headers to handler differed:\nhttp/1 (%v):\n\t%#v\nhttp/2 (%v):\n\t%#v", 171 cst1.ts.URL, eres1, cst2.ts.URL, eres2) 172 } 173 if !reflect.DeepEqual(res1body, res2body) { 174 t.Errorf("Response bodies to handler differed.\nhttp1: %v\nhttp2: %v\n", res1body, res2body) 175 } 176 if fn := tt.CheckResponse; fn != nil { 177 res1.Body, res2.Body = res1body, res2body 178 fn("HTTP/1.1", res1) 179 fn("HTTP/2.0", res2) 180 } 181 } 182 183 func mostlyCopy(r *Response) *Response { 184 c := *r 185 c.Body = nil 186 c.TransferEncoding = nil 187 c.TLS = nil 188 c.Request = nil 189 return &c 190 } 191 192 type slurpResult struct { 193 io.ReadCloser 194 body []byte 195 err error 196 } 197 198 func (sr slurpResult) String() string { return fmt.Sprintf("body %q; err %v", sr.body, sr.err) } 199 200 func (tt h12Compare) normalizeRes(t *testing.T, res *Response, wantProto string) { 201 if res.Proto == wantProto { 202 res.Proto, res.ProtoMajor, res.ProtoMinor = "", 0, 0 203 } else { 204 t.Errorf("got %q response; want %q", res.Proto, wantProto) 205 } 206 slurp, err := ioutil.ReadAll(res.Body) 207 res.Body.Close() 208 res.Body = slurpResult{ 209 ReadCloser: ioutil.NopCloser(bytes.NewReader(slurp)), 210 body: slurp, 211 err: err, 212 } 213 for i, v := range res.Header["Date"] { 214 res.Header["Date"][i] = strings.Repeat("x", len(v)) 215 } 216 if res.Request == nil { 217 t.Errorf("for %s, no request", wantProto) 218 } 219 if (res.TLS != nil) != (wantProto == "HTTP/2.0") { 220 t.Errorf("TLS set = %v; want %v", res.TLS != nil, res.TLS == nil) 221 } 222 } 223 224 // Issue 13532 225 func TestH12_HeadContentLengthNoBody(t *testing.T) { 226 h12Compare{ 227 ReqFunc: (*Client).Head, 228 Handler: func(w ResponseWriter, r *Request) { 229 }, 230 }.run(t) 231 } 232 233 func TestH12_HeadContentLengthSmallBody(t *testing.T) { 234 h12Compare{ 235 ReqFunc: (*Client).Head, 236 Handler: func(w ResponseWriter, r *Request) { 237 io.WriteString(w, "small") 238 }, 239 }.run(t) 240 } 241 242 func TestH12_HeadContentLengthLargeBody(t *testing.T) { 243 h12Compare{ 244 ReqFunc: (*Client).Head, 245 Handler: func(w ResponseWriter, r *Request) { 246 chunk := strings.Repeat("x", 512<<10) 247 for i := 0; i < 10; i++ { 248 io.WriteString(w, chunk) 249 } 250 }, 251 }.run(t) 252 } 253 254 func TestH12_200NoBody(t *testing.T) { 255 h12Compare{Handler: func(w ResponseWriter, r *Request) {}}.run(t) 256 } 257 258 func TestH2_204NoBody(t *testing.T) { testH12_noBody(t, 204) } 259 func TestH2_304NoBody(t *testing.T) { testH12_noBody(t, 304) } 260 func TestH2_404NoBody(t *testing.T) { testH12_noBody(t, 404) } 261 262 func testH12_noBody(t *testing.T, status int) { 263 h12Compare{Handler: func(w ResponseWriter, r *Request) { 264 w.WriteHeader(status) 265 }}.run(t) 266 } 267 268 func TestH12_SmallBody(t *testing.T) { 269 h12Compare{Handler: func(w ResponseWriter, r *Request) { 270 io.WriteString(w, "small body") 271 }}.run(t) 272 } 273 274 func TestH12_ExplicitContentLength(t *testing.T) { 275 h12Compare{Handler: func(w ResponseWriter, r *Request) { 276 w.Header().Set("Content-Length", "3") 277 io.WriteString(w, "foo") 278 }}.run(t) 279 } 280 281 func TestH12_FlushBeforeBody(t *testing.T) { 282 h12Compare{Handler: func(w ResponseWriter, r *Request) { 283 w.(Flusher).Flush() 284 io.WriteString(w, "foo") 285 }}.run(t) 286 } 287 288 func TestH12_FlushMidBody(t *testing.T) { 289 h12Compare{Handler: func(w ResponseWriter, r *Request) { 290 io.WriteString(w, "foo") 291 w.(Flusher).Flush() 292 io.WriteString(w, "bar") 293 }}.run(t) 294 } 295 296 func TestH12_Head_ExplicitLen(t *testing.T) { 297 h12Compare{ 298 ReqFunc: (*Client).Head, 299 Handler: func(w ResponseWriter, r *Request) { 300 if r.Method != "HEAD" { 301 t.Errorf("unexpected method %q", r.Method) 302 } 303 w.Header().Set("Content-Length", "1235") 304 }, 305 }.run(t) 306 } 307 308 func TestH12_Head_ImplicitLen(t *testing.T) { 309 h12Compare{ 310 ReqFunc: (*Client).Head, 311 Handler: func(w ResponseWriter, r *Request) { 312 if r.Method != "HEAD" { 313 t.Errorf("unexpected method %q", r.Method) 314 } 315 io.WriteString(w, "foo") 316 }, 317 }.run(t) 318 } 319 320 func TestH12_HandlerWritesTooLittle(t *testing.T) { 321 h12Compare{ 322 Handler: func(w ResponseWriter, r *Request) { 323 w.Header().Set("Content-Length", "3") 324 io.WriteString(w, "12") // one byte short 325 }, 326 CheckResponse: func(proto string, res *Response) { 327 sr, ok := res.Body.(slurpResult) 328 if !ok { 329 t.Errorf("%s body is %T; want slurpResult", proto, res.Body) 330 return 331 } 332 if sr.err != io.ErrUnexpectedEOF { 333 t.Errorf("%s read error = %v; want io.ErrUnexpectedEOF", proto, sr.err) 334 } 335 if string(sr.body) != "12" { 336 t.Errorf("%s body = %q; want %q", proto, sr.body, "12") 337 } 338 }, 339 }.run(t) 340 } 341 342 // Tests that the HTTP/1 and HTTP/2 servers prevent handlers from 343 // writing more than they declared. This test does not test whether 344 // the transport deals with too much data, though, since the server 345 // doesn't make it possible to send bogus data. For those tests, see 346 // transport_test.go (for HTTP/1) or x/net/http2/transport_test.go 347 // (for HTTP/2). 348 func TestH12_HandlerWritesTooMuch(t *testing.T) { 349 h12Compare{ 350 Handler: func(w ResponseWriter, r *Request) { 351 w.Header().Set("Content-Length", "3") 352 w.(Flusher).Flush() 353 io.WriteString(w, "123") 354 w.(Flusher).Flush() 355 n, err := io.WriteString(w, "x") // too many 356 if n > 0 || err == nil { 357 t.Errorf("for proto %q, final write = %v, %v; want 0, some error", r.Proto, n, err) 358 } 359 }, 360 }.run(t) 361 } 362 363 // Verify that both our HTTP/1 and HTTP/2 request and auto-decompress gzip. 364 // Some hosts send gzip even if you don't ask for it; see golang.org/issue/13298 365 func TestH12_AutoGzip(t *testing.T) { 366 h12Compare{ 367 Handler: func(w ResponseWriter, r *Request) { 368 if ae := r.Header.Get("Accept-Encoding"); ae != "gzip" { 369 t.Errorf("%s Accept-Encoding = %q; want gzip", r.Proto, ae) 370 } 371 w.Header().Set("Content-Encoding", "gzip") 372 gz := gzip.NewWriter(w) 373 io.WriteString(gz, "I am some gzipped content. Go go go go go go go go go go go go should compress well.") 374 gz.Close() 375 }, 376 }.run(t) 377 } 378 379 // Test304Responses verifies that 304s don't declare that they're 380 // chunking in their response headers and aren't allowed to produce 381 // output. 382 func Test304Responses_h1(t *testing.T) { test304Responses(t, h1Mode) } 383 func Test304Responses_h2(t *testing.T) { test304Responses(t, h2Mode) } 384 385 func test304Responses(t *testing.T, h2 bool) { 386 defer afterTest(t) 387 cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) { 388 w.WriteHeader(StatusNotModified) 389 _, err := w.Write([]byte("illegal body")) 390 if err != ErrBodyNotAllowed { 391 t.Errorf("on Write, expected ErrBodyNotAllowed, got %v", err) 392 } 393 })) 394 defer cst.close() 395 res, err := cst.c.Get(cst.ts.URL) 396 if err != nil { 397 t.Fatal(err) 398 } 399 if len(res.TransferEncoding) > 0 { 400 t.Errorf("expected no TransferEncoding; got %v", res.TransferEncoding) 401 } 402 body, err := ioutil.ReadAll(res.Body) 403 if err != nil { 404 t.Error(err) 405 } 406 if len(body) > 0 { 407 t.Errorf("got unexpected body %q", string(body)) 408 } 409 } 410 411 func TestH12_ServerEmptyContentLength(t *testing.T) { 412 h12Compare{ 413 Handler: func(w ResponseWriter, r *Request) { 414 w.Header()["Content-Type"] = []string{""} 415 io.WriteString(w, "<html><body>hi</body></html>") 416 }, 417 }.run(t) 418 } 419 420 // Tests that closing the Request.Cancel channel also while still 421 // reading the response body. Issue 13159. 422 func TestCancelRequestMidBody_h1(t *testing.T) { testCancelRequestMidBody(t, h1Mode) } 423 func TestCancelRequestMidBody_h2(t *testing.T) { testCancelRequestMidBody(t, h2Mode) } 424 func testCancelRequestMidBody(t *testing.T, h2 bool) { 425 defer afterTest(t) 426 unblock := make(chan bool) 427 didFlush := make(chan bool, 1) 428 cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) { 429 io.WriteString(w, "Hello") 430 w.(Flusher).Flush() 431 didFlush <- true 432 <-unblock 433 io.WriteString(w, ", world.") 434 })) 435 defer cst.close() 436 defer close(unblock) 437 438 req, _ := NewRequest("GET", cst.ts.URL, nil) 439 cancel := make(chan struct{}) 440 req.Cancel = cancel 441 442 res, err := cst.c.Do(req) 443 if err != nil { 444 t.Fatal(err) 445 } 446 defer res.Body.Close() 447 <-didFlush 448 449 // Read a bit before we cancel. (Issue 13626) 450 // We should have "Hello" at least sitting there. 451 firstRead := make([]byte, 10) 452 n, err := res.Body.Read(firstRead) 453 if err != nil { 454 t.Fatal(err) 455 } 456 firstRead = firstRead[:n] 457 458 close(cancel) 459 460 rest, err := ioutil.ReadAll(res.Body) 461 all := string(firstRead) + string(rest) 462 if all != "Hello" { 463 t.Errorf("Read %q (%q + %q); want Hello", all, firstRead, rest) 464 } 465 if !reflect.DeepEqual(err, ExportErrRequestCanceled) { 466 t.Errorf("ReadAll error = %v; want %v", err, ExportErrRequestCanceled) 467 } 468 } 469 470 // Tests that clients can send trailers to a server and that the server can read them. 471 func TestTrailersClientToServer_h1(t *testing.T) { testTrailersClientToServer(t, h1Mode) } 472 func TestTrailersClientToServer_h2(t *testing.T) { testTrailersClientToServer(t, h2Mode) } 473 474 func testTrailersClientToServer(t *testing.T, h2 bool) { 475 defer afterTest(t) 476 cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) { 477 var decl []string 478 for k := range r.Trailer { 479 decl = append(decl, k) 480 } 481 sort.Strings(decl) 482 483 slurp, err := ioutil.ReadAll(r.Body) 484 if err != nil { 485 t.Errorf("Server reading request body: %v", err) 486 } 487 if string(slurp) != "foo" { 488 t.Errorf("Server read request body %q; want foo", slurp) 489 } 490 if r.Trailer == nil { 491 io.WriteString(w, "nil Trailer") 492 } else { 493 fmt.Fprintf(w, "decl: %v, vals: %s, %s", 494 decl, 495 r.Trailer.Get("Client-Trailer-A"), 496 r.Trailer.Get("Client-Trailer-B")) 497 } 498 })) 499 defer cst.close() 500 501 var req *Request 502 req, _ = NewRequest("POST", cst.ts.URL, io.MultiReader( 503 eofReaderFunc(func() { 504 req.Trailer["Client-Trailer-A"] = []string{"valuea"} 505 }), 506 strings.NewReader("foo"), 507 eofReaderFunc(func() { 508 req.Trailer["Client-Trailer-B"] = []string{"valueb"} 509 }), 510 )) 511 req.Trailer = Header{ 512 "Client-Trailer-A": nil, // to be set later 513 "Client-Trailer-B": nil, // to be set later 514 } 515 req.ContentLength = -1 516 res, err := cst.c.Do(req) 517 if err != nil { 518 t.Fatal(err) 519 } 520 if err := wantBody(res, err, "decl: [Client-Trailer-A Client-Trailer-B], vals: valuea, valueb"); err != nil { 521 t.Error(err) 522 } 523 } 524 525 // Tests that servers send trailers to a client and that the client can read them. 526 func TestTrailersServerToClient_h1(t *testing.T) { testTrailersServerToClient(t, h1Mode, false) } 527 func TestTrailersServerToClient_h2(t *testing.T) { testTrailersServerToClient(t, h2Mode, false) } 528 func TestTrailersServerToClient_Flush_h1(t *testing.T) { testTrailersServerToClient(t, h1Mode, true) } 529 func TestTrailersServerToClient_Flush_h2(t *testing.T) { testTrailersServerToClient(t, h2Mode, true) } 530 531 func testTrailersServerToClient(t *testing.T, h2, flush bool) { 532 defer afterTest(t) 533 const body = "Some body" 534 cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) { 535 w.Header().Set("Trailer", "Server-Trailer-A, Server-Trailer-B") 536 w.Header().Add("Trailer", "Server-Trailer-C") 537 538 io.WriteString(w, body) 539 if flush { 540 w.(Flusher).Flush() 541 } 542 543 // How handlers set Trailers: declare it ahead of time 544 // with the Trailer header, and then mutate the 545 // Header() of those values later, after the response 546 // has been written (we wrote to w above). 547 w.Header().Set("Server-Trailer-A", "valuea") 548 w.Header().Set("Server-Trailer-C", "valuec") // skipping B 549 w.Header().Set("Server-Trailer-NotDeclared", "should be omitted") 550 })) 551 defer cst.close() 552 553 res, err := cst.c.Get(cst.ts.URL) 554 if err != nil { 555 t.Fatal(err) 556 } 557 558 wantHeader := Header{ 559 "Content-Type": {"text/plain; charset=utf-8"}, 560 } 561 wantLen := -1 562 if h2 && !flush { 563 // In HTTP/1.1, any use of trailers forces HTTP/1.1 564 // chunking and a flush at the first write. That's 565 // unnecessary with HTTP/2's framing, so the server 566 // is able to calculate the length while still sending 567 // trailers afterwards. 568 wantLen = len(body) 569 wantHeader["Content-Length"] = []string{fmt.Sprint(wantLen)} 570 } 571 if res.ContentLength != int64(wantLen) { 572 t.Errorf("ContentLength = %v; want %v", res.ContentLength, wantLen) 573 } 574 575 delete(res.Header, "Date") // irrelevant for test 576 if !reflect.DeepEqual(res.Header, wantHeader) { 577 t.Errorf("Header = %v; want %v", res.Header, wantHeader) 578 } 579 580 if got, want := res.Trailer, (Header{ 581 "Server-Trailer-A": nil, 582 "Server-Trailer-B": nil, 583 "Server-Trailer-C": nil, 584 }); !reflect.DeepEqual(got, want) { 585 t.Errorf("Trailer before body read = %v; want %v", got, want) 586 } 587 588 if err := wantBody(res, nil, body); err != nil { 589 t.Fatal(err) 590 } 591 592 if got, want := res.Trailer, (Header{ 593 "Server-Trailer-A": {"valuea"}, 594 "Server-Trailer-B": nil, 595 "Server-Trailer-C": {"valuec"}, 596 }); !reflect.DeepEqual(got, want) { 597 t.Errorf("Trailer after body read = %v; want %v", got, want) 598 } 599 } 600 601 // Don't allow a Body.Read after Body.Close. Issue 13648. 602 func TestResponseBodyReadAfterClose_h1(t *testing.T) { testResponseBodyReadAfterClose(t, h1Mode) } 603 func TestResponseBodyReadAfterClose_h2(t *testing.T) { testResponseBodyReadAfterClose(t, h2Mode) } 604 605 func testResponseBodyReadAfterClose(t *testing.T, h2 bool) { 606 defer afterTest(t) 607 const body = "Some body" 608 cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) { 609 io.WriteString(w, body) 610 })) 611 defer cst.close() 612 res, err := cst.c.Get(cst.ts.URL) 613 if err != nil { 614 t.Fatal(err) 615 } 616 res.Body.Close() 617 data, err := ioutil.ReadAll(res.Body) 618 if len(data) != 0 || err == nil { 619 t.Fatalf("ReadAll returned %q, %v; want error", data, err) 620 } 621 } 622 623 func TestConcurrentReadWriteReqBody_h1(t *testing.T) { testConcurrentReadWriteReqBody(t, h1Mode) } 624 func TestConcurrentReadWriteReqBody_h2(t *testing.T) { 625 t.Skip("known failing; golang.org/issue/13659") 626 testConcurrentReadWriteReqBody(t, h2Mode) 627 } 628 func testConcurrentReadWriteReqBody(t *testing.T, h2 bool) { 629 defer afterTest(t) 630 const reqBody = "some request body" 631 const resBody = "some response body" 632 cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) { 633 var wg sync.WaitGroup 634 wg.Add(2) 635 didRead := make(chan bool, 1) 636 // Read in one goroutine. 637 go func() { 638 defer wg.Done() 639 data, err := ioutil.ReadAll(r.Body) 640 if string(data) != reqBody { 641 t.Errorf("Handler read %q; want %q", data, reqBody) 642 } 643 if err != nil { 644 t.Errorf("Handler Read: %v", err) 645 } 646 didRead <- true 647 }() 648 // Write in another goroutine. 649 go func() { 650 defer wg.Done() 651 if !h2 { 652 // our HTTP/1 implementation intentionally 653 // doesn't permit writes during read (mostly 654 // due to it being undefined); if that is ever 655 // relaxed, fix this. 656 <-didRead 657 } 658 io.WriteString(w, resBody) 659 }() 660 wg.Wait() 661 })) 662 defer cst.close() 663 req, _ := NewRequest("POST", cst.ts.URL, strings.NewReader(reqBody)) 664 req.Header.Add("Expect", "100-continue") // just to complicate things 665 res, err := cst.c.Do(req) 666 if err != nil { 667 t.Fatal(err) 668 } 669 data, err := ioutil.ReadAll(res.Body) 670 defer res.Body.Close() 671 if err != nil { 672 t.Fatal(err) 673 } 674 if string(data) != resBody { 675 t.Errorf("read %q; want %q", data, resBody) 676 } 677 }