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