| 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