| 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
| 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 |
# 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
# File lib/rubygems/server.rb, line 332
332: def self.run(options)
333: new(options[:gemdir], options[:port], options[:daemon]).run
334: end
# 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
# 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
# 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
# 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
# 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
# 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
# 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