class Gem::Server - RDoc Documentation

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

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

    # File lib/rubygems/server.rb, line 430
    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

run(options) click to toggle source

    # File lib/rubygems/server.rb, line 425
    def self.run(options)
      new(options[:gemdir], options[:port], options[:daemon],
          options[:launch], options[:addresses]).run
    end

### Public Instance Methods

add_date(res) click to toggle source

    # File lib/rubygems/server.rb, line 457
    def add_date(res)
      res['date'] = @spec_dirs.map do |spec_dir|
        File.stat(spec_dir).mtime
      end.max
    end

doc_root(gem_name) click to toggle source

    # File lib/rubygems/server.rb, line 469
    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

have_rdoc_4_plus?() click to toggle source

    # File lib/rubygems/server.rb, line 477
    def have_rdoc_4_plus?
      @have_rdoc_4_plus ||=
        Gem::Requirement.new('>= 4.0.0.preview2').satisfied_by? Gem::RDoc.rdoc_version
    end

latest_specs(req, res) click to toggle source

    # File lib/rubygems/server.rb, line 482
    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

launch() click to toggle source

    # File lib/rubygems/server.rb, line 872
    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 lib/rubygems/server.rb, line 516
    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

prerelease_specs(req, res) click to toggle source

    # File lib/rubygems/server.rb, line 543
    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

quick(req, res) click to toggle source

    # File lib/rubygems/server.rb, line 573
    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

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 lib/rubygems/server.rb, line 734
    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 lib/rubygems/server.rb, line 601
    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

run() click to toggle source

    # File lib/rubygems/server.rb, line 791
    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

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 lib/rubygems/server.rb, line 760
    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

specs(req, res) click to toggle source

    # File lib/rubygems/server.rb, line 846
    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

uri_encode(str) click to toggle source

    # File lib/rubygems/server.rb, line 463
    def uri_encode(str)
      str.gsub(URI::UNSAFE) do |match|
        match.each_byte.map {|c| sprintf('%%%02X', c.ord) }.join
      end
    end