1 # Copyright (c) 2009 Eric Wong
2 # You can redistribute it and/or modify it under the same terms as Ruby.
4 # acts like tee(1) on an input input to provide a input-like stream
5 # while providing rewindable semantics through a File/StringIO
6 # backing store. On the first pass, the input is only read on demand
7 # so your Rack application can use input notification (upload progress
8 # and like). This should fully conform to the Rack::InputWrapper
9 # specification on the public API. This class is intended to be a
10 # strict interpretation of Rack::InputWrapper functionality and will
11 # not support any deviations from it.
14 class TeeInput < Struct.new(:socket, :req, :parser, :buf)
18 @size = parser.content_length
19 @tmp = @size && @size < Const::MAX_BODY ? StringIO.new(Z.dup) : Util.tmpio
22 parser.filter_body(@buf2, buf) and finalize_input
27 # give our socket object a readpartial if it can't handle it
28 if socket && ! socket.respond_to?(:readpartial)
29 def socket.readpartial(nr, buf = Unicorn::Z.dup)
35 # returns the size of the input. This is what the Content-Length
36 # header value should be, and how large our input is expected to be.
37 # For TE:chunked, this requires consuming all of the input stream
38 # before returning since there's no other way
40 @size and return @size
44 while tee(Const::CHUNK_SIZE, @buf2)
53 socket or return @tmp.read(*args)
57 rv = @tmp.read || Z.dup
58 while tee(Const::CHUNK_SIZE, @buf2)
63 rv = args.shift || @buf2.dup
64 diff = tmp_size - @tmp.pos
68 @tmp.read(diff > length ? length : diff, rv)
73 # takes zero arguments for strict Rack::Lint compatibility, unlike IO#gets
75 socket or return @tmp.gets
76 nil == $/ and return read
79 if @tmp.pos == orig_size
80 tee(Const::CHUNK_SIZE, @buf2) or return nil
84 line = @tmp.gets # cannot be nil here since size > pos
85 $/ == line[-$/.size, $/.size] and return line
87 # unlikely, if we got here, then @tmp is at EOF
90 tee(Const::CHUNK_SIZE, @buf2) or break
93 $/ == line[-$/.size, $/.size] and return line
94 # @tmp is at EOF again here, retry the loop
105 self # Rack does not specify what the return value is here
109 @tmp.rewind # Rack does not specify what the return value is here
114 # tees off a +length+ chunk of data from the input into the IO
115 # backing store as well as returning it. +buf+ must be specified.
116 # returns nil if reading from the input returns nil
118 unless parser.body_eof?
120 if parser.filter_body(dst, socket.readpartial(length, buf)).nil?
131 while parser.trailers(req, buf).nil?
132 buf << socket.readpartial(Const::CHUNK_SIZE, @buf2)
138 StringIO === @tmp ? @tmp.size : @tmp.stat.size