ctxhttp_pre17.go

  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
  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}