Class: Unicorn::TeeInput
- Inherits:
-
Object
- Object
- 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.
Constant Summary collapse
- @@client_body_buffer_size =
The maximum size (in
bytes
) to buffer in memory before resorting to a temporary file. Default is 112 kilobytes. Unicorn::Const::MAX_BODY
- @@io_chunk_size =
The I/O chunk size (in
bytes
) for I/O operations where the size cannot be user-specified when a method is called. The default is 16 kilobytes. Unicorn::Const::CHUNK_SIZE
Instance Attribute Summary collapse
-
#buf ⇒ Object
Returns the value of attribute buf.
-
#buf2 ⇒ Object
Returns the value of attribute buf2.
-
#env ⇒ Object
Returns the value of attribute env.
-
#len ⇒ Object
Returns the value of attribute len.
-
#parser ⇒ Object
Returns the value of attribute parser.
-
#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(socket, request) ⇒ 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(socket, request) ⇒ TeeInput
Initializes a new TeeInput object. You normally do not have to call this unless you are writing an HTTP server.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/unicorn/tee_input.rb', line 28 def initialize(socket, request) @socket = socket @parser = request @buf = request.buf @env = request.env @len = request.content_length @tmp = @len && @len < @@client_body_buffer_size ? StringIO.new("") : Unicorn::TmpIO.new @buf2 = "" if @buf.size > 0 @parser.filter_body(@buf2, @buf) and finalize_input @tmp.write(@buf2) @tmp.rewind end end |
Instance Attribute Details
#buf ⇒ Object
Returns the value of attribute buf.
15 16 17 |
# File 'lib/unicorn/tee_input.rb', line 15 def buf @buf end |
#buf2 ⇒ Object
Returns the value of attribute buf2.
15 16 17 |
# File 'lib/unicorn/tee_input.rb', line 15 def buf2 @buf2 end |
#env ⇒ Object
Returns the value of attribute env.
15 16 17 |
# File 'lib/unicorn/tee_input.rb', line 15 def env @env end |
#len ⇒ Object
Returns the value of attribute len.
15 16 17 |
# File 'lib/unicorn/tee_input.rb', line 15 def len @len end |
#parser ⇒ Object
Returns the value of attribute parser.
15 16 17 |
# File 'lib/unicorn/tee_input.rb', line 15 def parser @parser end |
#socket ⇒ Object
Returns the value of attribute socket.
15 16 17 |
# File 'lib/unicorn/tee_input.rb', line 15 def socket @socket end |
#tmp ⇒ Object
Returns the value of attribute tmp.
15 16 17 |
# File 'lib/unicorn/tee_input.rb', line 15 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”).
154 155 156 157 158 159 160 |
# File 'lib/unicorn/tee_input.rb', line 154 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.
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 |
# File 'lib/unicorn/tee_input.rb', line 122 def gets @socket or return @tmp.gets sep = $/ or return read orig_size = @tmp.size if @tmp.pos == orig_size tee(@@io_chunk_size, @buf2) or return nil @tmp.seek(orig_size) end sep_size = Rack::Utils.bytesize(sep) line = @tmp.gets # cannot be nil here since size > pos sep == line[-sep_size, sep_size] and return line # unlikely, if we got here, then @tmp is at EOF begin orig_size = @tmp.pos tee(@@io_chunk_size, @buf2) or break @tmp.seek(orig_size) line << @tmp.gets sep == line[-sep_size, sep_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).
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/unicorn/tee_input.rb', line 92 def read(*args) @socket or return @tmp.read(*args) length = args.shift if nil == length rv = @tmp.read || "" while tee(@@io_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.
168 169 170 |
# File 'lib/unicorn/tee_input.rb', line 168 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.
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
.
60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/unicorn/tee_input.rb', line 60 def size @len and return @len if socket pos = @tmp.pos while tee(@@io_chunk_size, @buf2) end @tmp.seek(pos) end @len = @tmp.size end |