ctxhttp_test.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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. // +build !plan9
  5. package ctxhttp
  6. import (
  7. "io/ioutil"
  8. "net"
  9. "net/http"
  10. "net/http/httptest"
  11. "sync"
  12. "testing"
  13. "time"
  14. "golang.org/x/net/context"
  15. )
  16. const (
  17. requestDuration = 100 * time.Millisecond
  18. requestBody = "ok"
  19. )
  20. func TestNoTimeout(t *testing.T) {
  21. ctx := context.Background()
  22. resp, err := doRequest(ctx)
  23. if resp == nil || err != nil {
  24. t.Fatalf("error received from client: %v %v", err, resp)
  25. }
  26. }
  27. func TestCancel(t *testing.T) {
  28. ctx, cancel := context.WithCancel(context.Background())
  29. go func() {
  30. time.Sleep(requestDuration / 2)
  31. cancel()
  32. }()
  33. resp, err := doRequest(ctx)
  34. if resp != nil || err == nil {
  35. t.Fatalf("expected error, didn't get one. resp: %v", resp)
  36. }
  37. if err != ctx.Err() {
  38. t.Fatalf("expected error from context but got: %v", err)
  39. }
  40. }
  41. func TestCancelAfterRequest(t *testing.T) {
  42. ctx, cancel := context.WithCancel(context.Background())
  43. resp, err := doRequest(ctx)
  44. // Cancel before reading the body.
  45. // Request.Body should still be readable after the context is canceled.
  46. cancel()
  47. b, err := ioutil.ReadAll(resp.Body)
  48. if err != nil || string(b) != requestBody {
  49. t.Fatalf("could not read body: %q %v", b, err)
  50. }
  51. }
  52. func TestCancelAfterHangingRequest(t *testing.T) {
  53. handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  54. w.WriteHeader(http.StatusOK)
  55. w.(http.Flusher).Flush()
  56. <-w.(http.CloseNotifier).CloseNotify()
  57. })
  58. serv := httptest.NewServer(handler)
  59. defer serv.Close()
  60. ctx, cancel := context.WithCancel(context.Background())
  61. resp, err := Get(ctx, nil, serv.URL)
  62. if err != nil {
  63. t.Fatalf("unexpected error in Get: %v", err)
  64. }
  65. // Cancel befer reading the body.
  66. // Reading Request.Body should fail, since the request was
  67. // canceled before anything was written.
  68. cancel()
  69. done := make(chan struct{})
  70. go func() {
  71. b, err := ioutil.ReadAll(resp.Body)
  72. if len(b) != 0 || err == nil {
  73. t.Errorf(`Read got (%q, %v); want ("", error)`, b, err)
  74. }
  75. close(done)
  76. }()
  77. select {
  78. case <-time.After(1 * time.Second):
  79. t.Errorf("Test timed out")
  80. case <-done:
  81. }
  82. }
  83. func doRequest(ctx context.Context) (*http.Response, error) {
  84. var okHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  85. time.Sleep(requestDuration)
  86. w.Write([]byte(requestBody))
  87. })
  88. serv := httptest.NewServer(okHandler)
  89. defer serv.Close()
  90. return Get(ctx, nil, serv.URL)
  91. }
  92. // golang.org/issue/14065
  93. func TestClosesResponseBodyOnCancel(t *testing.T) {
  94. defer func() { testHookContextDoneBeforeHeaders = nop }()
  95. defer func() { testHookDoReturned = nop }()
  96. defer func() { testHookDidBodyClose = nop }()
  97. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
  98. defer ts.Close()
  99. ctx, cancel := context.WithCancel(context.Background())
  100. // closed when Do enters select case <-ctx.Done()
  101. enteredDonePath := make(chan struct{})
  102. testHookContextDoneBeforeHeaders = func() {
  103. close(enteredDonePath)
  104. }
  105. testHookDoReturned = func() {
  106. // We now have the result (the Flush'd headers) at least,
  107. // so we can cancel the request.
  108. cancel()
  109. // But block the client.Do goroutine from sending
  110. // until Do enters into the <-ctx.Done() path, since
  111. // otherwise if both channels are readable, select
  112. // picks a random one.
  113. <-enteredDonePath
  114. }
  115. sawBodyClose := make(chan struct{})
  116. testHookDidBodyClose = func() { close(sawBodyClose) }
  117. tr := &http.Transport{}
  118. defer tr.CloseIdleConnections()
  119. c := &http.Client{Transport: tr}
  120. req, _ := http.NewRequest("GET", ts.URL, nil)
  121. _, doErr := Do(ctx, c, req)
  122. select {
  123. case <-sawBodyClose:
  124. case <-time.After(5 * time.Second):
  125. t.Fatal("timeout waiting for body to close")
  126. }
  127. if doErr != ctx.Err() {
  128. t.Errorf("Do error = %v; want %v", doErr, ctx.Err())
  129. }
  130. }
  131. type noteCloseConn struct {
  132. net.Conn
  133. onceClose sync.Once
  134. closefn func()
  135. }
  136. func (c *noteCloseConn) Close() error {
  137. c.onceClose.Do(c.closefn)
  138. return c.Conn.Close()
  139. }