Parent

Unicorn::TeeInput

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.

Public Class Methods

new(*args) click to toggle source

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 20
20:     def initialize(*args)
21:       super(*args)
22:       self.len = parser.content_length
23:       self.tmp = len && len < Const::MAX_BODY ? StringIO.new("") : Util.tmpio
24:       self.buf2 = ""
25:       if buf.size > 0
26:         parser.filter_body(buf2, buf) and finalize_input
27:         tmp.write(buf2)
28:         tmp.seek(0)
29:       end
30:     end

Public Instance Methods

each { |line| block } => ios click to toggle source

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 135
135:     def each(&block)
136:       while line = gets
137:         yield line
138:       end
139: 
140:       self # Rack does not specify what the return value is here
141:     end
gets => string or nil click to toggle source

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 104
104:     def gets
105:       socket or return tmp.gets
106:       nil == $/ and return read
107: 
108:       orig_size = tmp.size
109:       if tmp.pos == orig_size
110:         tee(Const::CHUNK_SIZE, buf2) or return nil
111:         tmp.seek(orig_size)
112:       end
113: 
114:       line = tmp.gets # cannot be nil here since size > pos
115:       $/ == line[-$/.size, $/.size] and return line
116: 
117:       # unlikely, if we got here, then tmp is at EOF
118:       begin
119:         orig_size = tmp.pos
120:         tee(Const::CHUNK_SIZE, buf2) or break
121:         tmp.seek(orig_size)
122:         line << tmp.gets
123:         $/ == line[-$/.size, $/.size] and return line
124:         # tmp is at EOF again here, retry the loop
125:       end while true
126: 
127:       line
128:     end
read([length [, buffer ]]) => string, buffer, or nil click to toggle source

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 74
74:     def read(*args)
75:       socket or return tmp.read(*args)
76: 
77:       length = args.shift
78:       if nil == length
79:         rv = tmp.read || ""
80:         while tee(Const::CHUNK_SIZE, buf2)
81:           rv << buf2
82:         end
83:         rv
84:       else
85:         rv = args.shift || ""
86:         diff = tmp.size - tmp.pos
87:         if 0 == diff
88:           ensure_length(tee(length, rv), length)
89:         else
90:           ensure_length(tmp.read(diff > length ? length : diff, rv), length)
91:         end
92:       end
93:     end
rewind => 0 click to toggle source

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 149
149:     def rewind
150:       tmp.rewind # Rack does not specify what the return value is here
151:     end
size => Integer click to toggle source

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.

    # File lib/unicorn/tee_input.rb, line 42
42:     def size
43:       len and return len
44: 
45:       if socket
46:         pos = tmp.pos
47:         while tee(Const::CHUNK_SIZE, buf2)
48:         end
49:         tmp.seek(pos)
50:       end
51: 
52:       self.len = tmp.size
53:     end

Private Instance Methods

client_error(e) click to toggle source

(Not documented)

     # File lib/unicorn/tee_input.rb, line 155
155:     def client_error(e)
156:       case e
157:       when EOFError
158:         # in case client only did a premature shutdown(SHUT_WR)
159:         # we do support clients that shutdown(SHUT_WR) after the
160:         # _entire_ request has been sent, and those will not have
161:         # raised EOFError on us.
162:         socket.close if socket
163:         raise ClientShutdown, "bytes_read=#{tmp.size}", []
164:       when HttpParserError
165:         e.set_backtrace([])
166:       end
167:       raise e
168:     end
ensure_length(dst, length) click to toggle source

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 203
203:     def ensure_length(dst, length)
204:       # len is nil for chunked bodies, so we can't ensure length for those
205:       # since they could be streaming bidirectionally and we don't want to
206:       # block the caller in that case.
207:       return dst if dst.nil? || len.nil?
208: 
209:       while dst.size < length && tee(length - dst.size, buf2)
210:         dst << buf2
211:       end
212: 
213:       dst
214:     end
finalize_input() click to toggle source

(Not documented)

     # File lib/unicorn/tee_input.rb, line 186
186:     def finalize_input
187:       while parser.trailers(req, buf).nil?
188:         # Don't worry about raising ClientShutdown here on EOFError, tee()
189:         # will catch EOFError when app is processing it, otherwise in
190:         # initialize we never get any chance to enter the app so the
191:         # EOFError will just get trapped by Unicorn and not the Rack app
192:         buf << socket.readpartial(Const::CHUNK_SIZE)
193:       end
194:       self.socket = nil
195:     end
tee(length, dst) click to toggle source

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 173
173:     def tee(length, dst)
174:       unless parser.body_eof?
175:         if parser.filter_body(dst, socket.readpartial(length, buf)).nil?
176:           tmp.write(dst)
177:           tmp.seek(0, IO::SEEK_END) # workaround FreeBSD/OSX + MRI 1.8.x bug
178:           return dst
179:         end
180:       end
181:       finalize_input
182:       rescue => e
183:         client_error(e)
184:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.