blob: 926870cc23fd642e319cabce8438750e0c6df677 [file] [log] [blame]
David K. Bainbridge215e0242017-09-05 23:18:24 -07001// 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// +build !go1.7
6
7package ctxhttp // import "golang.org/x/net/context/ctxhttp"
8
9import (
10 "io"
11 "net/http"
12 "net/url"
13 "strings"
14
15 "golang.org/x/net/context"
16)
17
18func nop() {}
19
20var (
21 testHookContextDoneBeforeHeaders = nop
22 testHookDoReturned = nop
23 testHookDidBodyClose = nop
24)
25
26// Do sends an HTTP request with the provided http.Client and returns an HTTP response.
27// If the client is nil, http.DefaultClient is used.
28// If the context is canceled or times out, ctx.Err() will be returned.
29func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
30 if client == nil {
31 client = http.DefaultClient
32 }
33
34 // TODO(djd): Respect any existing value of req.Cancel.
35 cancel := make(chan struct{})
36 req.Cancel = cancel
37
38 type responseAndError struct {
39 resp *http.Response
40 err error
41 }
42 result := make(chan responseAndError, 1)
43
44 // Make local copies of test hooks closed over by goroutines below.
45 // Prevents data races in tests.
46 testHookDoReturned := testHookDoReturned
47 testHookDidBodyClose := testHookDidBodyClose
48
49 go func() {
50 resp, err := client.Do(req)
51 testHookDoReturned()
52 result <- responseAndError{resp, err}
53 }()
54
55 var resp *http.Response
56
57 select {
58 case <-ctx.Done():
59 testHookContextDoneBeforeHeaders()
60 close(cancel)
61 // Clean up after the goroutine calling client.Do:
62 go func() {
63 if r := <-result; r.resp != nil {
64 testHookDidBodyClose()
65 r.resp.Body.Close()
66 }
67 }()
68 return nil, ctx.Err()
69 case r := <-result:
70 var err error
71 resp, err = r.resp, r.err
72 if err != nil {
73 return resp, err
74 }
75 }
76
77 c := make(chan struct{})
78 go func() {
79 select {
80 case <-ctx.Done():
81 close(cancel)
82 case <-c:
83 // The response's Body is closed.
84 }
85 }()
86 resp.Body = &notifyingReader{resp.Body, c}
87
88 return resp, nil
89}
90
91// Get issues a GET request via the Do function.
92func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
93 req, err := http.NewRequest("GET", url, nil)
94 if err != nil {
95 return nil, err
96 }
97 return Do(ctx, client, req)
98}
99
100// Head issues a HEAD request via the Do function.
101func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
102 req, err := http.NewRequest("HEAD", url, nil)
103 if err != nil {
104 return nil, err
105 }
106 return Do(ctx, client, req)
107}
108
109// Post issues a POST request via the Do function.
110func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) {
111 req, err := http.NewRequest("POST", url, body)
112 if err != nil {
113 return nil, err
114 }
115 req.Header.Set("Content-Type", bodyType)
116 return Do(ctx, client, req)
117}
118
119// PostForm issues a POST request via the Do function.
120func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) {
121 return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
122}
123
124// notifyingReader is an io.ReadCloser that closes the notify channel after
125// Close is called or a Read fails on the underlying ReadCloser.
126type notifyingReader struct {
127 io.ReadCloser
128 notify chan<- struct{}
129}
130
131func (r *notifyingReader) Read(p []byte) (int, error) {
132 n, err := r.ReadCloser.Read(p)
133 if err != nil && r.notify != nil {
134 close(r.notify)
135 r.notify = nil
136 }
137 return n, err
138}
139
140func (r *notifyingReader) Close() error {
141 err := r.ReadCloser.Close()
142 if r.notify != nil {
143 close(r.notify)
144 r.notify = nil
145 }
146 return err
147}