# -*- encoding: binary -*- module Kcar # This may be used to generate a Rack response # class Response < Struct.new(:sock, :hdr, :unchunk, :buf, :parser) # :stopdoc: LAST_CHUNK = "0\r\n" CRLF = "\r\n" # :startdoc: # By default we readpartial at most 16K off a socket at once READ_SIZE = 0x4000 # initializes a socket, +sock+ must respond to the "readpartial" # method. +unchunk+ may be set to disable transparent unchunking # +hdr+ may be a Hash, Array, or Rack::Utils::HeaderHash def initialize(sock, hdr = {}, unchunk = true) super(sock, hdr, unchunk, "", Parser.new) end # returns a 3-element array that resembles a Rack response, but is # more useful for additional processing by other code. # # [ status, headers, body ] # # Use Kcar::Response#rack if you want to guaranteee a proper Rack response. # # this method will not return until the response headers are fully parsed, # but the body returned will be this Kcar::Response handler itself. # +unchunk+ must be true to guarantee trailers will be stored in the # returned +header+ object 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 # returns a 3-element array suitable for use as a Rack response: # [ status, headers, body ] # # this method will not return until the response headers are fully parsed, # but the body returned will be this Kcar::Response handler itself. # It is not guaranteed that trailers will be stored in the returned +header+ def rack self.unchunk = false read end # this is expected to be called by our Rack server, it will close # our given +sock+ object if keepalive is not used otherwise it # will just reset the parser and clear the header object def close parser.keepalive? ? reset : sock.close end # this method allows our Kcar::Response object to be used as a Rack response # body. It may only be called once (usually by a Rack server) as it streams # the response body off the our socket object. 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 # :stopdoc: def reset parser.reset hdr.clear 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 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 # :startdoc: end # class Response end # module Kcar