理系学生日記

おまえはいつまで学生気分なのか

golangでHTTP_PROXY、http_proxy環境変数の差異を吸収する

HTTP_PROXY なのか http_proxy なのかという大文字・小文字問題は Golang において実装時にどのように吸収するべきなのか、という疑問があったのですが、 net/http/transport の実装方法がなるほどなぁというかんじでした。

まず、主役となるのは envOnce という private struct。 コメントはぼくが勝手に入れています。

type envOnce struct {
    names []string    // 揺れがある環境変数名のスライス。
    once  sync.Once   // おなじみの Once
    val   string      // 環境変数に設定された値
}

例えば HTTP_PROXYhttp_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_PROXYNO_PROXY も同様に実装されています。