context: Add ctxhttp package.

ctxhttp provides helper functions for performing context-aware HTTP
requests.

Fixes golang/go#11904

Change-Id: Ib9d2fef48953dbb52f0a70e1ed49ea7fe12b7801
Reviewed-on: https://go-review.googlesource.com/12755
Reviewed-by: David Symonds <dsymonds@golang.org>
Reviewed-by: Aaron Jacobs <jacobsa@google.com>
Reviewed-by: Dave Day <djd@golang.org>
diff --git a/context/ctxhttp/cancelreq.go b/context/ctxhttp/cancelreq.go
new file mode 100644
index 0000000..48610e3
--- /dev/null
+++ b/context/ctxhttp/cancelreq.go
@@ -0,0 +1,18 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.5
+
+package ctxhttp
+
+import "net/http"
+
+func canceler(client *http.Client, req *http.Request) func() {
+	ch := make(chan struct{})
+	req.Cancel = ch
+
+	return func() {
+		close(ch)
+	}
+}
diff --git a/context/ctxhttp/cancelreq_go14.go b/context/ctxhttp/cancelreq_go14.go
new file mode 100644
index 0000000..56bcbad
--- /dev/null
+++ b/context/ctxhttp/cancelreq_go14.go
@@ -0,0 +1,23 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.5
+
+package ctxhttp
+
+import "net/http"
+
+type requestCanceler interface {
+	CancelRequest(*http.Request)
+}
+
+func canceler(client *http.Client, req *http.Request) func() {
+	rc, ok := client.Transport.(requestCanceler)
+	if !ok {
+		return func() {}
+	}
+	return func() {
+		rc.CancelRequest(req)
+	}
+}
diff --git a/context/ctxhttp/ctxhttp.go b/context/ctxhttp/ctxhttp.go
new file mode 100644
index 0000000..504dd63
--- /dev/null
+++ b/context/ctxhttp/ctxhttp.go
@@ -0,0 +1,79 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package ctxhttp provides helper functions for performing context-aware HTTP requests.
+package ctxhttp // import "golang.org/x/net/context/ctxhttp"
+
+import (
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+
+	"golang.org/x/net/context"
+)
+
+// Do sends an HTTP request with the provided http.Client and returns an HTTP response.
+// If the client is nil, http.DefaultClient is used.
+// If the context is canceled or times out, ctx.Err() will be returned.
+func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
+	if client == nil {
+		client = http.DefaultClient
+	}
+
+	// Request cancelation changed in Go 1.5, see cancelreq.go and cancelreq_go14.go.
+	cancel := canceler(client, req)
+
+	type responseAndError struct {
+		resp *http.Response
+		err  error
+	}
+	result := make(chan responseAndError, 1)
+
+	go func() {
+		resp, err := client.Do(req)
+		result <- responseAndError{resp, err}
+	}()
+
+	select {
+	case <-ctx.Done():
+		cancel()
+		return nil, ctx.Err()
+	case r := <-result:
+		return r.resp, r.err
+	}
+}
+
+// Get issues a GET request via the Do function.
+func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+	return Do(ctx, client, req)
+}
+
+// Head issues a HEAD request via the Do function.
+func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
+	req, err := http.NewRequest("HEAD", url, nil)
+	if err != nil {
+		return nil, err
+	}
+	return Do(ctx, client, req)
+}
+
+// Post issues a POST request via the Do function.
+func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) {
+	req, err := http.NewRequest("POST", url, body)
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Set("Content-Type", bodyType)
+	return Do(ctx, client, req)
+}
+
+// PostForm issues a POST request via the Do function.
+func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) {
+	return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
+}
diff --git a/context/ctxhttp/ctxhttp_test.go b/context/ctxhttp/ctxhttp_test.go
new file mode 100644
index 0000000..47b53d7
--- /dev/null
+++ b/context/ctxhttp/ctxhttp_test.go
@@ -0,0 +1,72 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ctxhttp
+
+import (
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+	"time"
+
+	"golang.org/x/net/context"
+)
+
+const (
+	requestDuration = 100 * time.Millisecond
+	requestBody     = "ok"
+)
+
+func TestNoTimeout(t *testing.T) {
+	ctx := context.Background()
+	resp, err := doRequest(ctx)
+
+	if resp == nil || err != nil {
+		t.Fatalf("error received from client: %v %v", err, resp)
+	}
+}
+func TestCancel(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	go func() {
+		time.Sleep(requestDuration / 2)
+		cancel()
+	}()
+
+	resp, err := doRequest(ctx)
+
+	if resp != nil || err == nil {
+		t.Fatalf("expected error, didn't get one. resp: %v", resp)
+	}
+	if err != ctx.Err() {
+		t.Fatalf("expected error from context but got: %v", err)
+	}
+}
+
+func TestCancelAfterRequest(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+
+	resp, err := doRequest(ctx)
+
+	// Cancel before reading the body.
+	// Request.Body should still be readable after the context is canceled.
+	cancel()
+
+	b, err := ioutil.ReadAll(resp.Body)
+	if err != nil || string(b) != requestBody {
+		t.Fatalf("could not read body: %q %v", b, err)
+	}
+}
+
+func doRequest(ctx context.Context) (*http.Response, error) {
+	var okHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		time.Sleep(requestDuration)
+		w.Write([]byte(requestBody))
+	})
+
+	serv := httptest.NewServer(okHandler)
+	defer serv.Close()
+
+	return Get(ctx, nil, serv.URL)
+}