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::InputWrapper specification on the public API. This class is intended to be a strict interpretation of Rack::InputWrapper functionality and will not support any deviations from it.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ TeeInput

Returns a new instance of TeeInput.



15
16
17
18
19
20
21
22
23
24
25
# File 'lib/unicorn/tee_input.rb', line 15

def initialize(*args)
  super(*args)
  @size = parser.content_length
  @tmp = @size && @size < Const::MAX_BODY ? StringIO.new("") : Util.tmpio
  @buf2 = buf.dup
  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



13
14
15
# File 'lib/unicorn/tee_input.rb', line 13

def buf
  @buf
end

#parserObject

Returns the value of attribute parser

Returns:

  • (Object)

    the current value of parser



13
14
15
# File 'lib/unicorn/tee_input.rb', line 13

def parser
  @parser
end

#reqObject

Returns the value of attribute req

Returns:

  • (Object)

    the current value of req



13
14
15
# File 'lib/unicorn/tee_input.rb', line 13

def req
  @req
end

#socketObject

Returns the value of attribute socket

Returns:

  • (Object)

    the current value of socket



13
14
15
# File 'lib/unicorn/tee_input.rb', line 13

def socket
  @socket
end

Instance Method Details

#each(&block) ⇒ Object



112
113
114
115
116
117
118
# File 'lib/unicorn/tee_input.rb', line 112

def each(&block)
  while line = gets
    yield line
  end

  self # Rack does not specify what the return value is here
end

#getsObject

takes zero arguments for strict Rack::Lint compatibility, unlike IO#gets



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/unicorn/tee_input.rb', line 86

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 = env['rack.input']
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).



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/unicorn/tee_input.rb', line 64

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 || @buf2.dup
    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



120
121
122
# File 'lib/unicorn/tee_input.rb', line 120

def rewind
  @tmp.rewind # Rack does not specify what the return value is here
end

#sizeObject

returns the size of the input. This is what the Content-Length header value should be, and how large our input is expected to be. For TE:chunked, this requires consuming all of the input stream before returning since there’s no other way



31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/unicorn/tee_input.rb', line 31

def size
  @size and return @size

  if socket
    pos = @tmp.pos
    while tee(Const::CHUNK_SIZE, @buf2)
    end
    @tmp.seek(pos)
  end

  @size = tmp_size
end