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 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: @source_index = Gem::SourceIndex.new(@spec_dirs) 463: end
# 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
# File lib/rubygems/server.rb, line 465 465: def Marshal(req, res) 466: @source_index.refresh! 467: 468: add_date res 469: 470: index = Marshal.dump @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
# 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
# File lib/rubygems/server.rb, line 493 493: def latest_specs(req, res) 494: @source_index.refresh! 495: 496: res['content-type'] = 'application/x-gzip' 497: 498: add_date res 499: 500: specs = @source_index.latest_specs.sort.map do |spec| 501: platform = spec.original_platform 502: platform = Gem::Platform::RUBY if platform.nil? 503: [spec.name, spec.version, platform] 504: end 505: 506: specs = Marshal.dump specs 507: 508: if req.path =~ /\.gz$/ then 509: specs = Gem.gzip specs 510: res['content-type'] = 'application/x-gzip' 511: else 512: res['content-type'] = 'application/octet-stream' 513: end 514: 515: if req.request_method == 'HEAD' then 516: res['content-length'] = specs.length 517: else 518: res.body << specs 519: end 520: end
# File lib/rubygems/server.rb, line 826 826: def launch 827: listeners = @server.listeners.map{|l| l.addr[2] } 828: 829: host = listeners.any?{|l| l == '0.0.0.0'} ? 'localhost' : listeners.first 830: 831: say "Launching browser to http://#{host}:#{@port}" 832: 833: system("#{@launch} http://#{host}:#{@port}") 834: 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 526 526: def listen addresses = @addresses 527: addresses = [nil] unless addresses 528: 529: listeners = 0 530: 531: addresses.each do |address| 532: begin 533: @server.listen address, @port 534: @server.listeners[listeners..-1].each do |listener| 535: host, port = listener.addr.values_at 2, 1 536: host = "[#{host}]" if host =~ /:/ # we don't reverse lookup 537: say "Server started at http://#{host}:#{port}" 538: end 539: 540: listeners = @server.listeners.length 541: rescue SystemCallError 542: next 543: end 544: end 545: 546: if @server.listeners.empty? then 547: say "Unable to start a server." 548: say "Check for running servers or your --bind and --port arguments" 549: terminate_interaction 1 550: end 551: end
# File lib/rubygems/server.rb, line 553 553: def quick(req, res) 554: @source_index.refresh! 555: 556: res['content-type'] = 'text/plain' 557: add_date res 558: 559: case req.request_uri.path 560: when %r|^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)-([0-9.]+)(-.*?)?\.gemspec\.rz$| then 561: dep = Gem::Dependency.new $2, $3 562: specs = @source_index.search dep 563: marshal_format = $1 564: 565: selector = [$2, $3, $4].map { |s| s.inspect }.join ' ' 566: 567: platform = if $4 then 568: Gem::Platform.new $4.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:
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 707 707: def rdoc(req, res) 708: query = req.query['q'] 709: show_rdoc_for_pattern("#{query}*", res) && return 710: show_rdoc_for_pattern("*#{query}*", res) && return 711: 712: template = ERB.new RDOC_NO_DOCUMENTATION 713: 714: res['content-type'] = 'text/html' 715: res.body = template.result binding 716: end
# File lib/rubygems/server.rb, line 590 590: def root(req, res) 591: @source_index.refresh! 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: @source_index.each do |path, spec| 601: total_file_count += spec.files.size 602: deps = spec.dependencies.map do |dep| 603: { "name" => dep.name, 604: "type" => dep.type, 605: "version" => dep.requirement.to_s, } 606: end 607: 608: deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] } 609: deps.last["is_last"] = true unless deps.empty? 610: 611: # executables 612: executables = spec.executables.sort.collect { |exec| {"executable" => exec} } 613: executables = nil if executables.empty? 614: executables.last["is_last"] = true if executables 615: 616: specs << { 617: "authors" => spec.authors.sort.join(", "), 618: "date" => spec.date.to_s, 619: "dependencies" => deps, 620: "doc_path" => "/doc_root/#{spec.full_name}/rdoc/index.html", 621: "executables" => executables, 622: "only_one_executable" => (executables && executables.size == 1), 623: "full_name" => spec.full_name, 624: "has_deps" => !deps.empty?, 625: "homepage" => spec.homepage, 626: "name" => spec.name, 627: "rdoc_installed" => Gem::DocManager.new(spec).rdoc_installed?, 628: "summary" => spec.summary, 629: "version" => spec.version.to_s, 630: } 631: end 632: 633: specs << { 634: "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others", 635: "dependencies" => [], 636: "doc_path" => "/doc_root/rubygems-#{Gem::VERSION}/rdoc/index.html", 637: "executables" => [{"executable" => 'gem', "is_last" => true}], 638: "only_one_executable" => true, 639: "full_name" => "rubygems-#{Gem::VERSION}", 640: "has_deps" => false, 641: "homepage" => "http://docs.rubygems.org/", 642: "name" => 'rubygems', 643: "rdoc_installed" => true, 644: "summary" => "RubyGems itself", 645: "version" => Gem::VERSION, 646: } 647: 648: specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] } 649: specs.last["is_last"] = true 650: 651: # tag all specs with first_name_entry 652: last_spec = nil 653: specs.each do |spec| 654: is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase) 655: spec["first_name_entry"] = is_first 656: last_spec = spec 657: end 658: 659: # create page from template 660: template = ERB.new(DOC_TEMPLATE) 661: res['content-type'] = 'text/html' 662: 663: values = { "gem_count" => specs.size.to_s, "specs" => specs, 664: "total_file_count" => total_file_count.to_s } 665: 666: # suppress 1.9.3dev warning about unused variable 667: values = values 668: 669: result = template.result binding 670: res.body = result 671: end
# File lib/rubygems/server.rb, line 757 757: def run 758: listen 759: 760: WEBrick::Daemon.start if @daemon 761: 762: @server.mount_proc "/Marshal.#{Gem.marshal_version}", method(:Marshal) 763: @server.mount_proc "/Marshal.#{Gem.marshal_version}.Z", method(:Marshal) 764: 765: @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs) 766: @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs) 767: 768: @server.mount_proc "/latest_specs.#{Gem.marshal_version}", 769: method(:latest_specs) 770: @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz", 771: method(:latest_specs) 772: 773: @server.mount_proc "/quick/", method(:quick) 774: 775: @server.mount_proc("/gem-server-rdoc-style.css") do |req, res| 776: res['content-type'] = 'text/css' 777: add_date res 778: res.body << RDOC_CSS 779: end 780: 781: @server.mount_proc "/", method(:root) 782: 783: @server.mount_proc "/rdoc", method(:rdoc) 784: 785: paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" } 786: paths.each do |mount_point, mount_dir| 787: @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler, 788: File.join(@gem_dirs.first, mount_dir), true) 789: end 790: 791: trap("INT") { @server.shutdown; exit! } 792: trap("TERM") { @server.shutdown; exit! } 793: 794: launch if @launch 795: 796: @server.start 797: 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 726 726: def show_rdoc_for_pattern(pattern, res) 727: found_gems = Dir.glob("{#{@gem_dirs.join ','}}/doc/#{pattern}").select {|path| 728: File.exist? File.join(path, 'rdoc/index.html') 729: } 730: case found_gems.length 731: when 0 732: return false 733: when 1 734: new_path = File.basename(found_gems[0]) 735: res.status = 302 736: res['Location'] = "/doc_root/#{new_path}/rdoc/index.html" 737: return true 738: else 739: doc_items = [] 740: found_gems.each do |file_name| 741: base_name = File.basename(file_name) 742: doc_items << { 743: :name => base_name, 744: :url => "/doc_root/#{base_name}/rdoc/index.html", 745: :summary => '' 746: } 747: end 748: 749: template = ERB.new(RDOC_SEARCH_TEMPLATE) 750: res['content-type'] = 'text/html' 751: result = template.result binding 752: res.body = result 753: return true 754: end 755: end
# File lib/rubygems/server.rb, line 799 799: def specs(req, res) 800: @source_index.refresh! 801: 802: add_date res 803: 804: specs = @source_index.sort.map do |_, spec| 805: platform = spec.original_platform 806: platform = Gem::Platform::RUBY if platform.nil? 807: [spec.name, spec.version, platform] 808: end 809: 810: specs = Marshal.dump specs 811: 812: if req.path =~ /\.gz$/ then 813: specs = Gem.gzip specs 814: res['content-type'] = 'application/x-gzip' 815: else 816: res['content-type'] = 'application/octet-stream' 817: end 818: 819: if req.request_method == 'HEAD' then 820: res['content-length'] = specs.length 821: else 822: res.body << specs 823: end 824: end