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
gem_server = Gem::Server.new Gem.dir, 8089, false gem_server.run
CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108
Only the first directory in gem_dirs is used for serving gems
# File lib/rubygems/server.rb, line 436
436: def initialize(gem_dirs, port, daemon, launch = nil, addresses = nil)
437: Socket.do_not_reverse_lookup = true
438:
439: @gem_dirs = Array gem_dirs
440: @port = port
441: @daemon = daemon
442: @launch = launch
443: @addresses = addresses
444: logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL
445: @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger
446:
447: @spec_dirs = @gem_dirs.map do |gem_dir|
448: spec_dir = File.join gem_dir, 'specifications'
449:
450: unless File.directory? spec_dir then
451: raise ArgumentError, "#{gem_dir} does not appear to be a gem repository"
452: end
453:
454: spec_dir
455: end
456:
457: Gem::Specification.dirs = @gem_dirs
458: end
# File lib/rubygems/server.rb, line 460
460: def Marshal(req, res)
461: Gem::Specification.reset
462:
463: add_date res
464:
465: index = Gem::Deprecate.skip_during { Marshal.dump Gem.source_index }
466:
467: if req.request_method == 'HEAD' then
468: res['content-length'] = index.length
469: return
470: end
471:
472: if req.path =~ /Z$/ then
473: res['content-type'] = 'application/x-deflate'
474: index = Gem.deflate index
475: else
476: res['content-type'] = 'application/octet-stream'
477: end
478:
479: res.body << index
480: end
# File lib/rubygems/server.rb, line 482
482: def add_date res
483: res['date'] = @spec_dirs.map do |spec_dir|
484: File.stat(spec_dir).mtime
485: end.max
486: end
# File lib/rubygems/server.rb, line 488
488: def latest_specs(req, res)
489: Gem::Specification.reset
490:
491: res['content-type'] = 'application/x-gzip'
492:
493: add_date res
494:
495: latest_specs = Gem::Specification.latest_specs
496:
497: specs = latest_specs.sort.map do |spec|
498: platform = spec.original_platform || Gem::Platform::RUBY
499: [spec.name, spec.version, platform]
500: end
501:
502: specs = Marshal.dump specs
503:
504: if req.path =~ /\.gz$/ then
505: specs = Gem.gzip specs
506: res['content-type'] = 'application/x-gzip'
507: else
508: res['content-type'] = 'application/octet-stream'
509: end
510:
511: if req.request_method == 'HEAD' then
512: res['content-length'] = specs.length
513: else
514: res.body << specs
515: end
516: end
# File lib/rubygems/server.rb, line 822
822: def launch
823: listeners = @server.listeners.map{|l| l.addr[2] }
824:
825: # TODO: 0.0.0.0 == any, not localhost.
826: host = listeners.any?{|l| l == '0.0.0.0'} ? 'localhost' : listeners.first
827:
828: say "Launching browser to http://#{host}:#{@port}"
829:
830: system("#{@launch} http://#{host}:#{@port}")
831: end
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 522
522: def listen addresses = @addresses
523: addresses = [nil] unless addresses
524:
525: listeners = 0
526:
527: addresses.each do |address|
528: begin
529: @server.listen address, @port
530: @server.listeners[listeners..1].each do |listener|
531: host, port = listener.addr.values_at 2, 1
532: host = "[#{host}]" if host =~ /:/ # we don't reverse lookup
533: say "Server started at http://#{host}:#{port}"
534: end
535:
536: listeners = @server.listeners.length
537: rescue SystemCallError
538: next
539: end
540: end
541:
542: if @server.listeners.empty? then
543: say "Unable to start a server."
544: say "Check for running servers or your --bind and --port arguments"
545: terminate_interaction 1
546: end
547: end
# File lib/rubygems/server.rb, line 549
549: def quick(req, res)
550: Gem::Specification.reset
551:
552: res['content-type'] = 'text/plain'
553: add_date res
554:
555: case req.request_uri.path
556: when %^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)-([0-9.]+)(-.*?)?\.gemspec\.rz$| then
557: marshal_format, name, version, platform = $1, $2, $3, $4
558: specs = Gem::Specification.find_all_by_name name, version
559:
560: selector = [name, version, platform].map(&:inspect).join ' '
561:
562: platform = if platform then
563: Gem::Platform.new platform.sub(/^-/, '')
564: else
565: Gem::Platform::RUBY
566: end
567:
568: specs = specs.select { |s| s.platform == platform }
569:
570: if specs.empty? then
571: res.status = 404
572: res.body = "No gems found matching #{selector}"
573: elsif specs.length > 1 then
574: res.status = 500
575: res.body = "Multiple gems found matching #{selector}"
576: elsif marshal_format then
577: res['content-type'] = 'application/x-deflate'
578: res.body << Gem.deflate(Marshal.dump(specs.first))
579: end
580: else
581: raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found."
582: end
583: 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:
first try to find the gems and documentation folders which name starts with the search term
search for entries, that contain the search term
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.
Note: please adjust paths accordingly use for example ‘locate yaml.rb’ and ‘gem environment’ to identify directories, that are specific for your local installation
install ruby sources
cd /usr/src sudo apt-get source ruby
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 704
704: def rdoc(req, res)
705: query = req.query['q']
706: show_rdoc_for_pattern("#{query}*", res) && return
707: show_rdoc_for_pattern("*#{query}*", res) && return
708:
709: template = ERB.new RDOC_NO_DOCUMENTATION
710:
711: res['content-type'] = 'text/html'
712: res.body = template.result binding
713: end
# File lib/rubygems/server.rb, line 585
585: def root(req, res)
586: Gem::Specification.reset
587: add_date res
588:
589: raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless
590: req.path == '/'
591:
592: specs = []
593: total_file_count = 0
594:
595: Gem::Specification.each do |spec|
596: total_file_count += spec.files.size
597: deps = spec.dependencies.map { |dep|
598: {
599: "name" => dep.name,
600: "type" => dep.type,
601: "version" => dep.requirement.to_s,
602: }
603: }
604:
605: deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] }
606: deps.last["is_last"] = true unless deps.empty?
607:
608: # executables
609: executables = spec.executables.sort.collect { |exec| {"executable" => exec} }
610: executables = nil if executables.empty?
611: executables.last["is_last"] = true if executables
612:
613: specs << {
614: "authors" => spec.authors.sort.join(", "),
615: "date" => spec.date.to_s,
616: "dependencies" => deps,
617: "doc_path" => "/doc_root/#{spec.full_name}/rdoc/index.html",
618: "executables" => executables,
619: "only_one_executable" => (executables && executables.size == 1),
620: "full_name" => spec.full_name,
621: "has_deps" => !deps.empty?,
622: "homepage" => spec.homepage,
623: "name" => spec.name,
624: "rdoc_installed" => Gem::DocManager.new(spec).rdoc_installed?,
625: "summary" => spec.summary,
626: "version" => spec.version.to_s,
627: }
628: end
629:
630: specs << {
631: "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others",
632: "dependencies" => [],
633: "doc_path" => "/doc_root/rubygems-#{Gem::VERSION}/rdoc/index.html",
634: "executables" => [{"executable" => 'gem', "is_last" => true}],
635: "only_one_executable" => true,
636: "full_name" => "rubygems-#{Gem::VERSION}",
637: "has_deps" => false,
638: "homepage" => "http://docs.rubygems.org/",
639: "name" => 'rubygems',
640: "rdoc_installed" => true,
641: "summary" => "RubyGems itself",
642: "version" => Gem::VERSION,
643: }
644:
645: specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] }
646: specs.last["is_last"] = true
647:
648: # tag all specs with first_name_entry
649: last_spec = nil
650: specs.each do |spec|
651: is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase)
652: spec["first_name_entry"] = is_first
653: last_spec = spec
654: end
655:
656: # create page from template
657: template = ERB.new(DOC_TEMPLATE)
658: res['content-type'] = 'text/html'
659:
660: values = { "gem_count" => specs.size.to_s, "specs" => specs,
661: "total_file_count" => total_file_count.to_s }
662:
663: # suppress 1.9.3dev warning about unused variable
664: values = values
665:
666: result = template.result binding
667: res.body = result
668: end
# File lib/rubygems/server.rb, line 754
754: def run
755: listen
756:
757: WEBrick::Daemon.start if @daemon
758:
759: @server.mount_proc "/Marshal.#{Gem.marshal_version}", method(:Marshal)
760: @server.mount_proc "/Marshal.#{Gem.marshal_version}.Z", method(:Marshal)
761:
762: @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs)
763: @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs)
764:
765: @server.mount_proc "/latest_specs.#{Gem.marshal_version}",
766: method(:latest_specs)
767: @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz",
768: method(:latest_specs)
769:
770: @server.mount_proc "/quick/", method(:quick)
771:
772: @server.mount_proc("/gem-server-rdoc-style.css") do |req, res|
773: res['content-type'] = 'text/css'
774: add_date res
775: res.body << RDOC_CSS
776: end
777:
778: @server.mount_proc "/", method(:root)
779:
780: @server.mount_proc "/rdoc", method(:rdoc)
781:
782: paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" }
783: paths.each do |mount_point, mount_dir|
784: @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler,
785: File.join(@gem_dirs.first, mount_dir), true)
786: end
787:
788: trap("INT") { @server.shutdown; exit! }
789: trap("TERM") { @server.shutdown; exit! }
790:
791: launch if @launch
792:
793: @server.start
794: 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.
# File lib/rubygems/server.rb, line 723
723: def show_rdoc_for_pattern(pattern, res)
724: found_gems = Dir.glob("{#{@gem_dirs.join ','}}/doc/#{pattern}").select {|path|
725: File.exist? File.join(path, 'rdoc/index.html')
726: }
727: case found_gems.length
728: when 0
729: return false
730: when 1
731: new_path = File.basename(found_gems[0])
732: res.status = 302
733: res['Location'] = "/doc_root/#{new_path}/rdoc/index.html"
734: return true
735: else
736: doc_items = []
737: found_gems.each do |file_name|
738: base_name = File.basename(file_name)
739: doc_items << {
740: :name => base_name,
741: :url => "/doc_root/#{base_name}/rdoc/index.html",
742: :summary => ''
743: }
744: end
745:
746: template = ERB.new(RDOC_SEARCH_TEMPLATE)
747: res['content-type'] = 'text/html'
748: result = template.result binding
749: res.body = result
750: return true
751: end
752: end
# File lib/rubygems/server.rb, line 796
796: def specs(req, res)
797: Gem::Specification.reset
798:
799: add_date res
800:
801: specs = Gem::Specification.sort_by(&:sort_obj).map do |spec|
802: platform = spec.original_platform || Gem::Platform::RUBY
803: [spec.name, spec.version, platform]
804: end
805:
806: specs = Marshal.dump specs
807:
808: if req.path =~ /\.gz$/ then
809: specs = Gem.gzip specs
810: res['content-type'] = 'application/x-gzip'
811: else
812: res['content-type'] = 'application/octet-stream'
813: end
814:
815: if req.request_method == 'HEAD' then
816: res['content-length'] = specs.length
817: else
818: res.body << specs
819: end
820: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.