Browse Source

remote: Remove experimental remote support

Tom Denham 8 years ago
parent
commit
0786ece7ed
9 changed files with 9 additions and 2362 deletions
  1. 0 103
      Documentation/client-server.md
  2. 1 1
      Makefile
  3. 0 19
      README.md
  4. 8 33
      main.go
  5. 0 366
      remote/client.go
  6. 0 52
      remote/http_logger.go
  7. 0 274
      remote/remote_test.go
  8. 0 404
      remote/server.go
  9. 0 1110
      remote/transport.go

+ 0 - 103
Documentation/client-server.md

@@ -1,103 +0,0 @@
-## Client/Server mode (EXPERIMENTAL)
-
-### Getting Started
-
-By default flannel runs without a central controller, utilizing etcd for coordination.
-However, it can also be configured to run in client/server mode, where a special instance of the flannel daemon (the server) is the only one that communicates with etcd.
-This setup offers the advantange of having only a single server directly connecting to etcd, with the rest of the flannel daemons (clients) accessing etcd via the server.
-The server is completely stateless and does not assume that it has exclusive access to the etcd keyspace.
-In the future this will be exploited to provide failover; currently, however, the clients accept only a single endpoint to which to connect.
-The stateless server also makes it possible to run some nodes in client mode side-by-side with those connecting to etcd directly.
-
-To run the flannel daemon in server mode, simply provide the `--listen` flag:
-```
-$ flanneld --listen=0.0.0.0:8888
-```
-
-To run the flannel daemon in client mode, use the `--remote` flag to point it to a flannel server instance:
-```
-$ flanneld --remote=10.0.0.3:8888
-```
-
-It is important to note that the server itself does not join the flannel network (i.e. it won't assign itself a subnet) -- it just satisfies requests from the clients.
-As such, if the host running the flannel server also needs to participate in the overlay, it should start two instances of flannel - one in client mode and one in server mode.
-
-
-### Systemd Socket Activation
-
-The server mode supports [systemd socket activation](http://www.freedesktop.org/software/systemd/man/systemd.socket.html).
-To request the use of socket activation, use the `--listen` flag:
-```
-$ flanneld --listen=fd://
-```
-
-This assumes that the listening socket is passed in via the default descriptor 3.
-To specify a different descriptor number, such as 5, use the following form:
-```
-$ flanneld --listen=fd://5
-```
-
-### Use of SSL/TLS to secure client/server communication
-
-By default, the communication between the client and server is unencrypted (uses HTTP).
-Just like the link between flannel and etcd can be secured via SSL/TLS, so too can the link between the client and the server be encrypted using SSL/TLS.
-You will need a CA certificate and also a private key, certificate pair for the server.
-The server certificate must be signed by the corresponding CA.
-The easiest way to get started is by using the [etcd-ca](https://github.com/coreos/etcd-ca) project:
-
-```
-# Create a new Certificate Authority (CA)
-$ etcd-ca init
-
-# Export the CA certifcate -- this will generate ca.crt
-$ etcd-ca export | tar xv
-
-# Create a new private key for the server
-$ etcd-ca new-cert myserver
-
-# Sign the server private key by the CA
-$ etcd-ca sign myserver
-
-# Export the server key and certifiate
-# This will generate myserver.key and myserver.crt
-$ etcd-ca export myserver | tar xv
-```
-
-You can now start the flannel server, specifying the private key and corresponding signed certificate:
-```
-$ flanneld --listen=0.0.0.0 --remote-certfile=./myserver.crt --remote-keyfile=./myserver.key
-```
-
-Finally, start the flannel client(s) pointing them at the CA certificate that was used to sign the server certificate:
-
-```
-$ flanneld --remote=10.0.0.3:8888 --remote-cafile=./ca.crt
-```
-
-### Authenticating clients by use of client certificates
-
-You can use client SSL certificates to restrict connecting clients to those that have their certificate signed by your CA.
-Using [etcd-ca](https://github.com/coreos/etcd-ca) as the CA, first make sure you have executed ran the steps in the previous section.
-Next, generate and sign a certificate for the client (repeat steps below for each client):
-
-```
-# Create a private key for the client1
-$ etcd-ca new-cert client1
-
-# Sign the client1 private key by the CA
-$ etcd-ca sign client1
-
-# Export the client1 key and certifiate
-# This will generate client1.key and client1.crt
-$ etcd-ca export client1 | tar xv
-```
-
-Start the server, specifying the CA certificate that was used to sign the client certificates:
-```
-$ flanneld --listen=0.0.0.0 --remote-certfile=./myserver.crt --remote-keyfile=./myserver.key --remote-cafile=./ca.crt
-```
-
-Launch the clients by also specifying their private key and corresponding certificate:
-```
-$ flanneld --remote=10.0.0.3:8888 --remote-cafile=./ca.crt --remote-keyfile=./client1.key --remote-certfile=./client1.crt
-```

+ 1 - 1
Makefile

@@ -8,7 +8,7 @@ TAG?=$(shell git describe --tags --dirty)
 ARCH?=amd64
 
 # These variables can be overridden by setting an environment variable.
-TEST_PACKAGES?=pkg/ip subnet remote
+TEST_PACKAGES?=pkg/ip subnet
 TEST_PACKAGES_EXPANDED=$(TEST_PACKAGES:%=github.com/coreos/flannel/%)
 PACKAGES?=$(TEST_PACKAGES) network
 PACKAGES_EXPANDED=$(PACKAGES:%=github.com/coreos/flannel/%)

+ 0 - 19
README.md

@@ -141,10 +141,6 @@ Additionally it will monitor etcd for new members of the network and adjust the
 
 After flannel has acquired the subnet and configured backend, it will write out an environment variable file (`/run/flannel/subnet.env` by default) with subnet address and MTU that it supports.
 
-## Client/Server mode (EXPERIMENTAL)
-
-Please see [Documentation/client-server.md](https://github.com/coreos/flannel/tree/master/Documentation/client-server.md).
-
 ## Multi-network mode (EXPERIMENTAL)
 
 Multi-network mode allows a single flannel daemon to join multiple networks.
@@ -171,16 +167,6 @@ blue.env  green.env  red.env
 This is because some networks may initialize slower than others (or never).
 Use systemd.path files for unit synchronization.
 
-**Note**: Multi-network mode can work in conjunction with the client/server mode.
-The `--networks` flag is only passed to the client:
-
-```
-# Server daemon
-$ flanneld --listen=0.0.0.0:8888
-
-# Client daemon
-$ flanneld --remote=10.0.0.3:8888 --networks=blue,green
-```
 
 ## Key command line options
 
@@ -196,11 +182,6 @@ $ flanneld --remote=10.0.0.3:8888 --networks=blue,green
 --subnet-file=/run/flannel/subnet.env: filename where env variables (subnet and MTU values) will be written to.
 --subnet-lease-renew-margin=60: subnet lease renewal margin, in minutes.
 --ip-masq=false: setup IP masquerade for traffic destined for outside the flannel network. Flannel assumes that the default policy is ACCEPT in the NAT POSTROUTING chain.
---listen="": if specified, will run in server mode. Value is IP and port (e.g. `0.0.0.0:8888`) to listen on or `fd://` for [socket activation](http://www.freedesktop.org/software/systemd/man/systemd.socket.html).
---remote="": if specified, will run in client mode. Value is IP and port of the server.
---remote-keyfile="": SSL key file used to secure client/server communication.
---remote-certfile="": SSL certification file used to secure client/server communication.
---remote-cafile="": SSL Certificate Authority file used to secure client/server communication.
 --networks="": if specified, will run in multi-network mode. Value is comma separate list of networks to join.
 -v=0: log level for V logs. Set to 1 to see messages related to data path.
 --version: print version and exit

+ 8 - 33
main.go

@@ -28,7 +28,6 @@ import (
 	"golang.org/x/net/context"
 
 	"github.com/coreos/flannel/network"
-	"github.com/coreos/flannel/remote"
 	"github.com/coreos/flannel/subnet"
 	"github.com/coreos/flannel/subnet/kube"
 	"github.com/coreos/flannel/version"
@@ -53,11 +52,6 @@ type CmdLineOpts struct {
 	etcdPassword   string
 	help           bool
 	version        bool
-	listen         string
-	remote         string
-	remoteKeyfile  string
-	remoteCertfile string
-	remoteCAFile   string
 	kubeSubnetMgr  bool
 }
 
@@ -71,20 +65,12 @@ func init() {
 	flag.StringVar(&opts.etcdCAFile, "etcd-cafile", "", "SSL Certificate Authority file used to secure etcd communication")
 	flag.StringVar(&opts.etcdUsername, "etcd-username", "", "Username for BasicAuth to etcd")
 	flag.StringVar(&opts.etcdPassword, "etcd-password", "", "Password for BasicAuth to etcd")
-	flag.StringVar(&opts.listen, "listen", "", "run as server and listen on specified address (e.g. ':8080')")
-	flag.StringVar(&opts.remote, "remote", "", "run as client and connect to server on specified address (e.g. '10.1.2.3:8080')")
-	flag.StringVar(&opts.remoteKeyfile, "remote-keyfile", "", "SSL key file used to secure client/server communication")
-	flag.StringVar(&opts.remoteCertfile, "remote-certfile", "", "SSL certification file used to secure client/server communication")
-	flag.StringVar(&opts.remoteCAFile, "remote-cafile", "", "SSL Certificate Authority file used to secure client/server communication")
 	flag.BoolVar(&opts.kubeSubnetMgr, "kube-subnet-mgr", false, "Contact the Kubernetes API for subnet assignement instead of etcd or flannel-server.")
 	flag.BoolVar(&opts.help, "help", false, "print this message")
 	flag.BoolVar(&opts.version, "version", false, "print version and exit")
 }
 
 func newSubnetManager() (subnet.Manager, error) {
-	if opts.remote != "" {
-		return remote.NewRemoteManager(opts.remote, opts.remoteCAFile, opts.remoteCertfile, opts.remoteKeyfile)
-	}
 	if opts.kubeSubnetMgr {
 		return kube.NewSubnetManager()
 	}
@@ -138,25 +124,14 @@ func main() {
 
 	var runFunc func(ctx context.Context)
 
-	if opts.listen != "" {
-		if opts.remote != "" {
-			log.Error("--listen and --remote are mutually exclusive")
-			os.Exit(1)
-		}
-		log.Info("running as server")
-		runFunc = func(ctx context.Context) {
-			remote.RunServer(ctx, sm, opts.listen, opts.remoteCAFile, opts.remoteCertfile, opts.remoteKeyfile)
-		}
-	} else {
-		nm, err := network.NewNetworkManager(ctx, sm)
-		if err != nil {
-			log.Error("Failed to create NetworkManager: ", err)
-			os.Exit(1)
-		}
-
-		runFunc = func(ctx context.Context) {
-			nm.Run(ctx)
-		}
+	nm, err := network.NewNetworkManager(ctx, sm)
+	if err != nil {
+		log.Error("Failed to create NetworkManager: ", err)
+		os.Exit(1)
+	}
+
+	runFunc = func(ctx context.Context) {
+		nm.Run(ctx)
 	}
 
 	wg := sync.WaitGroup{}

+ 0 - 366
remote/client.go

@@ -1,366 +0,0 @@
-// Copyright 2015 flannel authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package remote
-
-import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"net"
-	"net/http"
-	"path"
-	"time"
-
-	"github.com/coreos/etcd/pkg/transport"
-	"golang.org/x/net/context"
-
-	"github.com/coreos/flannel/pkg/ip"
-	"github.com/coreos/flannel/subnet"
-)
-
-// implements subnet.Manager by sending requests to the server
-type RemoteManager struct {
-	base      string // includes scheme, host, and port, and version
-	transport *Transport
-}
-
-func NewTransport(info transport.TLSInfo) (*Transport, error) {
-	cfg, err := info.ClientConfig()
-	if err != nil {
-		return nil, err
-	}
-
-	t := &Transport{
-		// timeouts taken from http.DefaultTransport
-		Dial: (&net.Dialer{
-			Timeout:   30 * time.Second,
-			KeepAlive: 30 * time.Second,
-		}).Dial,
-		TLSHandshakeTimeout: 10 * time.Second,
-		TLSClientConfig:     cfg,
-	}
-
-	return t, nil
-}
-
-func NewRemoteManager(listenAddr, cafile, certfile, keyfile string) (subnet.Manager, error) {
-	tls := transport.TLSInfo{
-		CAFile:   cafile,
-		CertFile: certfile,
-		KeyFile:  keyfile,
-	}
-
-	t, err := NewTransport(tls)
-	if err != nil {
-		return nil, err
-	}
-
-	var scheme string
-	if tls.Empty() && tls.CAFile == "" {
-		scheme = "http://"
-	} else {
-		scheme = "https://"
-	}
-
-	return &RemoteManager{
-		base:      scheme + listenAddr + "/v1",
-		transport: t,
-	}, nil
-}
-
-func (m *RemoteManager) mkurl(network string, parts ...string) string {
-	if network == "" {
-		network = "/_"
-	}
-	if network[0] != '/' {
-		network = "/" + network
-	}
-	return m.base + path.Join(append([]string{network}, parts...)...)
-}
-
-func (m *RemoteManager) GetNetworkConfig(ctx context.Context, network string) (*subnet.Config, error) {
-	url := m.mkurl(network, "config")
-
-	resp, err := m.httpVerb(ctx, "GET", url, "", nil)
-	if err != nil {
-		return nil, err
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		return nil, httpError(resp)
-	}
-
-	body, err := ioutil.ReadAll(resp.Body)
-	if err != nil {
-		return nil, err
-	}
-
-	config, err := subnet.ParseConfig(string(body))
-	if err != nil {
-		return nil, err
-	}
-
-	return config, nil
-}
-
-func (m *RemoteManager) AcquireLease(ctx context.Context, network string, attrs *subnet.LeaseAttrs) (*subnet.Lease, error) {
-	url := m.mkurl(network, "leases/")
-
-	body, err := json.Marshal(attrs)
-	if err != nil {
-		return nil, err
-	}
-
-	resp, err := m.httpVerb(ctx, "POST", url, "application/json", body)
-	if err != nil {
-		return nil, err
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		return nil, httpError(resp)
-	}
-
-	newLease := &subnet.Lease{}
-	if err := json.NewDecoder(resp.Body).Decode(newLease); err != nil {
-		return nil, err
-	}
-
-	return newLease, nil
-}
-
-func (m *RemoteManager) RenewLease(ctx context.Context, network string, lease *subnet.Lease) error {
-	url := m.mkurl(network, "leases", lease.Key())
-
-	body, err := json.Marshal(lease)
-	if err != nil {
-		return err
-	}
-
-	resp, err := m.httpVerb(ctx, "PUT", url, "application/json", body)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		return httpError(resp)
-	}
-
-	newLease := &subnet.Lease{}
-	if err := json.NewDecoder(resp.Body).Decode(newLease); err != nil {
-		return err
-	}
-
-	*lease = *newLease
-	return nil
-}
-
-func (m *RemoteManager) RevokeLease(ctx context.Context, network string, sn ip.IP4Net) error {
-	url := m.mkurl(network, "leases", subnet.MakeSubnetKey(sn))
-
-	resp, err := m.httpVerb(ctx, "DELETE", url, "", nil)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		return httpError(resp)
-	}
-
-	return nil
-}
-
-func (m *RemoteManager) watch(ctx context.Context, url string, cursor interface{}, wr interface{}) error {
-	if cursor != nil {
-		c, ok := cursor.(string)
-		if !ok {
-			return fmt.Errorf("internal error: RemoteManager.watch received non-string cursor")
-		}
-
-		url = fmt.Sprintf("%v?next=%v", url, c)
-	}
-
-	resp, err := m.httpVerb(ctx, "GET", url, "", nil)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		return httpError(resp)
-	}
-
-	if err := json.NewDecoder(resp.Body).Decode(wr); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (m *RemoteManager) WatchLease(ctx context.Context, network string, sn ip.IP4Net, cursor interface{}) (subnet.LeaseWatchResult, error) {
-	url := m.mkurl(network, "leases", subnet.MakeSubnetKey(sn))
-
-	wr := subnet.LeaseWatchResult{}
-	err := m.watch(ctx, url, cursor, &wr)
-	if err != nil {
-		return subnet.LeaseWatchResult{}, err
-	}
-	if _, ok := wr.Cursor.(string); !ok {
-		return subnet.LeaseWatchResult{}, fmt.Errorf("watch returned non-string cursor")
-	}
-
-	return wr, nil
-}
-
-func (m *RemoteManager) WatchLeases(ctx context.Context, network string, cursor interface{}) (subnet.LeaseWatchResult, error) {
-	url := m.mkurl(network, "leases")
-
-	wr := subnet.LeaseWatchResult{}
-	err := m.watch(ctx, url, cursor, &wr)
-	if err != nil {
-		return subnet.LeaseWatchResult{}, err
-	}
-	if _, ok := wr.Cursor.(string); !ok {
-		return subnet.LeaseWatchResult{}, fmt.Errorf("watch returned non-string cursor")
-	}
-
-	return wr, nil
-}
-
-func (m *RemoteManager) WatchNetworks(ctx context.Context, cursor interface{}) (subnet.NetworkWatchResult, error) {
-	wr := subnet.NetworkWatchResult{}
-	err := m.watch(ctx, m.base+"/", cursor, &wr)
-	if err != nil {
-		return subnet.NetworkWatchResult{}, err
-	}
-
-	if _, ok := wr.Cursor.(string); !ok {
-		return subnet.NetworkWatchResult{}, fmt.Errorf("watch returned non-string cursor")
-	}
-
-	return wr, nil
-}
-
-func (m *RemoteManager) AddReservation(ctx context.Context, network string, r *subnet.Reservation) error {
-	url := m.mkurl(network, "reservations")
-
-	body, err := json.Marshal(r)
-	if err != nil {
-		return err
-	}
-
-	resp, err := m.httpVerb(ctx, "POST", url, "application/json", body)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		return httpError(resp)
-	}
-	return nil
-}
-
-func (m *RemoteManager) RemoveReservation(ctx context.Context, network string, sn ip.IP4Net) error {
-	url := m.mkurl(network, "reservations", subnet.MakeSubnetKey(sn))
-
-	resp, err := m.httpVerb(ctx, "DELETE", url, "", nil)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		return httpError(resp)
-	}
-
-	return nil
-}
-
-func (m *RemoteManager) ListReservations(ctx context.Context, network string) ([]subnet.Reservation, error) {
-	url := m.mkurl(network, "reservations")
-
-	resp, err := m.httpVerb(ctx, "GET", url, "", nil)
-	if err != nil {
-		return nil, err
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		return nil, httpError(resp)
-	}
-
-	rs := []subnet.Reservation{}
-	if err := json.NewDecoder(resp.Body).Decode(&rs); err != nil {
-		return nil, err
-	}
-
-	return rs, nil
-}
-
-func httpError(resp *http.Response) error {
-	b, err := ioutil.ReadAll(resp.Body)
-	if err != nil {
-		return err
-	}
-	return fmt.Errorf("%v: %v", resp.Status, string(b))
-}
-
-type httpRespErr struct {
-	resp *http.Response
-	err  error
-}
-
-func (m *RemoteManager) httpDo(ctx context.Context, req *http.Request) (*http.Response, error) {
-	// Run the HTTP request in a goroutine (so it can be canceled) and pass
-	// the result via the channel c
-	client := &http.Client{Transport: m.transport}
-	c := make(chan httpRespErr, 1)
-	go func() {
-		resp, err := client.Do(req)
-		c <- httpRespErr{resp, err}
-	}()
-
-	select {
-	case <-ctx.Done():
-		m.transport.CancelRequest(req)
-		<-c // Wait for f to return.
-		return nil, ctx.Err()
-	case r := <-c:
-		return r.resp, r.err
-	}
-}
-
-func (m *RemoteManager) httpVerb(ctx context.Context, method, url, contentType string, body []byte) (*http.Response, error) {
-	var r io.Reader
-	if body != nil {
-		r = bytes.NewBuffer(body)
-	}
-
-	req, err := http.NewRequest(method, url, r)
-	if err != nil {
-		return nil, err
-	}
-
-	if contentType != "" {
-		req.Header.Set("Content-Type", contentType)
-	}
-	return m.httpDo(ctx, req)
-}

+ 0 - 52
remote/http_logger.go

@@ -1,52 +0,0 @@
-// Copyright 2015 flannel authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package remote
-
-import (
-	log "github.com/golang/glog"
-	"net/http"
-)
-
-type httpResp struct {
-	writer http.ResponseWriter
-	status int
-}
-
-func (r *httpResp) Header() http.Header {
-	return r.writer.Header()
-}
-
-func (r *httpResp) Write(d []byte) (int, error) {
-	return r.writer.Write(d)
-}
-
-func (r *httpResp) WriteHeader(status int) {
-	r.status = status
-	r.writer.WriteHeader(status)
-}
-
-type httpLoggerHandler struct {
-	h http.Handler
-}
-
-func (lh httpLoggerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	resp := &httpResp{w, 0}
-	lh.h.ServeHTTP(resp, r)
-	log.Infof("%v %v - %v", r.Method, r.RequestURI, resp.status)
-}
-
-func httpLogger(h http.Handler) http.Handler {
-	return httpLoggerHandler{h}
-}

+ 0 - 274
remote/remote_test.go

@@ -1,274 +0,0 @@
-// Copyright 2015 flannel authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package remote
-
-import (
-	"fmt"
-	"net"
-	"net/url"
-	"os"
-	"sync"
-	"syscall"
-	"testing"
-	"time"
-
-	"golang.org/x/net/context"
-
-	"github.com/coreos/flannel/pkg/ip"
-	"github.com/coreos/flannel/subnet"
-)
-
-const expectedNetwork = "10.1.0.0/16"
-
-type fixture struct {
-	ctx      context.Context
-	cancel   context.CancelFunc
-	srvAddr  string
-	registry *subnet.MockSubnetRegistry
-	sm       subnet.Manager
-	wg       sync.WaitGroup
-}
-
-func newFixture(t *testing.T) *fixture {
-	f := &fixture{}
-
-	config := fmt.Sprintf(`{"Network": %q}`, expectedNetwork)
-	f.registry = subnet.NewMockRegistry("", config, nil)
-	sm := subnet.NewMockManager(f.registry)
-
-	f.srvAddr = "127.0.0.1:9999"
-
-	f.ctx, f.cancel = context.WithCancel(context.Background())
-	f.wg.Add(1)
-	go func() {
-		RunServer(f.ctx, sm, f.srvAddr, "", "", "")
-		f.wg.Done()
-	}()
-
-	var err error
-	f.sm, err = NewRemoteManager(f.srvAddr, "", "", "")
-	if err != nil {
-		panic(fmt.Sprintf("Failed to create remote mananager: %v", err))
-	}
-
-	for i := 0; ; i++ {
-		_, err := f.sm.GetNetworkConfig(f.ctx, "_")
-		if err == nil {
-			break
-		}
-
-		if isConnRefused(err) {
-			if i == 100 {
-				t.Fatalf("Out of connection retries")
-			}
-
-			fmt.Println("Connection refused, retrying...")
-			time.Sleep(300 * time.Millisecond)
-			continue
-		}
-
-		t.Fatalf("GetNetworkConfig failed: %v", err)
-	}
-
-	return f
-}
-
-func (f *fixture) Close() {
-	f.cancel()
-	f.wg.Wait()
-}
-
-func mustParseIP4(s string) ip.IP4 {
-	a, err := ip.ParseIP4(s)
-	if err != nil {
-		panic(err)
-	}
-	return a
-}
-
-func mustParseIP4Net(s string) ip.IP4Net {
-	_, n, err := net.ParseCIDR(s)
-	if err != nil {
-		panic(err)
-	}
-	return ip.FromIPNet(n)
-}
-
-func isConnRefused(err error) bool {
-	if uerr, ok := err.(*url.Error); ok {
-		if operr, ok := uerr.Err.(*net.OpError); ok {
-			if oserr, ok := operr.Err.(*os.SyscallError); ok {
-				return oserr.Err == syscall.ECONNREFUSED
-			}
-			return operr.Err == syscall.ECONNREFUSED
-		}
-	}
-	return false
-}
-
-func TestGetConfig(t *testing.T) {
-	f := newFixture(t)
-	defer f.Close()
-
-	cfg, err := f.sm.GetNetworkConfig(f.ctx, "_")
-	if err != nil {
-		t.Fatalf("GetNetworkConfig failed: %v", err)
-	}
-
-	if cfg.Network.String() != expectedNetwork {
-		t.Errorf("GetNetworkConfig returned bad network: %v vs %v", cfg.Network, expectedNetwork)
-	}
-}
-
-func TestAcquireRenewLease(t *testing.T) {
-	f := newFixture(t)
-	defer f.Close()
-
-	attrs := &subnet.LeaseAttrs{
-		PublicIP: mustParseIP4("1.1.1.1"),
-	}
-
-	l, err := f.sm.AcquireLease(f.ctx, "_", attrs)
-	if err != nil {
-		t.Fatalf("AcquireLease failed: %v", err)
-	}
-
-	if !mustParseIP4Net(expectedNetwork).Contains(l.Subnet.IP) {
-		t.Errorf("AcquireLease returned subnet not in network: %v (in %v)", l.Subnet, expectedNetwork)
-	}
-
-	if err = f.sm.RenewLease(f.ctx, "_", l); err != nil {
-		t.Errorf("RenewLease failed: %v", err)
-	}
-}
-
-func TestWatchLeases(t *testing.T) {
-	f := newFixture(t)
-	defer f.Close()
-
-	events := make(chan []subnet.Event)
-	f.wg.Add(1)
-	go func() {
-		subnet.WatchLeases(f.ctx, f.sm, "_", nil, events)
-		f.wg.Done()
-	}()
-
-	attrs := &subnet.LeaseAttrs{
-		PublicIP: mustParseIP4("1.1.1.2"),
-	}
-	l, err := f.sm.AcquireLease(f.ctx, "_", attrs)
-	if err != nil {
-		t.Errorf("AcquireLease failed: %v", err)
-		return
-	}
-	if !mustParseIP4Net(expectedNetwork).Contains(l.Subnet.IP) {
-		t.Errorf("AcquireLease returned subnet not in network: %v (in %v)", l.Subnet, expectedNetwork)
-	}
-
-	evtBatch := <-events
-
-	if len(evtBatch) != 1 {
-		t.Fatalf("WatchSubnets produced wrong sized event batch")
-	}
-
-	evt := evtBatch[0]
-	if evt.Type != subnet.EventAdded {
-		t.Fatalf("WatchSubnets produced wrong event type")
-	}
-
-	if evt.Lease.Key() != l.Key() {
-		t.Errorf("WatchSubnet produced wrong subnet: expected %s, got %s", l.Key(), evt.Lease.Key())
-	}
-}
-
-func TestRevokeLease(t *testing.T) {
-	f := newFixture(t)
-	defer f.Close()
-
-	attrs := &subnet.LeaseAttrs{
-		PublicIP: mustParseIP4("1.1.1.1"),
-	}
-
-	l, err := f.sm.AcquireLease(f.ctx, "_", attrs)
-	if err != nil {
-		t.Fatalf("AcquireLease failed: %v", err)
-	}
-
-	if err := f.sm.RevokeLease(f.ctx, "_", l.Subnet); err != nil {
-		t.Fatalf("RevokeLease failed: %v", err)
-	}
-
-	_, err = f.sm.WatchLease(f.ctx, "_", l.Subnet, nil)
-	if err == nil {
-		t.Fatalf("Revoked lease found")
-	}
-}
-
-func TestWatchNetworks(t *testing.T) {
-	f := newFixture(t)
-	defer f.Close()
-
-	events := make(chan []subnet.Event)
-	f.wg.Add(1)
-	go func() {
-		subnet.WatchNetworks(f.ctx, f.sm, events)
-		f.wg.Done()
-	}()
-
-	// skip over the initial snapshot
-	<-events
-
-	expectedNetname := "foobar"
-	config := fmt.Sprintf(`{"Network": %q}`, expectedNetwork)
-	err := f.registry.CreateNetwork(f.ctx, expectedNetname, config)
-	if err != nil {
-		t.Errorf("create network failed: %v", err)
-	}
-
-	evtBatch := <-events
-
-	if len(evtBatch) != 1 {
-		t.Fatalf("WatchNetworks create produced wrong sized event batch")
-	}
-
-	evt := evtBatch[0]
-	if evt.Type != subnet.EventAdded {
-		t.Fatalf("WatchNetworks create produced wrong event type")
-	}
-
-	if evt.Network != expectedNetname {
-		t.Errorf("WatchNetwork create produced wrong network: expected %s, got %s", expectedNetname, evt.Network)
-	}
-
-	err = f.registry.DeleteNetwork(f.ctx, expectedNetname)
-	if err != nil {
-		t.Errorf("delete network failed: %v", err)
-	}
-
-	evtBatch = <-events
-
-	if len(evtBatch) != 1 {
-		t.Fatalf("WatchNetworks delete produced wrong sized event batch")
-	}
-
-	evt = evtBatch[0]
-	if evt.Type != subnet.EventRemoved {
-		t.Fatalf("WatchNetworks delete produced wrong event type")
-	}
-
-	if evt.Network != expectedNetname {
-		t.Errorf("WatchNetwork delete produced wrong network: expected %s, got %s", expectedNetname, evt.Network)
-	}
-}

+ 0 - 404
remote/server.go

@@ -1,404 +0,0 @@
-// Copyright 2015 flannel authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package remote
-
-import (
-	"crypto/tls"
-	"encoding/json"
-	"fmt"
-	"net"
-	"net/http"
-	"net/url"
-	"regexp"
-	"strconv"
-
-	"github.com/coreos/etcd/pkg/transport"
-	"github.com/coreos/go-systemd/activation"
-	"github.com/coreos/go-systemd/daemon"
-	log "github.com/golang/glog"
-	"github.com/gorilla/mux"
-	"golang.org/x/net/context"
-
-	"github.com/coreos/flannel/subnet"
-)
-
-type handler func(context.Context, subnet.Manager, http.ResponseWriter, *http.Request)
-
-func jsonResponse(w http.ResponseWriter, code int, v interface{}) {
-	w.Header().Set("Content-Type", "application/json; charset=utf-8")
-	w.WriteHeader(code)
-	if err := json.NewEncoder(w).Encode(v); err != nil {
-		log.Error("Error JSON encoding response: %v", err)
-	}
-}
-
-// GET /{network}/config
-func handleGetNetworkConfig(ctx context.Context, sm subnet.Manager, w http.ResponseWriter, r *http.Request) {
-	network := mux.Vars(r)["network"]
-	if network == "_" {
-		network = ""
-	}
-
-	c, err := sm.GetNetworkConfig(ctx, network)
-	if err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		fmt.Fprint(w, err)
-		return
-	}
-
-	jsonResponse(w, http.StatusOK, c)
-}
-
-// POST /{network}/leases
-func handleAcquireLease(ctx context.Context, sm subnet.Manager, w http.ResponseWriter, r *http.Request) {
-	network := mux.Vars(r)["network"]
-	if network == "_" {
-		network = ""
-	}
-
-	attrs := subnet.LeaseAttrs{}
-	if err := json.NewDecoder(r.Body).Decode(&attrs); err != nil {
-		w.WriteHeader(http.StatusBadRequest)
-		fmt.Fprint(w, "JSON decoding error: ", err)
-		return
-	}
-
-	lease, err := sm.AcquireLease(ctx, network, &attrs)
-	if err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		fmt.Fprint(w, err)
-		return
-	}
-
-	jsonResponse(w, http.StatusOK, lease)
-}
-
-// PUT /{network}/{lease.network}
-func handleRenewLease(ctx context.Context, sm subnet.Manager, w http.ResponseWriter, r *http.Request) {
-	network := mux.Vars(r)["network"]
-	if network == "_" {
-		network = ""
-	}
-
-	lease := subnet.Lease{}
-	if err := json.NewDecoder(r.Body).Decode(&lease); err != nil {
-		w.WriteHeader(http.StatusBadRequest)
-		fmt.Fprint(w, "JSON decoding error: ", err)
-		return
-	}
-
-	if err := sm.RenewLease(ctx, network, &lease); err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		fmt.Fprint(w, err)
-		return
-	}
-
-	jsonResponse(w, http.StatusOK, lease)
-}
-
-func handleRevokeLease(ctx context.Context, sm subnet.Manager, w http.ResponseWriter, r *http.Request) {
-	network := mux.Vars(r)["network"]
-	if network == "_" {
-		network = ""
-	}
-
-	sn := subnet.ParseSubnetKey(mux.Vars(r)["subnet"])
-	if sn == nil {
-		w.WriteHeader(http.StatusBadRequest)
-		fmt.Fprint(w, "failed to parse subnet")
-		return
-	}
-
-	if err := sm.RevokeLease(ctx, network, *sn); err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		fmt.Fprint(w, err)
-		return
-	}
-}
-
-func getCursor(u *url.URL) interface{} {
-	vals, ok := u.Query()["next"]
-	if !ok {
-		return nil
-	}
-	return vals[0]
-}
-
-// GET /{network}/leases/subnet?next=cursor
-func handleWatchLease(ctx context.Context, sm subnet.Manager, w http.ResponseWriter, r *http.Request) {
-	network := mux.Vars(r)["network"]
-	if network == "_" {
-		network = ""
-	}
-
-	sn := subnet.ParseSubnetKey(mux.Vars(r)["subnet"])
-	if sn == nil {
-		w.WriteHeader(http.StatusBadRequest)
-		fmt.Fprint(w, "bad subnet")
-		return
-	}
-
-	cursor := getCursor(r.URL)
-
-	wr, err := sm.WatchLease(ctx, network, *sn, cursor)
-	if err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		fmt.Fprint(w, err)
-		return
-	}
-
-	switch wr.Cursor.(type) {
-	case string:
-	case fmt.Stringer:
-		wr.Cursor = wr.Cursor.(fmt.Stringer).String()
-	default:
-		w.WriteHeader(http.StatusInternalServerError)
-		fmt.Fprint(w, fmt.Errorf("internal error: watch cursor is of unknown type"))
-		return
-	}
-
-	jsonResponse(w, http.StatusOK, wr)
-}
-
-// GET /{network}/leases?next=cursor
-func handleWatchLeases(ctx context.Context, sm subnet.Manager, w http.ResponseWriter, r *http.Request) {
-	network := mux.Vars(r)["network"]
-	if network == "_" {
-		network = ""
-	}
-
-	cursor := getCursor(r.URL)
-
-	wr, err := sm.WatchLeases(ctx, network, cursor)
-	if err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		fmt.Fprint(w, err)
-		return
-	}
-
-	switch wr.Cursor.(type) {
-	case string:
-	case fmt.Stringer:
-		wr.Cursor = wr.Cursor.(fmt.Stringer).String()
-	default:
-		w.WriteHeader(http.StatusInternalServerError)
-		fmt.Fprint(w, fmt.Errorf("internal error: watch cursor is of unknown type"))
-		return
-	}
-
-	jsonResponse(w, http.StatusOK, wr)
-}
-
-// GET /?next=cursor watches
-// GET / retrieves all networks
-func handleNetworks(ctx context.Context, sm subnet.Manager, w http.ResponseWriter, r *http.Request) {
-	cursor := getCursor(r.URL)
-	wr, err := sm.WatchNetworks(ctx, cursor)
-	if err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		fmt.Fprint(w, err)
-		return
-	}
-
-	switch wr.Cursor.(type) {
-	case string:
-	case fmt.Stringer:
-		wr.Cursor = wr.Cursor.(fmt.Stringer).String()
-	default:
-		w.WriteHeader(http.StatusInternalServerError)
-		fmt.Fprint(w, fmt.Errorf("internal error: watch cursor is of unknown type"))
-		return
-	}
-
-	jsonResponse(w, http.StatusOK, wr)
-}
-
-// POST /{network}/reservations
-func handleAddReservation(ctx context.Context, sm subnet.Manager, w http.ResponseWriter, r *http.Request) {
-	network := mux.Vars(r)["network"]
-	if network == "_" {
-		network = ""
-	}
-
-	rsv := &subnet.Reservation{}
-	if err := json.NewDecoder(r.Body).Decode(rsv); err != nil {
-		w.WriteHeader(http.StatusBadRequest)
-		fmt.Fprint(w, "JSON decoding error: ", err)
-		return
-	}
-
-	if err := sm.AddReservation(ctx, network, rsv); err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		fmt.Fprint(w, fmt.Errorf("internal error: %v", err))
-		return
-	}
-}
-
-// DELETE /{network}/reservations/{subnet}
-func handleRemoveReservation(ctx context.Context, sm subnet.Manager, w http.ResponseWriter, r *http.Request) {
-	network := mux.Vars(r)["network"]
-	if network == "_" {
-		network = ""
-	}
-
-	sn := subnet.ParseSubnetKey(mux.Vars(r)["subnet"])
-	if sn == nil {
-		w.WriteHeader(http.StatusBadRequest)
-		fmt.Fprint(w, "bad subnet")
-		return
-	}
-
-	if err := sm.RemoveReservation(ctx, network, *sn); err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		fmt.Fprint(w, err)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-}
-
-// GET /{network}/reservations
-func handleListReservations(ctx context.Context, sm subnet.Manager, w http.ResponseWriter, r *http.Request) {
-	network := mux.Vars(r)["network"]
-	if network == "_" {
-		network = ""
-	}
-
-	leases, err := sm.ListReservations(ctx, network)
-	if err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		fmt.Fprint(w, err)
-		return
-	}
-
-	jsonResponse(w, http.StatusOK, leases)
-}
-
-func bindHandler(h handler, ctx context.Context, sm subnet.Manager) http.HandlerFunc {
-	return func(resp http.ResponseWriter, req *http.Request) {
-		h(ctx, sm, resp, req)
-	}
-}
-
-func fdListener(addr string) (net.Listener, error) {
-	fdOffset := 0
-	if addr != "" {
-		fd, err := strconv.Atoi(addr)
-		if err != nil {
-			return nil, fmt.Errorf("fd index is not a number")
-		}
-		fdOffset = fd - 3
-	}
-
-	listeners, err := activation.Listeners(false)
-	if err != nil {
-		return nil, err
-	}
-
-	if fdOffset >= len(listeners) {
-		return nil, fmt.Errorf("fd %v is out of range (%v)", addr, len(listeners)+3)
-	}
-
-	if listeners[fdOffset] == nil {
-		return nil, fmt.Errorf("fd %v was not socket activated", addr)
-	}
-
-	return listeners[fdOffset], nil
-}
-
-func listener(addr, cafile, certfile, keyfile string) (net.Listener, error) {
-	rex := regexp.MustCompile("(?:([a-z]+)://)?(.*)")
-	groups := rex.FindStringSubmatch(addr)
-
-	var l net.Listener
-	var err error
-
-	switch {
-	case groups == nil:
-		return nil, fmt.Errorf("bad listener address")
-
-	case groups[1] == "", groups[1] == "tcp":
-		if l, err = net.Listen("tcp", groups[2]); err != nil {
-			return nil, err
-		}
-
-	case groups[1] == "fd":
-		if l, err = fdListener(groups[2]); err != nil {
-			return nil, err
-		}
-
-	default:
-		return nil, fmt.Errorf("bad listener scheme")
-	}
-
-	tlsinfo := transport.TLSInfo{
-		CAFile:   cafile,
-		CertFile: certfile,
-		KeyFile:  keyfile,
-	}
-
-	if !tlsinfo.Empty() {
-		cfg, err := tlsinfo.ServerConfig()
-		if err != nil {
-			return nil, err
-		}
-
-		l = tls.NewListener(l, cfg)
-	}
-
-	return l, nil
-}
-
-func RunServer(ctx context.Context, sm subnet.Manager, listenAddr, cafile, certfile, keyfile string) {
-	// {network} is always required a the API level but to
-	// keep backward compat, special "_" network is allowed
-	// that means "no network"
-
-	r := mux.NewRouter()
-	r.HandleFunc("/v1/{network}/config", bindHandler(handleGetNetworkConfig, ctx, sm)).Methods("GET")
-
-	r.HandleFunc("/v1/{network}/leases", bindHandler(handleAcquireLease, ctx, sm)).Methods("POST")
-	r.HandleFunc("/v1/{network}/leases/{subnet}", bindHandler(handleWatchLease, ctx, sm)).Methods("GET")
-	r.HandleFunc("/v1/{network}/leases/{subnet}", bindHandler(handleRenewLease, ctx, sm)).Methods("PUT")
-	r.HandleFunc("/v1/{network}/leases/{subnet}", bindHandler(handleRevokeLease, ctx, sm)).Methods("DELETE")
-	r.HandleFunc("/v1/{network}/leases", bindHandler(handleWatchLeases, ctx, sm)).Methods("GET")
-	r.HandleFunc("/v1/", bindHandler(handleNetworks, ctx, sm)).Methods("GET")
-
-	r.HandleFunc("/v1/{network}/reservations", bindHandler(handleListReservations, ctx, sm)).Methods("GET")
-	r.HandleFunc("/v1/{network}/reservations", bindHandler(handleAddReservation, ctx, sm)).Methods("POST")
-	r.HandleFunc("/v1/{network}/reservations/{subnet}", bindHandler(handleRemoveReservation, ctx, sm)).Methods("DELETE")
-
-	l, err := listener(listenAddr, cafile, certfile, keyfile)
-	if err != nil {
-		log.Errorf("Error listening on %v: %v", listenAddr, err)
-		return
-	}
-
-	c := make(chan error, 1)
-	go func() {
-		c <- http.Serve(l, httpLogger(r))
-	}()
-
-	daemon.SdNotify(false, "READY=1")
-
-	select {
-	case <-ctx.Done():
-		l.Close()
-		<-c
-
-	case err := <-c:
-		log.Errorf("Error serving on %v: %v", listenAddr, err)
-	}
-}

+ 0 - 1110
remote/transport.go

@@ -1,1110 +0,0 @@
-// Copyright 2011 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.
-
-// HTTP client implementation. See RFC 2616.
-//
-// This is the low-level Transport implementation of RoundTripper.
-// The high-level interface is in client.go.
-
-package remote
-
-import (
-	"bufio"
-	"crypto/tls"
-	"errors"
-	"fmt"
-	"io"
-	"log"
-	"net"
-	"net/http"
-	"net/url"
-	"os"
-	"strings"
-	"sync"
-	"time"
-)
-
-// DefaultMaxIdleConnsPerHost is the default value of Transport's
-// MaxIdleConnsPerHost.
-const DefaultMaxIdleConnsPerHost = 2
-
-// Transport is an implementation of RoundTripper that supports HTTP,
-// HTTPS, and HTTP proxies (for either HTTP or HTTPS with CONNECT).
-// Transport can also cache connections for future re-use.
-type Transport struct {
-	idleMu     sync.Mutex
-	wantIdle   bool // user has requested to close all idle conns
-	idleConn   map[connectMethodKey][]*persistConn
-	idleConnCh map[connectMethodKey]chan *persistConn
-
-	reqMu       sync.Mutex
-	reqCanceler map[*http.Request]func()
-	reqCanceled map[*http.Request]bool
-
-	altMu    sync.RWMutex
-	altProto map[string]http.RoundTripper // nil or map of URI scheme => RoundTripper
-
-	// Dial specifies the dial function for creating unencrypted
-	// TCP connections.
-	// If Dial is nil, net.Dial is used.
-	Dial func(network, addr string) (net.Conn, error)
-
-	// DialTLS specifies an optional dial function for creating
-	// TLS connections for non-proxied HTTPS requests.
-	//
-	// If DialTLS is nil, Dial and TLSClientConfig are used.
-	//
-	// If DialTLS is set, the Dial hook is not used for HTTPS
-	// requests and the TLSClientConfig and TLSHandshakeTimeout
-	// are ignored. The returned net.Conn is assumed to already be
-	// past the TLS handshake.
-	DialTLS func(network, addr string) (net.Conn, error)
-
-	// TLSClientConfig specifies the TLS configuration to use with
-	// tls.Client. If nil, the default configuration is used.
-	TLSClientConfig *tls.Config
-
-	// TLSHandshakeTimeout specifies the maximum amount of time waiting to
-	// wait for a TLS handshake. Zero means no timeout.
-	TLSHandshakeTimeout time.Duration
-
-	// DisableKeepAlives, if true, prevents re-use of TCP connections
-	// between different HTTP requests.
-	DisableKeepAlives bool
-
-	// DisableCompression, if true, prevents the Transport from
-	// requesting compression with an "Accept-Encoding: gzip"
-	// request header when the Request contains no existing
-	// Accept-Encoding value. If the Transport requests gzip on
-	// its own and gets a gzipped response, it's transparently
-	// decoded in the Response.Body. However, if the user
-	// explicitly requested gzip it is not automatically
-	// uncompressed.
-	DisableCompression bool
-
-	// MaxIdleConnsPerHost, if non-zero, controls the maximum idle
-	// (keep-alive) to keep per-host.  If zero,
-	// DefaultMaxIdleConnsPerHost is used.
-	MaxIdleConnsPerHost int
-
-	// ResponseHeaderTimeout, if non-zero, specifies the amount of
-	// time to wait for a server's response headers after fully
-	// writing the request (including its body, if any). This
-	// time does not include the time to read the response body.
-	ResponseHeaderTimeout time.Duration
-
-	// TODO: tunable on global max cached connections
-	// TODO: tunable on timeout on cached connections
-}
-
-// transportRequest is a wrapper around a *Request that adds
-// optional extra headers to write.
-type transportRequest struct {
-	*http.Request             // original request, not to be mutated
-	extra         http.Header // extra headers to write, or nil
-}
-
-func (tr *transportRequest) extraHeaders() http.Header {
-	if tr.extra == nil {
-		tr.extra = make(http.Header)
-	}
-	return tr.extra
-}
-
-func closeBody(req *http.Request) {
-	if req.Body != nil {
-		req.Body.Close()
-	}
-}
-
-func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
-
-// RoundTrip implements the RoundTripper interface.
-//
-// For higher-level HTTP client support (such as handling of cookies
-// and redirects), see Get, Post, and the Client type.
-func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
-	if req.URL == nil {
-		closeBody(req)
-		return nil, errors.New("http: nil Request.URL")
-	}
-	if req.Header == nil {
-		closeBody(req)
-		return nil, errors.New("http: nil Request.Header")
-	}
-	if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
-		closeBody(req)
-		return nil, errors.New("http: bad scheme")
-	}
-	if req.URL.Host == "" {
-		closeBody(req)
-		return nil, errors.New("http: no Host in request URL")
-	}
-	treq := &transportRequest{Request: req}
-	cm := t.connectMethodForRequest(treq)
-
-	// Get the cached or newly-created connection to either the
-	// host (for http or https), the http proxy, or the http proxy
-	// pre-CONNECTed to https server.  In any case, we'll be ready
-	// to send it requests.
-	pconn, err := t.getConn(req, cm)
-	if err != nil {
-		t.setReqCanceler(req, nil)
-		closeBody(req)
-		return nil, err
-	}
-
-	return pconn.roundTrip(treq)
-}
-
-// RegisterProtocol registers a new protocol with scheme.
-// The Transport will pass requests using the given scheme to rt.
-// It is rt's responsibility to simulate HTTP request semantics.
-//
-// RegisterProtocol can be used by other packages to provide
-// implementations of protocol schemes like "ftp" or "file".
-func (t *Transport) RegisterProtocol(scheme string, rt http.RoundTripper) {
-	if scheme == "http" || scheme == "https" {
-		panic("protocol " + scheme + " already registered")
-	}
-	t.altMu.Lock()
-	defer t.altMu.Unlock()
-	if t.altProto == nil {
-		t.altProto = make(map[string]http.RoundTripper)
-	}
-	if _, exists := t.altProto[scheme]; exists {
-		panic("protocol " + scheme + " already registered")
-	}
-	t.altProto[scheme] = rt
-}
-
-// CloseIdleConnections closes any connections which were previously
-// connected from previous requests but are now sitting idle in
-// a "keep-alive" state. It does not interrupt any connections currently
-// in use.
-func (t *Transport) CloseIdleConnections() {
-	t.idleMu.Lock()
-	m := t.idleConn
-	t.idleConn = nil
-	t.idleConnCh = nil
-	t.wantIdle = true
-	t.idleMu.Unlock()
-	for _, conns := range m {
-		for _, pconn := range conns {
-			pconn.close()
-		}
-	}
-}
-
-// CancelRequest cancels an in-flight request by closing its connection.
-// CancelRequest should only be called after RoundTrip has returned.
-func (t *Transport) CancelRequest(req *http.Request) {
-	t.reqMu.Lock()
-	if cancel, ok := t.reqCanceler[req]; ok {
-		delete(t.reqCanceler, req)
-		t.reqMu.Unlock()
-		if cancel != nil {
-			cancel()
-		}
-	} else {
-		if t.reqCanceled == nil {
-			t.reqCanceled = make(map[*http.Request]bool)
-		}
-		t.reqCanceled[req] = true
-		t.reqMu.Unlock()
-	}
-}
-
-//
-// Private implementation past this point.
-//
-
-// envOnce looks up an environment variable (optionally by multiple
-// names) once. It mitigates expensive lookups on some platforms
-// (e.g. Windows).
-type envOnce struct {
-	names []string
-	once  sync.Once
-	val   string
-}
-
-func (e *envOnce) Get() string {
-	e.once.Do(e.init)
-	return e.val
-}
-
-func (e *envOnce) init() {
-	for _, n := range e.names {
-		e.val = os.Getenv(n)
-		if e.val != "" {
-			return
-		}
-	}
-}
-
-// reset is used by tests
-func (e *envOnce) reset() {
-	e.once = sync.Once{}
-	e.val = ""
-}
-
-func (t *Transport) connectMethodForRequest(treq *transportRequest) (cm connectMethod) {
-	cm.targetScheme = treq.URL.Scheme
-	cm.targetAddr = canonicalAddr(treq.URL)
-	return cm
-}
-
-// putIdleConn adds pconn to the list of idle persistent connections awaiting
-// a new request.
-// If pconn is no longer needed or not in a good state, putIdleConn
-// returns false.
-func (t *Transport) putIdleConn(pconn *persistConn) bool {
-	if t.DisableKeepAlives || t.MaxIdleConnsPerHost < 0 {
-		pconn.close()
-		return false
-	}
-	if pconn.isBroken() {
-		return false
-	}
-	key := pconn.cacheKey
-	max := t.MaxIdleConnsPerHost
-	if max == 0 {
-		max = DefaultMaxIdleConnsPerHost
-	}
-	t.idleMu.Lock()
-
-	waitingDialer := t.idleConnCh[key]
-	select {
-	case waitingDialer <- pconn:
-		// We're done with this pconn and somebody else is
-		// currently waiting for a conn of this type (they're
-		// actively dialing, but this conn is ready
-		// first). Chrome calls this socket late binding.  See
-		// https://insouciant.org/tech/connection-management-in-chromium/
-		t.idleMu.Unlock()
-		return true
-	default:
-		if waitingDialer != nil {
-			// They had populated this, but their dial won
-			// first, so we can clean up this map entry.
-			delete(t.idleConnCh, key)
-		}
-	}
-	if t.wantIdle {
-		t.idleMu.Unlock()
-		pconn.close()
-		return false
-	}
-	if t.idleConn == nil {
-		t.idleConn = make(map[connectMethodKey][]*persistConn)
-	}
-	if len(t.idleConn[key]) >= max {
-		t.idleMu.Unlock()
-		pconn.close()
-		return false
-	}
-	for _, exist := range t.idleConn[key] {
-		if exist == pconn {
-			log.Fatalf("dup idle pconn %p in freelist", pconn)
-		}
-	}
-	t.idleConn[key] = append(t.idleConn[key], pconn)
-	t.idleMu.Unlock()
-	return true
-}
-
-// getIdleConnCh returns a channel to receive and return idle
-// persistent connection for the given connectMethod.
-// It may return nil, if persistent connections are not being used.
-func (t *Transport) getIdleConnCh(cm connectMethod) chan *persistConn {
-	if t.DisableKeepAlives {
-		return nil
-	}
-	key := cm.key()
-	t.idleMu.Lock()
-	defer t.idleMu.Unlock()
-	t.wantIdle = false
-	if t.idleConnCh == nil {
-		t.idleConnCh = make(map[connectMethodKey]chan *persistConn)
-	}
-	ch, ok := t.idleConnCh[key]
-	if !ok {
-		ch = make(chan *persistConn)
-		t.idleConnCh[key] = ch
-	}
-	return ch
-}
-
-func (t *Transport) getIdleConn(cm connectMethod) (pconn *persistConn) {
-	key := cm.key()
-	t.idleMu.Lock()
-	defer t.idleMu.Unlock()
-	if t.idleConn == nil {
-		return nil
-	}
-	for {
-		pconns, ok := t.idleConn[key]
-		if !ok {
-			return nil
-		}
-		if len(pconns) == 1 {
-			pconn = pconns[0]
-			delete(t.idleConn, key)
-		} else {
-			// 2 or more cached connections; pop last
-			// TODO: queue?
-			pconn = pconns[len(pconns)-1]
-			t.idleConn[key] = pconns[:len(pconns)-1]
-		}
-		if !pconn.isBroken() {
-			return
-		}
-	}
-}
-
-func (t *Transport) setReqCanceler(r *http.Request, fn func()) {
-	t.reqMu.Lock()
-	defer t.reqMu.Unlock()
-	if t.reqCanceler == nil {
-		t.reqCanceler = make(map[*http.Request]func())
-	}
-	if fn != nil {
-		t.reqCanceler[r] = fn
-	} else {
-		delete(t.reqCanceler, r)
-	}
-}
-
-// replaceReqCanceler replaces an existing cancel function. If there is no cancel function
-// for the request, we don't set the function and return false.
-// Since CancelRequest will clear the canceler, we can use the return value to detect if
-// the request was canceled since the last setReqCancel call.
-func (t *Transport) replaceReqCanceler(r *http.Request, fn func()) bool {
-	t.reqMu.Lock()
-	defer t.reqMu.Unlock()
-	_, ok := t.reqCanceler[r]
-	if !ok {
-		return false
-	}
-	if fn != nil {
-		t.reqCanceler[r] = fn
-	} else {
-		delete(t.reqCanceler, r)
-	}
-	return true
-}
-
-func (t *Transport) dial(network, addr string) (c net.Conn, err error) {
-	if t.Dial != nil {
-		return t.Dial(network, addr)
-	}
-	return net.Dial(network, addr)
-}
-
-// Testing hooks:
-var prePendingDial, postPendingDial func()
-
-// getConn dials and creates a new persistConn to the target as
-// specified in the connectMethod.  This includes doing a proxy CONNECT
-// and/or setting up TLS.  If this doesn't return an error, the persistConn
-// is ready to write requests to.
-func (t *Transport) getConn(req *http.Request, cm connectMethod) (*persistConn, error) {
-	if pc := t.getIdleConn(cm); pc != nil {
-		// set request canceler to some non-nil function so we
-		// can detect whether it was cleared between now and when
-		// we enter roundTrip
-		t.setReqCanceler(req, func() {})
-
-		t.reqMu.Lock()
-		if _, ok := t.reqCanceled[req]; ok {
-			// request canceled before we knew about it
-			delete(t.reqCanceled, req)
-			pc.cancelRequest()
-		}
-		t.reqMu.Unlock()
-
-		return pc, nil
-	}
-
-	type dialRes struct {
-		pc  *persistConn
-		err error
-	}
-	dialc := make(chan dialRes)
-
-	// Copy these hooks so we don't race on the postPendingDial in
-	// the goroutine we launch. Issue 11136.
-	prePendingDial := prePendingDial
-	postPendingDial := postPendingDial
-
-	handlePendingDial := func() {
-		if prePendingDial != nil {
-			prePendingDial()
-		}
-		go func() {
-			if v := <-dialc; v.err == nil {
-				t.putIdleConn(v.pc)
-			}
-			if postPendingDial != nil {
-				postPendingDial()
-			}
-		}()
-	}
-
-	cancelc := make(chan struct{})
-	t.setReqCanceler(req, func() { close(cancelc) })
-
-	t.reqMu.Lock()
-	if _, ok := t.reqCanceled[req]; ok {
-		// request canceled before we knew about it
-		if cancel, ok := t.reqCanceler[req]; ok {
-			delete(t.reqCanceler, req)
-			delete(t.reqCanceled, req)
-			if cancel != nil {
-				cancel()
-			}
-		}
-	}
-	t.reqMu.Unlock()
-
-	go func() {
-		pc, err := t.dialConn(cm)
-		dialc <- dialRes{pc, err}
-	}()
-
-	idleConnCh := t.getIdleConnCh(cm)
-	select {
-	case v := <-dialc:
-		// Our dial finished.
-		return v.pc, v.err
-	case pc := <-idleConnCh:
-		// Another request finished first and its net.Conn
-		// became available before our dial. Or somebody
-		// else's dial that they didn't use.
-		// But our dial is still going, so give it away
-		// when it finishes:
-		handlePendingDial()
-		return pc, nil
-	case <-cancelc:
-		handlePendingDial()
-		return nil, errors.New("net/http: request canceled while waiting for connection")
-	}
-}
-
-func (t *Transport) dialConn(cm connectMethod) (*persistConn, error) {
-	pconn := &persistConn{
-		t:          t,
-		cacheKey:   cm.key(),
-		reqch:      make(chan requestAndChan, 1),
-		writech:    make(chan writeRequest, 1),
-		closech:    make(chan struct{}),
-		writeErrCh: make(chan error, 1),
-	}
-	tlsDial := t.DialTLS != nil && cm.targetScheme == "https"
-	if tlsDial {
-		var err error
-		pconn.conn, err = t.DialTLS("tcp", cm.addr())
-		if err != nil {
-			return nil, err
-		}
-		if tc, ok := pconn.conn.(*tls.Conn); ok {
-			cs := tc.ConnectionState()
-			pconn.tlsState = &cs
-		}
-	} else {
-		conn, err := t.dial("tcp", cm.addr())
-		if err != nil {
-			return nil, err
-		}
-		pconn.conn = conn
-	}
-
-	if cm.targetScheme == "https" && !tlsDial {
-		// Initiate TLS and check remote host name against certificate.
-		cfg := t.TLSClientConfig
-		if cfg == nil || cfg.ServerName == "" {
-			host := cm.tlsHost()
-			if cfg == nil {
-				cfg = &tls.Config{ServerName: host}
-			} else {
-				clone := *cfg // shallow clone
-				clone.ServerName = host
-				cfg = &clone
-			}
-		}
-		plainConn := pconn.conn
-		tlsConn := tls.Client(plainConn, cfg)
-		errc := make(chan error, 2)
-		var timer *time.Timer // for canceling TLS handshake
-		if d := t.TLSHandshakeTimeout; d != 0 {
-			timer = time.AfterFunc(d, func() {
-				errc <- tlsHandshakeTimeoutError{}
-			})
-		}
-		go func() {
-			err := tlsConn.Handshake()
-			if timer != nil {
-				timer.Stop()
-			}
-			errc <- err
-		}()
-		if err := <-errc; err != nil {
-			plainConn.Close()
-			return nil, err
-		}
-		if !cfg.InsecureSkipVerify {
-			if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
-				plainConn.Close()
-				return nil, err
-			}
-		}
-		cs := tlsConn.ConnectionState()
-		pconn.tlsState = &cs
-		pconn.conn = tlsConn
-	}
-
-	pconn.br = bufio.NewReader(noteEOFReader{pconn.conn, &pconn.sawEOF})
-	pconn.bw = bufio.NewWriter(pconn.conn)
-	go pconn.readLoop()
-	go pconn.writeLoop()
-	return pconn, nil
-}
-
-// connectMethod is the map key (in its String form) for keeping persistent
-// TCP connections alive for subsequent HTTP requests.
-//
-// A connect method may be of the following types:
-//
-// Cache key form                Description
-// -----------------             -------------------------
-// |http|foo.com                 http directly to server, no proxy
-// |https|foo.com                https directly to server, no proxy
-// http://proxy.com|https|foo.com  http to proxy, then CONNECT to foo.com
-// http://proxy.com|http           http to proxy, http to anywhere after that
-//
-// Note: no support to https to the proxy yet.
-//
-type connectMethod struct {
-	targetScheme string // "http" or "https"
-	targetAddr   string // Not used if proxy + http targetScheme (4th example in table)
-}
-
-func (cm *connectMethod) key() connectMethodKey {
-	return connectMethodKey{
-		scheme: cm.targetScheme,
-		addr:   cm.targetAddr,
-	}
-}
-
-// addr returns the first hop "host:port" to which we need to TCP connect.
-func (cm *connectMethod) addr() string {
-	return cm.targetAddr
-}
-
-// tlsHost returns the host name to match against the peer's
-// TLS certificate.
-func (cm *connectMethod) tlsHost() string {
-	h := cm.targetAddr
-	if hasPort(h) {
-		h = h[:strings.LastIndex(h, ":")]
-	}
-	return h
-}
-
-// connectMethodKey is the map key version of connectMethod, with a
-// stringified proxy URL (or the empty string) instead of a pointer to
-// a URL.
-type connectMethodKey struct {
-	scheme, addr string
-}
-
-func (k connectMethodKey) String() string {
-	// Only used by tests.
-	return fmt.Sprintf("%s|%s|%s", k.scheme, k.addr)
-}
-
-// persistConn wraps a connection, usually a persistent one
-// (but may be used for non-keep-alive requests as well)
-type persistConn struct {
-	t        *Transport
-	cacheKey connectMethodKey
-	conn     net.Conn
-	tlsState *tls.ConnectionState
-	br       *bufio.Reader       // from conn
-	sawEOF   bool                // whether we've seen EOF from conn; owned by readLoop
-	bw       *bufio.Writer       // to conn
-	reqch    chan requestAndChan // written by roundTrip; read by readLoop
-	writech  chan writeRequest   // written by roundTrip; read by writeLoop
-	closech  chan struct{}       // closed when conn closed
-	// writeErrCh passes the request write error (usually nil)
-	// from the writeLoop goroutine to the readLoop which passes
-	// it off to the res.Body reader, which then uses it to decide
-	// whether or not a connection can be reused. Issue 7569.
-	writeErrCh chan error
-
-	lk                   sync.Mutex // guards following fields
-	numExpectedResponses int
-	closed               bool // whether conn has been closed
-	broken               bool // an error has happened on this connection; marked broken so it's not reused.
-	canceled             bool // whether this conn was broken due a CancelRequest
-	// mutateHeaderFunc is an optional func to modify extra
-	// headers on each outbound request before it's written. (the
-	// original Request given to RoundTrip is not modified)
-	mutateHeaderFunc func(http.Header)
-}
-
-// isBroken reports whether this connection is in a known broken state.
-func (pc *persistConn) isBroken() bool {
-	pc.lk.Lock()
-	b := pc.broken
-	pc.lk.Unlock()
-	return b
-}
-
-// isCanceled reports whether this connection was closed due to CancelRequest.
-func (pc *persistConn) isCanceled() bool {
-	pc.lk.Lock()
-	defer pc.lk.Unlock()
-	return pc.canceled
-}
-
-func (pc *persistConn) cancelRequest() {
-	pc.lk.Lock()
-	defer pc.lk.Unlock()
-	pc.canceled = true
-	pc.closeLocked()
-}
-
-func (pc *persistConn) readLoop() {
-	// eofc is used to block http.Handler goroutines reading from Response.Body
-	// at EOF until this goroutines has (potentially) added the connection
-	// back to the idle pool.
-	eofc := make(chan struct{})
-	defer close(eofc) // unblock reader on errors
-
-	// Read this once, before loop starts. (to avoid races in tests)
-	testHookMu.Lock()
-	testHookReadLoopBeforeNextRead := testHookReadLoopBeforeNextRead
-	testHookMu.Unlock()
-
-	alive := true
-	for alive {
-		pb, err := pc.br.Peek(1)
-
-		pc.lk.Lock()
-		if pc.numExpectedResponses == 0 {
-			if !pc.closed {
-				pc.closeLocked()
-				if len(pb) > 0 {
-					log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v",
-						string(pb), err)
-				}
-			}
-			pc.lk.Unlock()
-			return
-		}
-		pc.lk.Unlock()
-
-		rc := <-pc.reqch
-
-		var resp *http.Response
-		if err == nil {
-			resp, err = http.ReadResponse(pc.br, rc.req)
-			if err == nil && resp.StatusCode == 100 {
-				// Skip any 100-continue for now.
-				// TODO(bradfitz): if rc.req had "Expect: 100-continue",
-				// actually block the request body write and signal the
-				// writeLoop now to begin sending it. (Issue 2184) For now we
-				// eat it, since we're never expecting one.
-				resp, err = http.ReadResponse(pc.br, rc.req)
-			}
-		}
-
-		if resp != nil {
-			resp.TLS = pc.tlsState
-		}
-
-		hasBody := resp != nil && rc.req.Method != "HEAD" && resp.ContentLength != 0
-
-		if err != nil {
-			pc.close()
-		} else {
-			resp.Body = &bodyEOFSignal{body: resp.Body}
-		}
-
-		if err != nil || resp.Close || rc.req.Close || resp.StatusCode <= 199 {
-			// Don't do keep-alive on error if either party requested a close
-			// or we get an unexpected informational (1xx) response.
-			// StatusCode 100 is already handled above.
-			alive = false
-		}
-
-		var waitForBodyRead chan bool // channel is nil when there's no body
-		if hasBody {
-			waitForBodyRead = make(chan bool, 2)
-			resp.Body.(*bodyEOFSignal).earlyCloseFn = func() error {
-				waitForBodyRead <- false
-				return nil
-			}
-			resp.Body.(*bodyEOFSignal).fn = func(err error) error {
-				isEOF := err == io.EOF
-				waitForBodyRead <- isEOF
-				if isEOF {
-					<-eofc // see comment at top
-				} else if err != nil && pc.isCanceled() {
-					return errRequestCanceled
-				}
-				return err
-			}
-		}
-
-		pc.lk.Lock()
-		pc.numExpectedResponses--
-		pc.lk.Unlock()
-
-		// The connection might be going away when we put the
-		// idleConn below. When that happens, we close the response channel to signal
-		// to roundTrip that the connection is gone. roundTrip waits for
-		// both closing and a response in a select, so it might choose
-		// the close channel, rather than the response.
-		// We send the response first so that roundTrip can check
-		// if there is a pending one with a non-blocking select
-		// on the response channel before erroring out.
-		rc.ch <- responseAndError{resp, err}
-
-		if hasBody {
-			// To avoid a race, wait for the just-returned
-			// response body to be fully consumed before peek on
-			// the underlying bufio reader.
-			select {
-			case bodyEOF := <-waitForBodyRead:
-				pc.t.setReqCanceler(rc.req, nil) // before pc might return to idle pool
-				alive = alive &&
-					bodyEOF &&
-					!pc.sawEOF &&
-					pc.wroteRequest() &&
-					pc.t.putIdleConn(pc)
-				if bodyEOF {
-					eofc <- struct{}{}
-				}
-			case <-pc.closech:
-				alive = false
-			}
-		} else {
-			pc.t.setReqCanceler(rc.req, nil) // before pc might return to idle pool
-			alive = alive &&
-				!pc.sawEOF &&
-				pc.wroteRequest() &&
-				pc.t.putIdleConn(pc)
-		}
-
-		if hook := testHookReadLoopBeforeNextRead; hook != nil {
-			hook()
-		}
-	}
-	pc.close()
-}
-
-func (pc *persistConn) writeLoop() {
-	for {
-		select {
-		case wr := <-pc.writech:
-			if pc.isBroken() {
-				wr.ch <- errors.New("http: can't write HTTP request on broken connection")
-				continue
-			}
-			err := wr.req.Request.Write(pc.bw)
-			if err == nil {
-				err = pc.bw.Flush()
-			}
-			if err != nil {
-				pc.markBroken()
-				closeBody(wr.req.Request)
-			}
-			pc.writeErrCh <- err // to the body reader, which might recycle us
-			wr.ch <- err         // to the roundTrip function
-		case <-pc.closech:
-			return
-		}
-	}
-}
-
-// wroteRequest is a check before recycling a connection that the previous write
-// (from writeLoop above) happened and was successful.
-func (pc *persistConn) wroteRequest() bool {
-	select {
-	case err := <-pc.writeErrCh:
-		// Common case: the write happened well before the response, so
-		// avoid creating a timer.
-		return err == nil
-	default:
-		// Rare case: the request was written in writeLoop above but
-		// before it could send to pc.writeErrCh, the reader read it
-		// all, processed it, and called us here. In this case, give the
-		// write goroutine a bit of time to finish its send.
-		//
-		// Less rare case: We also get here in the legitimate case of
-		// Issue 7569, where the writer is still writing (or stalled),
-		// but the server has already replied. In this case, we don't
-		// want to wait too long, and we want to return false so this
-		// connection isn't re-used.
-		select {
-		case err := <-pc.writeErrCh:
-			return err == nil
-		case <-time.After(50 * time.Millisecond):
-			return false
-		}
-	}
-}
-
-type responseAndError struct {
-	res *http.Response
-	err error
-}
-
-type requestAndChan struct {
-	req *http.Request
-	ch  chan responseAndError
-}
-
-// A writeRequest is sent by the readLoop's goroutine to the
-// writeLoop's goroutine to write a request while the read loop
-// concurrently waits on both the write response and the server's
-// reply.
-type writeRequest struct {
-	req *transportRequest
-	ch  chan<- error
-}
-
-type trHttpError struct {
-	err     string
-	timeout bool
-}
-
-func (e *trHttpError) Error() string   { return e.err }
-func (e *trHttpError) Timeout() bool   { return e.timeout }
-func (e *trHttpError) Temporary() bool { return true }
-
-var errTimeout error = &trHttpError{err: "net/http: timeout awaiting response headers", timeout: true}
-var errClosed error = &trHttpError{err: "net/http: transport closed before response was received"}
-var errRequestCanceled = errors.New("net/http: request canceled")
-
-// nil except for tests
-var (
-	testHookPersistConnClosedGotRes func()
-	testHookEnterRoundTrip          func()
-	testHookMu                      sync.Locker = fakeLocker{} // guards following
-	testHookReadLoopBeforeNextRead  func()
-)
-
-func (pc *persistConn) roundTrip(req *transportRequest) (resp *http.Response, err error) {
-	if hook := testHookEnterRoundTrip; hook != nil {
-		hook()
-	}
-	if !pc.t.replaceReqCanceler(req.Request, pc.cancelRequest) {
-		pc.t.putIdleConn(pc)
-		return nil, errRequestCanceled
-	}
-	pc.lk.Lock()
-	pc.numExpectedResponses++
-	headerFn := pc.mutateHeaderFunc
-	pc.lk.Unlock()
-
-	if headerFn != nil {
-		headerFn(req.extraHeaders())
-	}
-
-	// Write the request concurrently with waiting for a response,
-	// in case the server decides to reply before reading our full
-	// request body.
-	writeErrCh := make(chan error, 1)
-	pc.writech <- writeRequest{req, writeErrCh}
-
-	resc := make(chan responseAndError, 1)
-	pc.reqch <- requestAndChan{req.Request, resc}
-
-	var re responseAndError
-	var respHeaderTimer <-chan time.Time
-WaitResponse:
-	for {
-		select {
-		case err := <-writeErrCh:
-			if err != nil {
-				re = responseAndError{nil, err}
-				pc.close()
-				break WaitResponse
-			}
-			if d := pc.t.ResponseHeaderTimeout; d > 0 {
-				timer := time.NewTimer(d)
-				defer timer.Stop() // prevent leaks
-				respHeaderTimer = timer.C
-			}
-		case <-pc.closech:
-			// The persist connection is dead. This shouldn't
-			// usually happen (only with Connection: close responses
-			// with no response bodies), but if it does happen it
-			// means either a) the remote server hung up on us
-			// prematurely, or b) the readLoop sent us a response &
-			// closed its closech at roughly the same time, and we
-			// selected this case first. If we got a response, readLoop makes sure
-			// to send it before it puts the conn and closes the channel.
-			// That way, we can fetch the response, if there is one,
-			// with a non-blocking receive.
-			select {
-			case re = <-resc:
-				if fn := testHookPersistConnClosedGotRes; fn != nil {
-					fn()
-				}
-			default:
-				re = responseAndError{err: errClosed}
-			}
-			break WaitResponse
-		case <-respHeaderTimer:
-			pc.close()
-			re = responseAndError{err: errTimeout}
-			break WaitResponse
-		case re = <-resc:
-			break WaitResponse
-		}
-	}
-
-	if re.err != nil {
-		pc.t.setReqCanceler(req.Request, nil)
-	}
-	return re.res, re.err
-}
-
-// markBroken marks a connection as broken (so it's not reused).
-// It differs from close in that it doesn't close the underlying
-// connection for use when it's still being read.
-func (pc *persistConn) markBroken() {
-	pc.lk.Lock()
-	defer pc.lk.Unlock()
-	pc.broken = true
-}
-
-func (pc *persistConn) close() {
-	pc.lk.Lock()
-	defer pc.lk.Unlock()
-	pc.closeLocked()
-}
-
-func (pc *persistConn) closeLocked() {
-	pc.broken = true
-	if !pc.closed {
-		pc.conn.Close()
-		pc.closed = true
-		close(pc.closech)
-	}
-	pc.mutateHeaderFunc = nil
-}
-
-var portMap = map[string]string{
-	"http":  "80",
-	"https": "443",
-}
-
-// canonicalAddr returns url.Host but always with a ":port" suffix
-func canonicalAddr(url *url.URL) string {
-	addr := url.Host
-	if !hasPort(addr) {
-		return addr + ":" + portMap[url.Scheme]
-	}
-	return addr
-}
-
-// bodyEOFSignal wraps a ReadCloser but runs fn (if non-nil) at most
-// once, right before its final (error-producing) Read or Close call
-// returns. fn should return the new error to return from Read or Close.
-//
-// If earlyCloseFn is non-nil and Close is called before io.EOF is
-// seen, earlyCloseFn is called instead of fn, and its return value is
-// the return value from Close.
-type bodyEOFSignal struct {
-	body         io.ReadCloser
-	mu           sync.Mutex        // guards following 4 fields
-	closed       bool              // whether Close has been called
-	rerr         error             // sticky Read error
-	fn           func(error) error // err will be nil on Read io.EOF
-	earlyCloseFn func() error      // optional alt Close func used if io.EOF not seen
-}
-
-func (es *bodyEOFSignal) Read(p []byte) (n int, err error) {
-	es.mu.Lock()
-	closed, rerr := es.closed, es.rerr
-	es.mu.Unlock()
-	if closed {
-		return 0, errors.New("http: read on closed response body")
-	}
-	if rerr != nil {
-		return 0, rerr
-	}
-
-	n, err = es.body.Read(p)
-	if err != nil {
-		es.mu.Lock()
-		defer es.mu.Unlock()
-		if es.rerr == nil {
-			es.rerr = err
-		}
-		err = es.condfn(err)
-	}
-	return
-}
-
-func (es *bodyEOFSignal) Close() error {
-	es.mu.Lock()
-	defer es.mu.Unlock()
-	if es.closed {
-		return nil
-	}
-	es.closed = true
-	if es.earlyCloseFn != nil && es.rerr != io.EOF {
-		return es.earlyCloseFn()
-	}
-	err := es.body.Close()
-	return es.condfn(err)
-}
-
-// caller must hold es.mu.
-func (es *bodyEOFSignal) condfn(err error) error {
-	if es.fn == nil {
-		return err
-	}
-	err = es.fn(err)
-	es.fn = nil
-	return err
-}
-
-type readerAndCloser struct {
-	io.Reader
-	io.Closer
-}
-
-type tlsHandshakeTimeoutError struct{}
-
-func (tlsHandshakeTimeoutError) Timeout() bool   { return true }
-func (tlsHandshakeTimeoutError) Temporary() bool { return true }
-func (tlsHandshakeTimeoutError) Error() string   { return "net/http: TLS handshake timeout" }
-
-type noteEOFReader struct {
-	r      io.Reader
-	sawEOF *bool
-}
-
-func (nr noteEOFReader) Read(p []byte) (n int, err error) {
-	n, err = nr.r.Read(p)
-	if err == io.EOF {
-		*nr.sawEOF = true
-	}
-	return
-}
-
-// fakeLocker is a sync.Locker which does nothing. It's used to guard
-// test-only fields when not under test, to avoid runtime atomic
-// overhead.
-type fakeLocker struct{}
-
-func (fakeLocker) Lock()   {}
-func (fakeLocker) Unlock() {}