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