Extended maintenance of Ruby versions 1.8.7 and 1.9.2 will end on July 31, 2014. 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

    • “/yaml” - YAML dump of metadata for installed gems - deprecated

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, addresses = nil) click to toggle source

Only the first directory in gem_dirs is used for serving gems

 
               # File rubygems/server.rb, line 438
def initialize(gem_dirs, port, daemon, addresses = nil)
  Socket.do_not_reverse_lookup = true

  @gem_dirs = Array gem_dirs
  @port = port
  @daemon = daemon
  @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

  @source_index = Gem::SourceIndex.from_gems_in(*@spec_dirs)
end
            
run(options) click to toggle source
 
               # File rubygems/server.rb, line 430
def self.run(options)
  new(options[:gemdir], options[:port], options[:daemon],
      options[:addresses]).run
end
            

Public Instance Methods

Marshal(req, res) click to toggle source
 
               # File rubygems/server.rb, line 461
def Marshal(req, res)
  @source_index.refresh!

  add_date res

  index = Marshal.dump @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 483
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 489
def latest_specs(req, res)
  @source_index.refresh!

  res['content-type'] = 'application/x-gzip'

  add_date res

  specs = @source_index.latest_specs.sort.map do |spec|
    platform = spec.original_platform
    platform = Gem::Platform::RUBY if platform.nil?
    [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
            
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)
  @source_index.refresh!

  res['content-type'] = 'text/plain'
  add_date res

  case req.request_uri.path
  when '/quick/index' then
    res.body << @source_index.map { |name,| name }.sort.join("\n")
  when '/quick/index.rz' then
    index = @source_index.map { |name,| name }.sort.join("\n")
    res['content-type'] = 'application/x-deflate'
    res.body << Gem.deflate(index)
  when '/quick/latest_index' then
    index = @source_index.latest_specs.map { |spec| spec.full_name }
    res.body << index.sort.join("\n")
  when '/quick/latest_index.rz' then
    index = @source_index.latest_specs.map { |spec| spec.full_name }
    res['content-type'] = 'application/x-deflate'
    res.body << Gem.deflate(index.sort.join("\n"))
  when %r^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)-([0-9.]+)(-.*?)?\.gemspec\.rz$| then
    dep = Gem::Dependency.new $2, $3
    specs = @source_index.search dep
    marshal_format = $1

    selector = [$2, $3, $4].map { |s| s.inspect }.join ' '

    platform = if $4 then
                 Gem::Platform.new $4.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))
    else # deprecated YAML format
      res['content-type'] = 'application/x-deflate'
      res.body << Gem.deflate(specs.first.to_yaml)
    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 716
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 602
def root(req, res)
  @source_index.refresh!
  add_date res

  raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless
    req.path == '/'

  specs = []
  total_file_count = 0

  @source_index.each do |path, 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

    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 }

  result = template.result binding
  res.body = result
end
            
run() click to toggle source
 
               # File rubygems/server.rb, line 766
def run
  listen

  WEBrick::Daemon.start if @daemon

  @server.mount_proc "/yaml", method(:yaml)
  @server.mount_proc "/yaml.Z", method(:yaml)

  @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! }

  @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 735
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 809
def specs(req, res)
  @source_index.refresh!

  add_date res

  specs = @source_index.sort.map do |_, spec|
    platform = spec.original_platform
    platform = Gem::Platform::RUBY if platform.nil?
    [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
            
yaml(req, res) click to toggle source
 
               # File rubygems/server.rb, line 836
def yaml(req, res)
  @source_index.refresh!

  add_date res

  index = @source_index.to_yaml

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

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

  res.body << index
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.

blog comments powered by Disqus