ProxyConnectHeaderを使ってhttp.ClientによるCONNECTメソッドにヘッダーを付加する

## 事象
Go 1.14.2の環境で、認証Proxyを経由して任意のサイトにアクセスするHTTPクライアントを実装していたところ、接続先がHTTPSのときに認証proxyから407が返ってしまいました。

```go:再現コード(main.go)
func main() {
	u, _ := url.Parse("http://127.0.0.1:3128") // ローカルにsquidを立て、認証Proxyとした
	req, err := http.NewRequest("GET", "https://yahoo.co.jp", nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Add("Proxy-Authorization", "Basic <basic auth string>")
	c := &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyURL(u),
		},
	}
	resp, err := c.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
}
```

上記を実行すると、 `407 (Proxy Authentication Required)` が返って通信ができません。 `Proxy-Authorization` に然るべき認証情報は正しくセットしているのですが、なぜ通信できないのでしょうか。

```bash
$ go run main.go
2020/08/09 16:02:34 Get https://www.yahoo.co.jp/: Proxy Authentication Required
exit status 1
```
## 原因

接続先がHTTPSの場合、接続先とのTLSハンドシェイクの前に、仲介者である認証Proxyに対して `CONNECT` メソッドが発行されます。再現コードでヘッダーとして付与していた `Proxy-Authorization` が、このCONNECTメソッド実行時に付与されなかったため、本事象が発生していました。

CONNECTの実行は、内部的には `http.Transport` の `dialConn` メソッドにて実行されています。このメソッドでは、CONNECTを行うために `http.Request` を新たに生成していますが、ここではもともと再現コードでProxy-AuthorizationをセットしたHeaderを使いません。その代わり、 `http.Transport` の `ProxyConnectHeader` という項目の値をセットしています。

```go:http.Transport#dialConnより抜粋(transport.go#L1564-L1579)
	case cm.targetScheme == "https":
		conn := pconn.conn
		hdr := t.ProxyConnectHeader
		if hdr == nil {
			hdr = make(Header)
		}
		if pa := cm.proxyAuth(); pa != "" {
			hdr = hdr.Clone()
			hdr.Set("Proxy-Authorization", pa)
		}
		connectReq := &Request{
			Method: "CONNECT",
			URL:    &url.URL{Opaque: cm.targetAddr},
			Host:   cm.targetAddr,
			Header: hdr,
		}
```

このCONNECT時に元のHeaderを使わない仕様、私は最初ちょっと意外に感じたのですが、考えてみれば、元々の要求でセットしていたHeaderはあくまで本来の接続先のために用意しているものなので、認証Proxyとの会話であるCONNECT時にそれを渡してはならないですね。不要ですし、何よりセキュリティの観点から望ましくないです。

`ProxyConnectHeader` は、当然ながら `http.Transport` のコメントでも説明されています。

```go

    // ProxyConnectHeader optionally specifies headers to send to
    // proxies during CONNECT requests.
    ProxyConnectHeader Header // Go 1.8
```

https://golang.org/pkg/net/http/#Transport

## 対応

`http.Request.Header` にセットしていた値を、 `ProxyConnectHeader` に入れてあげるよう変更すれば、事象の解消が可能です。

```go:main.go
func main() {
	u, _ := url.Parse("http://127.0.0.1:3128")
	req, err := http.NewRequest("GET", "https://yahoo.co.jp", nil)
	if err != nil {
		log.Fatal(err)
	}
	hdr := make(http.Header)
	hdr.Add("Proxy-Authorization", "Basic <basic auth string>")
	c := &http.Client{
		Transport: &http.Transport{
			Proxy:              http.ProxyURL(u),
			ProxyConnectHeader: hdr,
		},
	}
	resp, err := c.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
}

```

いけましたね。

```bash
$ go run main.go
200 OK
```

また、`Proxy-Authorization` に固定の認証情報をセットしたいだけなら、ProxyのURLに入れても良いですね。

```go
func main() {
	u, _ := url.Parse("http://<username>:<password>@127.0.0.1:3128") // 認証情報を付加
	req, err := http.NewRequest("GET", "https://yahoo.co.jp", nil)
	if err != nil {
		log.Fatal(err)
	}
	c := &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyURL(u),
		},
	}
	resp, err := c.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resp.Status)
}
```
```bash
$ go run main.go
200 OK
```

おわり。