chunked_reader: simpler interface
[unicorn.git] / lib / unicorn / chunked_reader.rb
blob40d421dc20cbd1d6607d8f94946d0a45e02ecb36
1 # Copyright (c) 2009 Eric Wong
2 # You can redistribute it and/or modify it under the same terms as Ruby.
4 module Unicorn; end
6 module Unicorn
7   class ChunkedReader
9     def initialize(input, buf)
10       @input, @buf = input, buf
11       @chunk_left = 0
12       parse_chunk_header
13     end
15     def readpartial(max, buf = Z.dup)
16       while @input && @chunk_left <= 0 && ! parse_chunk_header
17         @buf << @input.readpartial(Const::CHUNK_SIZE, buf)
18       end
20       if @input
21         begin
22           @buf << @input.read_nonblock(Const::CHUNK_SIZE, buf)
23         rescue Errno::EAGAIN, Errno::EINTR
24         end
25       end
27       max = @chunk_left if max > @chunk_left
28       buf.replace(last_block(max) || Z)
29       @chunk_left -= buf.size
30       (0 == buf.size && @input.nil?) and raise EOFError
31       buf
32     end
34     def gets
35       line = nil
36       begin
37         line = readpartial(Const::CHUNK_SIZE)
38         begin
39           if line.sub!(%r{\A(.*?#{$/})}, Z)
40             @chunk_left += line.size
41             @buf = @buf ? (line << @buf) : line
42             return $1.dup
43           end
44           line << readpartial(Const::CHUNK_SIZE)
45         end while true
46       rescue EOFError
47         return line
48       end
49     end
51   private
53     def last_block(max = nil)
54       rv = @buf
55       if max && rv && max < rv.size
56         @buf = rv[max - rv.size, rv.size - max]
57         return rv[0, max]
58       end
59       @buf = Z.dup
60       rv
61     end
63     def parse_chunk_header
64       buf = @buf
65       # ignoring chunk-extension info for now, I haven't seen any use for it
66       # (or any users, and TE:chunked sent by clients is rare already)
67       # if there was not enough data in buffer to parse length of the chunk
68       # then just return
69       if buf.sub!(/\A(?:\r\n)?([a-fA-F0-9]{1,8})[^\r]*?\r\n/, Z)
70         @chunk_left = $1.to_i(16)
71         if 0 == @chunk_left # EOF
72           buf.sub!(/\A\r\n(?:\r\n)?/, Z) # cleanup for future requests
73           @input = nil
74         end
75         return @chunk_left
76       end
78       buf.size > 256 and
79           raise HttpParserError,
80                 "malformed chunk, chunk-length not found in buffer: " \
81                 "#{buf.inspect}"
82       nil
83     end
85   end
87 end