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   add_date   latest_specs   launch   listen   new   quick   rdoc   root   run   run   show_rdoc_for_pattern   specs  

Included Modules

ERB::Util Gem::UserInteraction

Constants

SEARCH = <<-SEARCH <form class="headerSearch" name="headerSearchForm" method="get" action="/rdoc"> <div id="search" style="float:right"> <label for="q">Filter/Search</label> <input id="q" type="text" style="width:10em" name="q"> <button type="submit" style="display:none"></button> </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

Attributes

spec_dirs  [R] 

Public Class methods

Only the first directory in gem_dirs is used for serving gems

[Source]

     # File lib/rubygems/server.rb, line 441
441:   def initialize(gem_dirs, port, daemon, launch = nil, addresses = nil)
442:     Socket.do_not_reverse_lookup = true
443: 
444:     @gem_dirs = Array gem_dirs
445:     @port = port
446:     @daemon = daemon
447:     @launch = launch
448:     @addresses = addresses
449:     logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL
450:     @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger
451: 
452:     @spec_dirs = @gem_dirs.map do |gem_dir|
453:       spec_dir = File.join gem_dir, 'specifications'
454: 
455:       unless File.directory? spec_dir then
456:         raise ArgumentError, "#{gem_dir} does not appear to be a gem repository"
457:       end
458: 
459:       spec_dir
460:     end
461: 
462:     Gem::Specification.dirs = @gem_dirs
463:   end

[Source]

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

Public Instance methods

[Source]

     # File lib/rubygems/server.rb, line 465
465:   def Marshal(req, res)
466:     Gem::Specification.reset
467: 
468:     add_date res
469: 
470:     index = Gem::Deprecate.skip_during { Marshal.dump Gem.source_index }
471: 
472:     if req.request_method == 'HEAD' then
473:       res['content-length'] = index.length
474:       return
475:     end
476: 
477:     if req.path =~ /Z$/ then
478:       res['content-type'] = 'application/x-deflate'
479:       index = Gem.deflate index
480:     else
481:       res['content-type'] = 'application/octet-stream'
482:     end
483: 
484:     res.body << index
485:   end

[Source]

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

[Source]

     # File lib/rubygems/server.rb, line 493
493:   def latest_specs(req, res)
494:     Gem::Specification.reset
495: 
496:     res['content-type'] = 'application/x-gzip'
497: 
498:     add_date res
499: 
500:     latest_specs = Gem::Specification.latest_specs
501: 
502:     specs = latest_specs.sort.map do |spec|
503:       platform = spec.original_platform || Gem::Platform::RUBY
504:       [spec.name, spec.version, platform]
505:     end
506: 
507:     specs = Marshal.dump specs
508: 
509:     if req.path =~ /\.gz$/ then
510:       specs = Gem.gzip specs
511:       res['content-type'] = 'application/x-gzip'
512:     else
513:       res['content-type'] = 'application/octet-stream'
514:     end
515: 
516:     if req.request_method == 'HEAD' then
517:       res['content-length'] = specs.length
518:     else
519:       res.body << specs
520:     end
521:   end

[Source]

     # File lib/rubygems/server.rb, line 827
827:   def launch
828:     listeners = @server.listeners.map{|l| l.addr[2] }
829: 
830:     # TODO: 0.0.0.0 == any, not localhost.
831:     host = listeners.any?{|l| l == '0.0.0.0'} ? 'localhost' : listeners.first
832: 
833:     say "Launching browser to http://#{host}:#{@port}"
834: 
835:     system("#{@launch} http://#{host}:#{@port}")
836:   end

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

[Source]

     # File lib/rubygems/server.rb, line 527
527:   def listen addresses = @addresses
528:     addresses = [nil] unless addresses
529: 
530:     listeners = 0
531: 
532:     addresses.each do |address|
533:       begin
534:         @server.listen address, @port
535:         @server.listeners[listeners..-1].each do |listener|
536:           host, port = listener.addr.values_at 2, 1
537:           host = "[#{host}]" if host =~ /:/ # we don't reverse lookup
538:           say "Server started at http://#{host}:#{port}"
539:         end
540: 
541:         listeners = @server.listeners.length
542:       rescue SystemCallError
543:         next
544:       end
545:     end
546: 
547:     if @server.listeners.empty? then
548:       say "Unable to start a server."
549:       say "Check for running servers or your --bind and --port arguments"
550:       terminate_interaction 1
551:     end
552:   end

[Source]

     # File lib/rubygems/server.rb, line 554
554:   def quick(req, res)
555:     Gem::Specification.reset
556: 
557:     res['content-type'] = 'text/plain'
558:     add_date res
559: 
560:     case req.request_uri.path
561:     when %r|^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)-([0-9.]+)(-.*?)?\.gemspec\.rz$| then
562:       marshal_format, name, version, platform = $1, $2, $3, $4
563:       specs = Gem::Specification.find_all_by_name name, version
564: 
565:       selector = [name, version, platform].map(&:inspect).join ' '
566: 
567:       platform = if platform then
568:                    Gem::Platform.new platform.sub(/^-/, '')
569:                  else
570:                    Gem::Platform::RUBY
571:                  end
572: 
573:       specs = specs.select { |s| s.platform == platform }
574: 
575:       if specs.empty? then
576:         res.status = 404
577:         res.body = "No gems found matching #{selector}"
578:       elsif specs.length > 1 then
579:         res.status = 500
580:         res.body = "Multiple gems found matching #{selector}"
581:       elsif marshal_format then
582:         res['content-type'] = 'application/x-deflate'
583:         res.body << Gem.deflate(Marshal.dump(specs.first))
584:       end
585:     else
586:       raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found."
587:     end
588:   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 709
709:   def rdoc(req, res)
710:     query = req.query['q']
711:     show_rdoc_for_pattern("#{query}*", res) && return
712:     show_rdoc_for_pattern("*#{query}*", res) && return
713: 
714:     template = ERB.new RDOC_NO_DOCUMENTATION
715: 
716:     res['content-type'] = 'text/html'
717:     res.body = template.result binding
718:   end

[Source]

     # File lib/rubygems/server.rb, line 590
590:   def root(req, res)
591:     Gem::Specification.reset
592:     add_date res
593: 
594:     raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless
595:       req.path == '/'
596: 
597:     specs = []
598:     total_file_count = 0
599: 
600:     Gem::Specification.each do |spec|
601:       total_file_count += spec.files.size
602:       deps = spec.dependencies.map { |dep|
603:         {
604:           "name"    => dep.name,
605:           "type"    => dep.type,
606:           "version" => dep.requirement.to_s,
607:         }
608:       }
609: 
610:       deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] }
611:       deps.last["is_last"] = true unless deps.empty?
612: 
613:       # executables
614:       executables = spec.executables.sort.collect { |exec| {"executable" => exec} }
615:       executables = nil if executables.empty?
616:       executables.last["is_last"] = true if executables
617: 
618:       specs << {
619:         "authors"             => spec.authors.sort.join(", "),
620:         "date"                => spec.date.to_s,
621:         "dependencies"        => deps,
622:         "doc_path"            => "/doc_root/#{spec.full_name}/rdoc/index.html",
623:         "executables"         => executables,
624:         "only_one_executable" => (executables && executables.size == 1),
625:         "full_name"           => spec.full_name,
626:         "has_deps"            => !deps.empty?,
627:         "homepage"            => (URI.parse(spec.homepage).is_a?(URI::HTTP) || URI.parse(spec.homepage).is_a?(URI::HTTPS)) ? spec.homepage : ".",
628:         "name"                => spec.name,
629:         "rdoc_installed"      => Gem::DocManager.new(spec).rdoc_installed?,
630:         "summary"             => spec.summary,
631:         "version"             => spec.version.to_s,
632:       }
633:     end
634: 
635:     specs << {
636:       "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others",
637:       "dependencies" => [],
638:       "doc_path" => "/doc_root/rubygems-#{Gem::VERSION}/rdoc/index.html",
639:       "executables" => [{"executable" => 'gem', "is_last" => true}],
640:       "only_one_executable" => true,
641:       "full_name" => "rubygems-#{Gem::VERSION}",
642:       "has_deps" => false,
643:       "homepage" => "http://docs.rubygems.org/",
644:       "name" => 'rubygems',
645:       "rdoc_installed" => true,
646:       "summary" => "RubyGems itself",
647:       "version" => Gem::VERSION,
648:     }
649: 
650:     specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] }
651:     specs.last["is_last"] = true
652: 
653:     # tag all specs with first_name_entry
654:     last_spec = nil
655:     specs.each do |spec|
656:       is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase)
657:       spec["first_name_entry"] = is_first
658:       last_spec = spec
659:     end
660: 
661:     # create page from template
662:     template = ERB.new(DOC_TEMPLATE)
663:     res['content-type'] = 'text/html'
664: 
665:     values = { "gem_count" => specs.size.to_s, "specs" => specs,
666:                "total_file_count" => total_file_count.to_s }
667: 
668:     # suppress 1.9.3dev warning about unused variable
669:     values = values
670: 
671:     result = template.result binding
672:     res.body = result
673:   end

[Source]

     # File lib/rubygems/server.rb, line 759
759:   def run
760:     listen
761: 
762:     WEBrick::Daemon.start if @daemon
763: 
764:     @server.mount_proc "/Marshal.#{Gem.marshal_version}", method(:Marshal)
765:     @server.mount_proc "/Marshal.#{Gem.marshal_version}.Z", method(:Marshal)
766: 
767:     @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs)
768:     @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs)
769: 
770:     @server.mount_proc "/latest_specs.#{Gem.marshal_version}",
771:                        method(:latest_specs)
772:     @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz",
773:                        method(:latest_specs)
774: 
775:     @server.mount_proc "/quick/", method(:quick)
776: 
777:     @server.mount_proc("/gem-server-rdoc-style.css") do |req, res|
778:       res['content-type'] = 'text/css'
779:       add_date res
780:       res.body << RDOC_CSS
781:     end
782: 
783:     @server.mount_proc "/", method(:root)
784: 
785:     @server.mount_proc "/rdoc", method(:rdoc)
786: 
787:     paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" }
788:     paths.each do |mount_point, mount_dir|
789:       @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler,
790:                     File.join(@gem_dirs.first, mount_dir), true)
791:     end
792: 
793:     trap("INT") { @server.shutdown; exit! }
794:     trap("TERM") { @server.shutdown; exit! }
795: 
796:     launch if @launch
797: 
798:     @server.start
799:   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 728
728:   def show_rdoc_for_pattern(pattern, res)
729:     found_gems = Dir.glob("{#{@gem_dirs.join ','}}/doc/#{pattern}").select {|path|
730:       File.exist? File.join(path, 'rdoc/index.html')
731:     }
732:     case found_gems.length
733:     when 0
734:       return false
735:     when 1
736:       new_path = File.basename(found_gems[0])
737:       res.status = 302
738:       res['Location'] = "/doc_root/#{new_path}/rdoc/index.html"
739:       return true
740:     else
741:       doc_items = []
742:       found_gems.each do |file_name|
743:         base_name = File.basename(file_name)
744:         doc_items << {
745:           :name => base_name,
746:           :url => "/doc_root/#{base_name}/rdoc/index.html",
747:           :summary => ''
748:         }
749:       end
750: 
751:       template = ERB.new(RDOC_SEARCH_TEMPLATE)
752:       res['content-type'] = 'text/html'
753:       result = template.result binding
754:       res.body = result
755:       return true
756:     end
757:   end

[Source]

     # File lib/rubygems/server.rb, line 801
801:   def specs(req, res)
802:     Gem::Specification.reset
803: 
804:     add_date res
805: 
806:     specs = Gem::Specification.sort_by(&:sort_obj).map do |spec|
807:       platform = spec.original_platform || Gem::Platform::RUBY
808:       [spec.name, spec.version, platform]
809:     end
810: 
811:     specs = Marshal.dump specs
812: 
813:     if req.path =~ /\.gz$/ then
814:       specs = Gem.gzip specs
815:       res['content-type'] = 'application/x-gzip'
816:     else
817:       res['content-type'] = 'application/octet-stream'
818:     end
819: 
820:     if req.request_method == 'HEAD' then
821:       res['content-length'] = specs.length
822:     else
823:       res.body << specs
824:     end
825:   end

[Validate]