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.