HTTP_PROXY
なのか http_proxy
なのかという大文字・小文字問題は Golang において実装時にどのように吸収するべきなのか、という疑問があったのですが、
net/http/transport の実装方法がなるほどなぁというかんじでした。
まず、主役となるのは envOnce
という private struct。
コメントはぼくが勝手に入れています。
type envOnce struct { names []string // 揺れがある環境変数名のスライス。 once sync.Once // おなじみの Once val string // 環境変数に設定された値 }
例えば HTTP_PROXY
と http_proxy
の差異の吸収は、以下の変数で行われます。
httpProxyEnv = &envOnce{ names: []string{"HTTP_PROXY", "http_proxy"}, }
環境変数の値の読込は、init メソッドで行います。
func (e *envOnce) init() { for _, n := range e.names { e.val = os.Getenv(n) if e.val != "" { return } } }
環境変数名をループさせて、実際に設定値があるかどうかをチェックするっていう素直な実装です。
問題は、PROXY を取得する都度、init()
を呼び出さないといけないのかってことで、それはちょっと面倒。
これを回避するのが once.Do です。
func (e *envOnce) Get() string { e.once.Do(e.init) return e.val }
クライアントが HTTP_PROXY の値を取得したいときは、上記の Get を呼び出すことになるのですが、 Get の呼び出し都度、あたかも init が呼び出されるように見えますが、この都度呼び出しを once.Do が防いでいます。
once.Do は、その引数の関数を、プロセスのライフタイムにおいて一度のみしか呼び出さない (初回以降呼び出された場合は、即実行を打ち切る) ようになっています。
このため、Get
の実装が上記のようになっていたとしても、init
は一度しか呼び出されないということになります。
このあたりの書き方がぼくにとってすごく面白く思えたので、エントリまで起こしてしまいました…。
なお、HTTPS_PROXY
、NO_PROXY
も同様に実装されています。