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