Class: Unicorn::TeeInput

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

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

#bufObject

Returns the value of attribute buf.



15
16
17
# File 'lib/unicorn/tee_input.rb', line 15

def buf
  @buf
end

#buf2Object

Returns the value of attribute buf2.



15
16
17
# File 'lib/unicorn/tee_input.rb', line 15

def buf2
  @buf2
end

#envObject

Returns the value of attribute env.



15
16
17
# File 'lib/unicorn/tee_input.rb', line 15

def env
  @env
end

#lenObject

Returns the value of attribute len.



15
16
17
# File 'lib/unicorn/tee_input.rb', line 15

def len
  @len
end

#parserObject

Returns the value of attribute parser.



15
16
17
# File 'lib/unicorn/tee_input.rb', line 15

def parser
  @parser
end

#socketObject

Returns the value of attribute socket.



15
16
17
# File 'lib/unicorn/tee_input.rb', line 15

def socket
  @socket
end

#tmpObject

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

#getsObject

: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

#rewindObject

: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

#sizeObject

: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