Class Gem::Server
In: lib/rubygems/server.rb
Parent: Object

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:

Usage

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

Methods

Marshal   latest_specs   new   quick   rdoc   root   run   run   show_rdoc_for_pattern   specs   yaml  

Included Modules

ERB::Util Gem::UserInteraction

Constants

SEARCH = <<-SEARCH <form class="headerSearch" name="headerSearchForm" method="get" action="/rdoc"> <div id="search" style="float:right"> <span>Filter/Search</span> <input id="q" type="text" style="width:10em" name="q"/> <button type="submit" style="display:none" /> </div> </form> SEARCH
DOC_TEMPLATE = <<-'DOC_TEMPLATE' <?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>RubyGems Documentation Index</title> <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" /> </head> <body> <div id="fileHeader"> <%= SEARCH %> <h1>RubyGems Documentation Index</h1> </div> <!-- banner header --> <div id="bodyContent"> <div id="contextContent"> <div id="description"> <h1>Summary</h1> <p>There are <%=values["gem_count"]%> gems installed:</p> <p> <%= values["specs"].map { |v| "<a href=\"##{v["name"]}\">#{v["name"]}</a>" }.join ', ' %>. <h1>Gems</h1> <dl> <% values["specs"].each do |spec| %> <dt> <% if spec["first_name_entry"] then %> <a name="<%=spec["name"]%>"></a> <% end %> <b><%=spec["name"]%> <%=spec["version"]%></b> <% if spec["rdoc_installed"] then %> <a href="<%=spec["doc_path"]%>">[rdoc]</a> <% else %> <span title="rdoc not installed">[rdoc]</span> <% end %> <% if spec["homepage"] then %> <a href="<%=spec["homepage"]%>" title="<%=spec["homepage"]%>">[www]</a> <% else %> <span title="no homepage available">[www]</span> <% end %> <% if spec["has_deps"] then %> - depends on <%= spec["dependencies"].map { |v| "<a href=\"##{v["name"]}\">#{v["name"]}</a>" }.join ', ' %>. <% end %> </dt> <dd> <%=spec["summary"]%> <% if spec["executables"] then %> <br/> <% if spec["only_one_executable"] then %> Executable is <% else %> Executables are <%end%> <%= spec["executables"].map { |v| "<span class=\"context-item-name\">#{v["executable"]}</span>"}.join ', ' %>. <%end%> <br/> <br/> </dd> <% end %> </dl> </div> </div> </div> <div id="validator-badges"> <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p> </div> </body> </html> DOC_TEMPLATE
RDOC_CSS = <<-RDOC_CSS body { font-family: Verdana,Arial,Helvetica,sans-serif; font-size: 90%; margin: 0; margin-left: 40px; padding: 0; background: white; } h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; } h1 { font-size: 150%; } h2,h3,h4 { margin-top: 1em; } a { background: #eef; color: #039; text-decoration: none; } a:hover { background: #039; color: #eef; } /* Override the base stylesheets Anchor inside a table cell */ td > a { background: transparent; color: #039; text-decoration: none; } /* and inside a section title */ .section-title > a { background: transparent; color: #eee; text-decoration: none; } /* === Structural elements =================================== */ div#index { margin: 0; margin-left: -40px; padding: 0; font-size: 90%; } div#index a { margin-left: 0.7em; } div#index .section-bar { margin-left: 0px; padding-left: 0.7em; background: #ccc; font-size: small; } div#classHeader, div#fileHeader { width: auto; color: white; padding: 0.5em 1.5em 0.5em 1.5em; margin: 0; margin-left: -40px; border-bottom: 3px solid #006; } div#classHeader a, div#fileHeader a { background: inherit; color: white; } div#classHeader td, div#fileHeader td { background: inherit; color: white; } div#fileHeader { background: #057; } div#classHeader { background: #048; } .class-name-in-header { font-size: 180%; font-weight: bold; } div#bodyContent { padding: 0 1.5em 0 1.5em; } div#description { padding: 0.5em 1.5em; background: #efefef; border: 1px dotted #999; } div#description h1,h2,h3,h4,h5,h6 { color: #125;; background: transparent; } div#validator-badges { text-align: center; } div#validator-badges img { border: 0; } div#copyright { color: #333; background: #efefef; font: 0.75em sans-serif; margin-top: 5em; margin-bottom: 0; padding: 0.5em 2em; } /* === Classes =================================== */ table.header-table { color: white; font-size: small; } .type-note { font-size: small; color: #DEDEDE; } .xxsection-bar { background: #eee; color: #333; padding: 3px; } .section-bar { color: #333; border-bottom: 1px solid #999; margin-left: -20px; } .section-title { background: #79a; color: #eee; padding: 3px; margin-top: 2em; margin-left: -30px; border: 1px solid #999; } .top-aligned-row { vertical-align: top } .bottom-aligned-row { vertical-align: bottom } /* --- Context section classes ----------------------- */ .context-row { } .context-item-name { font-family: monospace; font-weight: bold; color: black; } .context-item-value { font-size: small; color: #448; } .context-item-desc { color: #333; padding-left: 2em; } /* --- Method classes -------------------------- */ .method-detail { background: #efefef; padding: 0; margin-top: 0.5em; margin-bottom: 1em; border: 1px dotted #ccc; } .method-heading { color: black; background: #ccc; border-bottom: 1px solid #666; padding: 0.2em 0.5em 0 0.5em; } .method-signature { color: black; background: inherit; } .method-name { font-weight: bold; } .method-args { font-style: italic; } .method-description { padding: 0 0.5em 0 0.5em; } /* --- Source code sections -------------------- */ a.source-toggle { font-size: 90%; } div.method-source-code { background: #262626; color: #ffdead; margin: 1em; padding: 0.5em; border: 1px dashed #999; overflow: hidden; } div.method-source-code pre { color: #ffdead; overflow: hidden; } /* --- Ruby keyword styles --------------------- */ .standalone-code { background: #221111; color: #ffdead; overflow: hidden; } .ruby-constant { color: #7fffd4; background: transparent; } .ruby-keyword { color: #00ffff; background: transparent; } .ruby-ivar { color: #eedd82; background: transparent; } .ruby-operator { color: #00ffee; background: transparent; } .ruby-identifier { color: #ffdead; background: transparent; } .ruby-node { color: #ffa07a; background: transparent; } .ruby-comment { color: #b22222; font-weight: bold; background: transparent; } .ruby-regexp { color: #ffa07a; background: transparent; } .ruby-value { color: #7fffd4; background: transparent; } RDOC_CSS   CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108
RDOC_NO_DOCUMENTATION = <<-'NO_DOC' <?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Found documentation</title> <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" /> </head> <body> <div id="fileHeader"> <%= SEARCH %> <h1>No documentation found</h1> </div> <div id="bodyContent"> <div id="contextContent"> <div id="description"> <p>No gems matched <%= h query.inspect %></p> <p> Back to <a href="/">complete gem index</a> </p> </div> </div> </div> <div id="validator-badges"> <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p> </div> </body> </html> NO_DOC
RDOC_SEARCH_TEMPLATE = <<-'RDOC_SEARCH' <?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Found documentation</title> <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" /> </head> <body> <div id="fileHeader"> <%= SEARCH %> <h1>Found documentation</h1> </div> <!-- banner header --> <div id="bodyContent"> <div id="contextContent"> <div id="description"> <h1>Summary</h1> <p><%=doc_items.length%> documentation topics found.</p> <h1>Topics</h1> <dl> <% doc_items.each do |doc_item| %> <dt> <b><%=doc_item[:name]%></b> <a href="<%=doc_item[:url]%>">[rdoc]</a> </dt> <dd> <%=doc_item[:summary]%> <br/> <br/> </dd> <% end %> </dl> <p> Back to <a href="/">complete gem index</a> </p> </div> </div> </div> <div id="validator-badges"> <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p> </div> </body> </html> RDOC_SEARCH

Public Class methods

[Source]

     # File lib/rubygems/server.rb, line 437
437:   def initialize(gem_dir, port, daemon)
438:     Socket.do_not_reverse_lookup = true
439: 
440:     @gem_dir = gem_dir
441:     @port = port
442:     @daemon = daemon
443:     logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL
444:     @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger
445: 
446:     @spec_dir = File.join @gem_dir, 'specifications'
447: 
448:     unless File.directory? @spec_dir then
449:       raise ArgumentError, "#{@gem_dir} does not appear to be a gem repository"
450:     end
451: 
452:     @source_index = Gem::SourceIndex.from_gems_in @spec_dir
453:   end

[Source]

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

Public Instance methods

[Source]

     # File lib/rubygems/server.rb, line 455
455:   def Marshal(req, res)
456:     @source_index.refresh!
457: 
458:     res['date'] = File.stat(@spec_dir).mtime
459: 
460:     index = Marshal.dump @source_index
461: 
462:     if req.request_method == 'HEAD' then
463:       res['content-length'] = index.length
464:       return
465:     end
466: 
467:     if req.path =~ /Z$/ then
468:       res['content-type'] = 'application/x-deflate'
469:       index = Gem.deflate index
470:     else
471:       res['content-type'] = 'application/octet-stream'
472:     end
473: 
474:     res.body << index
475:   end

[Source]

     # File lib/rubygems/server.rb, line 477
477:   def latest_specs(req, res)
478:     @source_index.refresh!
479: 
480:     res['content-type'] = 'application/x-gzip'
481: 
482:     res['date'] = File.stat(@spec_dir).mtime
483: 
484:     specs = @source_index.latest_specs.sort.map do |spec|
485:       platform = spec.original_platform
486:       platform = Gem::Platform::RUBY if platform.nil?
487:       [spec.name, spec.version, platform]
488:     end
489: 
490:     specs = Marshal.dump specs
491: 
492:     if req.path =~ /\.gz$/ then
493:       specs = Gem.gzip specs
494:       res['content-type'] = 'application/x-gzip'
495:     else
496:       res['content-type'] = 'application/octet-stream'
497:     end
498: 
499:     if req.request_method == 'HEAD' then
500:       res['content-length'] = specs.length
501:     else
502:       res.body << specs
503:     end
504:   end

[Source]

     # File lib/rubygems/server.rb, line 506
506:   def quick(req, res)
507:     @source_index.refresh!
508: 
509:     res['content-type'] = 'text/plain'
510:     res['date'] = File.stat(@spec_dir).mtime
511: 
512:     case req.request_uri.path
513:     when '/quick/index' then
514:       res.body << @source_index.map { |name,| name }.sort.join("\n")
515:     when '/quick/index.rz' then
516:       index = @source_index.map { |name,| name }.sort.join("\n")
517:       res['content-type'] = 'application/x-deflate'
518:       res.body << Gem.deflate(index)
519:     when '/quick/latest_index' then
520:       index = @source_index.latest_specs.map { |spec| spec.full_name }
521:       res.body << index.sort.join("\n")
522:     when '/quick/latest_index.rz' then
523:       index = @source_index.latest_specs.map { |spec| spec.full_name }
524:       res['content-type'] = 'application/x-deflate'
525:       res.body << Gem.deflate(index.sort.join("\n"))
526:     when %r|^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)-([0-9.]+)(-.*?)?\.gemspec\.rz$| then
527:       dep = Gem::Dependency.new $2, $3
528:       specs = @source_index.search dep
529:       marshal_format = $1
530: 
531:       selector = [$2, $3, $4].map { |s| s.inspect }.join ' '
532: 
533:       platform = if $4 then
534:                    Gem::Platform.new $4.sub(/^-/, '')
535:                  else
536:                    Gem::Platform::RUBY
537:                  end
538: 
539:       specs = specs.select { |s| s.platform == platform }
540: 
541:       if specs.empty? then
542:         res.status = 404
543:         res.body = "No gems found matching #{selector}"
544:       elsif specs.length > 1 then
545:         res.status = 500
546:         res.body = "Multiple gems found matching #{selector}"
547:       elsif marshal_format then
548:         res['content-type'] = 'application/x-deflate'
549:         res.body << Gem.deflate(Marshal.dump(specs.first))
550:       else # deprecated YAML format
551:         res['content-type'] = 'application/x-deflate'
552:         res.body << Gem.deflate(specs.first.to_yaml)
553:       end
554:     else
555:       raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found."
556:     end
557:   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:

  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

[Source]

     # File lib/rubygems/server.rb, line 673
673:   def rdoc(req, res)
674:     query = req.query['q']
675:     show_rdoc_for_pattern("#{query}*", res) && return
676:     show_rdoc_for_pattern("*#{query}*", res) && return
677: 
678:     template = ERB.new RDOC_NO_DOCUMENTATION
679: 
680:     res['content-type'] = 'text/html'
681:     res.body = template.result binding
682:   end

[Source]

     # File lib/rubygems/server.rb, line 559
559:   def root(req, res)
560:     @source_index.refresh!
561:     res['date'] = File.stat(@spec_dir).mtime
562: 
563:     raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless
564:       req.path == '/'
565: 
566:     specs = []
567:     total_file_count = 0
568: 
569:     @source_index.each do |path, spec|
570:       total_file_count += spec.files.size
571:       deps = spec.dependencies.map do |dep|
572:         { "name"    => dep.name,
573:           "type"    => dep.type,
574:           "version" => dep.version_requirements.to_s, }
575:       end
576: 
577:       deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] }
578:       deps.last["is_last"] = true unless deps.empty?
579: 
580:       # executables
581:       executables = spec.executables.sort.collect { |exec| {"executable" => exec} }
582:       executables = nil if executables.empty?
583:       executables.last["is_last"] = true if executables
584: 
585:       specs << {
586:         "authors"             => spec.authors.sort.join(", "),
587:         "date"                => spec.date.to_s,
588:         "dependencies"        => deps,
589:         "doc_path"            => "/doc_root/#{spec.full_name}/rdoc/index.html",
590:         "executables"         => executables,
591:         "only_one_executable" => (executables && executables.size == 1),
592:         "full_name"           => spec.full_name,
593:         "has_deps"            => !deps.empty?,
594:         "homepage"            => spec.homepage,
595:         "name"                => spec.name,
596:         "rdoc_installed"      => Gem::DocManager.new(spec).rdoc_installed?,
597:         "summary"             => spec.summary,
598:         "version"             => spec.version.to_s,
599:       }
600:     end
601: 
602:     specs << {
603:       "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others",
604:       "dependencies" => [],
605:       "doc_path" => "/doc_root/rubygems-#{Gem::RubyGemsVersion}/rdoc/index.html",
606:       "executables" => [{"executable" => 'gem', "is_last" => true}],
607:       "only_one_executable" => true,
608:       "full_name" => "rubygems-#{Gem::RubyGemsVersion}",
609:       "has_deps" => false,
610:       "homepage" => "http://rubygems.org/",
611:       "name" => 'rubygems',
612:       "rdoc_installed" => true,
613:       "summary" => "RubyGems itself",
614:       "version" => Gem::RubyGemsVersion,
615:     }
616: 
617:     specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] }
618:     specs.last["is_last"] = true
619: 
620:     # tag all specs with first_name_entry
621:     last_spec = nil
622:     specs.each do |spec|
623:       is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase)
624:       spec["first_name_entry"] = is_first
625:       last_spec = spec
626:     end
627: 
628:     # create page from template
629:     template = ERB.new(DOC_TEMPLATE)
630:     res['content-type'] = 'text/html'
631: 
632:     values = { "gem_count" => specs.size.to_s, "specs" => specs,
633:                "total_file_count" => total_file_count.to_s }
634: 
635:     result = template.result binding
636:     res.body = result
637:   end

[Source]

     # File lib/rubygems/server.rb, line 723
723:   def run
724:     @server.listen nil, @port
725: 
726:     say "Starting gem server on http://localhost:#{@port}/"
727: 
728:     WEBrick::Daemon.start if @daemon
729: 
730:     @server.mount_proc "/yaml", method(:yaml)
731:     @server.mount_proc "/yaml.Z", method(:yaml)
732: 
733:     @server.mount_proc "/Marshal.#{Gem.marshal_version}", method(:Marshal)
734:     @server.mount_proc "/Marshal.#{Gem.marshal_version}.Z", method(:Marshal)
735: 
736:     @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs)
737:     @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs)
738: 
739:     @server.mount_proc "/latest_specs.#{Gem.marshal_version}",
740:                        method(:latest_specs)
741:     @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz",
742:                        method(:latest_specs)
743: 
744:     @server.mount_proc "/quick/", method(:quick)
745: 
746:     @server.mount_proc("/gem-server-rdoc-style.css") do |req, res|
747:       res['content-type'] = 'text/css'
748:       res['date'] = File.stat(@spec_dir).mtime
749:       res.body << RDOC_CSS
750:     end
751: 
752:     @server.mount_proc "/", method(:root)
753: 
754:     @server.mount_proc "/rdoc", method(:rdoc)
755: 
756:     paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" }
757:     paths.each do |mount_point, mount_dir|
758:       @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler,
759:                     File.join(@gem_dir, mount_dir), true)
760:     end
761: 
762:     trap("INT") { @server.shutdown; exit! }
763:     trap("TERM") { @server.shutdown; exit! }
764: 
765:     @server.start
766:   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.

[Source]

     # File lib/rubygems/server.rb, line 692
692:   def show_rdoc_for_pattern(pattern, res)
693:     found_gems = Dir.glob("#{@gem_dir}/doc/#{pattern}").select {|path|
694:       File.exist? File.join(path, 'rdoc/index.html')
695:     }
696:     case found_gems.length
697:     when 0
698:       return false
699:     when 1
700:       new_path = File.basename(found_gems[0])
701:       res.status = 302
702:       res['Location'] = "/doc_root/#{new_path}/rdoc/index.html"
703:       return true
704:     else
705:       doc_items = []
706:       found_gems.each do |file_name|
707:         base_name = File.basename(file_name)
708:         doc_items << {
709:           :name => base_name,
710:           :url => "/doc_root/#{base_name}/rdoc/index.html",
711:           :summary => ''
712:         }
713:       end
714: 
715:       template = ERB.new(RDOC_SEARCH_TEMPLATE)
716:       res['content-type'] = 'text/html'
717:       result = template.result binding
718:       res.body = result
719:       return true
720:     end
721:   end

[Source]

     # File lib/rubygems/server.rb, line 768
768:   def specs(req, res)
769:     @source_index.refresh!
770: 
771:     res['date'] = File.stat(@spec_dir).mtime
772: 
773:     specs = @source_index.sort.map do |_, spec|
774:       platform = spec.original_platform
775:       platform = Gem::Platform::RUBY if platform.nil?
776:       [spec.name, spec.version, platform]
777:     end
778: 
779:     specs = Marshal.dump specs
780: 
781:     if req.path =~ /\.gz$/ then
782:       specs = Gem.gzip specs
783:       res['content-type'] = 'application/x-gzip'
784:     else
785:       res['content-type'] = 'application/octet-stream'
786:     end
787: 
788:     if req.request_method == 'HEAD' then
789:       res['content-length'] = specs.length
790:     else
791:       res.body << specs
792:     end
793:   end

[Source]

     # File lib/rubygems/server.rb, line 795
795:   def yaml(req, res)
796:     @source_index.refresh!
797: 
798:     res['date'] = File.stat(@spec_dir).mtime
799: 
800:     index = @source_index.to_yaml
801: 
802:     if req.path =~ /Z$/ then
803:       res['content-type'] = 'application/x-deflate'
804:       index = Gem.deflate index
805:     else
806:       res['content-type'] = 'text/plain'
807:     end
808: 
809:     if req.request_method == 'HEAD' then
810:       res['content-length'] = index.length
811:       return
812:     end
813: 
814:     res.body << index
815:   end

[Validate]