about summary refs log tree commit homepage
path: root/lib/rainbows/base.rb
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-10-03 16:14:57 -0700
committerEric Wong <normalperson@yhbt.net>2009-10-03 16:17:16 -0700
commit5b14ec54da4b3dcd52c8755b6a036e5e94a565d1 (patch)
treeea90c4450dee5442fbcb9a804513211b2f035af0 /lib/rainbows/base.rb
parenta86d3be33ef2b433370df66d2ba026ab03b305b7 (diff)
downloadrainbows-5b14ec54da4b3dcd52c8755b6a036e5e94a565d1.tar.gz
They're similar enough (especially as far as the constants go)
and allows a :Base to be used which basically acts like plain
Unicorn but with HTTP keepalive + pipelining support
Diffstat (limited to 'lib/rainbows/base.rb')
-rw-r--r--lib/rainbows/base.rb69
1 files changed, 69 insertions, 0 deletions
diff --git a/lib/rainbows/base.rb b/lib/rainbows/base.rb
new file mode 100644
index 0000000..0e5843d
--- /dev/null
+++ b/lib/rainbows/base.rb
@@ -0,0 +1,69 @@
+# -*- encoding: binary -*-
+
+module Rainbows
+
+  # base class for Rainbows concurrency models
+  module Base
+
+    include Unicorn
+    include Rainbows::Const
+
+    # write a response without caring if it went out or not for error
+    # messages.
+    # TODO: merge into Unicorn::HttpServer
+    def emergency_response(client, response_str)
+      client.write_nonblock(response_str) rescue nil
+      client.close rescue nil
+    end
+
+    # 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)
+      buf = client.readpartial(CHUNK_SIZE)
+      hp = HttpParser.new
+      env = {}
+      remote_addr = TCPSocket === client ? client.peeraddr.last : LOCALHOST
+
+      begin
+        while ! hp.headers(env, buf)
+          buf << client.readpartial(CHUNK_SIZE)
+        end
+
+        env[RACK_INPUT] = 0 == hp.content_length ?
+                 HttpRequest::NULL_IO :
+                 Unicorn::TeeInput.new(client, env, hp, buf)
+        env[REMOTE_ADDR] = remote_addr
+        response = app.call(env.update(RACK_DEFAULTS))
+
+        if 100 == response.first.to_i
+          client.write(EXPECT_100_RESPONSE)
+          env.delete(HTTP_EXPECT)
+          response = app.call(env)
+        end
+
+        out = [ hp.keepalive? ? CONN_ALIVE : CONN_CLOSE ] if hp.headers?
+        HttpResponse.write(client, response, out)
+      end while hp.keepalive? and hp.reset.nil? and env.clear
+      client.close
+    # 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, ERROR_500_RESPONSE)
+    rescue HttpParserError # try to tell the client they're bad
+      buf.empty? or emergency_response(client, ERROR_400_RESPONSE)
+    rescue Object => e
+      emergency_response(client, ERROR_500_RESPONSE)
+      logger.error "Read error: #{e.inspect}"
+      logger.error e.backtrace.join("\n")
+    end
+
+    def self.included(klass)
+      HttpServer.constants.each do |x|
+        klass.const_set(x, HttpServer.const_get(x))
+      end
+    end
+
+  end
+end