http.Client 等で内部的に使われている http.Transport はプロキシの利用を隠蔽するが、HTTPプロキシを利用しておりそのプロキシが CONNECT に対して 200 OK 以外のステータスコードを返すとき、接続先がHTTPかHTTPSかで異なるふるまいをすることに気づいた

https://play.golang.org/p/8K_9FUxyU_r

package main

import (
	"crypto/tls"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"net/url"
	"regexp"
	"sync"
)

func main() {
	origin := origin()
	defer origin.Close()

	originTLS := originTLS()
	defer originTLS.Close()

	ok := okProxy()
	defer ok.Close()

	badGateway := badGatewayProxy()
	defer badGateway.Close()

	testCases := []struct {
		title string

		origin *httptest.Server
		proxy  *httptest.Server

		status int
		error  *regexp.Regexp
	}{
		{
			title: "オリジンがHTTP、プロキシがOK",

			origin: origin,
			proxy:  ok,

			status: http.StatusOK,
		},
		{
			title: "オリジンがHTTPS、プロキシがOK",

			origin: originTLS,
			proxy:  ok,

			status: http.StatusOK,
		},
		{
			title: "オリジンがHTTP、プロキシがBad Gateway",

			origin: origin,
			proxy:  badGateway,

			status: http.StatusBadGateway,
		},
		{
			title: "オリジンがHTTPS、プロキシがBad Gateway",

			origin: originTLS,
			proxy:  badGateway,

			error: regexp.MustCompile(`\AGet https://(?:.+): Bad Gateway\z`),
		},
	}

	for _, tc := range testCases {
		fmt.Printf("%s: ", tc.title)

		u, err := url.Parse(tc.proxy.URL)
		if err != nil {
			panic(err)
		}
		c := http.Client{
			Transport: &http.Transport{
				TLSClientConfig: &tls.Config{
					InsecureSkipVerify: true,
				},
				Proxy: http.ProxyURL(u),
			},
		}
		res, err := c.Get(tc.origin.URL)
		if tc.status != 0 {
			if res == nil {
				panic("res expected")
			}
			if tc.status != res.StatusCode {
				panic(res.StatusCode)
			}
			fmt.Printf("%dを返す\n", tc.status)
		}
		if tc.error != nil {
			if !tc.error.MatchString(err.Error()) {
				panic(err)
			}
			fmt.Printf("エラーになる\n")
		}
	}
}

func originTLS() *httptest.Server {
	return httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello TLS")
	}))
}

func origin() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello")
	}))
}

func okProxy() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		up, err := net.Dial("tcp", r.Host)
		if err != nil {
			panic(err)
		}
		w.WriteHeader(http.StatusOK)
		hijacker, _ := w.(http.Hijacker)
		down, _, err := hijacker.Hijack()
		if err != nil {
			panic(err)
		}
		var wg sync.WaitGroup

		wg.Add(1)
		go func() {
			defer wg.Done()
			if _, err := io.Copy(up, down); err != nil {
				panic(err)
			}
		}()

		wg.Add(1)
		go func() {
			defer wg.Done()
			if _, err := io.Copy(down, up); err != nil {
				panic(err)
			}
		}()

		wg.Wait()
		if err := up.Close(); err != nil {
			panic(err)
		}
		if err := down.Close(); err != nil {
			panic(err)
		}
	}))
}

func badGatewayProxy() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "", http.StatusBadGateway)
	}))
}
オリジンがHTTP、プロキシがOK: 200を返す
オリジンがHTTPS、プロキシがOK: 200を返す
オリジンがHTTP、プロキシがBad Gateway: 502を返す
オリジンがHTTPS、プロキシがBad Gateway: エラーになる

また、HTTPSだったときはプロキシのアウトバウンド側のコネクションを閉じたときもエラーになる