diff options
author | Yehuda Katz + Carl Lerche <ykatz+clerche@engineyard.com> | 2009-11-21 10:52:33 -0800 |
---|---|---|
committer | Joshua Peek <josh@joshpeek.com> | 2009-11-21 10:52:33 -0800 |
commit | 5db5d4e7432d026a7bddf344f97e8facd09128d8 (patch) | |
tree | 8d5e3586717a098ac6218cb571426d8fb2aaf537 | |
parent | 87957c6c76f4f1cc9310667f71a4c15a3f35eb23 (diff) | |
download | rack-5db5d4e7432d026a7bddf344f97e8facd09128d8.tar.gz |
Refactor rackup into Rack::Server
Signed-off-by: Joshua Peek <josh@joshpeek.com>
-rwxr-xr-x | bin/rackup | 178 | ||||
-rw-r--r-- | lib/rack.rb | 1 | ||||
-rw-r--r-- | lib/rack/builder.rb | 15 | ||||
-rw-r--r-- | lib/rack/handler.rb | 19 | ||||
-rw-r--r-- | lib/rack/server.rb | 189 | ||||
-rw-r--r-- | test/rackup/config.ru | 10 | ||||
-rw-r--r-- | test/spec_rackup.rb | 38 |
7 files changed, 267 insertions, 183 deletions
@@ -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." |