class Gem::Server
def self.run(options)
def self.run(options) new(options[:gemdir], options[:port], options[:daemon], options[:launch], options[:addresses]).run end
def add_date(res)
def add_date(res) res['date'] = @spec_dirs.map do |spec_dir| File.stat(spec_dir).mtime end.max end
def doc_root(gem_name)
def doc_root(gem_name) if have_rdoc_4_plus? "/doc_root/#{u gem_name}/" else "/doc_root/#{u gem_name}/rdoc/index.html" end end
def have_rdoc_4_plus?
def have_rdoc_4_plus? @have_rdoc_4_plus ||= Gem::Requirement.new('>= 4.0.0.preview2').satisfied_by? Gem::RDoc.rdoc_version end
def initialize(gem_dirs, port, daemon, launch = nil, addresses = nil)
def initialize(gem_dirs, port, daemon, launch = nil, addresses = nil) begin require 'webrick' rescue LoadError abort "webrick is not found. You may need to `gem install webrick` to install webrick." end Gem::RDoc.load_rdoc Socket.do_not_reverse_lookup = true @gem_dirs = Array gem_dirs @port = port @daemon = daemon @launch = launch @addresses = addresses logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger @spec_dirs = @gem_dirs.map {|gem_dir| File.join gem_dir, 'specifications' } @spec_dirs.reject! {|spec_dir| !File.directory? spec_dir } reset_gems @have_rdoc_4_plus = nil end
def latest_specs(req, res)
def latest_specs(req, res) reset_gems res['content-type'] = 'application/x-gzip' add_date res latest_specs = Gem::Specification.latest_specs specs = latest_specs.sort.map do |spec| platform = spec.original_platform || Gem::Platform::RUBY [spec.name, spec.version, platform] end specs = Marshal.dump specs if req.path =~ /\.gz$/ specs = Gem::Util.gzip specs res['content-type'] = 'application/x-gzip' else res['content-type'] = 'application/octet-stream' end if req.request_method == 'HEAD' res['content-length'] = specs.length else res.body << specs end end
def launch
def launch listeners = @server.listeners.map{|l| l.addr[2] } # TODO: 0.0.0.0 == any, not localhost. host = listeners.any?{|l| l == '0.0.0.0' } ? 'localhost' : listeners.first say "Launching browser to http://#{host}:#{@port}" system("#{@launch} http://#{host}:#{@port}") end
def listen(addresses = @addresses)
def listen(addresses = @addresses) addresses = [nil] unless addresses listeners = 0 addresses.each do |address| begin @server.listen address, @port @server.listeners[listeners..-1].each do |listener| host, port = listener.addr.values_at 2, 1 host = "[#{host}]" if host =~ /:/ # we don't reverse lookup say "Server started at http://#{host}:#{port}" end listeners = @server.listeners.length rescue SystemCallError next end end if @server.listeners.empty? say "Unable to start a server." say "Check for running servers or your --bind and --port arguments" terminate_interaction 1 end end
def prerelease_specs(req, res)
def prerelease_specs(req, res) reset_gems res['content-type'] = 'application/x-gzip' add_date res specs = Gem::Specification.select do |spec| spec.version.prerelease? end.sort.map do |spec| platform = spec.original_platform || Gem::Platform::RUBY [spec.name, spec.version, platform] end specs = Marshal.dump specs if req.path =~ /\.gz$/ specs = Gem::Util.gzip specs res['content-type'] = 'application/x-gzip' else res['content-type'] = 'application/octet-stream' end if req.request_method == 'HEAD' res['content-length'] = specs.length else res.body << specs end end
def quick(req, res)
def quick(req, res) reset_gems res['content-type'] = 'text/plain' add_date res case req.request_uri.path when %r{^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)\.gemspec\.rz$} then marshal_format, full_name = $1, $2 specs = Gem::Specification.find_all_by_full_name(full_name) selector = full_name.inspect if specs.empty? res.status = 404 res.body = "No gems found matching #{selector}" elsif specs.length > 1 res.status = 500 res.body = "Multiple gems found matching #{selector}" elsif marshal_format res['content-type'] = 'application/x-deflate' res.body << Gem.deflate(Marshal.dump(specs.first)) end else raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." end end
def rdoc(req, res)
def rdoc(req, res) query = req.query['q'] show_rdoc_for_pattern("#{query}*", res) && return show_rdoc_for_pattern("*#{query}*", res) && return template = ERB.new RDOC_NO_DOCUMENTATION res['content-type'] = 'text/html' res.body = template.result binding end
def reset_gems # :nodoc:
def reset_gems # :nodoc: Gem::Specification.dirs = @gem_dirs end
def root(req, res)
def root(req, res) reset_gems add_date res raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless req.path == '/' specs = [] total_file_count = 0 Gem::Specification.each do |spec| total_file_count += spec.files.size deps = spec.dependencies.map do |dep| { "name" => dep.name, "type" => dep.type, "version" => dep.requirement.to_s, } end deps = deps.sort_by {|dep| [dep["name"].downcase, dep["version"]] } deps.last["is_last"] = true unless deps.empty? # executables executables = spec.executables.sort.collect {|exec| {"executable" => exec} } executables = nil if executables.empty? executables.last["is_last"] = true if executables # Pre-process spec homepage for safety reasons begin homepage_uri = URI.parse(spec.homepage) if [URI::HTTP, URI::HTTPS].member? homepage_uri.class homepage_uri = spec.homepage else homepage_uri = "." end rescue URI::InvalidURIError homepage_uri = "." end specs << { "authors" => spec.authors.sort.join(", "), "date" => spec.date.to_s, "dependencies" => deps, "doc_path" => doc_root(spec.full_name), "executables" => executables, "only_one_executable" => (executables && executables.size == 1), "full_name" => spec.full_name, "has_deps" => !deps.empty?, "homepage" => homepage_uri, "name" => spec.name, "rdoc_installed" => Gem::RDoc.new(spec).rdoc_installed?, "ri_installed" => Gem::RDoc.new(spec).ri_installed?, "summary" => spec.summary, "version" => spec.version.to_s, } end specs << { "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others", "dependencies" => [], "doc_path" => doc_root("rubygems-#{Gem::VERSION}"), "executables" => [{"executable" => 'gem', "is_last" => true}], "only_one_executable" => true, "full_name" => "rubygems-#{Gem::VERSION}", "has_deps" => false, "homepage" => "https://guides.rubygems.org/", "name" => 'rubygems', "ri_installed" => true, "summary" => "RubyGems itself", "version" => Gem::VERSION, } specs = specs.sort_by {|spec| [spec["name"].downcase, spec["version"]] } specs.last["is_last"] = true # tag all specs with first_name_entry last_spec = nil specs.each do |spec| is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase) spec["first_name_entry"] = is_first last_spec = spec end # create page from template template = ERB.new(DOC_TEMPLATE) res['content-type'] = 'text/html' values = { "gem_count" => specs.size.to_s, "specs" => specs, "total_file_count" => total_file_count.to_s } # suppress 1.9.3dev warning about unused variable values = values result = template.result binding res.body = result end
def run
def run listen WEBrick::Daemon.start if @daemon @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs) @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs) @server.mount_proc "/latest_specs.#{Gem.marshal_version}", method(:latest_specs) @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz", method(:latest_specs) @server.mount_proc "/prerelease_specs.#{Gem.marshal_version}", method(:prerelease_specs) @server.mount_proc "/prerelease_specs.#{Gem.marshal_version}.gz", method(:prerelease_specs) @server.mount_proc "/quick/", method(:quick) @server.mount_proc("/gem-server-rdoc-style.css") do |req, res| res['content-type'] = 'text/css' add_date res res.body << RDOC_CSS end @server.mount_proc "/", method(:root) @server.mount_proc "/rdoc", method(:rdoc) file_handlers = { '/gems' => '/cache/', } if have_rdoc_4_plus? @server.mount '/doc_root', RDoc::Servlet, '/doc_root' else file_handlers['/doc_root'] = '/doc/' end @gem_dirs.each do |gem_dir| file_handlers.each do |mount_point, mount_dir| @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler, File.join(gem_dir, mount_dir), true) end end trap("INT") { @server.shutdown; exit! } trap("TERM") { @server.shutdown; exit! } launch if @launch @server.start end
def show_rdoc_for_pattern(pattern, res)
def show_rdoc_for_pattern(pattern, res) found_gems = Dir.glob("{#{@gem_dirs.join ','}}/doc/#{pattern}").select do |path| File.exist? File.join(path, 'rdoc/index.html') end case found_gems.length when 0 return false when 1 new_path = File.basename(found_gems[0]) res.status = 302 res['Location'] = doc_root new_path return true else doc_items = [] found_gems.each do |file_name| base_name = File.basename(file_name) doc_items << { :name => base_name, :url => doc_root(new_path), :summary => '', } end template = ERB.new(RDOC_SEARCH_TEMPLATE) res['content-type'] = 'text/html' result = template.result binding res.body = result return true end end
def specs(req, res)
def specs(req, res) reset_gems add_date res specs = Gem::Specification.sort_by(&:sort_obj).map do |spec| platform = spec.original_platform || Gem::Platform::RUBY [spec.name, spec.version, platform] end specs = Marshal.dump specs if req.path =~ /\.gz$/ specs = Gem::Util.gzip specs res['content-type'] = 'application/x-gzip' else res['content-type'] = 'application/octet-stream' end if req.request_method == 'HEAD' res['content-length'] = specs.length else res.body << specs end end
def uri_encode(str)
def uri_encode(str) str.gsub(URI::UNSAFE) do |match| match.each_byte.map {|c| sprintf('%%%02X', c.ord) }.join end end