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.

Methods

Included Modules

Gem::UserInteraction

Classes and Modules

Class Gem::RemoteFetcher::FetchError

Public Class methods

Cached RemoteFetcher instance.

[Source]

    # 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

  • [String]: explicit specification of proxy; overrides any environment
              variable setting
    
  • nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER,
         HTTP_PROXY_PASS)
    
  • :no_proxy: ignore environment variables and _don‘t_ use a proxy

[Source]

    # 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

Public Instance methods

Creates or an HTTP connection based on uri, or retrieves an existing connection, using a proxy if needed.

[Source]

     # 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.

[Source]

     # 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

[Source]

     # 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.

[Source]

     # 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.

[Source]

     # 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.

[Source]

     # 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.

[Source]

     # 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.

[Source]

     # 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.

[Source]

     # 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.

[Source]

     # 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

[Source]

     # File lib/rubygems/remote_fetcher.rb, line 189
189:   def unescape(str)
190:     return unless str
191:     URI.unescape(str)
192:   end

[Validate]