about summary refs log tree commit homepage
path: root/lib/unicorn/http_response.rb
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-02-05 15:16:18 -0800
committerEric Wong <normalperson@yhbt.net>2009-02-09 19:50:44 -0800
commita1c7992d47ac820c64604b8aeb8779a0bf741fcf (patch)
treeb40dd375c1de2abbb38e17a1e3f6041915aa518f /lib/unicorn/http_response.rb
parent07846bcac6604babf0dd1f296d99c148b63340d6 (diff)
downloadunicorn-a1c7992d47ac820c64604b8aeb8779a0bf741fcf.tar.gz
The previous API was very flexible, but I don't think many
people really cared for it... We now repeatedly use the
same HeaderOut in each process since I completely don't
care for multithreading.
Diffstat (limited to 'lib/unicorn/http_response.rb')
-rw-r--r--lib/unicorn/http_response.rb156
1 files changed, 26 insertions, 130 deletions
diff --git a/lib/unicorn/http_response.rb b/lib/unicorn/http_response.rb
index f4b30fd..4ffe64b 100644
--- a/lib/unicorn/http_response.rb
+++ b/lib/unicorn/http_response.rb
@@ -1,143 +1,39 @@
 module Unicorn
-  # Writes and controls your response to the client using the HTTP/1.1 specification.
+
+  # Writes a Rack response to your client using the HTTP/1.1 specification.
   # You use it by simply doing:
   #
-  #  response.start(200) do |head,out|
-  #    head['Content-Type'] = 'text/plain'
-  #    out.write("hello\n")
-  #  end
-  #
-  # The parameter to start is the response code--which Unicorn will translate for you
-  # based on HTTP_STATUS_CODES.  The head parameter is how you write custom headers.
-  # The out parameter is where you write your body.  The default status code for
-  # HttpResponse.start is 200 so the above example is redundant.
-  #
-  # As you can see, it's just like using a Hash and as you do this it writes the proper
-  # header to the output on the fly.  You can even intermix specifying headers and
-  # writing content.  The HttpResponse class with write the things in the proper order
-  # once the HttpResponse.block is ended.
+  #   status, headers, body = rack_app.call(env)
+  #   HttpResponse.send(socket, [ status, headers, body ])
   #
-  # You may also work the HttpResponse object directly using the various attributes available
-  # for the raw socket, body, header, and status codes.  If you do this you're on your own.
-  # A design decision was made to force the client to not pipeline requests.  HTTP/1.1
-  # pipelining really kills the performance due to how it has to be handled and how
-  # unclear the standard is.  To fix this the HttpResponse gives a "Connection: close"
-  # header which forces the client to close right away.  The bonus for this is that it
-  # gives a pretty nice speed boost to most clients since they can close their connection
-  # immediately.
+  # Most header correctness (including Content-Length) is the job of
+  # Rack, with the exception of the "Connection: close" and "Date"
+  # headers.
   #
-  # One additional caveat is that you don't have to specify the Content-length header
-  # as the HttpResponse will write this for you based on the out length.
-  class HttpResponse
-    attr_reader :socket
-    attr_reader :body
-    attr_writer :body
-    attr_reader :header
-    attr_reader :status
-    attr_writer :status
-    attr_reader :body_sent
-    attr_reader :header_sent
-    attr_reader :status_sent
-
-    def initialize(socket, app_response)
-      @socket = socket
-      @app_response = app_response
-      @body = StringIO.new
-      app_response[2].each {|x| @body << x}
-      @status = app_response[0]
-      @reason = nil
-      @header = HeaderOut.new
-      @header[Const::DATE] = Time.now.httpdate
-      @header.merge!(app_response[1])
-      @body_sent = false
-      @header_sent = false
-      @status_sent = false
-    end
-
-    # Receives a block passing it the header and body for you to work with.
-    # When the block is finished it writes everything you've done to
-    # the socket in the proper order.  This lets you intermix header and
-    # body content as needed.  Handlers are able to modify pretty much
-    # any part of the request in the chain, and can stop further processing
-    # by simple passing "finalize=true" to the start method.  By default
-    # all handlers run and then mongrel finalizes the request when they're
-    # all done.
-    # TODO: docs
-    def start #(status=200, finalize=false, reason=nil)
-      finished
-    end
-
-    # Primarily used in exception handling to reset the response output in order to write
-    # an alternative response.  It will abort with an exception if you have already
-    # sent the header or the body.  This is pretty catastrophic actually.
-    def reset
-      if @body_sent
-        raise "You have already sent the request body."
-      elsif @header_sent
-        raise "You have already sent the request headers."
-      else
-        # XXX Dubious ( http://mongrel.rubyforge.org/ticket/19 )
-        @header.reset!
-
-        @body.close
-        @body = StringIO.new
-      end
-    end
+  # A design decision was made to force the client to not pipeline or
+  # keepalive requests.  HTTP/1.1 pipelining really kills the
+  # performance due to how it has to be handled and how unclear the
+  # standard is.  To fix this the HttpResponse always gives a
+  # "Connection: close" header which forces the client to close right
+  # away.  The bonus for this is that it gives a pretty nice speed boost
+  # to most clients since they can close their connection immediately.
 
-    def send_status(content_length=@body.length)
-      if not @status_sent
-        @header['Content-Length'] = content_length if content_length and @status != 304
-        write(HTTP_STATUS_HEADERS[@status])
-        @status_sent = true
-      end
-    end
-
-    def send_header
-      if not @header_sent
-        write("#{@header.to_s}#{Const::LINE_END}")
-        @header_sent = true
-      end
-    end
-
-    def send_body
-      if not @body_sent
-        @body.rewind
-        write(@body.read)
-        @body_sent = true
-      end
-    end
-
-    def socket_error(details)
-      # ignore these since it means the client closed off early
-      @socket.close rescue nil
-      done = true
-      raise details
-    end
+  class HttpResponse
 
-    def write(data)
-      @socket.write(data)
-    rescue => details
-      socket_error(details)
-    end
+    # we'll have one of these per-process
+    HEADERS = HeaderOut.new unless defined?(HEADERS)
 
-    # This takes whatever has been done to header and body and then writes it in the
-    # proper format to make an HTTP/1.1 response.
-    def finished
-      send_status
-      send_header
-      send_body
-    end
+    def self.send(socket, rack_response)
+      status, headers, body = rack_response
+      HEADERS.reset!
 
-    # Used during error conditions to mark the response as "done" so there isn't any more processing
-    # sent to the client.
-    def done=(val)
-      @status_sent = true
-      @header_sent = true
-      @body_sent = true
-    end
+      # Rack does not set Date, but don't worry about Content-Length,
+      # since Rack enforces that in Rack::Lint
+      HEADERS[Const::DATE] = Time.now.httpdate
+      HEADERS.merge!(headers)
 
-    def done
-      (@status_sent and @header_sent and @body_sent)
+      socket.write("#{HTTP_STATUS_HEADERS[status]}#{HEADERS.to_s}\r\n")
+      body.each { |chunk| socket.write(chunk) }
     end
 
   end