From a1c7992d47ac820c64604b8aeb8779a0bf741fcf Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 5 Feb 2009 15:16:18 -0800 Subject: Simplify HttpResponse since we only handle Rack now 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. --- lib/unicorn/http_response.rb | 156 ++++++++----------------------------------- 1 file changed, 26 insertions(+), 130 deletions(-) (limited to 'lib/unicorn/http_response.rb') 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 -- cgit v1.2.3-24-ge0c7