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: Gem::Specification.dirs = @gem_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: Gem::Specification.reset 467: 468: add_date res 469: 470: index = Gem::Deprecate.skip_during { Marshal.dump Gem.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: Gem::Specification.reset 495: 496: res['content-type'] = 'application/x-gzip' 497: 498: add_date res 499: 500: latest_specs = Gem::Specification.latest_specs 501: 502: specs = latest_specs.sort.map do |spec| 503: platform = spec.original_platform || Gem::Platform::RUBY 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
# File lib/rubygems/server.rb, line 827 827: def launch 828: listeners = @server.listeners.map{|l| l.addr[2] } 829: 830: # TODO: 0.0.0.0 == any, not localhost. 831: host = listeners.any?{|l| l == '0.0.0.0'} ? 'localhost' : listeners.first 832: 833: say "Launching browser to http://#{host}:#{@port}" 834: 835: system("#{@launch} http://#{host}:#{@port}") 836: 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: Gem::Specification.reset 556: 557: res['content-type'] = 'text/plain' 558: add_date res 559: 560: case req.request_uri.path 561: when %r|^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)-([0-9.]+)(-.*?)?\.gemspec\.rz$| then 562: marshal_format, name, version, platform = $1, $2, $3, $4 563: specs = Gem::Specification.find_all_by_name name, version 564: 565: selector = [name, version, platform].map(&:inspect).join ' ' 566: 567: platform = if platform then 568: Gem::Platform.new platform.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 709 709: def rdoc(req, res) 710: query = req.query['q'] 711: show_rdoc_for_pattern("#{query}*", res) && return 712: show_rdoc_for_pattern("*#{query}*", res) && return 713: 714: template = ERB.new RDOC_NO_DOCUMENTATION 715: 716: res['content-type'] = 'text/html' 717: res.body = template.result binding 718: end
# File lib/rubygems/server.rb, line 590 590: def root(req, res) 591: Gem::Specification.reset 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: Gem::Specification.each do |spec| 601: total_file_count += spec.files.size 602: deps = spec.dependencies.map { |dep| 603: { 604: "name" => dep.name, 605: "type" => dep.type, 606: "version" => dep.requirement.to_s, 607: } 608: } 609: 610: deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] } 611: deps.last["is_last"] = true unless deps.empty? 612: 613: # executables 614: executables = spec.executables.sort.collect { |exec| {"executable" => exec} } 615: executables = nil if executables.empty? 616: executables.last["is_last"] = true if executables 617: 618: specs << { 619: "authors" => spec.authors.sort.join(", "), 620: "date" => spec.date.to_s, 621: "dependencies" => deps, 622: "doc_path" => "/doc_root/#{spec.full_name}/rdoc/index.html", 623: "executables" => executables, 624: "only_one_executable" => (executables && executables.size == 1), 625: "full_name" => spec.full_name, 626: "has_deps" => !deps.empty?, 627: "homepage" => spec.homepage, 628: "name" => spec.name, 629: "rdoc_installed" => Gem::DocManager.new(spec).rdoc_installed?, 630: "summary" => spec.summary, 631: "version" => spec.version.to_s, 632: } 633: end 634: 635: specs << { 636: "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others", 637: "dependencies" => [], 638: "doc_path" => "/doc_root/rubygems-#{Gem::VERSION}/rdoc/index.html", 639: "executables" => [{"executable" => 'gem', "is_last" => true}], 640: "only_one_executable" => true, 641: "full_name" => "rubygems-#{Gem::VERSION}", 642: "has_deps" => false, 643: "homepage" => "http://docs.rubygems.org/", 644: "name" => 'rubygems', 645: "rdoc_installed" => true, 646: "summary" => "RubyGems itself", 647: "version" => Gem::VERSION, 648: } 649: 650: specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] } 651: specs.last["is_last"] = true 652: 653: # tag all specs with first_name_entry 654: last_spec = nil 655: specs.each do |spec| 656: is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase) 657: spec["first_name_entry"] = is_first 658: last_spec = spec 659: end 660: 661: # create page from template 662: template = ERB.new(DOC_TEMPLATE) 663: res['content-type'] = 'text/html' 664: 665: values = { "gem_count" => specs.size.to_s, "specs" => specs, 666: "total_file_count" => total_file_count.to_s } 667: 668: # suppress 1.9.3dev warning about unused variable 669: values = values 670: 671: result = template.result binding 672: res.body = result 673: end
# File lib/rubygems/server.rb, line 759 759: def run 760: listen 761: 762: WEBrick::Daemon.start if @daemon 763: 764: @server.mount_proc "/Marshal.#{Gem.marshal_version}", method(:Marshal) 765: @server.mount_proc "/Marshal.#{Gem.marshal_version}.Z", method(:Marshal) 766: 767: @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs) 768: @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs) 769: 770: @server.mount_proc "/latest_specs.#{Gem.marshal_version}", 771: method(:latest_specs) 772: @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz", 773: method(:latest_specs) 774: 775: @server.mount_proc "/quick/", method(:quick) 776: 777: @server.mount_proc("/gem-server-rdoc-style.css") do |req, res| 778: res['content-type'] = 'text/css' 779: add_date res 780: res.body << RDOC_CSS 781: end 782: 783: @server.mount_proc "/", method(:root) 784: 785: @server.mount_proc "/rdoc", method(:rdoc) 786: 787: paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" } 788: paths.each do |mount_point, mount_dir| 789: @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler, 790: File.join(@gem_dirs.first, mount_dir), true) 791: end 792: 793: trap("INT") { @server.shutdown; exit! } 794: trap("TERM") { @server.shutdown; exit! } 795: 796: launch if @launch 797: 798: @server.start 799: 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 728 728: def show_rdoc_for_pattern(pattern, res) 729: found_gems = Dir.glob("{#{@gem_dirs.join ','}}/doc/#{pattern}").select {|path| 730: File.exist? File.join(path, 'rdoc/index.html') 731: } 732: case found_gems.length 733: when 0 734: return false 735: when 1 736: new_path = File.basename(found_gems[0]) 737: res.status = 302 738: res['Location'] = "/doc_root/#{new_path}/rdoc/index.html" 739: return true 740: else 741: doc_items = [] 742: found_gems.each do |file_name| 743: base_name = File.basename(file_name) 744: doc_items << { 745: :name => base_name, 746: :url => "/doc_root/#{base_name}/rdoc/index.html", 747: :summary => '' 748: } 749: end 750: 751: template = ERB.new(RDOC_SEARCH_TEMPLATE) 752: res['content-type'] = 'text/html' 753: result = template.result binding 754: res.body = result 755: return true 756: end 757: end
# File lib/rubygems/server.rb, line 801 801: def specs(req, res) 802: Gem::Specification.reset 803: 804: add_date res 805: 806: specs = Gem::Specification.sort_by(&:sort_obj).map do |spec| 807: platform = spec.original_platform || Gem::Platform::RUBY 808: [spec.name, spec.version, platform] 809: end 810: 811: specs = Marshal.dump specs 812: 813: if req.path =~ /\.gz$/ then 814: specs = Gem.gzip specs 815: res['content-type'] = 'application/x-gzip' 816: else 817: res['content-type'] = 'application/octet-stream' 818: end 819: 820: if req.request_method == 'HEAD' then 821: res['content-length'] = specs.length 822: else 823: res.body << specs 824: end 825: end