1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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
111
112
113
114
115
116
117
118
119
120
121
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
| | # -*- 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
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
# :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)
rescue EOFError
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
|