summary refs log tree commit
diff options
context:
space:
mode:
authorYehuda Katz + Carl Lerche <ykatz+clerche@engineyard.com>2009-11-21 10:52:33 -0800
committerJoshua Peek <josh@joshpeek.com>2009-11-21 10:52:33 -0800
commit5db5d4e7432d026a7bddf344f97e8facd09128d8 (patch)
tree8d5e3586717a098ac6218cb571426d8fb2aaf537
parent87957c6c76f4f1cc9310667f71a4c15a3f35eb23 (diff)
downloadrack-5db5d4e7432d026a7bddf344f97e8facd09128d8.tar.gz
Refactor rackup into Rack::Server
Signed-off-by: Joshua Peek <josh@joshpeek.com>
-rwxr-xr-xbin/rackup178
-rw-r--r--lib/rack.rb1
-rw-r--r--lib/rack/builder.rb15
-rw-r--r--lib/rack/handler.rb19
-rw-r--r--lib/rack/server.rb189
-rw-r--r--test/rackup/config.ru10
-rw-r--r--test/spec_rackup.rb38
7 files changed, 267 insertions, 183 deletions
diff --git a/bin/rackup b/bin/rackup
index 91abe17b..a6f98913 100755
--- a/bin/rackup
+++ b/bin/rackup
@@ -1,176 +1,2 @@
-#!/usr/bin/env ruby
-# -*- ruby -*-
-
-require 'rack'
-
-require 'optparse'
-
-automatic = false
-server = nil
-env = "development"
-daemonize = false
-pid = nil
-options = {:Port => 9292, :Host => "0.0.0.0", :AccessLog => []}
-
-# Don't evaluate CGI ISINDEX parameters.
-# http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
-ARGV.clear  if ENV.include?("REQUEST_METHOD")
-
-opts = OptionParser.new("", 24, '  ') { |opts|
-  opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
-
-  opts.separator ""
-  opts.separator "Ruby options:"
-
-  lineno = 1
-  opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
-    eval line, TOPLEVEL_BINDING, "-e", lineno
-    lineno += 1
-  }
-
-  opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
-    $DEBUG = true
-  }
-  opts.on("-w", "--warn", "turn warnings on for your script") {
-    $-w = true
-  }
-
-  opts.on("-I", "--include PATH",
-          "specify $LOAD_PATH (may be used more than once)") { |path|
-    $LOAD_PATH.unshift(*path.split(":"))
-  }
-
-  opts.on("-r", "--require LIBRARY",
-          "require the library, before executing your script") { |library|
-    require library
-  }
-
-  opts.separator ""
-  opts.separator "Rack options:"
-  opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s|
-    server = s
-  }
-
-  opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host|
-    options[:Host] = host
-  }
-
-  opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
-    options[:Port] = port
-  }
-
-  opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
-    env = e
-  }
-
-  opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
-    daemonize = d ? true : false
-  }
-
-  opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
-    pid = File.expand_path(f)
-  }
-
-  opts.separator ""
-  opts.separator "Common options:"
-
-  opts.on_tail("-h", "--help", "Show this message") do
-    puts opts
-    exit
-  end
-
-  opts.on_tail("--version", "Show version") do
-    puts "Rack #{Rack.version}"
-    exit
-  end
-
-  opts.parse! ARGV
-}
-
-require 'pp'  if $DEBUG
-
-config = ARGV[0] || "config.ru"
-if !File.exist? config
-  abort "configuration #{config} not found"
-end
-
-if config =~ /\.ru$/
-  cfgfile = File.read(config)
-  if cfgfile[/^#\\(.*)/]
-    opts.parse! $1.split(/\s+/)
-  end
-  cfgfile.sub!(/^__END__\n.*/, '')
-  inner_app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app",
-                   nil, config
-else
-  require config
-  inner_app = Object.const_get(File.basename(config, '.rb').capitalize)
-end
-
-unless server = Rack::Handler.get(server)
-  # Guess.
-  if ENV.include?("PHP_FCGI_CHILDREN")
-    server = Rack::Handler::FastCGI
-
-    # We already speak FastCGI
-    options.delete :File
-    options.delete :Port
-  elsif ENV.include?("REQUEST_METHOD")
-    server = Rack::Handler::CGI
-  else
-    begin
-      server = Rack::Handler::Mongrel
-    rescue LoadError => e
-      server = Rack::Handler::WEBrick
-    end
-  end
-end
-
-p server  if $DEBUG
-
-case env
-when "development"
-  app = Rack::Builder.new {
-    use Rack::CommonLogger, $stderr  unless server.name =~ /CGI/
-    use Rack::ShowExceptions
-    use Rack::Lint
-    run inner_app
-  }.to_app
-
-when "deployment"
-  app = Rack::Builder.new {
-    use Rack::CommonLogger, $stderr  unless server.name =~ /CGI/
-    run inner_app
-  }.to_app
-
-when "none"
-  app = inner_app
-
-end
-
-if $DEBUG
-  pp app
-  pp inner_app
-end
-
-if daemonize
-  if RUBY_VERSION < "1.9"
-    exit if fork
-    Process.setsid
-    exit if fork
-    Dir.chdir "/"
-    File.umask 0000
-    STDIN.reopen "/dev/null"
-    STDOUT.reopen "/dev/null", "a"
-    STDERR.reopen "/dev/null", "a"
-  else
-    Process.daemon
-  end
-
-  if pid
-    File.open(pid, 'w'){ |f| f.write("#{Process.pid}") }
-    at_exit { File.delete(pid) if File.exist?(pid) }
-  end
-end
-
-server.run app, options
+require "rack"
+Rack::Server.start
diff --git a/lib/rack.rb b/lib/rack.rb
index 8d0815b6..703649cd 100644
--- a/lib/rack.rb
+++ b/lib/rack.rb
@@ -42,6 +42,7 @@ module Rack
   autoload :Mime, "rack/mime"
   autoload :Recursive, "rack/recursive"
   autoload :Reloader, "rack/reloader"
+  autoload :Server, "rack/server"
   autoload :ShowExceptions, "rack/showexceptions"
   autoload :ShowStatus, "rack/showstatus"
   autoload :Static, "rack/static"
diff --git a/lib/rack/builder.rb b/lib/rack/builder.rb
index 295235e5..f769b5fd 100644
--- a/lib/rack/builder.rb
+++ b/lib/rack/builder.rb
@@ -24,6 +24,21 @@ module Rack
   # You can use +map+ to construct a Rack::URLMap in a convenient way.
 
   class Builder
+    def self.parse_file(config, opts = nil)
+      if config =~ /\.ru$/
+        cfgfile = ::File.read(config)
+        if cfgfile[/^#\\(.*)/] && opts
+          opts.parse! $1.split(/\s+/)
+        end
+        cfgfile.sub!(/^__END__\n.*/, '')
+        eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app",
+          TOPLEVEL_BINDING, config
+      else
+        require config
+        Object.const_get(::File.basename(config, '.rb').capitalize)
+      end
+    end
+
     def initialize(&block)
       @ins = []
       instance_eval(&block) if block_given?
diff --git a/lib/rack/handler.rb b/lib/rack/handler.rb
index 5624a1e7..3c09883e 100644
--- a/lib/rack/handler.rb
+++ b/lib/rack/handler.rb
@@ -22,6 +22,25 @@ module Rack
       end
     end
 
+    def self.default(options = {})
+      # Guess.
+      if ENV.include?("PHP_FCGI_CHILDREN")
+        # We already speak FastCGI
+        options.delete :File
+        options.delete :Port
+
+        Rack::Handler::FastCGI
+      elsif ENV.include?("REQUEST_METHOD")
+        Rack::Handler::CGI
+      else
+        begin
+          Rack::Handler::Mongrel
+        rescue LoadError => e
+          Rack::Handler::WEBrick
+        end
+      end
+    end
+
     # Transforms server-name constants to their canonical form as filenames,
     # then tries to require them but silences the LoadError if not found
     #
diff --git a/lib/rack/server.rb b/lib/rack/server.rb
new file mode 100644
index 00000000..ff041b3d
--- /dev/null
+++ b/lib/rack/server.rb
@@ -0,0 +1,189 @@
+require 'optparse'
+
+module Rack
+  class Server
+    def self.start
+      new.start
+    end
+
+    attr_accessor :options
+
+    def initialize(options = nil)
+      @options = options
+    end
+
+    def options
+      @options ||= begin
+        parse_options(ARGV)
+      end
+    end
+
+    def default_options
+      {
+        :environment => "development",
+        :pid => nil,
+        :Port => 9292,
+        :Host => "0.0.0.0",
+        :AccessLog => []
+      }
+    end
+
+    def app
+      @app ||= begin
+        if !::File.exist? options[:rack_file]
+          abort "configuration #{options[:rack_file]} not found"
+        end
+
+        Rack::Builder.parse_file(options[:rack_file], opt_parser)
+      end
+    end
+
+    def self.middleware
+      @middleware ||= begin
+        m = Hash.new {|h,k| h[k] = []}
+        m["deployment"].concat  [lambda {|server| server.server =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr] }]
+        m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]]
+        m
+      end
+    end
+
+    def middleware
+      self.class.middleware
+    end
+
+    def start
+      if $DEBUG
+        require 'pp'
+        p options[:server]
+        pp wrapped_app
+        pp app
+      end
+
+      daemonize_app if options[:daemonize]
+      write_pid if options[:pid]
+      server.run wrapped_app, options
+    end
+
+    def server
+      @_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default
+    end
+
+    private
+      def parse_options(args)
+        @options = default_options
+
+        # Don't evaluate CGI ISINDEX parameters.
+        # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
+        args.clear if ENV.include?("REQUEST_METHOD")
+
+        opt_parser.parse! args
+        @options[:rack_file] = args.last || ::File.expand_path("config.ru")
+        @options
+      end
+
+      def opt_parser
+        @opt_parser ||= OptionParser.new("", 24, '  ') do |opts|
+          opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
+
+          opts.separator ""
+          opts.separator "Ruby options:"
+
+          lineno = 1
+          opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
+            eval line, TOPLEVEL_BINDING, "-e", lineno
+            lineno += 1
+          }
+
+          opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
+            $DEBUG = true
+          }
+          opts.on("-w", "--warn", "turn warnings on for your script") {
+            $-w = true
+          }
+
+          opts.on("-I", "--include PATH",
+                  "specify $LOAD_PATH (may be used more than once)") { |path|
+            $LOAD_PATH.unshift(*path.split(":"))
+          }
+
+          opts.on("-r", "--require LIBRARY",
+                  "require the library, before executing your script") { |library|
+            require library
+          }
+
+          opts.separator ""
+          opts.separator "Rack options:"
+          opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s|
+            @options[:server] = s
+          }
+
+          opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host|
+            @options[:Host] = host
+          }
+
+          opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
+            @options[:Port] = port
+          }
+
+          opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
+            @options[:environment] = e
+          }
+
+          opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
+            @options[:daemonize] = d ? true : false
+          }
+
+          opts.on("-P", "--pid FILE", "file to store PID") { |f|
+            @options[:pid] = ::File.expand_path(f)
+          }
+
+          opts.separator ""
+          opts.separator "Common options:"
+
+          opts.on_tail("-h", "--help", "Show this message") do
+            puts opts
+            exit
+          end
+
+          opts.on_tail("--version", "Show version") do
+            puts "Rack #{Rack.version}"
+            exit
+          end
+        end
+      end
+
+      def build_app(app)
+        middleware[options[:environment]].reverse_each do |middleware|
+          middleware = middleware.call(self) if middleware.respond_to?(:call)
+          next unless middleware
+          klass = middleware.shift
+          app = klass.new(app, *middleware)
+        end
+        app
+      end
+
+      def wrapped_app
+        @wrapped_app ||= build_app app
+      end
+
+      def daemonize_app
+        if RUBY_VERSION < "1.9"
+          exit if fork
+          Process.setsid
+          exit if fork
+          Dir.chdir "/"
+          ::File.umask 0000
+          STDIN.reopen "/dev/null"
+          STDOUT.reopen "/dev/null", "a"
+          STDERR.reopen "/dev/null", "a"
+        else
+          Process.daemon
+        end
+      end
+
+      def write_pid
+        ::File.open(options[:pid], 'w'){ |f| f.write("#{Process.pid}") }
+        at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) }
+      end
+  end
+end
diff --git a/test/rackup/config.ru b/test/rackup/config.ru
index 2490c6ed..f1e2e1f3 100644
--- a/test/rackup/config.ru
+++ b/test/rackup/config.ru
@@ -1,22 +1,28 @@
 require "#{File.dirname(__FILE__)}/../testrequest"
 
-$stderr = StringIO.new
+$stderr = File.open("#{File.dirname(__FILE__)}/log_output", "w")
 
 class EnvMiddleware
   def initialize(app)
     @app = app
   end
-  
+
   def call(env)
+    # provides a way to test that lint is present
     if env["PATH_INFO"] == "/broken_lint"
       return [200, {}, ["Broken Lint"]]
+    # provides a way to kill the process without knowing the pid
+    elsif env["PATH_INFO"] == "/die"
+      exit!
     end
 
     env["test.$DEBUG"]      = $DEBUG
     env["test.$EVAL"]       = BUKKIT if defined?(BUKKIT)
     env["test.$VERBOSE"]    = $VERBOSE
     env["test.$LOAD_PATH"]  = $LOAD_PATH
+    env["test.stderr"]      = File.expand_path($stderr.path)
     env["test.Ping"]        = defined?(Ping)
+    env["test.pid"]         = Process.pid
     @app.call(env)
   end
 end
diff --git a/test/spec_rackup.rb b/test/spec_rackup.rb
index 99f48e3a..d9926fda 100644
--- a/test/spec_rackup.rb
+++ b/test/spec_rackup.rb
@@ -1,5 +1,6 @@
 require 'test/spec'
 require 'testrequest'
+require 'rack/server'
 require 'open3'
 
 begin
@@ -15,7 +16,7 @@ context "rackup" do
     @port = options[:port] || 9292
 
     Dir.chdir("#{root}/test/rackup") do
-      @rackup = IO.popen("#{Gem.ruby} -S #{rackup} #{flags}")
+      @in, @rackup, @err = Open3.popen3("#{Gem.ruby} -S #{rackup} #{flags}")
     end
 
     return if options[:port] == false
@@ -34,12 +35,14 @@ context "rackup" do
   end
 
   after do
-    Process.kill(9, @rackup.pid) if @rackup
+    # This doesn't actually return a response, so we rescue
+    GET "/die" rescue nil
 
     Dir["#{root}/**/*.pid"].each do |file|
-      Process.kill(9, File.read(file).to_i)
       File.delete(file)
     end
+
+    File.delete("#{root}/log_output") if File.exist?("#{root}/log_output")
   end
 
   specify "rackup" do
@@ -97,13 +100,19 @@ context "rackup" do
     response["REMOTE_ADDR"].should.equal "127.0.0.1"
   end
 
-  specify "rackup --pid" do
+  specify "rackup --daemonize --pid" do
     run_rackup %{--daemonize --pid testing.pid}
     status.should.be 200
     @rackup.should.be.eof?
     Dir["#{root}/**/testing.pid"].should.not.be.empty?
   end
 
+  specify "rackup --pid" do
+    run_rackup %{--pid testing.pid}
+    status.should.be 200
+    Dir["#{root}/**/testing.pid"].should.not.be.empty?
+  end
+
   specify "rackup --version" do
     run_rackup %{--version}, :port => false
     output.should =~ /1.0/
@@ -115,11 +124,30 @@ context "rackup" do
     status.should.be 500
   end
 
-  specify "rackup --env" do
+  specify "rackup --env deployment does not include lint" do
     run_rackup %{--env deployment}
     GET("/broken_lint")
     status.should.be 200
   end
+
+  specify "rackup --env none does not include lint" do
+    run_rackup %{--env none}
+    GET("/broken_lint")
+    status.should.be 200
+  end
+
+  specify "rackup --env deployment does log" do
+    run_rackup %{--env deployment}
+    log = File.read(response["test.stderr"])
+    log.should.be.empty?
+  end
+
+  specify "rackup --env none does not log" do
+    run_rackup %{--env none}
+    GET("/")
+    log = File.read(response["test.stderr"])
+    log.should.be.empty?
+  end
 end
 rescue LoadError
   $stderr.puts "Skipping rackup --server tests (mongrel is required). `gem install thin` and try again."