Extended maintenance of Ruby 1.9.3 ended on February 23, 2015. Read more

In Files

  • rubygems/server.rb

Class/Module Index [+]

Quicksearch

Gem::Server

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

  • legacy indexes:

    • “/Marshal.#{Gem.marshal_version}” - Full SourceIndex dump of metadata for installed gems

Usage

gem_server = Gem::Server.new Gem.dir, 8089, false
gem_server.run

Constants

DOC_TEMPLATE
RDOC_CSS

CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108

RDOC_NO_DOCUMENTATION
RDOC_SEARCH_TEMPLATE
SEARCH

Attributes

spec_dirs[R]

Public Class Methods

new(gem_dirs, port, daemon, launch = nil, addresses = nil) click to toggle source

Only the first directory in gem_dirs is used for serving gems

 
               # File rubygems/server.rb, line 436
def initialize(gem_dirs, port, daemon, launch = nil, addresses = nil)
  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 do |gem_dir|
    spec_dir = File.join gem_dir, 'specifications'

    unless File.directory? spec_dir then
      raise ArgumentError, "#{gem_dir} does not appear to be a gem repository"
    end

    spec_dir
  end

  Gem::Specification.dirs = @gem_dirs
end
            
run(options) click to toggle source
 
               # File rubygems/server.rb, line 428
def self.run(options)
  new(options[:gemdir], options[:port], options[:daemon],
      options[:launch], options[:addresses]).run
end
            

Public Instance Methods

Marshal(req, res) click to toggle source
 
               # File rubygems/server.rb, line 460
def Marshal(req, res)
  Gem::Specification.reset

  add_date res

  index = Gem::Deprecate.skip_during { Marshal.dump Gem.source_index }

  if req.request_method == 'HEAD' then
    res['content-length'] = index.length
    return
  end

  if req.path =~ /Z$/ then
    res['content-type'] = 'application/x-deflate'
    index = Gem.deflate index
  else
    res['content-type'] = 'application/octet-stream'
  end

  res.body << index
end
            
add_date(res) click to toggle source
 
               # File rubygems/server.rb, line 482
def add_date res
  res['date'] = @spec_dirs.map do |spec_dir|
    File.stat(spec_dir).mtime
  end.max
end
            
latest_specs(req, res) click to toggle source
 
               # File rubygems/server.rb, line 488
def latest_specs(req, res)
  Gem::Specification.reset

  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
            
launch() click to toggle source
 
               # File rubygems/server.rb, line 822
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
            
listen(addresses = @addresses) click to toggle source

Creates server sockets based on the addresses option. If no addresses were given a server socket for all interfaces is created.

 
               # File rubygems/server.rb, line 522
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
            
quick(req, res) click to toggle source
 
               # File rubygems/server.rb, line 549
def quick(req, res)
  Gem::Specification.reset

  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
            
rdoc(req, res) click to toggle source

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:

  1. first try to find the gems and documentation folders which name starts with the search term

  2. search for entries, that contain the search term

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

  1. install ruby sources

    cd /usr/src
    sudo apt-get source ruby
  2. 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 rubygems/server.rb, line 704
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
            
root(req, res) click to toggle source
 
               # File rubygems/server.rb, line 585
def root(req, res)
  Gem::Specification.reset
  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

    specs << {
      "authors"             => spec.authors.sort.join(", "),
      "date"                => spec.date.to_s,
      "dependencies"        => deps,
      "doc_path"            => "/doc_root/#{spec.full_name}/rdoc/index.html",
      "executables"         => executables,
      "only_one_executable" => (executables && executables.size == 1),
      "full_name"           => spec.full_name,
      "has_deps"            => !deps.empty?,
      "homepage"            => spec.homepage,
      "name"                => spec.name,
      "rdoc_installed"      => Gem::DocManager.new(spec).rdoc_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}/rdoc/index.html",
    "executables" => [{"executable" => 'gem', "is_last" => true}],
    "only_one_executable" => true,
    "full_name" => "rubygems-#{Gem::VERSION}",
    "has_deps" => false,
    "homepage" => "http://docs.rubygems.org/",
    "name" => 'rubygems',
    "rdoc_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
            
run() click to toggle source
 
               # File rubygems/server.rb, line 754
def run
  listen

  WEBrick::Daemon.start if @daemon

  @server.mount_proc "/Marshal.#{Gem.marshal_version}", method(:Marshal)
  @server.mount_proc "/Marshal.#{Gem.marshal_version}.Z", method(:Marshal)

  @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 "/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)

  paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" }
  paths.each do |mount_point, mount_dir|
    @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler,
                  File.join(@gem_dirs.first, mount_dir), true)
  end

  trap("INT") { @server.shutdown; exit! }
  trap("TERM") { @server.shutdown; exit! }

  launch if @launch

  @server.start
end
            
show_rdoc_for_pattern(pattern, res) click to toggle source

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 rubygems/server.rb, line 723
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}/rdoc/index.html"
    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/#{base_name}/rdoc/index.html",
        :summary => ''
      }
    end

    template = ERB.new(RDOC_SEARCH_TEMPLATE)
    res['content-type'] = 'text/html'
    result = template.result binding
    res.body = result
    return true
  end
end
            
specs(req, res) click to toggle source
 
               # File rubygems/server.rb, line 796
def specs(req, res)
  Gem::Specification.reset

  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
            

Commenting is here to help enhance the documentation. For example, code samples, or clarification of the documentation.

If you have questions about Ruby or the documentation, please post to one of the Ruby mailing lists. You will get better, faster, help that way.

If you wish to post a correction of the docs, please do so, but also file bug report so that it can be corrected for the next release. Thank you.

If you want to help improve the Ruby documentation, please visit Documenting-ruby.org.