1 # -*- encoding: binary -*-
4 # This may be used to generate a Rack response synchronously.
8 attr_accessor :sock, :hdr, :unchunk, :buf, :parser
11 # By default we readpartial at most 16K off a socket at once
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
22 # returns a 3-element array that resembles a Rack response, but is
23 # more useful for additional processing by other code.
25 # [ status, headers, body ]
27 # Use Kcar::Response#rack if you want to guarantee a proper Rack response.
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
34 @buf << @sock.readpartial(READ_SIZE) if @buf.empty?
35 until response = @parser.headers(@hdr, @buf)
36 @buf << @sock.readpartial(READ_SIZE)
41 # returns a 3-element array suitable for use as a Rack response:
42 # [ status, headers, body ]
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+
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
56 @parser.keepalive? ? reset : @sock.close
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.
63 return if @parser.body_eof?
65 @parser.chunked? ? each_unchunk { |buf| yield buf } :
66 each_identity { |buf| yield buf }
68 @parser.chunked? ? each_rechunk { |buf| yield buf } :
69 each_identity { |buf| yield buf }
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 :)
87 @parser.filter_body(dst, @buf) and break
90 yield("#{size.to_s(16)}\r\n")
91 yield(dst << "\r\n".freeze)
93 break if @parser.body_eof?
94 end while @buf << @sock.readpartial(READ_SIZE, dst)
98 until @parser.trailers(@hdr, @buf)
99 @buf << @sock.readpartial(READ_SIZE, dst)
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)
110 yield @buf unless @buf.empty?
111 # easy, just read and write everything until EOFError
112 dst = @sock.readpartial(READ_SIZE)
115 end while @sock.readpartial(READ_SIZE, dst)
120 len = @parser.body_bytes_left
122 each_until_eof { |x| yield x }
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
130 tmp = dst[len, dst.size]
141 len -= @sock.readpartial(len > READ_SIZE ? READ_SIZE : len, dst).size
147 @parser.body_bytes_left = 0
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)