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

[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:     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

[Source]

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

[Source]

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

[Source]

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

[Source]

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

[Source]

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

[Source]

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

[Source]

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

[Source]

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

[Source]

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

[Source]

     # 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

[Source]

     # File lib/rubygems/remote_fetcher.rb, line 167
167:   def unescape(str)
168:     return unless str
169:     URI.unescape(str)
170:   end

[Validate]