lib/kcar/response.rb in kcar-0.1.2 vs lib/kcar/response.rb in kcar-0.2.0

- old
+ new

@@ -1,26 +1,27 @@ # -*- encoding: binary -*- -module Kcar # This may be used to generate a Rack response # -class Response < Struct.new(:sock, :hdr, :unchunk, :buf, :parser) +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) - super(sock, hdr, unchunk, "", Parser.new) + @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. # @@ -31,13 +32,13 @@ # 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) + @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: @@ -45,129 +46,129 @@ # # 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 + @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 + @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) - if parser.body_eof? + def each + if @parser.body_eof? return end - if unchunk - parser.chunked? ? each_unchunk(&block) : each_identity(&block) + if @unchunk + @parser.chunked? ? each_unchunk { |x| yield x } : + each_identity { |x| yield x } else - if parser.keepalive? - parser.chunked? ? each_rechunk(&block) : each_identity(&block) + if @parser.keepalive? + @parser.chunked? ? each_rechunk { |x| yield x } : + each_identity { |x| yield x } else - each_until_eof(&block) # fastest path + each_until_eof { |x| yield x } # fastest path end end rescue EOFError end # :stopdoc: def reset - parser.reset - hdr.clear + @parser.reset + @hdr.clear end - def each_rechunk(&block) + 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 + @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) + 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) + 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) + trailers = @parser.extract_trailers(@hdr) yield(trailers.map! { |k,v| "#{k}: #{v}\r\n" }.join("") << CRLF) end - def each_until_eof(&block) - yield buf unless buf.empty? + def each_until_eof + yield @buf unless @buf.empty? # easy, just read and write everything until EOFError - dst = sock.readpartial(READ_SIZE) + dst = @sock.readpartial(READ_SIZE) begin yield dst - end while sock.readpartial(READ_SIZE, 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) + def each_identity + len = @parser.body_bytes_left + if len == nil + each_until_eof { |x| yield x } else - dst = buf + 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) + @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 + len -= @sock.readpartial(len > READ_SIZE ? READ_SIZE : len, dst).size yield dst end while len > 0 - dst.respond_to?(:clear) ? dst.clear : self.buf = '' + dst.respond_to?(:clear) ? dst.clear : @buf = "" end end end - def each_unchunk(&block) + def each_unchunk dst = "" begin - parser.filter_body(dst, buf) and break + @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) + @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) + until @parser.headers(@hdr, @buf) + @buf << @sock.readpartial(READ_SIZE, dst) end end # :startdoc: - -end # class Response -end # module Kcar +end