port test/unit/test_ccc.rb to Perl 5
[unicorn.git] / lib / unicorn / stream_input.rb
blob23a9976e02787496a7d7c385b08d8a7065c16626
1 # -*- encoding: binary -*-
2 # frozen_string_literal: false
4 # When processing uploads, unicorn may expose a StreamInput object under
5 # "rack.input" of the Rack environment when
6 # Unicorn::Configurator#rewindable_input is set to +false+
7 class Unicorn::StreamInput
8   # The I/O chunk size (in +bytes+) for I/O operations where
9   # the size cannot be user-specified when a method is called.
10   # The default is 16 kilobytes.
11   @@io_chunk_size = Unicorn::Const::CHUNK_SIZE # :nodoc:
13   # Initializes a new StreamInput object.  You normally do not have to call
14   # this unless you are writing an HTTP server.
15   def initialize(socket, request) # :nodoc:
16     @chunked = request.content_length.nil?
17     @socket = socket
18     @parser = request
19     @buf = request.buf
20     @rbuf = ''
21     @bytes_read = 0
22     filter_body(@rbuf, @buf) unless @buf.empty?
23   end
25   # :call-seq:
26   #   ios.read([length [, buffer ]]) => string, buffer, or nil
27   #
28   # Reads at most length bytes from the I/O stream, or to the end of
29   # file if length is omitted or is nil. length must be a non-negative
30   # integer or nil. If the optional buffer argument is present, it
31   # must reference a String, which will receive the data.
32   #
33   # At end of file, it returns nil or '' depend on length.
34   # ios.read() and ios.read(nil) returns ''.
35   # ios.read(length [, buffer]) returns nil.
36   #
37   # If the Content-Length of the HTTP request is known (as is the common
38   # case for POST requests), then ios.read(length [, buffer]) will block
39   # until the specified length is read (or it is the last chunk).
40   # Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
41   # ios.read(length [, buffer]) will return immediately if there is
42   # any data and only block when nothing is available (providing
43   # IO#readpartial semantics).
44   def read(length = nil, rv = '')
45     if length
46       if length <= @rbuf.size
47         length < 0 and raise ArgumentError, "negative length #{length} given"
48         rv.replace(@rbuf.slice!(0, length))
49       else
50         to_read = length - @rbuf.size
51         rv.replace(@rbuf.slice!(0, @rbuf.size))
52         until to_read == 0 || eof? || (rv.size > 0 && @chunked)
53           filter_body(@rbuf, @socket.readpartial(to_read, @buf))
54           rv << @rbuf
55           to_read -= @rbuf.size
56         end
57         @rbuf.clear
58       end
59       rv = nil if rv.empty? && length != 0
60     else
61       read_all(rv)
62     end
63     rv
64   rescue EOFError
65     return eof!
66   end
68   # :call-seq:
69   #   ios.gets   => string or nil
70   #
71   # Reads the next ``line'' from the I/O stream; lines are separated
72   # by the global record separator ($/, typically "\n"). A global
73   # record separator of nil reads the entire unread contents of ios.
74   # Returns nil if called at the end of file.
75   # This takes zero arguments for strict Rack::Lint compatibility,
76   # unlike IO#gets.
77   def gets
78     sep = $/
79     if sep.nil?
80       read_all(rv = '')
81       return rv.empty? ? nil : rv
82     end
83     re = /\A(.*?#{Regexp.escape(sep)})/
85     begin
86       @rbuf.sub!(re, '') and return $1
87       return @rbuf.empty? ? nil : @rbuf.slice!(0, @rbuf.size) if eof?
88       filter_body(once = '', @socket.readpartial(@@io_chunk_size, @buf))
89       @rbuf << once
90     rescue EOFError
91       return eof!
92     end while true
93   end
95   # :call-seq:
96   #   ios.each { |line| block }  => ios
97   #
98   # Executes the block for every ``line'' in *ios*, where lines are
99   # separated by the global record separator ($/, typically "\n").
100   def each
101     while line = gets
102       yield line
103     end
105     self # Rack does not specify what the return value is here
106   end
108 private
110   def eof?
111     if @parser.body_eof?
112       while @chunked && ! @parser.parse
113         @buf << @socket.readpartial(@@io_chunk_size)
114       end
115       @socket = nil
116       true
117     else
118       false
119     end
120   rescue EOFError
121     return eof!
122   end
124   def filter_body(dst, src)
125     rv = @parser.filter_body(dst, src)
126     @bytes_read += dst.size
127     rv
128   end
130   def read_all(dst)
131     dst.replace(@rbuf)
132     @socket or return
133     until eof?
134       filter_body(@rbuf, @socket.readpartial(@@io_chunk_size, @buf))
135       dst << @rbuf
136     end
137   rescue EOFError
138     return eof!
139   ensure
140     @rbuf.clear
141   end
143   def eof!
144     # in case client only did a premature shutdown(SHUT_WR)
145     # we do support clients that shutdown(SHUT_WR) after the
146     # _entire_ request has been sent, and those will not have
147     # raised EOFError on us.
148     @socket.shutdown if @socket
149   ensure
150     raise Unicorn::ClientShutdown, "bytes_read=#{@bytes_read}", []
151   end