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