Class: Unicorn::TeeInput

Inherits:
Struct
  • 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.

Instance Attribute Summary collapse

Instance Method Summary collapse

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

#bufObject

Returns the value of attribute buf

Returns:

  • (Object)

    the current value of buf



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

def buf
  @buf
end

#buf2Object

Returns the value of attribute buf2

Returns:

  • (Object)

    the current value of buf2



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

def buf2
  @buf2
end

#lenObject

Returns the value of attribute len

Returns:

  • (Object)

    the current value of len



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

def len
  @len
end

#parserObject

Returns the value of attribute parser

Returns:

  • (Object)

    the current value of parser



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

def parser
  @parser
end

#reqObject

Returns the value of attribute req

Returns:

  • (Object)

    the current value of req



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

def req
  @req
end

#socketObject

Returns the value of attribute socket

Returns:

  • (Object)

    the current value of socket



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

def socket
  @socket
end

#tmpObject

Returns the value of attribute tmp

Returns:

  • (Object)

    the current value of 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

#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.



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

#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.



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

#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.



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