http_response: just barely faster
[unicorn.git] / lib / unicorn / http_response.rb
blob658184b9977f616d8966f9e2bf9682e093671c1b
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     CONNECTION_CLOSE = "Connection: close".freeze #:nodoc
30     # writes the rack_response to socket as an HTTP response
31     def self.write(socket, rack_response)
32       status, headers, body = rack_response
33       status = "#{status} #{HTTP_STATUS_CODES[status]}"
34       out = [ CONNECTION_CLOSE ]
36       # Don't bother enforcing duplicate supression, it's a Hash most of
37       # the time anyways so just hope our app knows what it's doing
38       headers.each do |key, value|
39         next if SKIP.include?(key.downcase)
40         if value =~ /\n/
41           value.split(/\n/).each { |v| out << "#{key}: #{v}" }
42         else
43           out << "#{key}: #{value}"
44         end
45       end
47       # Rack should enforce Content-Length or chunked transfer encoding,
48       # so don't worry or care about them.
49       # Date is required by HTTP/1.1 as long as our clock can be trusted.
50       # Some broken clients require a "Status" header so we accomodate them
51       socket_write(socket,
52                    "HTTP/1.1 #{status}\r\n" \
53                    "Date: #{Time.now.httpdate}\r\n" \
54                    "Status: #{status}\r\n" \
55                    "#{out.join("\r\n")}\r\n\r\n")
56       body.each { |chunk| socket_write(socket, chunk) }
57       socket.close # uncorks the socket immediately
58       ensure
59         body.respond_to?(:close) and body.close rescue nil
60     end
62     private
64       # write(2) can return short on slow devices like sockets as well
65       # as fail with EINTR if a signal was caught.
66       def self.socket_write(socket, buffer)
67         begin
68           written = socket.syswrite(buffer)
69           return written if written == buffer.length
70           buffer = buffer[written..-1]
71         rescue Errno::EINTR
72         end while true
73       end
75   end
76 end