Struct.new(:socket, :req, :parser, :buf, :len, :tmp, :buf2)
acts like tee(1) on an input input to provide a input-like stream while providing rewindable semantics through a File/StringIO backing store. On the first pass, the input is only read on demand so your Rack application can use input notification (upload progress and like). This should fully conform to the Rack::Lint::InputWrapper specification on the public API. This class is intended to be a strict interpretation of Rack::Lint::InputWrapper functionality and will not support any deviations from it.
When processing uploads, Unicorn exposes a TeeInput object under “rack.input“ of the Rack environment.
Initializes a new TeeInput object. You normally do not have to call this unless you are writing an HTTP server.
# File lib/unicorn/tee_input.rb, line 28
28: def initialize(*args)
29: super(*args)
30: self.len = parser.content_length
31: self.tmp = len && len < @@client_body_buffer_size ?
32: StringIO.new("") : Unicorn::Util.tmpio
33: self.buf2 = ""
34: if buf.size > 0
35: parser.filter_body(buf2, buf) and finalize_input
36: tmp.write(buf2)
37: tmp.rewind
38: end
39: end
Executes the block for every ``line’’ in ios, where lines are separated by the global record separator ($/, typically “n“).
# File lib/unicorn/tee_input.rb, line 151
151: def each(&block)
152: while line = gets
153: yield line
154: end
155:
156: self # Rack does not specify what the return value is here
157: end
Reads the next ``line’’ from the I/O stream; lines are separated by the global record separator ($/, typically “n“). A global record separator of nil reads the entire unread contents of ios. Returns nil if called at the end of file. This takes zero arguments for strict Rack::Lint compatibility, unlike IO#gets.
# File lib/unicorn/tee_input.rb, line 119
119: def gets
120: socket or return tmp.gets
121: sep = $/ or return read
122:
123: orig_size = tmp.size
124: if tmp.pos == orig_size
125: tee(@@io_chunk_size, buf2) or return nil
126: tmp.seek(orig_size)
127: end
128:
129: sep_size = Rack::Utils.bytesize(sep)
130: line = tmp.gets # cannot be nil here since size > pos
131: sep == line[-sep_size, sep_size] and return line
132:
133: # unlikely, if we got here, then tmp is at EOF
134: begin
135: orig_size = tmp.pos
136: tee(@@io_chunk_size, buf2) or break
137: tmp.seek(orig_size)
138: line << tmp.gets
139: sep == line[-sep_size, sep_size] and return line
140: # tmp is at EOF again here, retry the loop
141: end while true
142:
143: line
144: end
Reads at most length bytes from the I/O stream, or to the end of file if length is omitted or is nil. length must be a non-negative integer or nil. If the optional buffer argument is present, it must reference a String, which will receive the data.
At end of file, it returns nil or “” depend on length. ios.read() and ios.read(nil) returns “”. ios.read(length [, buffer]) returns nil.
If the Content-Length of the HTTP request is known (as is the common case for POST requests), then ios.read(length [, buffer]) will block until the specified length is read (or it is the last chunk). Otherwise, for uncommon “Transfer-Encoding: chunked” requests, ios.read(length [, buffer]) will return immediately if there is any data and only block when nothing is available (providing IO#readpartial semantics).
# File lib/unicorn/tee_input.rb, line 89
89: def read(*args)
90: socket or return tmp.read(*args)
91:
92: length = args.shift
93: if nil == length
94: rv = tmp.read || ""
95: while tee(@@io_chunk_size, buf2)
96: rv << buf2
97: end
98: rv
99: else
100: rv = args.shift || ""
101: diff = tmp.size - tmp.pos
102: if 0 == diff
103: ensure_length(tee(length, rv), length)
104: else
105: ensure_length(tmp.read(diff > length ? length : diff, rv), length)
106: end
107: end
108: end
Positions the ios pointer to the beginning of input, returns the offset (zero) of the ios pointer. Subsequent reads will start from the beginning of the previously-buffered input.
# File lib/unicorn/tee_input.rb, line 165
165: def rewind
166: tmp.rewind # Rack does not specify what the return value is here
167: end
Returns the size of the input. For requests with a Content-Length header value, this will not read data off the socket and just return the value of the Content-Length header as an Integer.
For Transfer-Encoding:chunked requests, this requires consuming all of the input stream before returning since there’s no other way to determine the size of the request body beforehand.
This method is no longer part of the Rack specification as of Rack 1.2, so its use is not recommended. This method only exists for compatibility with Rack applications designed for Rack 1.1 and earlier. Most applications should only need to call read with a specified length in a loop until it returns nil.
# File lib/unicorn/tee_input.rb, line 57
57: def size
58: len and return len
59:
60: if socket
61: pos = tmp.pos
62: while tee(@@io_chunk_size, buf2)
63: end
64: tmp.seek(pos)
65: end
66:
67: self.len = tmp.size
68: end
# File lib/unicorn/tee_input.rb, line 171
171: def client_error(e)
172: case e
173: when EOFError
174: # in case client only did a premature shutdown(SHUT_WR)
175: # we do support clients that shutdown(SHUT_WR) after the
176: # _entire_ request has been sent, and those will not have
177: # raised EOFError on us.
178: socket.close if socket
179: raise Unicorn::ClientShutdown, "bytes_read=#{tmp.size}", []
180: when Unicorn::HttpParserError
181: e.set_backtrace([])
182: end
183: raise e
184: end
tee()s into dst until it is of length bytes (or until we’ve reached the Content-Length of the request body). Returns dst (the exact object, not a duplicate) To continue supporting applications that need near-real-time streaming input bodies, this is a no-op for “Transfer-Encoding: chunked” requests.
# File lib/unicorn/tee_input.rb, line 219
219: def ensure_length(dst, length)
220: # len is nil for chunked bodies, so we can't ensure length for those
221: # since they could be streaming bidirectionally and we don't want to
222: # block the caller in that case.
223: return dst if dst.nil? || len.nil?
224:
225: while dst.size < length && tee(length - dst.size, buf2)
226: dst << buf2
227: end
228:
229: dst
230: end
# File lib/unicorn/tee_input.rb, line 202
202: def finalize_input
203: while parser.trailers(req, buf).nil?
204: # Don't worry about raising ClientShutdown here on EOFError, tee()
205: # will catch EOFError when app is processing it, otherwise in
206: # initialize we never get any chance to enter the app so the
207: # EOFError will just get trapped by Unicorn and not the Rack app
208: buf << socket.readpartial(@@io_chunk_size)
209: end
210: self.socket = nil
211: end
tees off a length chunk of data from the input into the IO backing store as well as returning it. dst must be specified. returns nil if reading from the input returns nil
# File lib/unicorn/tee_input.rb, line 189
189: def tee(length, dst)
190: unless parser.body_eof?
191: if parser.filter_body(dst, socket.readpartial(length, buf)).nil?
192: tmp.write(dst)
193: tmp.seek(0, IO::SEEK_END) # workaround FreeBSD/OSX + MRI 1.8.x bug
194: return dst
195: end
196: end
197: finalize_input
198: rescue => e
199: client_error(e)
200: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.