Rack 1.0.0 compatibility
[unicorn.git] / lib / unicorn / http_response.rb
blob5480b5d6448b334149723151107323bc316139f6
1 require 'time'
3 module Unicorn
4   # Writes a Rack response to your client using the HTTP/1.1 specification.
5   # You use it by simply doing:
6   #
7   #   status, headers, body = rack_app.call(env)
8   #   HttpResponse.write(socket, [ status, headers, body ])
9   #
10   # Most header correctness (including Content-Length and Content-Type)
11   # is the job of Rack, with the exception of the "Connection: close"
12   # and "Date" headers.
13   #
14   # A design decision was made to force the client to not pipeline or
15   # keepalive requests.  HTTP/1.1 pipelining really kills the
16   # performance due to how it has to be handled and how unclear the
17   # standard is.  To fix this the HttpResponse always gives a
18   # "Connection: close" header which forces the client to close right
19   # away.  The bonus for this is that it gives a pretty nice speed boost
20   # to most clients since they can close their connection immediately.
22   class HttpResponse
24     # Rack does not set/require a Date: header.  We always override the
25     # Connection: and Date: headers no matter what (if anything) our
26     # Rack application sent us.
27     SKIP = { 'connection' => true, 'date' => true, 'status' => true }.freeze
28     EMPTY = ''.freeze # :nodoc
29     OUT = [] # :nodoc
31     # writes the rack_response to socket as an HTTP response
32     def self.write(socket, rack_response)
33       status, headers, body = rack_response
34       status = HTTP_STATUS_CODES[status]
35       OUT.clear
37       # Don't bother enforcing duplicate supression, it's a Hash most of
38       # the time anyways so just hope our app knows what it's doing
39       headers.each do |key, value|
40         next if SKIP.include?(key.downcase)
41         if value =~ /\n/
42           value.split(/\n/).each { |v| OUT << "#{key}: #{v}\r\n" }
43         else
44           OUT << "#{key}: #{value}\r\n"
45         end
46       end
48       # Rack should enforce Content-Length or chunked transfer encoding,
49       # so don't worry or care about them.
50       # Date is required by HTTP/1.1 as long as our clock can be trusted.
51       # Some broken clients require a "Status" header so we accomodate them
52       socket_write(socket,
53                    "HTTP/1.1 #{status}\r\n" \
54                    "Date: #{Time.now.httpdate}\r\n" \
55                    "Status: #{status}\r\n" \
56                    "Connection: close\r\n" \
57                    "#{OUT.join(EMPTY)}\r\n")
58       body.each { |chunk| socket_write(socket, chunk) }
59       socket.close # uncorks the socket immediately
60       ensure
61         body.respond_to?(:close) and body.close rescue nil
62     end
64     private
66       # write(2) can return short on slow devices like sockets as well
67       # as fail with EINTR if a signal was caught.
68       def self.socket_write(socket, buffer)
69         begin
70           written = socket.syswrite(buffer)
71           return written if written == buffer.length
72           buffer = buffer[written..-1]
73         rescue Errno::EINTR
74         end while true
75       end
77   end
78 end