# -*- encoding: binary -*- # This may be used to generate a Rack response # class Kcar::Response attr_accessor :sock, :hdr, :unchunk, :buf, :parser # :stopdoc: LAST_CHUNK = "0\r\n" CRLF = "\r\n" Parser = Kcar::Parser # :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) @sock, @hdr, @unchunk, @buf, @parser = 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? until response = @parser.headers(@hdr, @buf) @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 @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 if @parser.body_eof? return end if @unchunk @parser.chunked? ? each_unchunk { |x| yield x } : each_identity { |x| yield x } else if @parser.keepalive? @parser.chunked? ? each_rechunk { |x| yield x } : each_identity { |x| yield x } else each_until_eof { |x| yield x } # fastest path end end rescue EOFError end # :stopdoc: def reset @parser.reset @hdr.clear end def each_rechunk # 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 until @parser.trailers(@hdr, @buf) @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. trailers = @parser.extract_trailers(@hdr) yield(trailers.map! { |k,v| "#{k}: #{v}\r\n" }.join("") << CRLF) end def each_until_eof 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 len = @parser.body_bytes_left if len == nil each_until_eof { |x| yield x } 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 if len > 0 begin len -= @sock.readpartial(len > READ_SIZE ? READ_SIZE : len, dst).size yield dst end while len > 0 dst.respond_to?(:clear) ? dst.clear : @buf = "" end end end def each_unchunk 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... until @parser.headers(@hdr, @buf) @buf << @sock.readpartial(READ_SIZE, dst) end end # :startdoc: end