/tools/Ruby/lib/ruby/site_ruby/1.8/rubygems/server.rb

http://github.com/agross/netopenspace · Ruby · 832 lines · 608 code · 151 blank · 73 comment · 35 complexity · c1d4e4c5c134fc87ec3adf14db094dea MD5 · raw file

  1. require 'webrick'
  2. require 'zlib'
  3. require 'erb'
  4. require 'rubygems'
  5. require 'rubygems/doc_manager'
  6. ##
  7. # Gem::Server and allows users to serve gems for consumption by
  8. # `gem --remote-install`.
  9. #
  10. # gem_server starts an HTTP server on the given port and serves the following:
  11. # * "/" - Browsing of gem spec files for installed gems
  12. # * "/specs.#{Gem.marshal_version}.gz" - specs name/version/platform index
  13. # * "/latest_specs.#{Gem.marshal_version}.gz" - latest specs
  14. # name/version/platform index
  15. # * "/quick/" - Individual gemspecs
  16. # * "/gems" - Direct access to download the installable gems
  17. # * "/rdoc?q=" - Search for installed rdoc documentation
  18. # * legacy indexes:
  19. # * "/Marshal.#{Gem.marshal_version}" - Full SourceIndex dump of metadata
  20. # for installed gems
  21. #
  22. # == Usage
  23. #
  24. # gem_server = Gem::Server.new Gem.dir, 8089, false
  25. # gem_server.run
  26. #
  27. #--
  28. # TODO Refactor into a real WEBrick servlet to remove code duplication.
  29. class Gem::Server
  30. attr_reader :spec_dirs
  31. include ERB::Util
  32. include Gem::UserInteraction
  33. SEARCH = <<-SEARCH
  34. <form class="headerSearch" name="headerSearchForm" method="get" action="/rdoc">
  35. <div id="search" style="float:right">
  36. <label for="q">Filter/Search</label>
  37. <input id="q" type="text" style="width:10em" name="q">
  38. <button type="submit" style="display:none"></button>
  39. </div>
  40. </form>
  41. SEARCH
  42. DOC_TEMPLATE = <<-'DOC_TEMPLATE'
  43. <?xml version="1.0" encoding="iso-8859-1"?>
  44. <!DOCTYPE html
  45. PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  46. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  47. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  48. <head>
  49. <title>RubyGems Documentation Index</title>
  50. <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
  51. </head>
  52. <body>
  53. <div id="fileHeader">
  54. <%= SEARCH %>
  55. <h1>RubyGems Documentation Index</h1>
  56. </div>
  57. <!-- banner header -->
  58. <div id="bodyContent">
  59. <div id="contextContent">
  60. <div id="description">
  61. <h1>Summary</h1>
  62. <p>There are <%=values["gem_count"]%> gems installed:</p>
  63. <p>
  64. <%= values["specs"].map { |v| "<a href=\"##{v["name"]}\">#{v["name"]}</a>" }.join ', ' %>.
  65. <h1>Gems</h1>
  66. <dl>
  67. <% values["specs"].each do |spec| %>
  68. <dt>
  69. <% if spec["first_name_entry"] then %>
  70. <a name="<%=spec["name"]%>"></a>
  71. <% end %>
  72. <b><%=spec["name"]%> <%=spec["version"]%></b>
  73. <% if spec["rdoc_installed"] then %>
  74. <a href="<%=spec["doc_path"]%>">[rdoc]</a>
  75. <% else %>
  76. <span title="rdoc not installed">[rdoc]</span>
  77. <% end %>
  78. <% if spec["homepage"] then %>
  79. <a href="<%=spec["homepage"]%>" title="<%=spec["homepage"]%>">[www]</a>
  80. <% else %>
  81. <span title="no homepage available">[www]</span>
  82. <% end %>
  83. <% if spec["has_deps"] then %>
  84. - depends on
  85. <%= spec["dependencies"].map { |v| "<a href=\"##{v["name"]}\">#{v["name"]}</a>" }.join ', ' %>.
  86. <% end %>
  87. </dt>
  88. <dd>
  89. <%=spec["summary"]%>
  90. <% if spec["executables"] then %>
  91. <br/>
  92. <% if spec["only_one_executable"] then %>
  93. Executable is
  94. <% else %>
  95. Executables are
  96. <%end%>
  97. <%= spec["executables"].map { |v| "<span class=\"context-item-name\">#{v["executable"]}</span>"}.join ', ' %>.
  98. <%end%>
  99. <br/>
  100. <br/>
  101. </dd>
  102. <% end %>
  103. </dl>
  104. </div>
  105. </div>
  106. </div>
  107. <div id="validator-badges">
  108. <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
  109. </div>
  110. </body>
  111. </html>
  112. DOC_TEMPLATE
  113. # CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108
  114. RDOC_CSS = <<-RDOC_CSS
  115. body {
  116. font-family: Verdana,Arial,Helvetica,sans-serif;
  117. font-size: 90%;
  118. margin: 0;
  119. margin-left: 40px;
  120. padding: 0;
  121. background: white;
  122. }
  123. h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
  124. h1 { font-size: 150%; }
  125. h2,h3,h4 { margin-top: 1em; }
  126. a { background: #eef; color: #039; text-decoration: none; }
  127. a:hover { background: #039; color: #eef; }
  128. /* Override the base stylesheets Anchor inside a table cell */
  129. td > a {
  130. background: transparent;
  131. color: #039;
  132. text-decoration: none;
  133. }
  134. /* and inside a section title */
  135. .section-title > a {
  136. background: transparent;
  137. color: #eee;
  138. text-decoration: none;
  139. }
  140. /* === Structural elements =================================== */
  141. div#index {
  142. margin: 0;
  143. margin-left: -40px;
  144. padding: 0;
  145. font-size: 90%;
  146. }
  147. div#index a {
  148. margin-left: 0.7em;
  149. }
  150. div#index .section-bar {
  151. margin-left: 0px;
  152. padding-left: 0.7em;
  153. background: #ccc;
  154. font-size: small;
  155. }
  156. div#classHeader, div#fileHeader {
  157. width: auto;
  158. color: white;
  159. padding: 0.5em 1.5em 0.5em 1.5em;
  160. margin: 0;
  161. margin-left: -40px;
  162. border-bottom: 3px solid #006;
  163. }
  164. div#classHeader a, div#fileHeader a {
  165. background: inherit;
  166. color: white;
  167. }
  168. div#classHeader td, div#fileHeader td {
  169. background: inherit;
  170. color: white;
  171. }
  172. div#fileHeader {
  173. background: #057;
  174. }
  175. div#classHeader {
  176. background: #048;
  177. }
  178. .class-name-in-header {
  179. font-size: 180%;
  180. font-weight: bold;
  181. }
  182. div#bodyContent {
  183. padding: 0 1.5em 0 1.5em;
  184. }
  185. div#description {
  186. padding: 0.5em 1.5em;
  187. background: #efefef;
  188. border: 1px dotted #999;
  189. }
  190. div#description h1,h2,h3,h4,h5,h6 {
  191. color: #125;;
  192. background: transparent;
  193. }
  194. div#validator-badges {
  195. text-align: center;
  196. }
  197. div#validator-badges img { border: 0; }
  198. div#copyright {
  199. color: #333;
  200. background: #efefef;
  201. font: 0.75em sans-serif;
  202. margin-top: 5em;
  203. margin-bottom: 0;
  204. padding: 0.5em 2em;
  205. }
  206. /* === Classes =================================== */
  207. table.header-table {
  208. color: white;
  209. font-size: small;
  210. }
  211. .type-note {
  212. font-size: small;
  213. color: #DEDEDE;
  214. }
  215. .xxsection-bar {
  216. background: #eee;
  217. color: #333;
  218. padding: 3px;
  219. }
  220. .section-bar {
  221. color: #333;
  222. border-bottom: 1px solid #999;
  223. margin-left: -20px;
  224. }
  225. .section-title {
  226. background: #79a;
  227. color: #eee;
  228. padding: 3px;
  229. margin-top: 2em;
  230. margin-left: -30px;
  231. border: 1px solid #999;
  232. }
  233. .top-aligned-row { vertical-align: top }
  234. .bottom-aligned-row { vertical-align: bottom }
  235. /* --- Context section classes ----------------------- */
  236. .context-row { }
  237. .context-item-name { font-family: monospace; font-weight: bold; color: black; }
  238. .context-item-value { font-size: small; color: #448; }
  239. .context-item-desc { color: #333; padding-left: 2em; }
  240. /* --- Method classes -------------------------- */
  241. .method-detail {
  242. background: #efefef;
  243. padding: 0;
  244. margin-top: 0.5em;
  245. margin-bottom: 1em;
  246. border: 1px dotted #ccc;
  247. }
  248. .method-heading {
  249. color: black;
  250. background: #ccc;
  251. border-bottom: 1px solid #666;
  252. padding: 0.2em 0.5em 0 0.5em;
  253. }
  254. .method-signature { color: black; background: inherit; }
  255. .method-name { font-weight: bold; }
  256. .method-args { font-style: italic; }
  257. .method-description { padding: 0 0.5em 0 0.5em; }
  258. /* --- Source code sections -------------------- */
  259. a.source-toggle { font-size: 90%; }
  260. div.method-source-code {
  261. background: #262626;
  262. color: #ffdead;
  263. margin: 1em;
  264. padding: 0.5em;
  265. border: 1px dashed #999;
  266. overflow: hidden;
  267. }
  268. div.method-source-code pre { color: #ffdead; overflow: hidden; }
  269. /* --- Ruby keyword styles --------------------- */
  270. .standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
  271. .ruby-constant { color: #7fffd4; background: transparent; }
  272. .ruby-keyword { color: #00ffff; background: transparent; }
  273. .ruby-ivar { color: #eedd82; background: transparent; }
  274. .ruby-operator { color: #00ffee; background: transparent; }
  275. .ruby-identifier { color: #ffdead; background: transparent; }
  276. .ruby-node { color: #ffa07a; background: transparent; }
  277. .ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
  278. .ruby-regexp { color: #ffa07a; background: transparent; }
  279. .ruby-value { color: #7fffd4; background: transparent; }
  280. RDOC_CSS
  281. RDOC_NO_DOCUMENTATION = <<-'NO_DOC'
  282. <?xml version="1.0" encoding="iso-8859-1"?>
  283. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  284. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  285. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  286. <head>
  287. <title>Found documentation</title>
  288. <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
  289. </head>
  290. <body>
  291. <div id="fileHeader">
  292. <%= SEARCH %>
  293. <h1>No documentation found</h1>
  294. </div>
  295. <div id="bodyContent">
  296. <div id="contextContent">
  297. <div id="description">
  298. <p>No gems matched <%= h query.inspect %></p>
  299. <p>
  300. Back to <a href="/">complete gem index</a>
  301. </p>
  302. </div>
  303. </div>
  304. </div>
  305. <div id="validator-badges">
  306. <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
  307. </div>
  308. </body>
  309. </html>
  310. NO_DOC
  311. RDOC_SEARCH_TEMPLATE = <<-'RDOC_SEARCH'
  312. <?xml version="1.0" encoding="iso-8859-1"?>
  313. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  314. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  315. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  316. <head>
  317. <title>Found documentation</title>
  318. <link rel="stylesheet" href="gem-server-rdoc-style.css" type="text/css" media="screen" />
  319. </head>
  320. <body>
  321. <div id="fileHeader">
  322. <%= SEARCH %>
  323. <h1>Found documentation</h1>
  324. </div>
  325. <!-- banner header -->
  326. <div id="bodyContent">
  327. <div id="contextContent">
  328. <div id="description">
  329. <h1>Summary</h1>
  330. <p><%=doc_items.length%> documentation topics found.</p>
  331. <h1>Topics</h1>
  332. <dl>
  333. <% doc_items.each do |doc_item| %>
  334. <dt>
  335. <b><%=doc_item[:name]%></b>
  336. <a href="<%=doc_item[:url]%>">[rdoc]</a>
  337. </dt>
  338. <dd>
  339. <%=doc_item[:summary]%>
  340. <br/>
  341. <br/>
  342. </dd>
  343. <% end %>
  344. </dl>
  345. <p>
  346. Back to <a href="/">complete gem index</a>
  347. </p>
  348. </div>
  349. </div>
  350. </div>
  351. <div id="validator-badges">
  352. <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
  353. </div>
  354. </body>
  355. </html>
  356. RDOC_SEARCH
  357. def self.run(options)
  358. new(options[:gemdir], options[:port], options[:daemon],
  359. options[:launch], options[:addresses]).run
  360. end
  361. ##
  362. # Only the first directory in gem_dirs is used for serving gems
  363. def initialize(gem_dirs, port, daemon, launch = nil, addresses = nil)
  364. Socket.do_not_reverse_lookup = true
  365. @gem_dirs = Array gem_dirs
  366. @port = port
  367. @daemon = daemon
  368. @launch = launch
  369. @addresses = addresses
  370. logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL
  371. @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger
  372. @spec_dirs = @gem_dirs.map do |gem_dir|
  373. spec_dir = File.join gem_dir, 'specifications'
  374. unless File.directory? spec_dir then
  375. raise ArgumentError, "#{gem_dir} does not appear to be a gem repository"
  376. end
  377. spec_dir
  378. end
  379. @source_index = Gem::SourceIndex.new(@spec_dirs)
  380. end
  381. def Marshal(req, res)
  382. @source_index.refresh!
  383. add_date res
  384. index = Marshal.dump @source_index
  385. if req.request_method == 'HEAD' then
  386. res['content-length'] = index.length
  387. return
  388. end
  389. if req.path =~ /Z$/ then
  390. res['content-type'] = 'application/x-deflate'
  391. index = Gem.deflate index
  392. else
  393. res['content-type'] = 'application/octet-stream'
  394. end
  395. res.body << index
  396. end
  397. def add_date res
  398. res['date'] = @spec_dirs.map do |spec_dir|
  399. File.stat(spec_dir).mtime
  400. end.max
  401. end
  402. def latest_specs(req, res)
  403. @source_index.refresh!
  404. res['content-type'] = 'application/x-gzip'
  405. add_date res
  406. specs = @source_index.latest_specs.sort.map do |spec|
  407. platform = spec.original_platform
  408. platform = Gem::Platform::RUBY if platform.nil?
  409. [spec.name, spec.version, platform]
  410. end
  411. specs = Marshal.dump specs
  412. if req.path =~ /\.gz$/ then
  413. specs = Gem.gzip specs
  414. res['content-type'] = 'application/x-gzip'
  415. else
  416. res['content-type'] = 'application/octet-stream'
  417. end
  418. if req.request_method == 'HEAD' then
  419. res['content-length'] = specs.length
  420. else
  421. res.body << specs
  422. end
  423. end
  424. ##
  425. # Creates server sockets based on the addresses option. If no addresses
  426. # were given a server socket for all interfaces is created.
  427. def listen addresses = @addresses
  428. addresses = [nil] unless addresses
  429. listeners = 0
  430. addresses.each do |address|
  431. begin
  432. @server.listen address, @port
  433. @server.listeners[listeners..-1].each do |listener|
  434. host, port = listener.addr.values_at 2, 1
  435. host = "[#{host}]" if host =~ /:/ # we don't reverse lookup
  436. say "Server started at http://#{host}:#{port}"
  437. end
  438. listeners = @server.listeners.length
  439. rescue SystemCallError
  440. next
  441. end
  442. end
  443. if @server.listeners.empty? then
  444. say "Unable to start a server."
  445. say "Check for running servers or your --bind and --port arguments"
  446. terminate_interaction 1
  447. end
  448. end
  449. def quick(req, res)
  450. @source_index.refresh!
  451. res['content-type'] = 'text/plain'
  452. add_date res
  453. case req.request_uri.path
  454. when %r|^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)-([0-9.]+)(-.*?)?\.gemspec\.rz$| then
  455. dep = Gem::Dependency.new $2, $3
  456. specs = @source_index.search dep
  457. marshal_format = $1
  458. selector = [$2, $3, $4].map { |s| s.inspect }.join ' '
  459. platform = if $4 then
  460. Gem::Platform.new $4.sub(/^-/, '')
  461. else
  462. Gem::Platform::RUBY
  463. end
  464. specs = specs.select { |s| s.platform == platform }
  465. if specs.empty? then
  466. res.status = 404
  467. res.body = "No gems found matching #{selector}"
  468. elsif specs.length > 1 then
  469. res.status = 500
  470. res.body = "Multiple gems found matching #{selector}"
  471. elsif marshal_format then
  472. res['content-type'] = 'application/x-deflate'
  473. res.body << Gem.deflate(Marshal.dump(specs.first))
  474. end
  475. else
  476. raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found."
  477. end
  478. end
  479. def root(req, res)
  480. @source_index.refresh!
  481. add_date res
  482. raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless
  483. req.path == '/'
  484. specs = []
  485. total_file_count = 0
  486. @source_index.each do |path, spec|
  487. total_file_count += spec.files.size
  488. deps = spec.dependencies.map do |dep|
  489. { "name" => dep.name,
  490. "type" => dep.type,
  491. "version" => dep.requirement.to_s, }
  492. end
  493. deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] }
  494. deps.last["is_last"] = true unless deps.empty?
  495. # executables
  496. executables = spec.executables.sort.collect { |exec| {"executable" => exec} }
  497. executables = nil if executables.empty?
  498. executables.last["is_last"] = true if executables
  499. specs << {
  500. "authors" => spec.authors.sort.join(", "),
  501. "date" => spec.date.to_s,
  502. "dependencies" => deps,
  503. "doc_path" => "/doc_root/#{spec.full_name}/rdoc/index.html",
  504. "executables" => executables,
  505. "only_one_executable" => (executables && executables.size == 1),
  506. "full_name" => spec.full_name,
  507. "has_deps" => !deps.empty?,
  508. "homepage" => spec.homepage,
  509. "name" => spec.name,
  510. "rdoc_installed" => Gem::DocManager.new(spec).rdoc_installed?,
  511. "summary" => spec.summary,
  512. "version" => spec.version.to_s,
  513. }
  514. end
  515. specs << {
  516. "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others",
  517. "dependencies" => [],
  518. "doc_path" => "/doc_root/rubygems-#{Gem::VERSION}/rdoc/index.html",
  519. "executables" => [{"executable" => 'gem', "is_last" => true}],
  520. "only_one_executable" => true,
  521. "full_name" => "rubygems-#{Gem::VERSION}",
  522. "has_deps" => false,
  523. "homepage" => "http://docs.rubygems.org/",
  524. "name" => 'rubygems',
  525. "rdoc_installed" => true,
  526. "summary" => "RubyGems itself",
  527. "version" => Gem::VERSION,
  528. }
  529. specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] }
  530. specs.last["is_last"] = true
  531. # tag all specs with first_name_entry
  532. last_spec = nil
  533. specs.each do |spec|
  534. is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase)
  535. spec["first_name_entry"] = is_first
  536. last_spec = spec
  537. end
  538. # create page from template
  539. template = ERB.new(DOC_TEMPLATE)
  540. res['content-type'] = 'text/html'
  541. values = { "gem_count" => specs.size.to_s, "specs" => specs,
  542. "total_file_count" => total_file_count.to_s }
  543. # suppress 1.9.3dev warning about unused variable
  544. values = values
  545. result = template.result binding
  546. res.body = result
  547. end
  548. ##
  549. # Can be used for quick navigation to the rdoc documentation. You can then
  550. # define a search shortcut for your browser. E.g. in Firefox connect
  551. # 'shortcut:rdoc' to http://localhost:8808/rdoc?q=%s template. Then you can
  552. # directly open the ActionPack documentation by typing 'rdoc actionp'. If
  553. # there are multiple hits for the search term, they are presented as a list
  554. # with links.
  555. #
  556. # Search algorithm aims for an intuitive search:
  557. # 1. first try to find the gems and documentation folders which name
  558. # starts with the search term
  559. # 2. search for entries, that *contain* the search term
  560. # 3. show all the gems
  561. #
  562. # If there is only one search hit, user is immediately redirected to the
  563. # documentation for the particular gem, otherwise a list with results is
  564. # shown.
  565. #
  566. # === Additional trick - install documentation for ruby core
  567. #
  568. # Note: please adjust paths accordingly use for example 'locate yaml.rb' and
  569. # 'gem environment' to identify directories, that are specific for your
  570. # local installation
  571. #
  572. # 1. install ruby sources
  573. # cd /usr/src
  574. # sudo apt-get source ruby
  575. #
  576. # 2. generate documentation
  577. # rdoc -o /usr/lib/ruby/gems/1.8/doc/core/rdoc \
  578. # /usr/lib/ruby/1.8 ruby1.8-1.8.7.72
  579. #
  580. # By typing 'rdoc core' you can now access the core documentation
  581. def rdoc(req, res)
  582. query = req.query['q']
  583. show_rdoc_for_pattern("#{query}*", res) && return
  584. show_rdoc_for_pattern("*#{query}*", res) && return
  585. template = ERB.new RDOC_NO_DOCUMENTATION
  586. res['content-type'] = 'text/html'
  587. res.body = template.result binding
  588. end
  589. ##
  590. # Returns true and prepares http response, if rdoc for the requested gem
  591. # name pattern was found.
  592. #
  593. # The search is based on the file system content, not on the gems metadata.
  594. # This allows additional documentation folders like 'core' for the ruby core
  595. # documentation - just put it underneath the main doc folder.
  596. def show_rdoc_for_pattern(pattern, res)
  597. found_gems = Dir.glob("{#{@gem_dirs.join ','}}/doc/#{pattern}").select {|path|
  598. File.exist? File.join(path, 'rdoc/index.html')
  599. }
  600. case found_gems.length
  601. when 0
  602. return false
  603. when 1
  604. new_path = File.basename(found_gems[0])
  605. res.status = 302
  606. res['Location'] = "/doc_root/#{new_path}/rdoc/index.html"
  607. return true
  608. else
  609. doc_items = []
  610. found_gems.each do |file_name|
  611. base_name = File.basename(file_name)
  612. doc_items << {
  613. :name => base_name,
  614. :url => "/doc_root/#{base_name}/rdoc/index.html",
  615. :summary => ''
  616. }
  617. end
  618. template = ERB.new(RDOC_SEARCH_TEMPLATE)
  619. res['content-type'] = 'text/html'
  620. result = template.result binding
  621. res.body = result
  622. return true
  623. end
  624. end
  625. def run
  626. listen
  627. WEBrick::Daemon.start if @daemon
  628. @server.mount_proc "/Marshal.#{Gem.marshal_version}", method(:Marshal)
  629. @server.mount_proc "/Marshal.#{Gem.marshal_version}.Z", method(:Marshal)
  630. @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs)
  631. @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs)
  632. @server.mount_proc "/latest_specs.#{Gem.marshal_version}",
  633. method(:latest_specs)
  634. @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz",
  635. method(:latest_specs)
  636. @server.mount_proc "/quick/", method(:quick)
  637. @server.mount_proc("/gem-server-rdoc-style.css") do |req, res|
  638. res['content-type'] = 'text/css'
  639. add_date res
  640. res.body << RDOC_CSS
  641. end
  642. @server.mount_proc "/", method(:root)
  643. @server.mount_proc "/rdoc", method(:rdoc)
  644. paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" }
  645. paths.each do |mount_point, mount_dir|
  646. @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler,
  647. File.join(@gem_dirs.first, mount_dir), true)
  648. end
  649. trap("INT") { @server.shutdown; exit! }
  650. trap("TERM") { @server.shutdown; exit! }
  651. launch if @launch
  652. @server.start
  653. end
  654. def specs(req, res)
  655. @source_index.refresh!
  656. add_date res
  657. specs = @source_index.sort.map do |_, spec|
  658. platform = spec.original_platform
  659. platform = Gem::Platform::RUBY if platform.nil?
  660. [spec.name, spec.version, platform]
  661. end
  662. specs = Marshal.dump specs
  663. if req.path =~ /\.gz$/ then
  664. specs = Gem.gzip specs
  665. res['content-type'] = 'application/x-gzip'
  666. else
  667. res['content-type'] = 'application/octet-stream'
  668. end
  669. if req.request_method == 'HEAD' then
  670. res['content-length'] = specs.length
  671. else
  672. res.body << specs
  673. end
  674. end
  675. def launch
  676. listeners = @server.listeners.map{|l| l.addr[2] }
  677. host = listeners.any?{|l| l == '0.0.0.0'} ? 'localhost' : listeners.first
  678. say "Launching browser to http://#{host}:#{@port}"
  679. system("#{@launch} http://#{host}:#{@port}")
  680. end
  681. end