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:

Usage

  gem_server = Gem::Server.new Gem.dir, 8089, false
  gem_server.run

Methods

Marshal   latest_specs   new   quick   root   run   run   specs   yaml  

Included Modules

Gem::UserInteraction

Constants

DOC_TEMPLATE = <<-'WEBPAGE' <?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"> <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> WEBPAGE
RDOC_CSS = <<-RDOCCSS 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; } RDOCCSS   CSS is copy & paste from rdoc-style.css, RDoc V1.0.1 - 20041108

Public Class methods

[Source]

     # File lib/rubygems/server.rb, line 336
336:   def initialize(gem_dir, port, daemon)
337:     Socket.do_not_reverse_lookup = true
338: 
339:     @gem_dir = gem_dir
340:     @port = port
341:     @daemon = daemon
342:     logger = WEBrick::Log.new nil, WEBrick::BasicLog::FATAL
343:     @server = WEBrick::HTTPServer.new :DoNotListen => true, :Logger => logger
344: 
345:     @spec_dir = File.join @gem_dir, 'specifications'
346: 
347:     unless File.directory? @spec_dir then
348:       raise ArgumentError, "#{@gem_dir} does not appear to be a gem repository"
349:     end
350: 
351:     @source_index = Gem::SourceIndex.from_gems_in @spec_dir
352:   end

[Source]

     # File lib/rubygems/server.rb, line 332
332:   def self.run(options)
333:     new(options[:gemdir], options[:port], options[:daemon]).run
334:   end

Public Instance methods

[Source]

     # File lib/rubygems/server.rb, line 354
354:   def Marshal(req, res)
355:     @source_index.refresh!
356: 
357:     res['date'] = File.stat(@spec_dir).mtime
358: 
359:     index = Marshal.dump @source_index
360: 
361:     if req.request_method == 'HEAD' then
362:       res['content-length'] = index.length
363:       return
364:     end
365: 
366:     if req.path =~ /Z$/ then
367:       res['content-type'] = 'application/x-deflate'
368:       index = Gem.deflate index
369:     else
370:       res['content-type'] = 'application/octet-stream'
371:     end
372: 
373:     res.body << index
374:   end

[Source]

     # File lib/rubygems/server.rb, line 376
376:   def latest_specs(req, res)
377:     @source_index.refresh!
378: 
379:     res['content-type'] = 'application/x-gzip'
380: 
381:     res['date'] = File.stat(@spec_dir).mtime
382: 
383:     specs = @source_index.latest_specs.sort.map do |spec|
384:       platform = spec.original_platform
385:       platform = Gem::Platform::RUBY if platform.nil?
386:       [spec.name, spec.version, platform]
387:     end
388: 
389:     specs = Marshal.dump specs
390: 
391:     if req.path =~ /\.gz$/ then
392:       specs = Gem.gzip specs
393:       res['content-type'] = 'application/x-gzip'
394:     else
395:       res['content-type'] = 'application/octet-stream'
396:     end
397: 
398:     if req.request_method == 'HEAD' then
399:       res['content-length'] = specs.length
400:     else
401:       res.body << specs
402:     end
403:   end

[Source]

     # File lib/rubygems/server.rb, line 405
405:   def quick(req, res)
406:     @source_index.refresh!
407: 
408:     res['content-type'] = 'text/plain'
409:     res['date'] = File.stat(@spec_dir).mtime
410: 
411:     case req.request_uri.path
412:     when '/quick/index' then
413:       res.body << @source_index.map { |name,| name }.sort.join("\n")
414:     when '/quick/index.rz' then
415:       index = @source_index.map { |name,| name }.sort.join("\n")
416:       res['content-type'] = 'application/x-deflate'
417:       res.body << Gem.deflate(index)
418:     when '/quick/latest_index' then
419:       index = @source_index.latest_specs.map { |spec| spec.full_name }
420:       res.body << index.sort.join("\n")
421:     when '/quick/latest_index.rz' then
422:       index = @source_index.latest_specs.map { |spec| spec.full_name }
423:       res['content-type'] = 'application/x-deflate'
424:       res.body << Gem.deflate(index.sort.join("\n"))
425:     when %r|^/quick/(Marshal.#{Regexp.escape Gem.marshal_version}/)?(.*?)-([0-9.]+)(-.*?)?\.gemspec\.rz$| then
426:       dep = Gem::Dependency.new $2, $3
427:       specs = @source_index.search dep
428:       marshal_format = $1
429: 
430:       selector = [$2, $3, $4].map { |s| s.inspect }.join ' '
431: 
432:       platform = if $4 then
433:                    Gem::Platform.new $4.sub(/^-/, '')
434:                  else
435:                    Gem::Platform::RUBY
436:                  end
437: 
438:       specs = specs.select { |s| s.platform == platform }
439: 
440:       if specs.empty? then
441:         res.status = 404
442:         res.body = "No gems found matching #{selector}"
443:       elsif specs.length > 1 then
444:         res.status = 500
445:         res.body = "Multiple gems found matching #{selector}"
446:       elsif marshal_format then
447:         res['content-type'] = 'application/x-deflate'
448:         res.body << Gem.deflate(Marshal.dump(specs.first))
449:       else # deprecated YAML format
450:         res['content-type'] = 'application/x-deflate'
451:         res.body << Gem.deflate(specs.first.to_yaml)
452:       end
453:     else
454:       raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found."
455:     end
456:   end

[Source]

     # File lib/rubygems/server.rb, line 458
458:   def root(req, res)
459:     @source_index.refresh!
460:     res['date'] = File.stat(@spec_dir).mtime
461: 
462:     raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless
463:       req.path == '/'
464: 
465:     specs = []
466:     total_file_count = 0
467: 
468:     @source_index.each do |path, spec|
469:       total_file_count += spec.files.size
470:       deps = spec.dependencies.map do |dep|
471:         { "name"    => dep.name,
472:           "type"    => dep.type,
473:           "version" => dep.version_requirements.to_s, }
474:       end
475: 
476:       deps = deps.sort_by { |dep| [dep["name"].downcase, dep["version"]] }
477:       deps.last["is_last"] = true unless deps.empty?
478: 
479:       # executables
480:       executables = spec.executables.sort.collect { |exec| {"executable" => exec} }
481:       executables = nil if executables.empty?
482:       executables.last["is_last"] = true if executables
483: 
484:       specs << {
485:         "authors"             => spec.authors.sort.join(", "),
486:         "date"                => spec.date.to_s,
487:         "dependencies"        => deps,
488:         "doc_path"            => "/doc_root/#{spec.full_name}/rdoc/index.html",
489:         "executables"         => executables,
490:         "only_one_executable" => (executables && executables.size == 1),
491:         "full_name"           => spec.full_name,
492:         "has_deps"            => !deps.empty?,
493:         "homepage"            => spec.homepage,
494:         "name"                => spec.name,
495:         "rdoc_installed"      => Gem::DocManager.new(spec).rdoc_installed?,
496:         "summary"             => spec.summary,
497:         "version"             => spec.version.to_s,
498:       }
499:     end
500: 
501:     specs << {
502:       "authors" => "Chad Fowler, Rich Kilmer, Jim Weirich, Eric Hodel and others",
503:       "dependencies" => [],
504:       "doc_path" => "/doc_root/rubygems-#{Gem::RubyGemsVersion}/rdoc/index.html",
505:       "executables" => [{"executable" => 'gem', "is_last" => true}],
506:       "only_one_executable" => true,
507:       "full_name" => "rubygems-#{Gem::RubyGemsVersion}",
508:       "has_deps" => false,
509:       "homepage" => "http://rubygems.org/",
510:       "name" => 'rubygems',
511:       "rdoc_installed" => true,
512:       "summary" => "RubyGems itself",
513:       "version" => Gem::RubyGemsVersion,
514:     }
515: 
516:     specs = specs.sort_by { |spec| [spec["name"].downcase, spec["version"]] }
517:     specs.last["is_last"] = true
518: 
519:     # tag all specs with first_name_entry
520:     last_spec = nil
521:     specs.each do |spec|
522:       is_first = last_spec.nil? || (last_spec["name"].downcase != spec["name"].downcase)
523:       spec["first_name_entry"] = is_first
524:       last_spec = spec
525:     end
526: 
527:     # create page from template
528:     template = ERB.new(DOC_TEMPLATE)
529:     res['content-type'] = 'text/html'
530: 
531:     values = { "gem_count" => specs.size.to_s, "specs" => specs,
532:                "total_file_count" => total_file_count.to_s }
533: 
534:     result = template.result binding
535:     res.body = result
536:   end

[Source]

     # File lib/rubygems/server.rb, line 538
538:   def run
539:     @server.listen nil, @port
540: 
541:     say "Starting gem server on http://localhost:#{@port}/"
542: 
543:     WEBrick::Daemon.start if @daemon
544: 
545:     @server.mount_proc "/yaml", method(:yaml)
546:     @server.mount_proc "/yaml.Z", method(:yaml)
547: 
548:     @server.mount_proc "/Marshal.#{Gem.marshal_version}", method(:Marshal)
549:     @server.mount_proc "/Marshal.#{Gem.marshal_version}.Z", method(:Marshal)
550: 
551:     @server.mount_proc "/specs.#{Gem.marshal_version}", method(:specs)
552:     @server.mount_proc "/specs.#{Gem.marshal_version}.gz", method(:specs)
553: 
554:     @server.mount_proc "/latest_specs.#{Gem.marshal_version}",
555:                        method(:latest_specs)
556:     @server.mount_proc "/latest_specs.#{Gem.marshal_version}.gz",
557:                        method(:latest_specs)
558: 
559:     @server.mount_proc "/quick/", method(:quick)
560: 
561:     @server.mount_proc("/gem-server-rdoc-style.css") do |req, res|
562:       res['content-type'] = 'text/css'
563:       res['date'] = File.stat(@spec_dir).mtime
564:       res.body << RDOC_CSS
565:     end
566: 
567:     @server.mount_proc "/", method(:root)
568: 
569:     paths = { "/gems" => "/cache/", "/doc_root" => "/doc/" }
570:     paths.each do |mount_point, mount_dir|
571:       @server.mount(mount_point, WEBrick::HTTPServlet::FileHandler,
572:                     File.join(@gem_dir, mount_dir), true)
573:     end
574: 
575:     trap("INT") { @server.shutdown; exit! }
576:     trap("TERM") { @server.shutdown; exit! }
577: 
578:     @server.start
579:   end

[Source]

     # File lib/rubygems/server.rb, line 581
581:   def specs(req, res)
582:     @source_index.refresh!
583: 
584:     res['date'] = File.stat(@spec_dir).mtime
585: 
586:     specs = @source_index.sort.map do |_, spec|
587:       platform = spec.original_platform
588:       platform = Gem::Platform::RUBY if platform.nil?
589:       [spec.name, spec.version, platform]
590:     end
591: 
592:     specs = Marshal.dump specs
593: 
594:     if req.path =~ /\.gz$/ then
595:       specs = Gem.gzip specs
596:       res['content-type'] = 'application/x-gzip'
597:     else
598:       res['content-type'] = 'application/octet-stream'
599:     end
600: 
601:     if req.request_method == 'HEAD' then
602:       res['content-length'] = specs.length
603:     else
604:       res.body << specs
605:     end
606:   end

[Source]

     # File lib/rubygems/server.rb, line 608
608:   def yaml(req, res)
609:     @source_index.refresh!
610: 
611:     res['date'] = File.stat(@spec_dir).mtime
612: 
613:     index = @source_index.to_yaml
614: 
615:     if req.path =~ /Z$/ then
616:       res['content-type'] = 'application/x-deflate'
617:       index = Gem.deflate index
618:     else
619:       res['content-type'] = 'text/plain'
620:     end
621: 
622:     if req.request_method == 'HEAD' then
623:       res['content-length'] = index.length
624:       return
625:     end
626: 
627:     res.body << index
628:   end

[Validate]