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
“/yaml” - YAML dump of metadata for installed gems - deprecated
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 438 438: def initialize(gem_dirs, port, daemon, addresses = nil) 439: Socket.do_not_reverse_lookup = true 440: 441: @gem_dirs = Array gem_dirs 442: @port = port 443: @daemon = daemon 444: @addresses = addresses 445: logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL 446: @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger 447: 448: @spec_dirs = @gem_dirs.map do |gem_dir| 449: spec_dir = File.join gem_dir, 'specifications' 450: 451: unless File.directory? spec_dir then 452: raise ArgumentError, "#{gem_dir} does not appear to be a gem repository" 453: end 454: 455: spec_dir 456: end 457: 458: @source_index = Gem::SourceIndex.from_gems_in(*@spec_dirs) 459: end
# File lib/rubygems/server.rb, line 461 461: def Marshal(req, res) 462: @source_index.refresh! 463: 464: add_date res 465: 466: index = Marshal.dump @source_index 467: 468: if req.request_method == 'HEAD' then 469: res['content-length'] = index.length 470: return 471: end 472: 473: if req.path =~ /Z$/ then 474: res['content-type'] = 'application/x-deflate' 475: index = Gem.deflate index 476: else 477: res['content-type'] = 'application/octet-stream' 478: end 479: 480: res.body << index 481: end
# File lib/rubygems/server.rb, line 483 483: def add_date res 484: res['date'] = @spec_dirs.map do |spec_dir| 485: File.stat(spec_dir).mtime 486: end.max 487: end
# File lib/rubygems/server.rb, line 489 489: def latest_specs(req, res) 490: @source_index.refresh! 491: 492: res['content-type'] = 'application/x-gzip' 493: 494: add_date res 495: 496: specs = @source_index.latest_specs.sort.map do |spec| 497: platform = spec.original_platform 498: platform = Gem::Platform::RUBY if platform.nil? 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
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: @source_index.refresh! 551: 552: res['content-type'] = 'text/plain' 553: add_date res 554: 555: case req.request_uri.path 556: when '/quick/index' then 557: res.body << @source_index.map { |name,| name }.sort.join("\n") 558: when '/quick/index.rz' then 559: index = @source_index.map { |name,| name }.sort.join("\n") 560: res['content-type'] = 'application/x-deflate' 561: res.body << Gem.deflate(index) 562: when '/quick/latest_index' then 563: index = @source_index.latest_specs.map { |spec| spec.full_name } 564: res.body << index.sort.join("\n") 565: when '/quick/latest_index.rz' then 566: index = @source_index.latest_specs.map { |spec| spec.full_name } 567: res['content-type'] = 'application/x-deflate' 568: res.body << Gem.deflate(index.sort.join("\n")) 569: when %^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)-([0-9.]+)(-.*?)?\.gemspec\.rz$| then 570: dep = Gem::Dependency.new $2, $3 571: specs = @source_index.search dep 572: marshal_format = $1 573: 574: selector = [$2, $3, $4].map { |s| s.inspect }.join ' ' 575: 576: platform = if $4 then 577: Gem::Platform.new $4.sub(/^-/, '') 578: else 579: Gem::Platform::RUBY 580: end 581: 582: specs = specs.select { |s| s.platform == platform } 583: 584: if specs.empty? then 585: res.status = 404 586: res.body = "No gems found matching #{selector}" 587: elsif specs.length > 1 then 588: res.status = 500 589: res.body = "Multiple gems found matching #{selector}" 590: elsif marshal_format then 591: res['content-type'] = 'application/x-deflate' 592: res.body << Gem.deflate(Marshal.dump(specs.first)) 593: else # deprecated YAML format 594: res['content-type'] = 'application/x-deflate' 595: res.body << Gem.deflate(specs.first.to_yaml) 596: end 597: else 598: raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." 599: end 600: 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 716 716: def rdoc(req, res) 717: query = req.query['q'] 718: show_rdoc_for_pattern("#{query}*", res) && return 719: show_rdoc_for_pattern("*#{query}*", res) && return 720: 721: template = ERB.new RDOC_NO_DOCUMENTATION 722: 723: res['content-type'] = 'text/html' 724: res.body = template.result binding 725: end
# File lib/rubygems/server.rb, line 602 602: def root(req, res) 603: @source_index.refresh! 604: add_date res 605: 606: raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless 607: req.path == '/' 608: 609: specs = [] 610: total_file_count = 0 611: 612: @source_index.each do |path, spec| 613: total_file_count += spec.files.size 614: deps = spec.dependencies.map do |dep| 615: { "name" => dep.name, 616: "type" => dep.type, 617: "version" => dep.requirement.to_s, } 618: end 619: 620: deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] } 621: deps.last["is_last"] = true unless deps.empty? 622: 623: # executables 624: executables = spec.executables.sort.collect { |exec| {"executable" => exec} } 625: executables = nil if executables.empty? 626: executables.last["is_last"] = true if executables 627: 628: specs << { 629: "authors" => spec.authors.sort.join(", "), 630: "date" => spec.date.to_s, 631: "dependencies" => deps, 632: "doc_path" => "/doc_root/#{spec.full_name}/rdoc/index.html", 633: "executables" => executables, 634: "only_one_executable" => (executables && executables.size == 1), 635: "full_name" => spec.full_name, 636: "has_deps" => !deps.empty?, 637: "homepage" => spec.homepage, 638: "name" => spec.name, 639: "rdoc_installed" => Gem::DocManager.new(spec).rdoc_installed?, 640: "summary" => spec.summary, 641: "version" => spec.version.to_s, 642: } 643: end 644: 645: specs << { 646: "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others", 647: "dependencies" => [], 648: "doc_path" => "/doc_root/rubygems-#{Gem::VERSION}/rdoc/index.html", 649: "executables" => [{"executable" => 'gem', "is_last" => true}], 650: "only_one_executable" => true, 651: "full_name" => "rubygems-#{Gem::VERSION}", 652: "has_deps" => false, 653: "homepage" => "http://docs.rubygems.org/", 654: "name" => 'rubygems', 655: "rdoc_installed" => true, 656: "summary" => "RubyGems itself", 657: "version" => Gem::VERSION, 658: } 659: 660: specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] } 661: specs.last["is_last"] = true 662: 663: # tag all specs with first_name_entry 664: last_spec = nil 665: specs.each do |spec| 666: is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase) 667: spec["first_name_entry"] = is_first 668: last_spec = spec 669: end 670: 671: # create page from template 672: template = ERB.new(DOC_TEMPLATE) 673: res['content-type'] = 'text/html' 674: 675: values = { "gem_count" => specs.size.to_s, "specs" => specs, 676: "total_file_count" => total_file_count.to_s } 677: 678: result = template.result binding 679: res.body = result 680: end
# File lib/rubygems/server.rb, line 766 766: def run 767: listen 768: 769: WEBrick::Daemon.start if @daemon 770: 771: @server.mount_proc "/yaml", method(:yaml) 772: @server.mount_proc "/yaml.Z", method(:yaml) 773: 774: @server.mount_proc "/Marshal.#{Gem.marshal_version}", method(:Marshal) 775: @server.mount_proc "/Marshal.#{Gem.marshal_version}.Z", method(:Marshal) 776: 777: @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs) 778: @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs) 779: 780: @server.mount_proc "/latest_specs.#{Gem.marshal_version}", 781: method(:latest_specs) 782: @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz", 783: method(:latest_specs) 784: 785: @server.mount_proc "/quick/", method(:quick) 786: 787: @server.mount_proc("/gem-server-rdoc-style.css") do |req, res| 788: res['content-type'] = 'text/css' 789: add_date res 790: res.body << RDOC_CSS 791: end 792: 793: @server.mount_proc "/", method(:root) 794: 795: @server.mount_proc "/rdoc", method(:rdoc) 796: 797: paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" } 798: paths.each do |mount_point, mount_dir| 799: @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler, 800: File.join(@gem_dirs.first, mount_dir), true) 801: end 802: 803: trap("INT") { @server.shutdown; exit! } 804: trap("TERM") { @server.shutdown; exit! } 805: 806: @server.start 807: 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 735 735: def show_rdoc_for_pattern(pattern, res) 736: found_gems = Dir.glob("{#{@gem_dirs.join ','}}/doc/#{pattern}").select {|path| 737: File.exist? File.join(path, 'rdoc/index.html') 738: } 739: case found_gems.length 740: when 0 741: return false 742: when 1 743: new_path = File.basename(found_gems[0]) 744: res.status = 302 745: res['Location'] = "/doc_root/#{new_path}/rdoc/index.html" 746: return true 747: else 748: doc_items = [] 749: found_gems.each do |file_name| 750: base_name = File.basename(file_name) 751: doc_items << { 752: :name => base_name, 753: :url => "/doc_root/#{base_name}/rdoc/index.html", 754: :summary => '' 755: } 756: end 757: 758: template = ERB.new(RDOC_SEARCH_TEMPLATE) 759: res['content-type'] = 'text/html' 760: result = template.result binding 761: res.body = result 762: return true 763: end 764: end
# File lib/rubygems/server.rb, line 809 809: def specs(req, res) 810: @source_index.refresh! 811: 812: add_date res 813: 814: specs = @source_index.sort.map do |_, spec| 815: platform = spec.original_platform 816: platform = Gem::Platform::RUBY if platform.nil? 817: [spec.name, spec.version, platform] 818: end 819: 820: specs = Marshal.dump specs 821: 822: if req.path =~ /\.gz$/ then 823: specs = Gem.gzip specs 824: res['content-type'] = 'application/x-gzip' 825: else 826: res['content-type'] = 'application/octet-stream' 827: end 828: 829: if req.request_method == 'HEAD' then 830: res['content-length'] = specs.length 831: else 832: res.body << specs 833: end 834: end
# File lib/rubygems/server.rb, line 836 836: def yaml(req, res) 837: @source_index.refresh! 838: 839: add_date res 840: 841: index = @source_index.to_yaml 842: 843: if req.path =~ /Z$/ then 844: res['content-type'] = 'application/x-deflate' 845: index = Gem.deflate index 846: else 847: res['content-type'] = 'text/plain' 848: end 849: 850: if req.request_method == 'HEAD' then 851: res['content-length'] = index.length 852: return 853: end 854: 855: res.body << index 856: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.