about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2010-02-18 19:11:50 -0800
committerEric Wong <normalperson@yhbt.net>2010-02-18 19:29:44 -0800
commit3e80ccb60e2b3632916094ac436806ab1cf03b11 (patch)
tree3995f8dd3532fc490ff42f785c23528b850c0697
parent8963e87841501c8e79b8434f8887e0d3a78b580c (diff)
downloadunicorn-3e80ccb60e2b3632916094ac436806ab1cf03b11.tar.gz
This should make it easier to reuse code in derivative
servers like Rainbows! and Zbatery.  Unfortunately, we
can't depend on Rack::Builder/Rack::Server yet since
Rack 1.1 just got them and notable frameworks (like
Rails 2.3.x) do not fully work with Rack 1.1 yet).

This also fixes subtle issue with config.ru files that could
have variables that conflict with the Unicorn-specific
namespace (this bug still affects "unicorn_rails", which
could use some reworking as well).
-rwxr-xr-xbin/unicorn40
-rw-r--r--lib/unicorn.rb38
-rwxr-xr-xt/t0002-config-conflict.sh49
3 files changed, 92 insertions, 35 deletions
diff --git a/bin/unicorn b/bin/unicorn
index 5af021d..0da0869 100755
--- a/bin/unicorn
+++ b/bin/unicorn
@@ -108,49 +108,19 @@ opts = OptionParser.new("", 24, '  ') do |opts|
   opts.parse! ARGV
 end
 
-config = ARGV[0] || "config.ru"
-abort "configuration file #{config} not found" unless File.exist?(config)
+ru = ARGV[0] || "config.ru"
+abort "configuration file #{ru} not found" unless File.exist?(ru)
 
-if config =~ /\.ru$/
+if ru =~ /\.ru$/
   # parse embedded command-line options in config.ru comments
-  if File.open(config, "rb") { |fp| fp.sysread(fp.stat.size) } =~ /^#\\(.*)/
+  if File.open(ru, "rb") { |fp| fp.sysread(fp.stat.size) } =~ /^#\\(.*)/
     opts.parse! $1.split(/\s+/)
   end
 end
 
 require 'pp' if $DEBUG
 
-app = lambda do ||
-  # require Rack as late as possible in case $LOAD_PATH is modified
-  # in config.ru or command-line
-  inner_app = case config
-  when /\.ru$/
-    raw = File.open(config, "rb") { |fp| fp.sysread(fp.stat.size) }
-    raw.sub!(/^__END__\n.*/, '')
-    eval("Rack::Builder.new {(#{raw}\n)}.to_app", nil, config)
-  else
-    require config
-    Object.const_get(File.basename(config, '.rb').capitalize)
-  end
-  pp({ :inner_app => inner_app }) if $DEBUG
-  case ENV["RACK_ENV"]
-  when "development"
-    Rack::Builder.new do
-      use Rack::CommonLogger, $stderr
-      use Rack::ShowExceptions
-      use Rack::Lint
-      run inner_app
-    end.to_app
-  when "deployment"
-    Rack::Builder.new do
-      use Rack::CommonLogger, $stderr
-      run inner_app
-    end.to_app
-  else
-    inner_app
-  end
-end
-
+app = Unicorn.builder(ru)
 listeners << "#{host}:#{port}" if set_listener
 
 if $DEBUG
diff --git a/lib/unicorn.rb b/lib/unicorn.rb
index 6dec03e..d3e9c3d 100644
--- a/lib/unicorn.rb
+++ b/lib/unicorn.rb
@@ -27,6 +27,44 @@ module Unicorn
     def run(app, options = {})
       HttpServer.new(app, options).start.join
     end
+
+    # This returns a lambda to pass in as the app, this does not "build" the
+    # app (which we defer based on the outcome of "preload_app" in the
+    # Unicorn config).  The returned lambda will be called when it is
+    # time to build the app.
+    def builder(ru)
+      lambda do ||
+        inner_app = case ru
+        when /\.ru$/
+          raw = File.open(ru, "rb") { |fp| fp.sysread(fp.stat.size) }
+          raw.sub!(/^__END__\n.*/, '')
+          eval("Rack::Builder.new {(#{raw}\n)}.to_app", TOPLEVEL_BINDING, ru)
+        else
+          require ru
+          Object.const_get(File.basename(ru, '.rb').capitalize)
+        end
+
+        pp({ :inner_app => inner_app }) if $DEBUG
+
+        # return value, matches rackup defaults based on env
+        case ENV["RACK_ENV"]
+        when "development"
+          Rack::Builder.new do
+            use Rack::CommonLogger, $stderr
+            use Rack::ShowExceptions
+            use Rack::Lint
+            run inner_app
+          end.to_app
+        when "deployment"
+          Rack::Builder.new do
+            use Rack::CommonLogger, $stderr
+            run inner_app
+          end.to_app
+        else
+          inner_app
+        end
+      end
+    end
   end
 
   # This is the process manager of Unicorn. This manages worker
diff --git a/t/t0002-config-conflict.sh b/t/t0002-config-conflict.sh
new file mode 100755
index 0000000..d7b2181
--- /dev/null
+++ b/t/t0002-config-conflict.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+. ./test-lib.sh
+t_plan 6 "config variables conflict with preload_app"
+
+t_begin "setup and start" && {
+        unicorn_setup
+        rtmpfiles ru rutmp
+
+        cat > $ru <<\EOF
+use Rack::ContentLength
+use Rack::ContentType, "text/plain"
+config = ru = { "hello" => "world" }
+run lambda { |env| [ 200, {}, [ ru.inspect << "\n" ] ] }
+EOF
+        echo 'preload_app true' >> $unicorn_config
+        unicorn -D -c $unicorn_config $ru
+        unicorn_wait_start
+}
+
+t_begin "hit with curl" && {
+        out=$(curl -sSf http://$listen/)
+        test x"$out" = x'{"hello"=>"world"}'
+}
+
+t_begin "modify rackup file" && {
+        sed -e 's/world/WORLD/' < $ru > $rutmp
+        mv $rutmp $ru
+}
+
+t_begin "reload signal succeeds" && {
+        kill -HUP $unicorn_pid
+        while ! egrep '(done|error) reloading' < $r_err >/dev/null
+        do
+                sleep 1
+        done
+
+        grep 'done reloading' $r_err >/dev/null
+}
+
+t_begin "hit with curl" && {
+        out=$(curl -sSf http://$listen/)
+        test x"$out" = x'{"hello"=>"WORLD"}'
+}
+
+t_begin "killing succeeds" && {
+        kill $unicorn_pid
+}
+
+t_done