vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17.go
changeset 251 1c52a0eeb952
parent 250 c040f992052f
child 252 8399cd48111b
equal deleted inserted replaced
250:c040f992052f 251:1c52a0eeb952
     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 = &notifyingReader{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 }