http_response: pass through unknown status codes
[unicorn.git] / lib / unicorn / http_response.rb
blob5602a4317dbd1797b9d8fcbe1c49d3a88e485b2d
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     # Every standard HTTP code mapped to the appropriate message.
25     CODES = Rack::Utils::HTTP_STATUS_CODES.inject({}) { |hash,(code,msg)|
26       hash[code] = "#{code} #{msg}"
27       hash
28     }
30     # Rack does not set/require a Date: header.  We always override the
31     # Connection: and Date: headers no matter what (if anything) our
32     # Rack application sent us.
33     SKIP = { 'connection' => true, 'date' => true, 'status' => true }.freeze
34     OUT = [] # :nodoc
36     # writes the rack_response to socket as an HTTP response
37     def self.write(socket, rack_response)
38       status, headers, body = rack_response
39       status = CODES[status.to_i] || status
40       OUT.clear
42       # Don't bother enforcing duplicate supression, it's a Hash most of
43       # the time anyways so just hope our app knows what it's doing
44       headers.each do |key, value|
45         next if SKIP.include?(key.downcase)
46         if value =~ /\n/
47           value.split(/\n/).each { |v| OUT << "#{key}: #{v}\r\n" }
48         else
49           OUT << "#{key}: #{value}\r\n"
50         end
51       end
53       # Rack should enforce Content-Length or chunked transfer encoding,
54       # so don't worry or care about them.
55       # Date is required by HTTP/1.1 as long as our clock can be trusted.
56       # Some broken clients require a "Status" header so we accomodate them
57       socket.write("HTTP/1.1 #{status}\r\n" \
58                    "Date: #{Time.now.httpdate}\r\n" \
59                    "Status: #{status}\r\n" \
60                    "Connection: close\r\n" \
61                    "#{OUT.join(Z)}\r\n")
62       body.each { |chunk| socket.write(chunk) }
63       socket.close # flushes and uncorks the socket immediately
64       ensure
65         body.respond_to?(:close) and body.close rescue nil
66     end
68   end
69 end