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 // +build !go1.7 |
|
6 |
|
7 package ctxhttp // import "golang.org/x/net/context/ctxhttp" |
|
8 |
|
9 import ( |
|
10 "io" |
|
11 "net/http" |
|
12 "net/url" |
|
13 "strings" |
|
14 |
|
15 "golang.org/x/net/context" |
|
16 ) |
|
17 |
|
18 func nop() {} |
|
19 |
|
20 var ( |
|
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. |
|
29 func 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 = ¬ifyingReader{resp.Body, c} |
|
87 |
|
88 return resp, nil |
|
89 } |
|
90 |
|
91 // Get issues a GET request via the Do function. |
|
92 func 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. |
|
101 func 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. |
|
110 func 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. |
|
120 func 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. |
|
126 type notifyingReader struct { |
|
127 io.ReadCloser |
|
128 notify chan<- struct{} |
|
129 } |
|
130 |
|
131 func (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 |
|
140 func (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 } |
|