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 = nil) 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 232 232: def connection_for(uri) 233: net_http_args = [uri.host, uri.port] 234: 235: if @proxy_uri then 236: net_http_args += [ 237: @proxy_uri.host, 238: @proxy_uri.port, 239: @proxy_uri.user, 240: @proxy_uri.password 241: ] 242: end 243: 244: connection_id = net_http_args.join ':' 245: @connections[connection_id] ||= Net::HTTP.new(*net_http_args) 246: connection = @connections[connection_id] 247: 248: if uri.scheme == 'https' and not connection.started? then 249: require 'net/https' 250: connection.use_ssl = true 251: connection.verify_mode = OpenSSL::SSL::VERIFY_NONE 252: end 253: 254: connection.start unless connection.started? 255: 256: connection 257: rescue Errno::EHOSTDOWN => e 258: raise FetchError.new(e.message, uri) 259: 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.file_name 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: # Always escape URI's to deal with potential spaces and such 90: unless URI::Generic === source_uri 91: source_uri = URI.parse(URI.const_defined?(:DEFAULT_PARSER) ? 92: URI::DEFAULT_PARSER.escape(source_uri) : 93: URI.escape(source_uri)) 94: end 95: 96: scheme = source_uri.scheme 97: 98: # URI.parse gets confused by MS Windows paths with forward slashes. 99: scheme = nil if scheme =~ /^[a-z]$/i 100: 101: case scheme 102: when 'http', 'https' then 103: unless File.exist? local_gem_path then 104: begin 105: say "Downloading gem #{gem_file_name}" if 106: Gem.configuration.really_verbose 107: 108: remote_gem_path = source_uri + "gems/#{gem_file_name}" 109: 110: gem = self.fetch_path remote_gem_path 111: rescue Gem::RemoteFetcher::FetchError 112: raise if spec.original_platform == spec.platform 113: 114: alternate_name = "#{spec.original_name}.gem" 115: 116: say "Failed, downloading gem #{alternate_name}" if 117: Gem.configuration.really_verbose 118: 119: remote_gem_path = source_uri + "gems/#{alternate_name}" 120: 121: gem = self.fetch_path remote_gem_path 122: end 123: 124: File.open local_gem_path, 'wb' do |fp| 125: fp.write gem 126: end 127: end 128: when 'file' then 129: begin 130: path = source_uri.path 131: path = File.dirname(path) if File.extname(path) == '.gem' 132: 133: remote_gem_path = File.join(path, 'gems', gem_file_name) 134: 135: FileUtils.cp(remote_gem_path, local_gem_path) 136: rescue Errno::EACCES 137: local_gem_path = source_uri.to_s 138: end 139: 140: say "Using local gem #{local_gem_path}" if 141: Gem.configuration.really_verbose 142: when nil then # TODO test for local overriding cache 143: source_path = if Gem.win_platform? && source_uri.scheme && 144: !source_uri.path.include?(':') then 145: "#{source_uri.scheme}:#{source_uri.path}" 146: else 147: source_uri.path 148: end 149: 150: source_path = URI.unescape source_path 151: 152: begin 153: FileUtils.cp source_path, local_gem_path unless 154: File.expand_path(source_path) == File.expand_path(local_gem_path) 155: rescue Errno::EACCES 156: local_gem_path = source_uri.to_s 157: end 158: 159: say "Using local gem #{local_gem_path}" if 160: Gem.configuration.really_verbose 161: else 162: raise Gem::InstallError, "unsupported URI scheme #{source_uri.scheme}" 163: end 164: 165: local_gem_path 166: end
# File lib/rubygems/remote_fetcher.rb, line 192 192: def escape(str) 193: return unless str 194: URI.escape(str) 195: end
Downloads uri and returns it as a String.
# File lib/rubygems/remote_fetcher.rb, line 171 171: def fetch_path(uri, mtime = nil, head = false) 172: data = open_uri_or_path uri, mtime, head 173: data = Gem.gunzip data if data and not head and uri.to_s =~ /gz$/ 174: data 175: rescue FetchError 176: raise 177: rescue Timeout::Error 178: raise FetchError.new('timed out', uri) 179: rescue IOError, SocketError, SystemCallError => e 180: raise FetchError.new("#{e.class}: #{e}", uri) 181: end
Returns the size of uri in bytes.
# File lib/rubygems/remote_fetcher.rb, line 186 186: def fetch_size(uri) # TODO: phase this out 187: response = fetch_path(uri, nil, true) 188: 189: response['content-length'].to_i 190: end
Returns an HTTP proxy URI if one is set in the environment variables.
# File lib/rubygems/remote_fetcher.rb, line 205 205: def get_proxy_from_env 206: env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY'] 207: 208: return nil if env_proxy.nil? or env_proxy.empty? 209: 210: uri = URI.parse(normalize_uri(env_proxy)) 211: 212: if uri and uri.user.nil? and uri.password.nil? then 213: # Probably we have http_proxy_* variables? 214: uri.user = escape(ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']) 215: uri.password = escape(ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS']) 216: end 217: 218: uri 219: end
Normalize the URI by adding "http://" if it is missing.
# File lib/rubygems/remote_fetcher.rb, line 224 224: def normalize_uri(uri) 225: (uri =~ /^(https?|ftp|file):/) ? uri : "http://#{uri}" 226: 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 265 265: def open_uri_or_path(uri, last_modified = nil, head = false, depth = 0) 266: raise "block is dead" if block_given? 267: 268: uri = URI.parse uri unless URI::Generic === uri 269: 270: # This check is redundant unless Gem::RemoteFetcher is likely 271: # to be used directly, since the scheme is checked elsewhere. 272: # - Daniel Berger 273: unless ['http', 'https', 'file'].include?(uri.scheme) 274: raise ArgumentError, 'uri scheme is invalid' 275: end 276: 277: if uri.scheme == 'file' 278: path = uri.path 279: 280: # Deal with leading slash on Windows paths 281: if path[0].chr == '/' && path[1].chr =~ /[a-zA-Z]/ && path[2].chr == ':' 282: path = path[1..-1] 283: end 284: 285: return Gem.read_binary(path) 286: end 287: 288: fetch_type = head ? Net::HTTP::Head : Net::HTTP::Get 289: response = request uri, fetch_type, last_modified 290: 291: case response 292: when Net::HTTPOK, Net::HTTPNotModified then 293: head ? response : response.body 294: when Net::HTTPMovedPermanently, Net::HTTPFound, Net::HTTPSeeOther, 295: Net::HTTPTemporaryRedirect then 296: raise FetchError.new('too many redirects', uri) if depth > 10 297: 298: open_uri_or_path(response['Location'], last_modified, head, depth + 1) 299: else 300: raise FetchError.new("bad response #{response.message} #{response.code}", uri) 301: end 302: 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 309 309: def request(uri, request_class, last_modified = nil) 310: request = request_class.new uri.request_uri 311: 312: unless uri.nil? || uri.user.nil? || uri.user.empty? then 313: request.basic_auth uri.user, uri.password 314: end 315: 316: ua = "RubyGems/#{Gem::VERSION} #{Gem::Platform.local}" 317: ua << " Ruby/#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}" 318: ua << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL 319: ua << ")" 320: 321: request.add_field 'User-Agent', ua 322: request.add_field 'Connection', 'keep-alive' 323: request.add_field 'Keep-Alive', '30' 324: 325: if last_modified then 326: last_modified = last_modified.utc 327: request.add_field 'If-Modified-Since', last_modified.rfc2822 328: end 329: 330: yield request if block_given? 331: 332: connection = connection_for uri 333: 334: retried = false 335: bad_response = false 336: 337: begin 338: @requests[connection.object_id] += 1 339: 340: say "#{request.method} #{uri}" if 341: Gem.configuration.really_verbose 342: response = connection.request request 343: say "#{response.code} #{response.message}" if 344: Gem.configuration.really_verbose 345: 346: rescue Net::HTTPBadResponse 347: say "bad response" if Gem.configuration.really_verbose 348: 349: reset connection 350: 351: raise FetchError.new('too many bad responses', uri) if bad_response 352: 353: bad_response = true 354: retry 355: # HACK work around EOFError bug in Net::HTTP 356: # NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible 357: # to install gems. 358: rescue EOFError, Timeout::Error, 359: Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE 360: 361: requests = @requests[connection.object_id] 362: say "connection reset after #{requests} requests, retrying" if 363: Gem.configuration.really_verbose 364: 365: raise FetchError.new('too many connection resets', uri) if retried 366: 367: reset connection 368: 369: retried = true 370: retry 371: end 372: 373: response 374: end
Resets HTTP connection connection.
# File lib/rubygems/remote_fetcher.rb, line 379 379: def reset(connection) 380: @requests.delete connection.object_id 381: 382: connection.finish 383: connection.start 384: end