response: remove Ruby 1.8-compatibility check
[kcar.git] / lib / kcar / response.rb
blob58a7148b3fd220689bf944161ff32474bcccf0fb
1 # -*- encoding: binary -*-
4 # This may be used to generate a Rack response synchronously.
6 class Kcar::Response
7   # :stopdoc:
8   attr_accessor :sock, :hdr, :unchunk, :buf, :parser
9   # :startdoc:
11   # By default we readpartial at most 16K off a socket at once
12   READ_SIZE = 0x4000
14   # initializes a socket, +sock+ must respond to the "readpartial"
15   # method.  +unchunk+ may be set to disable transparent unchunking
16   # +hdr+ may be a Hash, Array, or Rack::Utils::HeaderHash
17   def initialize(sock, hdr = {}, unchunk = true)
18     @sock, @hdr, @unchunk, @buf =  sock, hdr, unchunk, ""
19     @parser = Kcar::Parser.new
20   end
22   # returns a 3-element array that resembles a Rack response, but is
23   # more useful for additional processing by other code.
24   #
25   #    [ status, headers, body ]
26   #
27   # Use Kcar::Response#rack if you want to guarantee a proper Rack response.
28   #
29   # this method will not return until the response headers are fully parsed,
30   # but the body returned will be this Kcar::Response handler itself.
31   # +unchunk+ must be true to guarantee trailers will be stored in the
32   # returned +header+ object
33   def read
34     @buf << @sock.readpartial(READ_SIZE) if @buf.empty?
35     until response = @parser.headers(@hdr, @buf)
36       @buf << @sock.readpartial(READ_SIZE)
37     end
38     response << self
39   end
41   # returns a 3-element array suitable for use as a Rack response:
42   #    [ status, headers, body ]
43   #
44   # this method will not return until the response headers are fully parsed,
45   # but the body returned will be this Kcar::Response handler itself.
46   # It is not guaranteed that trailers will be stored in the returned +header+
47   def rack
48     @unchunk = false
49     read
50   end
52   # this is expected to be called by our Rack server, it will close
53   # our given +sock+ object if keepalive is not used otherwise it
54   # will just reset the parser and clear the header object
55   def close
56     @parser.keepalive? ? reset : @sock.close
57   end
59   # this method allows our Kcar::Response object to be used as a Rack response
60   # body.  It may only be called once (usually by a Rack server) as it streams
61   # the response body off the our socket object.
62   def each
63     return if @parser.body_eof?
64     if @unchunk
65       @parser.chunked? ? each_unchunk { |buf| yield buf } :
66                          each_identity { |buf| yield buf }
67     else
68       @parser.chunked? ? each_rechunk { |buf| yield buf } :
69                          each_identity { |buf| yield buf }
70     end
71   end
73   # :stopdoc:
74   def reset
75     @parser.reset
76     @hdr.clear
77   end
79   def each_rechunk
80     # We have to filter_body to keep track of parser state
81     # (which sucks).  Also, as a benefit to clients we'll rechunk
82     # to increase the likelyhood of network transfers being on
83     # chunk boundaries so we're less likely to trigger bugs in
84     # other people's code :)
85     dst = ""
86     begin
87       @parser.filter_body(dst, @buf) and break
88       size = dst.size
89       if size > 0
90         yield("#{size.to_s(16)}\r\n")
91         yield(dst << "\r\n".freeze)
92       end
93       break if @parser.body_eof?
94     end while @buf << @sock.readpartial(READ_SIZE, dst)
96     yield "0\r\n".freeze
98     until @parser.trailers(@hdr, @buf)
99       @buf << @sock.readpartial(READ_SIZE, dst)
100     end
102     # since Rack does not provide a way to explicitly send trailers
103     # in the response, we'll just yield a stringified version to our
104     # server and pretend it's part of the body.
105     trailers = @parser.extract_trailers(@hdr)
106     yield(trailers.map! { |k,v| "#{k}: #{v}\r\n" }.join << "\r\n".freeze)
107   end
109   def each_until_eof
110     yield @buf unless @buf.empty?
111     # easy, just read and write everything until EOFError
112     dst = @sock.readpartial(READ_SIZE)
113     begin
114       yield dst
115     end while @sock.readpartial(READ_SIZE, dst)
116   rescue EOFError
117   end
119   def each_identity
120     len = @parser.body_bytes_left
121     if len == nil
122       each_until_eof { |x| yield x }
123     else
124       dst = @buf
125       if dst.size > 0
126         # in case of keepalive we need to read the second response,
127         # so modify buf so that the second response is at the front
128         # of the buffer
129         if dst.size >= len
130           tmp = dst[len, dst.size]
131           dst = dst[0, len]
132           @buf.replace(tmp)
133         end
135         len -= dst.size
136         yield dst
137       end
139       if len > 0
140         begin
141           len -= @sock.readpartial(len > READ_SIZE ? READ_SIZE : len, dst).size
142           yield dst
143         end while len > 0
144         dst.clear
145       end
146     end
147     @parser.body_bytes_left = 0
148   end
150   def each_unchunk
151     dst = ""
152     begin
153       @parser.filter_body(dst, @buf) and break
154       yield dst if dst.size > 0
155       @parser.body_eof? and break
156     end while @buf << @sock.readpartial(READ_SIZE, dst)
158     # we can't pass trailers to the client since we unchunk
159     # the response, so just read them off the socket and
160     # stash them in hdr just in case...
161     until @parser.headers(@hdr, @buf)
162       @buf << @sock.readpartial(READ_SIZE, dst)
163     end
164   end
165   # :startdoc: