Class | Gem::RemoteFetcher |
In: |
lib/rubygems/remote_fetcher.rb
|
Parent: | Object |
RemoteFetcher handles the details of fetching gems and gem information from a remote source.
Cached RemoteFetcher instance.
# File lib/rubygems/remote_fetcher.rb, line 43 43: def self.fetcher 44: @fetcher ||= self.new Gem.configuration[:http_proxy] 45: end
Initialize a remote fetcher using the source URI and possible proxy information.
proxy
variable setting
HTTP_PROXY_PASS)
# File lib/rubygems/remote_fetcher.rb, line 58 58: def initialize(proxy) 59: Socket.do_not_reverse_lookup = true 60: 61: @connections = {} 62: @requests = Hash.new 0 63: @proxy_uri = 64: case proxy 65: when :no_proxy then nil 66: when nil then get_proxy_from_env 67: when URI::HTTP then proxy 68: else URI.parse(proxy) 69: end 70: end
Creates or an HTTP connection based on uri, or retrieves an existing connection, using a proxy if needed.
# File lib/rubygems/remote_fetcher.rb, line 202 202: def connection_for(uri) 203: net_http_args = [uri.host, uri.port] 204: 205: if @proxy_uri then 206: net_http_args += [ 207: @proxy_uri.host, 208: @proxy_uri.port, 209: @proxy_uri.user, 210: @proxy_uri.password 211: ] 212: end 213: 214: connection_id = net_http_args.join ':' 215: @connections[connection_id] ||= Net::HTTP.new(*net_http_args) 216: connection = @connections[connection_id] 217: 218: if uri.scheme == 'https' and not connection.started? then 219: require 'net/https' 220: connection.use_ssl = true 221: connection.verify_mode = OpenSSL::SSL::VERIFY_NONE 222: end 223: 224: connection.start unless connection.started? 225: 226: connection 227: end
Moves the gem spec from source_uri to the cache dir unless it is already there. If the source_uri is local the gem cache dir copy is always replaced.
# File lib/rubygems/remote_fetcher.rb, line 77 77: def download(spec, source_uri, install_dir = Gem.dir) 78: if File.writable?(install_dir) 79: cache_dir = File.join install_dir, 'cache' 80: else 81: cache_dir = File.join(Gem.user_dir, 'cache') 82: end 83: 84: gem_file_name = "#{spec.full_name}.gem" 85: local_gem_path = File.join cache_dir, gem_file_name 86: 87: FileUtils.mkdir_p cache_dir rescue nil unless File.exist? cache_dir 88: 89: source_uri = URI.parse source_uri unless URI::Generic === source_uri 90: scheme = source_uri.scheme 91: 92: # URI.parse gets confused by MS Windows paths with forward slashes. 93: scheme = nil if scheme =~ /^[a-z]$/i 94: 95: case scheme 96: when 'http', 'https' then 97: unless File.exist? local_gem_path then 98: begin 99: say "Downloading gem #{gem_file_name}" if 100: Gem.configuration.really_verbose 101: 102: remote_gem_path = source_uri + "gems/#{gem_file_name}" 103: 104: gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path 105: rescue Gem::RemoteFetcher::FetchError 106: raise if spec.original_platform == spec.platform 107: 108: alternate_name = "#{spec.original_name}.gem" 109: 110: say "Failed, downloading gem #{alternate_name}" if 111: Gem.configuration.really_verbose 112: 113: remote_gem_path = source_uri + "gems/#{alternate_name}" 114: 115: gem = Gem::RemoteFetcher.fetcher.fetch_path remote_gem_path 116: end 117: 118: File.open local_gem_path, 'wb' do |fp| 119: fp.write gem 120: end 121: end 122: when nil, 'file' then # TODO test for local overriding cache 123: begin 124: FileUtils.cp source_uri.to_s, local_gem_path 125: rescue Errno::EACCES 126: local_gem_path = source_uri.to_s 127: end 128: 129: say "Using local gem #{local_gem_path}" if 130: Gem.configuration.really_verbose 131: else 132: raise Gem::InstallError, "unsupported URI scheme #{source_uri.scheme}" 133: end 134: 135: local_gem_path 136: end
# File lib/rubygems/remote_fetcher.rb, line 162 162: def escape(str) 163: return unless str 164: URI.escape(str) 165: end
Downloads uri and returns it as a String.
# File lib/rubygems/remote_fetcher.rb, line 141 141: def fetch_path(uri, mtime = nil, head = false) 142: data = open_uri_or_path uri, mtime, head 143: data = Gem.gunzip data if data and not head and uri.to_s =~ /gz$/ 144: data 145: rescue FetchError 146: raise 147: rescue Timeout::Error 148: raise FetchError.new('timed out', uri) 149: rescue IOError, SocketError, SystemCallError => e 150: raise FetchError.new("#{e.class}: #{e}", uri) 151: end
Returns the size of uri in bytes.
# File lib/rubygems/remote_fetcher.rb, line 156 156: def fetch_size(uri) # TODO: phase this out 157: response = fetch_path(uri, nil, true) 158: 159: response['content-length'].to_i 160: end
Checks if the provided string is a file:// URI.
# File lib/rubygems/remote_fetcher.rb, line 332 332: def file_uri?(uri) 333: uri =~ %r{\Afile://} 334: end
Given a file:// URI, returns its local path.
# File lib/rubygems/remote_fetcher.rb, line 339 339: def get_file_uri_path(uri) 340: uri.sub(%r{\Afile://}, '') 341: end
Returns an HTTP proxy URI if one is set in the environment variables.
# File lib/rubygems/remote_fetcher.rb, line 175 175: def get_proxy_from_env 176: env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY'] 177: 178: return nil if env_proxy.nil? or env_proxy.empty? 179: 180: uri = URI.parse env_proxy 181: 182: if uri and uri.user.nil? and uri.password.nil? then 183: # Probably we have http_proxy_* variables? 184: uri.user = escape(ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']) 185: uri.password = escape(ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS']) 186: end 187: 188: uri 189: end
Normalize the URI by adding "http://" if it is missing.
# File lib/rubygems/remote_fetcher.rb, line 194 194: def normalize_uri(uri) 195: (uri =~ /^(https?|ftp|file):/) ? uri : "http://#{uri}" 196: end
Read the data from the (source based) URI, but if it is a file:// URI, read from the filesystem instead.
# File lib/rubygems/remote_fetcher.rb, line 233 233: def open_uri_or_path(uri, last_modified = nil, head = false, depth = 0) 234: raise "block is dead" if block_given? 235: 236: return open(get_file_uri_path(uri)) if file_uri? uri 237: 238: uri = URI.parse uri unless URI::Generic === uri 239: raise ArgumentError, 'uri is not an HTTP URI' unless URI::HTTP === uri 240: 241: fetch_type = head ? Net::HTTP::Head : Net::HTTP::Get 242: response = request uri, fetch_type, last_modified 243: 244: case response 245: when Net::HTTPOK, Net::HTTPNotModified then 246: head ? response : response.body 247: when Net::HTTPMovedPermanently, Net::HTTPFound, Net::HTTPSeeOther, 248: Net::HTTPTemporaryRedirect then 249: raise FetchError.new('too many redirects', uri) if depth > 10 250: 251: open_uri_or_path(response['Location'], last_modified, head, depth + 1) 252: else 253: raise FetchError.new("bad response #{response.message} #{response.code}", uri) 254: end 255: end
Performs a Net::HTTP request of type request_class on uri returning a Net::HTTP response object. request maintains a table of persistent connections to reduce connect overhead.
# File lib/rubygems/remote_fetcher.rb, line 262 262: def request(uri, request_class, last_modified = nil) 263: request = request_class.new uri.request_uri 264: 265: unless uri.nil? || uri.user.nil? || uri.user.empty? then 266: request.basic_auth uri.user, uri.password 267: end 268: 269: ua = "RubyGems/#{Gem::RubyGemsVersion} #{Gem::Platform.local}" 270: ua << " Ruby/#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}" 271: ua << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL 272: ua << ")" 273: 274: request.add_field 'User-Agent', ua 275: request.add_field 'Connection', 'keep-alive' 276: request.add_field 'Keep-Alive', '30' 277: 278: if last_modified then 279: last_modified = last_modified.utc 280: request.add_field 'If-Modified-Since', last_modified.rfc2822 281: end 282: 283: connection = connection_for uri 284: 285: retried = false 286: bad_response = false 287: 288: begin 289: @requests[connection.object_id] += 1 290: response = connection.request request 291: say "#{request.method} #{response.code} #{response.message}: #{uri}" if 292: Gem.configuration.really_verbose 293: rescue Net::HTTPBadResponse 294: reset connection 295: 296: raise FetchError.new('too many bad responses', uri) if bad_response 297: 298: bad_response = true 299: retry 300: # HACK work around EOFError bug in Net::HTTP 301: # NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible 302: # to install gems. 303: rescue EOFError, Errno::ECONNABORTED, Errno::ECONNRESET 304: requests = @requests[connection.object_id] 305: say "connection reset after #{requests} requests, retrying" if 306: Gem.configuration.really_verbose 307: 308: raise FetchError.new('too many connection resets', uri) if retried 309: 310: reset connection 311: 312: retried = true 313: retry 314: end 315: 316: response 317: end
Resets HTTP connection connection.
# File lib/rubygems/remote_fetcher.rb, line 322 322: def reset(connection) 323: @requests.delete connection.object_id 324: 325: connection.finish 326: connection.start 327: end