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