diff options
Diffstat (limited to 'lib/kcar/response.rb')
-rw-r--r-- | lib/kcar/response.rb | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/lib/kcar/response.rb b/lib/kcar/response.rb new file mode 100644 index 0000000..724e1df --- /dev/null +++ b/lib/kcar/response.rb @@ -0,0 +1,130 @@ +# -*- encoding: binary -*- + +module Kcar +class Response < Struct.new(:sock, :unchunk, :hdr, :buf, :parser) + + LAST_CHUNK = "0\r\n" + CRLF = "\r\n" + READ_SIZE = 0x4000 + + def initialize(sock, unchunk = true, hdr = {}) + super(sock, unchunk, hdr, "", Parser.new) + end + + def read + buf << sock.readpartial(READ_SIZE) if buf.empty? + while (response = parser.headers(hdr, buf)).nil? + buf << sock.readpartial(READ_SIZE) + end + response << self + end + + def reset + parser.reset + hdr.clear + end + + # this method allows Kcar::Session to be used as a Rack response body + def each(&block) + return if parser.body_eof? + if unchunk + parser.chunked? ? each_unchunk(&block) : each_identity(&block) + else + if parser.keepalive? + parser.chunked? ? each_rechunk(&block) : each_identity(&block) + else + each_until_eof(&block) # fastest path + end + end + rescue EOFError + end + + def each_rechunk(&block) + # We have to filter_body to keep track of parser state + # (which sucks). Also, as a benefit to clients we'll rechunk + # to increase the likelyhood of network transfers being on + # chunk boundaries so we're less likely to trigger bugs in + # other people's code :) + dst = "" + begin + parser.filter_body(dst, buf) and break + size = dst.size + if size > 0 + yield("#{size.to_s(16)}\r\n") + yield(dst << CRLF) + end + break if parser.body_eof? + end while buf << sock.readpartial(READ_SIZE, dst) + + yield LAST_CHUNK + + while parser.trailers(hdr, buf).nil? + buf << sock.readpartial(READ_SIZE, dst) + end + + # since Rack does not provide a way to explicitly send trailers + # in the response, we'll just yield a stringified version to our + # server and pretend it's part of the body. + if trailers = parser.extract_trailers(hdr) + yield(trailers.map! { |k,v| "#{k}: #{v}\r\n" }.join("") << "\r\n") + end + end + + # this is called by our Rack server + def close + parser.keepalive? ? reset : sock.close + end + + def each_until_eof(&block) + yield buf unless buf.empty? + # easy, just read and write everything until EOFError + dst = sock.readpartial(READ_SIZE) + begin + yield dst + end while sock.readpartial(READ_SIZE, dst) + end + + def each_identity(&block) + len = parser.body_bytes_left + if len.nil? + each_until_eof(&block) + else + dst = buf + if dst.size > 0 + # in case of keepalive we need to read the second response, + # so modify buf so that the second response is at the front + # of the buffer + if dst.size >= len + tmp = dst[len, dst.size] + dst = dst[0, len] + buf.replace(tmp) + end + + len -= dst.size + yield dst + end + while len > 0 + len -= sock.readpartial(len > READ_SIZE ? CHUNK_SIZE : len, dst).size + yield dst + end + end + end + + def each_unchunk(&block) + dst = "" + begin + parser.filter_body(dst, buf) and break + yield dst if dst.size > 0 + parser.body_eof? and break + end while buf << sock.readpartial(READ_SIZE, dst) + + # we can't pass trailers to the client since we unchunk + # the response, so just read them off the socket and + # stash them in hdr just in case... + while parser.headers(hdr, buf).nil? + buf << sock.readpartial(READ_SIZE, dst) + end + end + +end # class Session +end # module Kcar |