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 = &notifyingReader{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  }