diff options
Diffstat (limited to 'lib/unicorn/app')
-rw-r--r-- | lib/unicorn/app/exec_cgi.rb | 63 | ||||
-rw-r--r-- | lib/unicorn/app/inetd.rb | 106 | ||||
-rw-r--r-- | lib/unicorn/app/old_rails/static.rb | 20 |
3 files changed, 147 insertions, 42 deletions
diff --git a/lib/unicorn/app/exec_cgi.rb b/lib/unicorn/app/exec_cgi.rb index 8f81d78..147b279 100644 --- a/lib/unicorn/app/exec_cgi.rb +++ b/lib/unicorn/app/exec_cgi.rb @@ -5,7 +5,7 @@ module Unicorn::App # This class is highly experimental (even more so than the rest of Unicorn) # and has never run anything other than cgit. - class ExecCgi + class ExecCgi < Struct.new(:args) CHUNK_SIZE = 16384 PASS_VARS = %w( @@ -32,21 +32,21 @@ module Unicorn::App # run Unicorn::App::ExecCgi.new("/path/to/cgit.cgi") # end def initialize(*args) - @args = args.dup - first = @args[0] or + self.args = args + first = args[0] or raise ArgumentError, "need path to executable" - first[0..0] == "/" or @args[0] = ::File.expand_path(first) - File.executable?(@args[0]) or - raise ArgumentError, "#{@args[0]} is not executable" + first[0..0] == "/" or args[0] = ::File.expand_path(first) + File.executable?(args[0]) or + raise ArgumentError, "#{args[0]} is not executable" end # Calls the app def call(env) - out, err = Tempfile.new(''), Tempfile.new('') + out, err = Tempfile.new(nil), Tempfile.new(nil) out.unlink err.unlink inp = force_file_input(env) - inp.sync = out.sync = err.sync = true + out.sync = err.sync = true pid = fork { run_child(inp, out, err, env) } inp.close pid, status = Process.waitpid2(pid) @@ -65,14 +65,14 @@ module Unicorn::App val = env[key] or next ENV[key] = val end - ENV['SCRIPT_NAME'] = @args[0] + ENV['SCRIPT_NAME'] = args[0] ENV['GATEWAY_INTERFACE'] = 'CGI/1.1' env.keys.grep(/^HTTP_/) { |key| ENV[key] = env[key] } a = IO.new(0).reopen(inp) b = IO.new(1).reopen(out) c = IO.new(2).reopen(err) - exec(*@args) + exec(*args) end # Extracts headers from CGI out, will change the offset of out. @@ -89,23 +89,24 @@ module Unicorn::App offset = 4 end offset += head.length - out.instance_variable_set('@unicorn_app_exec_cgi_offset', offset) - size -= offset # Allows +out+ to be used as a Rack body. - def out.each - sysseek(@unicorn_app_exec_cgi_offset) - - # don't use a preallocated buffer for sysread since we can't - # guarantee an actual socket is consuming the yielded string - # (or if somebody is pushing to an array for eventual concatenation - begin - yield(sysread(CHUNK_SIZE)) - rescue EOFError - return - end while true - end + out.instance_eval { class << self; self; end }.instance_eval { + define_method(:each) { |&blk| + sysseek(offset) + + # don't use a preallocated buffer for sysread since we can't + # guarantee an actual socket is consuming the yielded string + # (or if somebody is pushing to an array for eventual concatenation + begin + blk.call(sysread(CHUNK_SIZE)) + rescue EOFError + break + end while true + } + } + size -= offset prev = nil headers = Rack::Utils::HeaderHash.new head.split(/\r?\n/).each do |line| @@ -121,17 +122,15 @@ module Unicorn::App # ensures rack.input is a file handle that we can redirect stdin to def force_file_input(env) inp = env['rack.input'] - if inp.respond_to?(:fileno) && Integer === inp.fileno - inp - elsif inp.size == 0 # inp could be a StringIO or StringIO-like object - ::File.open('/dev/null') + if inp.size == 0 # inp could be a StringIO or StringIO-like object + ::File.open('/dev/null', 'rb') else - tmp = Tempfile.new('') + tmp = Tempfile.new(nil) tmp.unlink tmp.binmode + tmp.sync = true - # Rack::Lint::InputWrapper doesn't allow sysread :( - buf = '' + buf = Z.dup while inp.read(CHUNK_SIZE, buf) tmp.syswrite(buf) end @@ -146,7 +145,7 @@ module Unicorn::App err.seek(0) dst = env['rack.errors'] pid = status.pid - dst.write("#{pid}: #{@args.inspect} status=#{status} stderr:\n") + dst.write("#{pid}: #{args.inspect} status=#{status} stderr:\n") err.each_line { |line| dst.write("#{pid}: #{line}") } dst.flush end diff --git a/lib/unicorn/app/inetd.rb b/lib/unicorn/app/inetd.rb new file mode 100644 index 0000000..580b456 --- /dev/null +++ b/lib/unicorn/app/inetd.rb @@ -0,0 +1,106 @@ +# Copyright (c) 2009 Eric Wong +# You can redistribute it and/or modify it under the same terms as Ruby. + +# this class *must* be used with Rack::Chunked + +module Unicorn::App + class Inetd < Struct.new(:cmd) + + class CatBody < Struct.new(:errors, :err_rd, :out_rd, :pid_map) + def initialize(env, cmd) + self.errors = env['rack.errors'] + in_rd, in_wr = IO.pipe + self.err_rd, err_wr = IO.pipe + self.out_rd, out_wr = IO.pipe + + cmd_pid = fork { + inp, out, err = (0..2).map { |i| IO.new(i) } + inp.reopen(in_rd) + out.reopen(out_wr) + err.reopen(err_wr) + [ in_rd, in_wr, err_rd, err_wr, out_rd, out_wr ].each { |i| i.close } + exec(*cmd) + } + [ in_rd, err_wr, out_wr ].each { |io| io.close } + [ in_wr, err_rd, out_rd ].each { |io| io.binmode } + in_wr.sync = true + + # Unfortunately, input here must be processed inside a seperate + # thread/process using blocking I/O since env['rack.input'] is not + # IO.select-able and attempting to make it so would trip Rack::Lint + inp_pid = fork { + input = env['rack.input'] + [ err_rd, out_rd ].each { |io| io.close } + buf = Unicorn::Z.dup + + # this is dependent on input.read having readpartial semantics: + while input.read(16384, buf) + in_wr.write(buf) + end + in_wr.close + } + in_wr.close + self.pid_map = { + inp_pid => 'input streamer', + cmd_pid => cmd.inspect, + } + end + + def each(&block) + begin + rd, = IO.select([err_rd, out_rd]) + rd && rd.first or next + + if rd.include?(err_rd) + begin + errors.write(err_rd.read_nonblock(16384)) + rescue Errno::EINTR + rescue Errno::EAGAIN + break + end while true + end + + rd.include?(out_rd) or next + + begin + yield out_rd.read_nonblock(16384) + rescue Errno::EINTR + rescue Errno::EAGAIN + break + end while true + rescue EOFError,Errno::EPIPE,Errno::EBADF,Errno::EINVAL + break + end while true + + self + end + + def close + pid_map.each { |pid, str| + begin + pid, status = Process.waitpid2(pid) + status.success? or + errors.write("#{str}: #{status.inspect} (PID:#{pid})\n") + rescue Errno::ECHILD + errors.write("Failed to reap #{str} (PID:#{pid})\n") + end + } + end + + end + + def initialize(*cmd) + self.cmd = cmd + end + + def call(env) + /\A100-continue\z/i =~ env[Unicorn::Const::HTTP_EXPECT] and + return [ 100, {} , [] ] + + [ 200, { 'Content-Type' => 'application/octet-stream' }, + CatBody.new(env, cmd) ] + end + + end + +end diff --git a/lib/unicorn/app/old_rails/static.rb b/lib/unicorn/app/old_rails/static.rb index 17c007c..51a0017 100644 --- a/lib/unicorn/app/old_rails/static.rb +++ b/lib/unicorn/app/old_rails/static.rb @@ -19,28 +19,28 @@ require 'rack/file' # This means that if you are using page caching it will actually work # with Unicorn and you should see a decent speed boost (but not as # fast as if you use a static server like nginx). -class Unicorn::App::OldRails::Static +class Unicorn::App::OldRails::Static < Struct.new(:app, :root, :file_server) FILE_METHODS = { 'GET' => true, 'HEAD' => true }.freeze REQUEST_METHOD = 'REQUEST_METHOD'.freeze REQUEST_URI = 'REQUEST_URI'.freeze PATH_INFO = 'PATH_INFO'.freeze def initialize(app) - @app = app - @root = "#{::RAILS_ROOT}/public" - @file_server = ::Rack::File.new(@root) + self.app = app + self.root = "#{::RAILS_ROOT}/public" + self.file_server = ::Rack::File.new(root) end def call(env) # short circuit this ASAP if serving non-file methods - FILE_METHODS.include?(env[REQUEST_METHOD]) or return @app.call(env) + FILE_METHODS.include?(env[REQUEST_METHOD]) or return app.call(env) # first try the path as-is path_info = env[PATH_INFO].chomp("/") - if File.file?("#@root/#{::Rack::Utils.unescape(path_info)}") + if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}") # File exists as-is so serve it up env[PATH_INFO] = path_info - return @file_server.call(env) + return file_server.call(env) end # then try the cached version: @@ -50,11 +50,11 @@ class Unicorn::App::OldRails::Static env[REQUEST_URI] =~ /^#{Regexp.escape(path_info)}(;[^\?]+)/ path_info << "#$1#{ActionController::Base.page_cache_extension}" - if File.file?("#@root/#{::Rack::Utils.unescape(path_info)}") + if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}") env[PATH_INFO] = path_info - return @file_server.call(env) + return file_server.call(env) end - @app.call(env) # call OldRails + app.call(env) # call OldRails end end if defined?(Unicorn::App::OldRails) |