Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial import

  • Loading branch information...
commit 46d267b434f401a9fe66767705d47cd2e30056ab 0 parents
Jesse Newland authored
4 Rakefile
@@ -0,0 +1,4 @@
+task :default => :spec
+task :spec do
+ system("spec --options spec/spec.opts spec/*_spec.rb") || raise
+end
7 config.ru
@@ -0,0 +1,7 @@
+LIB_PATH = File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
+$:.unshift LIB_PATH
+require 'rack_rubygems'
+use Rack::Lint
+use GemsAndRdocs, :urls => ['/cache', '/doc'], :root => Gem.dir
+use Rack::Compress
+run RackRubygems.new
23 lib/gems_and_rdocs.rb
@@ -0,0 +1,23 @@
+class GemsAndRdocs
+
+ def initialize(app, options={})
+ @app = app
+ @urls = options[:urls]
+ @file_server = Rack::File.new(options[:root])
+ end
+
+ def call(env)
+ old_path_info = env["PATH_INFO"]
+ env["PATH_INFO"] = env["PATH_INFO"].to_s.gsub(/^\/gems/,'/cache')
+ env["PATH_INFO"] = env["PATH_INFO"].to_s.gsub(/^\/doc_root/,'/doc')
+ path = env["PATH_INFO"]
+ can_serve = @urls.any? { |url| path.index(url) == 0 }
+
+ if can_serve
+ @file_server.call(env)
+ else
+ env["PATH_INFO"] = old_path_info
+ @app.call(env)
+ end
+ end
+end
46 lib/rack_compress.rb
@@ -0,0 +1,46 @@
+require 'zlib'
+require 'stringio'
+module Rack
+ class Compress
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ request = Rack::Request.new(env)
+ case request.path_info
+ when /\.Z$/
+ env["PATH_INFO"] = env["PATH_INFO"].to_s.gsub(/\.Z$/,'')
+ status, headers, response = @app.call(env)
+ response = deflate(response)
+ headers['Content-Type'] = 'application/x-deflate'
+ headers['Content-Length'] = response.length.to_s
+ when /\.gz$/
+ env["PATH_INFO"] = env["PATH_INFO"].to_s.gsub(/\.gz$/,'')
+ status, headers, response = @app.call(env)
+ response = gzip(response)
+ headers['Content-Type'] = 'application/x-gzip'
+ headers['Content-Length'] = response.length.to_s
+ else
+ status, headers, response = @app.call(env)
+ end
+ [status, headers, response]
+ end
+
+ def gzip(response)
+ zipped = StringIO.new
+ Zlib::GzipWriter.wrap zipped do |io|
+ io.write response
+ end
+ zipped.string
+ end
+
+ def deflate(response)
+ body = ''
+ response.each{ |s| body << s }
+ Zlib::Deflate.deflate body
+ end
+
+ end
+end
84 lib/rack_rubygems.rb
@@ -0,0 +1,84 @@
+require 'rubygems'
+require 'sinatra/base'
+require 'yaml'
+require 'zlib'
+require 'erb'
+require 'rubygems'
+require 'rubygems/doc_manager'
+require File.expand_path(File.dirname(__FILE__) + "/rack_compress")
+require File.expand_path(File.dirname(__FILE__) + "/gems_and_rdocs")
+
+class RackRubygems < Sinatra::Base
+
+ head "/Marshal.#{Gem.marshal_version}" do
+ response['Content-Type'] = 'application/octet-stream'
+ response['Content-Length'] = source_index.length.to_s
+ end
+
+ get "/Marshal.#{Gem.marshal_version}" do
+ marshal(source_index)
+ end
+
+ head "/latest_specs.#{Gem.marshal_version}" do
+ response['Content-Type'] = 'application/octet-stream'
+ response['Content-Length'] = latest_specs.length.to_s
+ end
+
+ get "/latest_specs.#{Gem.marshal_version}" do
+ marshal(latest_specs)
+ end
+
+ head "/specs.#{Gem.marshal_version}" do
+ response['Content-Type'] = 'application/octet-stream'
+ response['Content-Length'] = specs.length.to_s
+ end
+
+ get "/specs.#{Gem.marshal_version}" do
+ marshal(specs)
+ end
+
+ head "/yaml.#{Gem.marshal_version}" do
+ response['Content-Type'] = 'text/plain'
+ response['Content-Length'] = source_index.length.to_s
+ end
+
+ get "/yaml.#{Gem.marshal_version}" do
+ response['Content-Type'] = 'text/plain'
+ yaml
+ end
+
+ def source_index
+ @gem_dir = Gem.dir
+ @spec_dir = File.join @gem_dir, 'specifications'
+ @source_index = Gem::SourceIndex.from_gems_in @spec_dir
+ response['Date'] = File.stat(@spec_dir).mtime.to_s
+ @source_index.refresh!
+ @source_index
+ end
+
+ def marshal(data)
+ response['Content-Type'] = 'application/octet-stream'
+ Marshal.dump(data)
+ end
+
+ def latest_specs
+ source_index.latest_specs.sort.map do |spec|
+ platform = spec.original_platform
+ platform = Gem::Platform::RUBY if platform.nil?
+ [spec.name, spec.version, platform]
+ end
+ end
+
+ def specs
+ specs = source_index.sort.map do |_, spec|
+ platform = spec.original_platform
+ platform = Gem::Platform::RUBY if platform.nil?
+ [spec.name, spec.version, platform]
+ end
+ end
+
+ def yaml
+ source_index.to_yaml
+ end
+
+end
66 spec/rack_rubygems_spec.rb
@@ -0,0 +1,66 @@
+require File.expand_path(File.dirname(__FILE__) + "/spec_helper.rb")
+
+describe "The Rack Rubygems Server" do
+
+ it "serves a list of gems up at the root"
+
+ it 'serves rdocs' do
+ get '/doc_root/rubygems-1.3.1/rdoc/index.html'
+ @response.should be_ok
+ end
+
+ it 'serves gems' do
+ get '/gems/rack-0.9.1.gem'
+ @response.should be_ok
+ end
+
+ it 'provides marshal data' do
+ should_match_webrick_behavior "/Marshal.#{Gem.marshal_version}", :Marshal
+ should_match_webrick_behavior "/Marshal.#{Gem.marshal_version}", :Marshal, :head
+ end
+
+ it 'provides compressed marshal data' do
+ should_match_webrick_behavior "/Marshal.#{Gem.marshal_version}.Z", :Marshal
+ end
+
+ it 'provides latest specs' do
+ should_match_webrick_behavior "/latest_specs.#{Gem.marshal_version}", :latest_specs
+ should_match_webrick_behavior "/latest_specs.#{Gem.marshal_version}", :latest_specs, :head
+ end
+
+ it 'provides compressed latest specs' do
+ should_match_webrick_behavior "/latest_specs.#{Gem.marshal_version}.gz", :latest_specs
+ end
+
+ it 'provides specs' do
+ should_match_webrick_behavior "/specs.#{Gem.marshal_version}", :specs
+ should_match_webrick_behavior "/specs.#{Gem.marshal_version}", :specs, :head
+ end
+
+ it 'provides compressed specs' do
+ should_match_webrick_behavior "/specs.#{Gem.marshal_version}.gz", :specs
+ end
+
+ it 'provides yaml' do
+ should_match_webrick_behavior "/yaml.#{Gem.marshal_version}", :yaml
+ should_match_webrick_behavior "/yaml.#{Gem.marshal_version}", :yaml, :head
+ end
+
+ it 'provides compressed yaml' do
+ should_match_webrick_behavior "/yaml.#{Gem.marshal_version}.Z", :yaml
+ end
+
+ describe "provides access to individual gemspecs" do
+ it "via name and version"
+ it "via name, version, and platform"
+ it "performing substring matching"
+ it "and a quick index"
+ it "and a quick compressed index"
+ it "and a latest index"
+ it "and a compressed latest index"
+ it "returns a 404 when accessing a missing gem"
+ it "marshalled via name and version"
+ it "marshalled via name, version, and platform"
+ end
+
+end
4 spec/spec.opts
@@ -0,0 +1,4 @@
+--colour
+--format s
+--loadby mtime
+--reverse
114 spec/spec_helper.rb
@@ -0,0 +1,114 @@
+require 'rubygems'
+require 'rubygems/server'
+require 'sinatra/test'
+require 'sinatra/test/unit'
+require 'spec'
+require 'spec/interop/test'
+require 'stringio'
+require 'webrick'
+
+Sinatra::Default.set(
+ :environment => :test,
+ :run => false,
+ :raise_errors => true,
+ :logging => false
+)
+
+require File.expand_path(File.dirname(__FILE__) + "/../lib/rack_rubygems.rb")
+
+module RackRubygemsTestHelpers
+
+ def should_match_webrick_behavior(url, server_method, method = :get)
+ #webrick
+ data = StringIO.new "#{method.to_s.capitalize} #{url} HTTP/1.0\r\n\r\n"
+ @webrick_request.parse data
+
+ @webrick.send(server_method, @webrick_request, @webrick_response)
+ #sinatra
+ send(method, url)
+
+ #verify
+ @response['Content-Type'].should == @webrick_response['Content-Type']
+ @response.status.should == @webrick_response.status
+
+ if method == :head
+ #the default gem server misbehaves and never sends a Content-Length
+ #header, so we only check on HEAD requests, which are only implemented
+ #in the gem server for providing gem counts via Content-Length
+ @response.headers['Content-Length'].to_i.should > 0
+ else
+ #the default gem server misbehaves and returns a body when retrieving a
+ #HEAD request, so we don't verify
+ @response.body.length.should == @webrick_response.body.length
+ end
+ end
+
+ def quick_gem(gemname, version='2')
+ require 'rubygems/specification'
+
+ spec = Gem::Specification.new do |s|
+ s.platform = Gem::Platform::RUBY
+ s.name = gemname
+ s.version = version
+ s.author = 'A User'
+ s.email = 'example@example.com'
+ s.homepage = 'http://example.com'
+ s.has_rdoc = true
+ s.summary = "this is a summary"
+ s.description = "This is a test description"
+
+ yield(s) if block_given?
+ end
+
+ path = File.join "specifications", "#{spec.full_name}.gemspec"
+ written_path = write_file path do |io|
+ io.write(spec.to_ruby)
+ end
+
+ spec.loaded_from = written_path
+
+ Gem.source_index.add_spec spec
+
+ return spec
+ end
+
+ def write_file(path)
+ tmpdir = nil
+ Dir.chdir Dir.tmpdir do tmpdir = Dir.pwd end # HACK OSX /private/tmp
+ @tempdir = File.join tmpdir, "test_rubygems_#{$$}"
+ @tempdir.untaint
+ @gemhome = File.join @tempdir, "gemhome"
+ path = File.join(@gemhome, path)
+ dir = File.dirname path
+ FileUtils.mkdir_p dir
+
+ open path, 'wb' do |io|
+ yield io
+ end
+
+ path
+ end
+
+ def process_based_port
+ @@process_based_port ||= 8000 + $$ % 1000
+ end
+
+end
+
+Spec::Runner.configure do |config|
+ config.before(:each) {
+ @app = Rack::Builder.new {
+ use GemsAndRdocs, :urls => ['/cache', '/doc'], :root => Gem.dir
+ use Rack::Compress
+ run RackRubygems.new
+ }
+ @a1 = quick_gem 'a', '1'
+ @a2 = quick_gem 'a', '2'
+
+ @webrick = Gem::Server.new Gem.dir, process_based_port, false
+ @webrick_request = WEBrick::HTTPRequest.new :Logger => nil
+ @webrick_response = WEBrick::HTTPResponse.new :HTTPVersion => '1.0'
+ }
+ config.include Sinatra::Test
+ config.include RackRubygemsTestHelpers
+end
Please sign in to comment.
Something went wrong with that request. Please try again.