Gem::Server
and allows users to serve gems for consumption by ‘gem –remote-install`.
gem_server starts an HTTP server on the given port and serves the following:
-
“/” - Browsing of gem spec files for installed gems
-
“/specs.#{Gem.marshal_version}.gz” - specs name/version/platform index
-
“/latest_specs.#{Gem.marshal_version}.gz” - latest specs name/version/platform index
-
“/quick/” - Individual gemspecs
-
“/gems” - Direct access to download the installable gems
-
“/rdoc?q=” - Search for installed rdoc documentation
Usage
gem_server = Gem::Server.new Gem.dir, 8089, false gem_server.run
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 432
def initialize(gem_dirs, port, daemon, launch = nil, addresses = nil)
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
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 427
def self.run(options)
new(options[:gemdir], options[:port], options[:daemon],
options[:launch], options[:addresses]).run
end
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 453
def add_date res
res['date'] = @spec_dirs.map do |spec_dir|
File.stat(spec_dir).mtime
end.max
end
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 465
def doc_root gem_name
if have_rdoc_4_plus? then
"/doc_root/#{u gem_name}/"
else
"/doc_root/#{u gem_name}/rdoc/index.html"
end
end
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 473
def have_rdoc_4_plus?
@have_rdoc_4_plus ||=
Gem::Requirement.new('>= 4.0.0.preview2').satisfied_by? Gem::RDoc.rdoc_version
end
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 478
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$/ then
specs = Gem.gzip specs
res['content-type'] = 'application/x-gzip'
else
res['content-type'] = 'application/octet-stream'
end
if req.request_method == 'HEAD' then
res['content-length'] = specs.length
else
res.body << specs
end
end
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 876
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
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 512
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? then
say "Unable to start a server."
say "Check for running servers or your --bind and --port arguments"
terminate_interaction 1
end
end
Creates server sockets based on the addresses option. If no addresses were given a server socket for all interfaces is created.
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 539
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$/ then
specs = Gem.gzip specs
res['content-type'] = 'application/x-gzip'
else
res['content-type'] = 'application/octet-stream'
end
if req.request_method == 'HEAD' then
res['content-length'] = specs.length
else
res.body << specs
end
end
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 569
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}/)?(.*?)-([0-9.]+[^-]*?)(-.*?)?\.gemspec\.rz$| then
marshal_format, name, version, platform = $1, $2, $3, $4
specs = Gem::Specification.find_all_by_name name, version
selector = [name, version, platform].map(&:inspect).join ' '
platform = if platform then
Gem::Platform.new platform.sub(/^-/, '')
else
Gem::Platform::RUBY
end
specs = specs.select { |s| s.platform == platform }
if specs.empty? then
res.status = 404
res.body = "No gems found matching #{selector}"
elsif specs.length > 1 then
res.status = 500
res.body = "Multiple gems found matching #{selector}"
elsif marshal_format then
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
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 738
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
Can be used for quick navigation to the rdoc documentation. You can then define a search shortcut for your browser. E.g. in Firefox connect ‘shortcut:rdoc’ to localhost:8808/rdoc?q=%s template. Then you can directly open the ActionPack documentation by typing ‘rdoc actionp’. If there are multiple hits for the search term, they are presented as a list with links.
Search algorithm aims for an intuitive search:
-
first try to find the gems and documentation folders which name starts with the search term
-
search for entries, that contain the search term
-
show all the gems
If there is only one search hit, user is immediately redirected to the documentation for the particular gem, otherwise a list with results is shown.
Additional trick - install documentation for Ruby core
Note: please adjust paths accordingly use for example ‘locate yaml.rb’ and ‘gem environment’ to identify directories, that are specific for your local installation
-
install Ruby sources
cd /usr/src sudo apt-get source ruby
-
generate documentation
rdoc -o /usr/lib/ruby/gems/1.8/doc/core/rdoc \ /usr/lib/ruby/1.8 ruby1.8-1.8.7.72
By typing ‘rdoc core’ you can now access the core documentation
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 605
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 { |dep|
{
"name" => dep.name,
"type" => dep.type,
"version" => dep.requirement.to_s,
}
}
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" => "http://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
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 795
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? then
@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
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 764
def show_rdoc_for_pattern(pattern, res)
found_gems = Dir.glob("{#{@gem_dirs.join ','}}/doc/#{pattern}").select {|path|
File.exist? File.join(path, 'rdoc/index.html')
}
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
Returns true and prepares http response, if rdoc for the requested gem name pattern was found.
The search is based on the file system content, not on the gems metadata. This allows additional documentation folders like ‘core’ for the Ruby core documentation - just put it underneath the main doc folder.
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 850
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$/ then
specs = Gem.gzip specs
res['content-type'] = 'application/x-gzip'
else
res['content-type'] = 'application/octet-stream'
end
if req.request_method == 'HEAD' then
res['content-length'] = specs.length
else
res.body << specs
end
end
# File tmp/rubies/ruby-2.4.10/lib/rubygems/server.rb, line 459
def uri_encode(str)
str.gsub(URI::UNSAFE) do |match|
match.each_byte.map { |c| sprintf('%%%02X', c.ord) }.join
end
end