From 6a0859a20149cafa8908ae57e725c2f373c65348 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 18 Aug 2009 14:03:06 -0700 Subject: Rainbows! --- TODO | 2 - lib/unicorn/rainbows.rb | 116 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 lib/unicorn/rainbows.rb diff --git a/TODO b/TODO index 0b73934..1213a9e 100644 --- a/TODO +++ b/TODO @@ -6,6 +6,4 @@ * launchers - add stop/start/kill/upgrade commands -* Rainbows! - * Rubinius support diff --git a/lib/unicorn/rainbows.rb b/lib/unicorn/rainbows.rb new file mode 100644 index 0000000..33064c9 --- /dev/null +++ b/lib/unicorn/rainbows.rb @@ -0,0 +1,116 @@ +require 'unicorn' +require 'revactor' + +# :stopdoc: +module Unicorn + + # People get the impression that we're a big bad, old-school + # Unix server and then we start talking about Rainbows... + class Rainbows < HttpServer + DEFAULTS = HttpRequest::DEFAULTS.merge({ + # we need to observe many of the rules for thread-safety with Revactor + "rack.multithread" => true, + "SERVER_SOFTWARE" => "Unicorn Rainbows! #{Const::UNICORN_VERSION}", + }) + SKIP = HttpResponse::SKIP + CODES = HttpResponse::CODES + + # once a client is accepted, it is processed in its entirety here + # in 3 easy steps: read request, call app, write app response + def process_client(client) + env = { Const::REMOTE_ADDR => client.remote_addr } + hp = HttpParser.new + buf = client.read + while ! hp.headers(env, buf) + buf << client.read + end + + env[Const::RACK_INPUT] = 0 == hp.content_length ? + HttpRequest::NULL_IO : TeeInput.new(client, env, hp, buf) + response = app.call(env.update(DEFAULTS)) + + if 100 == response.first.to_i + client.write(Const::EXPECT_100_RESPONSE) + env.delete(Const::HTTP_EXPECT) + response = app.call(env) + end + HttpResponse.write(client, response) + # if we get any error, try to write something back to the client + # assuming we haven't closed the socket, but don't get hung up + # if the socket is already closed or broken. We'll always ensure + # the socket is closed at the end of this function + rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF + emergency_response(client, Const::ERROR_500_RESPONSE) + rescue HttpParserError # try to tell the client they're bad + emergency_response(client, Const::ERROR_400_RESPONSE) + rescue Object => e + emergency_response(client, Const::ERROR_500_RESPONSE) + logger.error "Read error: #{e.inspect}" + logger.error e.backtrace.join("\n") + end + + # runs inside each forked worker, this sits around and waits + # for connections and doesn't die until the parent dies (or is + # given a INT, QUIT, or TERM signal) + def worker_loop(worker) + ppid = master_pid + init_worker_process(worker) + alive = worker.tmp # tmp is our lifeline to the master process + ready = Revactor::TCP.listen(LISTENERS.first, nil) + Actor.current.trap_exit = true + + trap(:USR1) { reopen_worker_logs(worker.nr) } + trap(:QUIT) { alive = false; LISTENERS.each { |s| s.close rescue nil } } + [:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown + logger.info "worker=#{worker.nr} ready" + + begin + Actor.spawn(ready.accept) { |s| process_client(s) } + ppid == Process.ppid or alive = false + alive.chmod(t = 0) + rescue Errno::EAGAIN, Errno::ECONNABORTED + rescue Object => e + if alive + logger.error "Unhandled listen loop exception #{e.inspect}." + logger.error e.backtrace.join("\n") + end + end while alive + end + + def murder_lazy_workers + end + + private + + # write a response without caring if it went out or not + # This is in the case of untrappable errors + def emergency_response(client, response_str) + client.instance_eval { @_io.write_nonblock(response_str) rescue nil } + client.close rescue nil + end + + end +end + +if $0 == __FILE__ + app = lambda { |env| + if /\A100-continue\z/i =~ env['HTTP_EXPECT'] + header = { + 'Content-Type' => 'application/octet-stream', + 'Content-Length' => '0' + } + return [ 100, header, [] ] + end + digest = Digest::SHA1.new + input = env['rack.input'] + buf = Unicorn::Z.dup + while buf = input.read(16384, buf) + digest.update(buf) + end + [ 200, { 'X-SHA1' => digest.hexdigest }, [ env.inspect << "\n" ] ] + } + options = { + :listeners => %w(0.0.0.0:8080), + } + Unicorn::Rainbows.new(app, options).start.join +end -- cgit v1.2.3-24-ge0c7