てきとうなメモ

本の感想とか技術メモとか

Bundlerのproxy設定はメタデータの取得とgemのインストールで異なる

メタデータの取得は*_proxy環境変数を見るのみなのだが、gemのダウンロードはrubygemを利用していているので、環境変数だけでなく.gemrcのhttp_proxyの設定を見てしまう。さらに、.gemrcの方を優先してしまう。

そのため、.gemrcに間違った設定を入れていると環境変数の方に正しい設定をしていても、メタデータのみ取得してgemは取得できないというようなことになってしまう。

例えば、proxyのない環境で.gemrcに

http_proxy: http://hogehoge:8080

と存在しないproxyの設定をして、bundle installを実行すると

$ bundle install
Fetching gem metadata from https://rubygems.org/...........
Fetching version metadata from https://rubygems.org/...
Fetching dependency metadata from https://rubygems.org/..
Using rake 10.4.2

とうまくいっているように見えて、gemをインストールする段階になると以下のようなエラーになる。

Gem::RemoteFetcher::UnknownHostError: no such name (https://rubygems.org/gems/faker-1.1.2.gem)
An error occurred while installing faker (1.1.2), and Bundler cannot continue.
Make sure that `gem install faker -v '1.1.2'` succeeds before bundling.

コード的にはだいたいこんな感じ。(Bundler v1.10.0)

メタデータの取得時にはlib/bundler/fetcher.rbのfetch_specメソッドの中で、

    def fetch_spec(spec)
      spec = spec - [nil, 'ruby', ''] 
      spec_file_name = "#{spec.join '-'}.gemspec"

      uri = URI.parse("#{remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}.rz")
      if uri.scheme == 'file'
        Bundler.load_marshal Gem.inflate(Gem.read_binary(uri.path))
      elsif cached_spec_path = gemspec_cached_path(spec_file_name)
        Bundler.load_gemspec(cached_spec_path)
      else
        Bundler.load_marshal Gem.inflate(downloader.fetch uri)
      end 
    rescue MarshalError
      raise HTTPError, "Gemspec #{spec} contained invalid data.\n" \
        "Your network or your gem server is probably having issues right now."
    end 

downloader.fetchして、lib/bundler/fetcher/downloader.rbのfetchメソッドの中で

      def fetch(uri, counter = 0)
        raise HTTPError, "Too many redirects" if counter >= redirect_limit

        response = request(uri)

requestメソッドを呼び、requestメソッドの中で

      def request(uri)
        Bundler.ui.debug "HTTP GET #{uri}"
        req = Net::HTTP::Get.new uri.request_uri

Net::HTTPを利用しているので*_proxy環境変数を見ることになる。

gemのインストールの場合はlib/bundler/rubygems_integration.rbのdownload_gemメソッドの中で

    def download_gem(spec, uri, path)
      uri = Bundler.settings.mirror_for(uri)
      fetcher = Gem::RemoteFetcher.new(configuration[:http_proxy])
      fetcher.download(spec, uri, path)
    end

configuration[:http_proxy]を引数にGem::RemoteFetcherを生成している。configurationはgemの設定ファイル(.gemrc)であり、Gem::RemoteFetcherのコンストラクタは引数としてproxyをとり、*_proxy環境変数より優先させる。