Class: Kcar::Response

Inherits:
Object
  • Object
show all
Defined in:
lib/kcar/response.rb

Overview

This may be used to generate a Rack response

Constant Summary collapse

LAST_CHUNK =

:stopdoc:

"0\r\n"
CRLF =
"\r\n"
Parser =
Kcar::Parser
READ_SIZE =

By default we readpartial at most 16K off a socket at once

0x4000

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(sock, hdr = {}, unchunk = true) ⇒ Response

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



21
22
23
# File 'lib/kcar/response.rb', line 21

def initialize(sock, hdr = {}, unchunk = true)
  @sock, @hdr, @unchunk, @buf, @parser = sock, hdr, unchunk, "", Parser.new
end

Instance Attribute Details

#bufObject

Returns the value of attribute buf.



7
8
9
# File 'lib/kcar/response.rb', line 7

def buf
  @buf
end

#hdrObject

Returns the value of attribute hdr.



7
8
9
# File 'lib/kcar/response.rb', line 7

def hdr
  @hdr
end

#parserObject

Returns the value of attribute parser.



7
8
9
# File 'lib/kcar/response.rb', line 7

def parser
  @parser
end

#sockObject

Returns the value of attribute sock.



7
8
9
# File 'lib/kcar/response.rb', line 7

def sock
  @sock
end

#unchunkObject

Returns the value of attribute unchunk.



7
8
9
# File 'lib/kcar/response.rb', line 7

def unchunk
  @unchunk
end

Instance Method Details

#closeObject

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



58
59
60
# File 'lib/kcar/response.rb', line 58

def close
  @parser.keepalive? ? reset : @sock.close
end

#eachObject

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.



65
66
67
68
69
70
71
72
73
74
# File 'lib/kcar/response.rb', line 65

def each
  return if @parser.body_eof?
  if @unchunk
    @parser.chunked? ? each_unchunk { |x| yield x } :
                       each_identity { |x| yield x }
  else
    @parser.chunked? ? each_rechunk { |x| yield x } :
                       each_identity { |x| yield x }
  end
end

#each_identityObject



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/kcar/response.rb', line 122

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

#each_rechunk {|LAST_CHUNK| ... } ⇒ Object

Yields:



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/kcar/response.rb', line 82

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

#each_unchunkObject



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/kcar/response.rb', line 152

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

#each_until_eofObject



112
113
114
115
116
117
118
119
120
# File 'lib/kcar/response.rb', line 112

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)
rescue EOFError
end

#rackObject

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



50
51
52
53
# File 'lib/kcar/response.rb', line 50

def rack
  @unchunk = false
  read
end

#readObject

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



36
37
38
39
40
41
42
# File 'lib/kcar/response.rb', line 36

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

#resetObject

:stopdoc:



77
78
79
80
# File 'lib/kcar/response.rb', line 77

def reset
  @parser.reset
  @hdr.clear
end