1 # -*- encoding: binary -*-
6 # Writes a Rack response to your client using the HTTP/1.1 specification.
7 # You use it by simply doing:
9 # status, headers, body = rack_app.call(env)
10 # HttpResponse.write(socket, [ status, headers, body ])
12 # Most header correctness (including Content-Length and Content-Type)
13 # is the job of Rack, with the exception of the "Connection: close"
16 # A design decision was made to force the client to not pipeline or
17 # keepalive requests. HTTP/1.1 pipelining really kills the
18 # performance due to how it has to be handled and how unclear the
19 # standard is. To fix this the HttpResponse always gives a
20 # "Connection: close" header which forces the client to close right
21 # away. The bonus for this is that it gives a pretty nice speed boost
22 # to most clients since they can close their connection immediately.
26 # Every standard HTTP code mapped to the appropriate message.
27 CODES = Rack::Utils::HTTP_STATUS_CODES.inject({}) { |hash,(code,msg)|
28 hash[code] = "#{code} #{msg}"
32 # Rack does not set/require a Date: header. We always override the
33 # Connection: and Date: headers no matter what (if anything) our
34 # Rack application sent us.
35 SKIP = { 'connection' => true, 'date' => true, 'status' => true }
37 # writes the rack_response to socket as an HTTP response
38 def self.write(socket, rack_response, have_header = true)
39 status, headers, body = rack_response
42 status = CODES[status.to_i] || status
45 # Don't bother enforcing duplicate supression, it's a Hash most of
46 # the time anyways so just hope our app knows what it's doing
47 headers.each do |key, value|
48 next if SKIP.include?(key.downcase)
50 # avoiding blank, key-only cookies with /\n+/
51 out.concat(value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" })
53 out << "#{key}: #{value}\r\n"
57 # Rack should enforce Content-Length or chunked transfer encoding,
58 # so don't worry or care about them.
59 # Date is required by HTTP/1.1 as long as our clock can be trusted.
60 # Some broken clients require a "Status" header so we accomodate them
61 socket.write("HTTP/1.1 #{status}\r\n" \
62 "Date: #{Time.now.httpdate}\r\n" \
63 "Status: #{status}\r\n" \
64 "Connection: close\r\n" \
65 "#{out.join('')}\r\n")
68 body.each { |chunk| socket.write(chunk) }
69 socket.close # flushes and uncorks the socket immediately
71 body.respond_to?(:close) and body.close