Parent

Included Modules

Class Index [+]

Quicksearch

Gem::RemoteFetcher

RemoteFetcher handles the details of fetching gems and gem information from a remote source.

Public Class Methods

fetcher() click to toggle 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
new(proxy = nil) click to toggle source

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

    # 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

connection_for(uri) click to toggle source

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
download(spec, source_uri, install_dir = Gem.dir) click to toggle source

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]$/
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
escape(str) click to toggle source
     # File lib/rubygems/remote_fetcher.rb, line 192
192:   def escape(str)
193:     return unless str
194:     URI.escape(str)
195:   end
fetch_path(uri, mtime = nil, head = false) click to toggle source

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
fetch_size(uri) click to toggle source

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
get_proxy_from_env() click to toggle source

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_uri(uri) click to toggle source

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
open_uri_or_path(uri, last_modified = nil, head = false, depth = 0) click to toggle source

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
request(uri, request_class, last_modified = nil) click to toggle source

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
reset(connection) click to toggle source

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
unescape(str) click to toggle source
     # File lib/rubygems/remote_fetcher.rb, line 197
197:   def unescape(str)
198:     return unless str
199:     URI.unescape(str)
200:   end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.