diff options
-rw-r--r-- | Manifest | 3 | ||||
-rwxr-xr-x | examples/cat-chunk-proxy.rb | 60 | ||||
-rw-r--r-- | examples/git.ru | 9 | ||||
-rw-r--r-- | lib/unicorn/app/inetd.rb | 106 |
4 files changed, 178 insertions, 0 deletions
@@ -14,7 +14,9 @@ TODO TUNING bin/unicorn bin/unicorn_rails +examples/cat-chunk-proxy.rb examples/echo.ru +examples/git.ru examples/init.sh ext/unicorn/http11/ext_help.h ext/unicorn/http11/extconf.rb @@ -24,6 +26,7 @@ ext/unicorn/http11/http11_parser.rl ext/unicorn/http11/http11_parser_common.rl lib/unicorn.rb lib/unicorn/app/exec_cgi.rb +lib/unicorn/app/inetd.rb lib/unicorn/app/old_rails.rb lib/unicorn/app/old_rails/static.rb lib/unicorn/cgi_wrapper.rb diff --git a/examples/cat-chunk-proxy.rb b/examples/cat-chunk-proxy.rb new file mode 100755 index 0000000..3a5921f --- /dev/null +++ b/examples/cat-chunk-proxy.rb @@ -0,0 +1,60 @@ +#!/home/ew/bin/ruby +# I wish I could just use curl -sfNT- http://host:port/, but +# unfortunately curl will attempt to read stdin in blocking mode, +# preventing it from getting responses from the server until +# stdin has been written to. +# +# Usage: GIT_PROXY_COMMAND=/path/to/here git clone git://host:port/project +# +# Where host:port is what the Unicorn server is bound to + +require 'socket' +require 'unicorn' +require 'unicorn/chunked_reader' + +$stdin.sync = $stdout.sync = $stderr.sync = true +$stdin.binmode +$stdout.binmode + +usage = "#$0 HOST PORT" +host = ARGV.shift or abort usage +port = ARGV.shift or abort usage +s = TCPSocket.new(host, port.to_i) +s.sync = true +s.write("PUT / HTTP/1.1\r\n" \ + "Host: #{host}\r\n" \ + "Transfer-Encoding: chunked\r\n\r\n") +buf = s.readpartial(16384) +while /\r\n\r\n/ !~ buf + buf << s.readpartial(16384) +end + +head, body = buf.split(/\r\n\r\n/, 2) + +input = fork { + $0 = "input #$0" + begin + loop { + $stdin.readpartial(16384, buf) + s.write("#{'%x' % buf.size}\r\n#{buf}\r\n") + } + rescue EOFError,Errno::EPIPE,Errno::EBADF,Errno::EINVAL => e + end + s.write("0\r\n\r\n") +} + +output = fork { + $0 = "output #$0" + + c = Unicorn::ChunkedReader.new + c.reopen(s, body) + begin + loop { $stdout.write(c.readpartial(16384, buf)) } + rescue EOFError,Errno::EPIPE,Errno::EBADF,Errno::EINVAL => e + end +} + +2.times { + pid, status = Process.waitpid2 + $stderr.write("reaped: #{status.inspect}\n") unless status.success? +} diff --git a/examples/git.ru b/examples/git.ru new file mode 100644 index 0000000..3762d3d --- /dev/null +++ b/examples/git.ru @@ -0,0 +1,9 @@ +#\-E none +require 'unicorn/app/inetd' + +use Rack::Lint +use Rack::Chunked +# run Unicorn::App::Inetd.new('tee', '/tmp/tee.out') +run Unicorn::App::Inetd.new( + *%w(git daemon --verbose --inetd --export-all --base-path=/home/ew/unicorn) +) diff --git a/lib/unicorn/app/inetd.rb b/lib/unicorn/app/inetd.rb new file mode 100644 index 0000000..97dc5d3 --- /dev/null +++ b/lib/unicorn/app/inetd.rb @@ -0,0 +1,106 @@ +# this class *must* be used with Rack::Chunked + +module Unicorn::App + class Inetd + + Z = '' + Z.force_encoding(Encoding::BINARY) if Z.respond_to?(:force_encoding) + + class CatBody + def initialize(env, cmd) + @cmd = cmd + @input, @errors = env['rack.input'], env['rack.errors'] + in_rd, in_wr = IO.pipe + @err_rd, err_wr = IO.pipe + @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 { |io| + io.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 { + [ @err_rd, @out_rd ].each { |io| io.close } + buf = 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 + end + + def each(&block) + buf = Z.dup + 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, buf)) + rescue Errno::EINTR + rescue Errno::EAGAIN + break + end while true + end + + rd.include?(@out_rd) or next + + begin + yield @out_rd.read_nonblock(16384, buf) + 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 + @input = nil + [ [ @cmd.inspect, @cmd_pid ], [ 'input streamer', @inp_pid ] + ].each { |str, pid| + 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) + # enable streaming input mode in Unicorn + Unicorn::HttpRequest::DEFAULTS["unicorn.stream_input"] = true + @cmd = cmd + end + + def call(env) + [ 200, { 'Content-Type' => 'application/octet-stream' }, + CatBody.new(env, @cmd) ] + end + + end + +end |