Class: Unicorn::TeeInput
- Inherits:
-
Struct
- Object
- Struct
- Unicorn::TeeInput
- Defined in:
- lib/unicorn/tee_input.rb
Overview
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.
Instance Attribute Summary collapse
-
#buf ⇒ Object
Returns the value of attribute buf.
-
#buf2 ⇒ Object
Returns the value of attribute buf2.
-
#len ⇒ Object
Returns the value of attribute len.
-
#parser ⇒ Object
Returns the value of attribute parser.
-
#req ⇒ Object
Returns the value of attribute req.
-
#socket ⇒ Object
Returns the value of attribute socket.
-
#tmp ⇒ Object
Returns the value of attribute tmp.
Instance Method Summary collapse
-
#each(&block) ⇒ Object
:call-seq: ios.each { |line| block } => ios.
-
#gets ⇒ Object
:call-seq: ios.gets => string or nil.
-
#initialize(*args) ⇒ TeeInput
constructor
Initializes a new TeeInput object.
-
#read(*args) ⇒ Object
:call-seq: ios.read([length [, buffer ]]) => string, buffer, or nil.
-
#rewind ⇒ Object
:call-seq: ios.rewind => 0.
-
#size ⇒ Object
:call-seq: ios.size => Integer.
Constructor Details
#initialize(*args) ⇒ TeeInput
Initializes a new TeeInput object. You normally do not have to call this unless you are writing an HTTP server.
20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/unicorn/tee_input.rb', line 20 def initialize(*args) super(*args) self.len = parser.content_length self.tmp = len && len < Const::MAX_BODY ? StringIO.new("") : Util.tmpio self.buf2 = "" if buf.size > 0 parser.filter_body(buf2, buf) and finalize_input tmp.write(buf2) tmp.seek(0) end end |
Instance Attribute Details
#buf ⇒ Object
Returns the value of attribute buf
16 17 18 |
# File 'lib/unicorn/tee_input.rb', line 16 def buf @buf end |
#buf2 ⇒ Object
Returns the value of attribute buf2
16 17 18 |
# File 'lib/unicorn/tee_input.rb', line 16 def buf2 @buf2 end |
#len ⇒ Object
Returns the value of attribute len
16 17 18 |
# File 'lib/unicorn/tee_input.rb', line 16 def len @len end |
#parser ⇒ Object
Returns the value of attribute parser
16 17 18 |
# File 'lib/unicorn/tee_input.rb', line 16 def parser @parser end |
#req ⇒ Object
Returns the value of attribute req
16 17 18 |
# File 'lib/unicorn/tee_input.rb', line 16 def req @req end |
#socket ⇒ Object
Returns the value of attribute socket
16 17 18 |
# File 'lib/unicorn/tee_input.rb', line 16 def socket @socket end |
#tmp ⇒ Object
Returns the value of attribute tmp
16 17 18 |
# File 'lib/unicorn/tee_input.rb', line 16 def tmp @tmp end |
Instance Method Details
#each(&block) ⇒ Object
:call-seq:
ios.each { |line| block } => ios
Executes the block for every “line” in ios, where lines are separated by the global record separator ($/, typically “n”).
135 136 137 138 139 140 141 |
# File 'lib/unicorn/tee_input.rb', line 135 def each(&block) while line = gets yield line end self # Rack does not specify what the return value is here end |
#gets ⇒ Object
:call-seq:
ios.gets => string or nil
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.
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 |
# File 'lib/unicorn/tee_input.rb', line 104 def gets socket or return tmp.gets nil == $/ and return read orig_size = tmp.size if tmp.pos == orig_size tee(Const::CHUNK_SIZE, buf2) or return nil tmp.seek(orig_size) end line = tmp.gets # cannot be nil here since size > pos $/ == line[-$/.size, $/.size] and return line # unlikely, if we got here, then tmp is at EOF begin orig_size = tmp.pos tee(Const::CHUNK_SIZE, buf2) or break tmp.seek(orig_size) line << tmp.gets $/ == line[-$/.size, $/.size] and return line # tmp is at EOF again here, retry the loop end while true line end |
#read(*args) ⇒ Object
:call-seq:
ios.read([length [, buffer ]]) => string, buffer, or nil
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).
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/unicorn/tee_input.rb', line 74 def read(*args) socket or return tmp.read(*args) length = args.shift if nil == length rv = tmp.read || "" while tee(Const::CHUNK_SIZE, buf2) rv << buf2 end rv else rv = args.shift || "" diff = tmp.size - tmp.pos if 0 == diff ensure_length(tee(length, rv), length) else ensure_length(tmp.read(diff > length ? length : diff, rv), length) end end end |
#rewind ⇒ Object
:call-seq:
ios.rewind => 0
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.
149 150 151 |
# File 'lib/unicorn/tee_input.rb', line 149 def rewind tmp.rewind # Rack does not specify what the return value is here end |
#size ⇒ Object
:call-seq:
ios.size => Integer
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.
42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/unicorn/tee_input.rb', line 42 def size len and return len if socket pos = tmp.pos while tee(Const::CHUNK_SIZE, buf2) end tmp.seek(pos) end self.len = tmp.size end |