rainbows 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/GIT-VERSION-GEN CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v1.0.0.GIT
4
+ DEF_VER=v2.0.0.GIT
5
5
 
6
6
  LF='
7
7
  '
data/lib/rainbows.rb CHANGED
@@ -39,7 +39,6 @@ module Rainbows
39
39
  require 'rainbows/http_server'
40
40
  require 'rainbows/response'
41
41
  require 'rainbows/client'
42
- require 'rainbows/tee_input'
43
42
  require 'rainbows/process_client'
44
43
  autoload :Base, 'rainbows/base'
45
44
  autoload :Sendfile, 'rainbows/sendfile'
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
3
 
4
- require 'rainbows/read_timeout'
4
+ require 'rainbows/timed_read'
5
5
 
6
6
  class Rainbows::Client < Kgio::Socket
7
- include Rainbows::ReadTimeout
7
+ include Rainbows::TimedRead
8
8
  end
9
9
  Kgio.accept_class = Rainbows::Client
@@ -2,7 +2,7 @@
2
2
  # :enddoc:
3
3
  module Rainbows::Const
4
4
 
5
- RAINBOWS_VERSION = '1.0.0'
5
+ RAINBOWS_VERSION = '2.0.0'
6
6
 
7
7
  include Unicorn::Const
8
8
 
@@ -54,7 +54,7 @@ class Rainbows::DevFdResponse < Struct.new(:app)
54
54
  # we need to make sure our pipe output is Fiber-compatible
55
55
  case env["rainbows.model"]
56
56
  when :FiberSpawn, :FiberPool, :RevFiberSpawn
57
- io.respond_to?(:wait_readable) or
57
+ io.respond_to?(:kgio_wait_readable) or
58
58
  io = Rainbows::Fiber::IO.new(io)
59
59
  when :Revactor
60
60
  io = Rainbows::Revactor::Proxy.new(io)
@@ -56,19 +56,6 @@ module Rainbows::Fiber::Base
56
56
  max.nil? || max > (now + 1) ? 1 : max - now
57
57
  end
58
58
 
59
- def wait_headers_readable(client)
60
- io = client.to_io
61
- expire = nil
62
- begin
63
- return io.recv_nonblock(1, Socket::MSG_PEEK)
64
- rescue Errno::EAGAIN
65
- return if expire && expire < Time.now
66
- expire ||= Time.now + G.kato
67
- client.wait_readable
68
- retry
69
- end
70
- end
71
-
72
59
  def process(client)
73
60
  G.cur += 1
74
61
  process_client(client)
@@ -18,7 +18,7 @@ module Rainbows::Fiber::Body # :nodoc:
18
18
  begin
19
19
  offset += (n = sock.sendfile_nonblock(body, offset, count))
20
20
  rescue Errno::EAGAIN
21
- client.wait_writable
21
+ client.kgio_wait_writable
22
22
  retry
23
23
  rescue EOFError
24
24
  break
@@ -52,8 +52,8 @@ class Rainbows::Fiber::IO
52
52
  return
53
53
  when String
54
54
  buf = rv
55
- when Kgio::WaitWritable
56
- wait_writable
55
+ when :wait_writable
56
+ kgio_wait_writable
57
57
  end
58
58
  end while true
59
59
  else
@@ -61,7 +61,7 @@ class Rainbows::Fiber::IO
61
61
  (rv = @to_io.write_nonblock(buf)) == buf.bytesize and return
62
62
  buf = byte_slice(buf, rv..-1)
63
63
  rescue Errno::EAGAIN
64
- wait_writable
64
+ kgio_wait_writable
65
65
  end while true
66
66
  end
67
67
  end
@@ -75,15 +75,28 @@ class Rainbows::Fiber::IO
75
75
  end
76
76
 
77
77
  # used for reading headers (respecting keepalive_timeout)
78
- def read_timeout
78
+ def timed_read(buf)
79
79
  expire = nil
80
- begin
81
- return @to_io.read_nonblock(16384)
82
- rescue Errno::EAGAIN
83
- return if expire && expire < Time.now
84
- expire ||= Time.now + G.kato
85
- wait_readable
86
- end while true
80
+ if @to_io.respond_to?(:kgio_tryread)
81
+ begin
82
+ case rv = @to_io.kgio_tryread(16384, buf)
83
+ when :wait_readable
84
+ return if expire && expire < Time.now
85
+ expire ||= Time.now + G.kato
86
+ kgio_wait_readable
87
+ else
88
+ return rv
89
+ end
90
+ end while true
91
+ else
92
+ begin
93
+ return @to_io.read_nonblock(16384, buf)
94
+ rescue Errno::EAGAIN
95
+ return if expire && expire < Time.now
96
+ expire ||= Time.now + G.kato
97
+ kgio_wait_readable
98
+ end while true
99
+ end
87
100
  end
88
101
 
89
102
  def readpartial(length, buf = "")
@@ -93,8 +106,8 @@ class Rainbows::Fiber::IO
93
106
  case rv
94
107
  when nil
95
108
  raise EOFError, "end of file reached", []
96
- when Kgio::WaitReadable
97
- wait_readable
109
+ when :wait_readable
110
+ kgio_wait_readable
98
111
  else
99
112
  return rv
100
113
  end
@@ -103,7 +116,7 @@ class Rainbows::Fiber::IO
103
116
  begin
104
117
  return @to_io.read_nonblock(length, buf)
105
118
  rescue Errno::EAGAIN
106
- wait_readable
119
+ kgio_wait_readable
107
120
  end while true
108
121
  end
109
122
  end
@@ -128,7 +141,9 @@ end
128
141
  require 'rainbows/fiber/io/methods'
129
142
  require 'rainbows/fiber/io/compat'
130
143
  Rainbows::Client.__send__(:include, Rainbows::Fiber::IO::Methods)
131
- Rainbows::Fiber::IO.__send__(:include, Rainbows::Fiber::IO::Compat)
132
- Rainbows::Fiber::IO.__send__(:include, Rainbows::Fiber::IO::Methods)
133
- Kgio.wait_readable = :wait_readable
134
- Kgio.wait_writable = :wait_writable
144
+ class Rainbows::Fiber::IO
145
+ include Rainbows::Fiber::IO::Compat
146
+ include Rainbows::Fiber::IO::Methods
147
+ alias_method :wait_readable, :kgio_wait_readable
148
+ alias_method :wait_writable, :kgio_wait_writable
149
+ end
@@ -25,7 +25,7 @@ module Rainbows::Fiber::IO::Methods
25
25
  super
26
26
  end
27
27
 
28
- def wait_readable
28
+ def kgio_wait_readable
29
29
  fd = fileno
30
30
  @f = Fiber.current
31
31
  RD[fd] = self
@@ -33,7 +33,7 @@ module Rainbows::Fiber::IO::Methods
33
33
  RD[fd] = nil
34
34
  end
35
35
 
36
- def wait_writable
36
+ def kgio_wait_writable
37
37
  fd = fileno
38
38
  @f = Fiber.current
39
39
  WR[fd] = self
@@ -3,7 +3,7 @@
3
3
  module Rainbows::Fiber::Rev::Methods
4
4
  class Watcher < Rev::IOWatcher
5
5
  def initialize(fio, flag)
6
- @f = fio.f || Fiber.current
6
+ @f = Fiber.current
7
7
  super(fio, flag)
8
8
  attach(Rev::Loop.default)
9
9
  end
@@ -15,30 +15,23 @@ module Rainbows::Fiber::Rev::Methods
15
15
  alias on_writable on_readable
16
16
  end
17
17
 
18
- def initialize(*args)
19
- @f = Fiber.current
20
- super(*args)
21
- @r = @w = false
22
- end
23
-
24
18
  def close
25
- @w.detach if @w
26
- @r.detach if @r
27
- @r = @w = false
19
+ @w.detach if defined?(@w) && @w.attached?
20
+ @r.detach if defined?(@r) && @r.attached?
28
21
  super
29
22
  end
30
23
 
31
- def wait_writable
32
- @w ||= Watcher.new(self, :w)
24
+ def kgio_wait_writable
25
+ @w = Watcher.new(self, :w) unless defined?(@w)
33
26
  @w.enable unless @w.enabled?
34
27
  Fiber.yield
35
28
  @w.disable
36
29
  end
37
30
 
38
- def wait_readable
39
- @r ||= Watcher.new(self, :r)
31
+ def kgio_wait_readable
32
+ @r = Watcher.new(self, :r) unless defined?(@r)
40
33
  @r.enable unless @r.enabled?
41
- KATO << @f
34
+ KATO << Fiber.current
42
35
  Fiber.yield
43
36
  @r.disable
44
37
  end
@@ -1,81 +1,78 @@
1
1
  # -*- encoding: binary -*-
2
- # :enddoc:
3
2
 
4
- # middleware used to enforce client_max_body_size for TeeInput users,
5
- # there is no need to configure this middleware manually, it will
3
+ # Middleware used to enforce client_max_body_size for TeeInput users.
4
+ #
5
+ # There is no need to configure this middleware manually, it will
6
6
  # automatically be configured for you based on the client_max_body_size
7
- # setting
8
- class Rainbows::MaxBody < Struct.new(:app)
7
+ # setting.
8
+ #
9
+ # For more fine-grained conrol, you may also define it per-endpoint in
10
+ # your Rack config.ru like this:
11
+ #
12
+ # map "/limit_1M" do
13
+ # use Rainbows::MaxBody, 1024*1024
14
+ # run MyApp
15
+ # end
16
+ # map "/limit_10M" do
17
+ # use Rainbows::MaxBody, 1024*1024*10
18
+ # run MyApp
19
+ # end
9
20
 
10
- # this is meant to be included in Rainbows::TeeInput (and derived
11
- # classes) to limit body sizes
12
- module Limit
13
- TmpIO = Unicorn::TmpIO
14
- MAX_BODY = Rainbows::Const::MAX_BODY
21
+ class Rainbows::MaxBody
15
22
 
16
- def initialize(socket, request)
17
- @parser = request
18
- @buf = request.buf
19
- @env = request.env
20
- @len = request.content_length
21
23
 
22
- max = Rainbows.max_bytes # never nil, see MaxBody.setup
23
- if @len && @len > max
24
- socket.write(Rainbows::Const::ERROR_413_RESPONSE)
25
- socket.close
26
- raise IOError, "Content-Length too big: #@len > #{max}", []
27
- end
24
+ # :call-seq:
25
+ # # in config.ru:
26
+ # use Rainbows::MaxBody, 4096
27
+ # run YourApplication.new
28
+ def initialize(app, limit = Rainbows.max_bytes)
29
+ Integer === limit or raise ArgumentError, "limit not an Integer"
30
+ @app, @limit = app, limit
31
+ end
28
32
 
29
- @socket = socket
30
- @buf2 = ""
31
- if @buf.size > 0
32
- parser.filter_body(@buf2, @buf) and finalize_input
33
- @buf2.size > max and raise IOError, "chunked request body too big", []
34
- end
35
- @tmp = @len && @len < MAX_BODY ? StringIO.new("") : TmpIO.new
36
- if @buf2.size > 0
37
- @tmp.write(@buf2)
38
- @tmp.rewind
39
- max -= @buf2.size
40
- end
41
- @max_body = max
42
- end
33
+ # :stopdoc:
34
+ RACK_INPUT = "rack.input".freeze
35
+ CONTENT_LENGTH = "CONTENT_LENGTH"
36
+ HTTP_TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING"
43
37
 
44
- def tee(length, dst)
45
- rv = super
46
- if rv && ((@max_body -= rv.size) < 0)
47
- # make HttpParser#keepalive? => false to force an immediate disconnect
48
- # after we write
49
- @parser.reset
50
- throw :rainbows_EFBIG
38
+ # our main Rack middleware endpoint
39
+ def call(env)
40
+ catch(:rainbows_EFBIG) do
41
+ len = env[CONTENT_LENGTH]
42
+ if len && len.to_i > @limit
43
+ return err
44
+ elsif /\Achunked\z/i =~ env[HTTP_TRANSFER_ENCODING]
45
+ limit_input!(env)
51
46
  end
52
- rv
53
- end
54
-
47
+ @app.call(env)
48
+ end || err
55
49
  end
56
50
 
57
51
  # this is called after forking, so it won't ever affect the master
58
52
  # if it's reconfigured
59
- def self.setup
53
+ def self.setup # :nodoc:
60
54
  Rainbows.max_bytes or return
61
55
  case Rainbows::G.server.use
62
- when :Rev, :EventMachine, :NeverBlock
56
+ when :Rev, :EventMachine, :NeverBlock, :RevThreadSpawn, :RevThreadPool
63
57
  return
64
58
  end
65
59
 
66
- Rainbows::TeeInput.__send__(:include, Limit)
67
-
68
60
  # force ourselves to the outermost middleware layer
69
61
  Rainbows::G.server.app = self.new(Rainbows::G.server.app)
70
62
  end
71
63
 
72
64
  # Rack response returned when there's an error
73
- def err(env)
74
- [ 413, [ %w(Content-Length 0), %w(Content-Type text/plain) ], [] ]
65
+ def err # :nodoc:
66
+ [ 413, { 'Content-Length' => '0', 'Content-Type' => 'text/plain' }, [] ]
75
67
  end
76
68
 
77
- # our main Rack middleware endpoint
78
- def call(env)
79
- catch(:rainbows_EFBIG) { app.call(env) } || err(env)
69
+ def limit_input!(env)
70
+ input = env[RACK_INPUT]
71
+ klass = input.respond_to?(:rewind) ? RewindableWrapper : Wrapper
72
+ env[RACK_INPUT] = klass.new(input, @limit)
80
73
  end
74
+
75
+ # :startdoc:
81
76
  end
77
+ require 'rainbows/max_body/wrapper'
78
+ require 'rainbows/max_body/rewindable_wrapper'
@@ -0,0 +1,18 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ class Rainbows::MaxBody::RewindableWrapper < Rainbows::MaxBody::Wrapper
4
+ def initialize(rack_input, limit)
5
+ @orig_limit = limit
6
+ super
7
+ end
8
+
9
+ def rewind
10
+ @limit = @orig_limit
11
+ @rbuf = ''
12
+ @input.rewind
13
+ end
14
+
15
+ def size
16
+ @input.size
17
+ end
18
+ end
@@ -0,0 +1,70 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ #
4
+ # This is only used for chunked request bodies, which are rare
5
+ class Rainbows::MaxBody::Wrapper
6
+ def initialize(rack_input, limit)
7
+ @input, @limit, @rbuf = rack_input, limit, ''
8
+ end
9
+
10
+ def each(&block)
11
+ while line = gets
12
+ yield line
13
+ end
14
+ end
15
+
16
+ # chunked encoding means this method behaves more like readpartial,
17
+ # since Rack does not support a method named "readpartial"
18
+ def read(length = nil, rv = '')
19
+ if length
20
+ if length <= @rbuf.size
21
+ length < 0 and raise ArgumentError, "negative length #{length} given"
22
+ rv.replace(@rbuf.slice!(0, length))
23
+ elsif @rbuf.empty?
24
+ checked_read(length, rv) or return
25
+ else
26
+ rv.replace(@rbuf.slice!(0, @rbuf.size))
27
+ end
28
+ rv.empty? && length != 0 ? nil : rv
29
+ else
30
+ rv.replace(read_all)
31
+ end
32
+ end
33
+
34
+ def gets
35
+ sep = $/
36
+ if sep.nil?
37
+ rv = read_all
38
+ return rv.empty? ? nil : rv
39
+ end
40
+ re = /\A(.*?#{Regexp.escape(sep)})/
41
+
42
+ begin
43
+ @rbuf.sub!(re, '') and return $1
44
+
45
+ if tmp = checked_read(16384)
46
+ @rbuf << tmp
47
+ elsif @rbuf.empty? # EOF
48
+ return nil
49
+ else # EOF, return whatever is left
50
+ return @rbuf.slice!(0, @rbuf.size)
51
+ end
52
+ end while true
53
+ end
54
+
55
+ def checked_read(length = 16384, buf = '')
56
+ if @input.read(length, buf)
57
+ throw :rainbows_EFBIG if ((@limit -= buf.size) < 0)
58
+ return buf
59
+ end
60
+ end
61
+
62
+ def read_all
63
+ rv = @rbuf.slice!(0, @rbuf.size)
64
+ tmp = ''
65
+ while checked_read(16384, tmp)
66
+ rv << tmp
67
+ end
68
+ rv
69
+ end
70
+ end
@@ -6,13 +6,9 @@ module Rainbows::ProcessClient
6
6
  HttpParser = Unicorn::HttpParser
7
7
  NULL_IO = Unicorn::HttpRequest::NULL_IO
8
8
  RACK_INPUT = Unicorn::HttpRequest::RACK_INPUT
9
- TeeInput = Rainbows::TeeInput
9
+ TeeInput = Unicorn::TeeInput
10
10
  include Rainbows::Const
11
11
 
12
- def wait_headers_readable(client)
13
- IO.select([client], nil, nil, G.kato)
14
- end
15
-
16
12
  # once a client is accepted, it is processed in its entirety here
17
13
  # in 3 easy steps: read request, call app, write app response
18
14
  # this is used by synchronous concurrency models
@@ -21,11 +17,12 @@ module Rainbows::ProcessClient
21
17
  hp = HttpParser.new
22
18
  client.kgio_read!(16384, buf = hp.buf)
23
19
  remote_addr = client.kgio_addr
20
+ alive = false
24
21
 
25
22
  begin # loop
26
23
  until env = hp.parse
27
- wait_headers_readable(client) or return
28
- buf << client.kgio_read!(16384)
24
+ client.timed_read(buf2 ||= "") or return
25
+ buf << buf2
29
26
  end
30
27
 
31
28
  env[CLIENT_IO] = client
@@ -43,12 +40,12 @@ module Rainbows::ProcessClient
43
40
  if hp.headers?
44
41
  headers = HH.new(headers)
45
42
  range = make_range!(env, status, headers) and status = range.shift
46
- env = hp.keepalive? && G.alive
47
- headers[CONNECTION] = env ? KEEP_ALIVE : CLOSE
43
+ alive = hp.next? && G.alive
44
+ headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE
48
45
  client.write(response_header(status, headers))
49
46
  end
50
47
  write_body(client, body, range)
51
- end while env && hp.reset.nil?
48
+ end while alive
52
49
  # if we get any error, try to write something back to the client
53
50
  # assuming we haven't closed the socket, but don't get hung up
54
51
  # if the socket is already closed or broken. We'll always ensure
@@ -30,7 +30,7 @@ module Rainbows
30
30
  case rv = @_io.kgio_trywrite(buf)
31
31
  when nil
32
32
  return enable_write_watcher
33
- when Kgio::WaitWritable
33
+ when :wait_writable
34
34
  break # fall through to super(buf)
35
35
  when String
36
36
  buf = rv # retry, skb could grow or been drained
@@ -42,6 +42,19 @@ module Rainbows
42
42
  super(buf)
43
43
  end
44
44
 
45
+ def on_readable
46
+ buf = @_io.kgio_tryread(16384)
47
+ case buf
48
+ when :wait_readable
49
+ when nil # eof
50
+ close
51
+ else
52
+ on_read buf
53
+ end
54
+ rescue Errno::ECONNRESET
55
+ close
56
+ end
57
+
45
58
  # queued, optional response bodies, it should only be unpollable "fast"
46
59
  # devices where read(2) is uninterruptable. Unfortunately, NFS and ilk
47
60
  # are also part of this. We'll also stick DeferredResponse bodies in
@@ -2,20 +2,23 @@
2
2
  # :enddoc:
3
3
  require 'rainbows/rev'
4
4
 
5
- class Rainbows::Rev::Master < Rev::AsyncWatcher
5
+ class Rainbows::Rev::Master < Rev::IOWatcher
6
6
 
7
7
  def initialize(queue)
8
- super()
8
+ @reader, @writer = Kgio::Pipe.new
9
+ super(@reader)
9
10
  @queue = queue
10
11
  end
11
12
 
12
13
  def <<(output)
13
14
  @queue << output
14
- signal
15
+ @writer.kgio_trywrite("\0")
15
16
  end
16
17
 
17
- def on_signal
18
- client, response = @queue.pop
19
- client.response_write(response)
18
+ def on_readable
19
+ if String === @reader.kgio_tryread(1)
20
+ client, response = @queue.pop
21
+ client.response_write(response)
22
+ end
20
23
  end
21
24
  end
@@ -43,6 +43,7 @@ module Rainbows::Revactor
43
43
  end
44
44
  hp = Unicorn::HttpParser.new
45
45
  buf = hp.buf
46
+ alive = false
46
47
 
47
48
  begin
48
49
  until env = hp.parse
@@ -51,7 +52,7 @@ module Rainbows::Revactor
51
52
 
52
53
  env[CLIENT_IO] = client
53
54
  env[RACK_INPUT] = 0 == hp.content_length ?
54
- NULL_IO : TeeInput.new(TeeSocket.new(client), hp)
55
+ NULL_IO : Unicorn::TeeInput.new(TeeSocket.new(client), hp)
55
56
  env[REMOTE_ADDR] = remote_addr
56
57
  status, headers, body = app.call(env.update(RACK_DEFAULTS))
57
58
 
@@ -64,12 +65,12 @@ module Rainbows::Revactor
64
65
  if hp.headers?
65
66
  headers = HH.new(headers)
66
67
  range = make_range!(env, status, headers) and status = range.shift
67
- env = hp.keepalive? && G.alive && G.kato > 0
68
- headers[CONNECTION] = env ? KEEP_ALIVE : CLOSE
68
+ alive = hp.next? && G.alive && G.kato > 0
69
+ headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE
69
70
  client.write(response_header(status, headers))
70
71
  end
71
72
  write_body(client, body, range)
72
- end while env && hp.reset.nil?
73
+ end while alive
73
74
  rescue ::Revactor::TCP::ReadError
74
75
  rescue => e
75
76
  Rainbows::Error.write(io, e)
@@ -1,25 +1,21 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
- module Rainbows::ReadTimeout
3
+ module Rainbows::TimedRead
4
4
  G = Rainbows::G # :nodoc:
5
5
 
6
- def wait_readable
6
+ def kgio_wait_readable
7
7
  IO.select([self], nil, nil, G.kato)
8
8
  end
9
9
 
10
10
  # used for reading headers (respecting keepalive_timeout)
11
- def read_timeout(buf = "")
11
+ def timed_read(buf)
12
12
  expire = nil
13
13
  begin
14
14
  case rv = kgio_tryread(16384, buf)
15
15
  when :wait_readable
16
- now = Time.now.to_f
17
- if expire
18
- now > expire and return
19
- else
20
- expire = now + G.kato
21
- end
22
- wait_readable
16
+ return if expire && expire < Time.now
17
+ expire ||= Time.now + G.kato
18
+ kgio_wait_readable
23
19
  else
24
20
  return rv
25
21
  end
@@ -42,6 +42,10 @@ module Rainbows
42
42
  to_io.kgio_trywrite(buf)
43
43
  end
44
44
 
45
+ def timed_read(buf)
46
+ to_io.timed_read(buf)
47
+ end
48
+
45
49
  def write(buf)
46
50
  q << [ to_io, buf ]
47
51
  end
@@ -47,6 +47,10 @@ module Rainbows
47
47
  to_io.kgio_trywrite(buf)
48
48
  end
49
49
 
50
+ def timed_read(buf)
51
+ to_io.timed_read(buf)
52
+ end
53
+
50
54
  def queue_writer
51
55
  # not using Thread.pass here because that spins the CPU during
52
56
  # I/O wait and will eat cycles from other worker processes.
data/rainbows.gemspec CHANGED
@@ -44,7 +44,7 @@ Gem::Specification.new do |s|
44
44
  s.add_dependency(%q<rack>, ['~> 1.1'])
45
45
 
46
46
  # we need Unicorn for the HTTP parser and process management
47
- s.add_dependency(%q<unicorn>, ["~> 2.0.0"])
47
+ s.add_dependency(%q<unicorn>, ["~> 3.0.0"])
48
48
  s.add_development_dependency(%q<isolate>, "~> 3.0.0")
49
49
 
50
50
  # optional runtime dependencies depending on configuration
@@ -7,11 +7,25 @@ app = lambda do |env|
7
7
  return [ 100, {}, [] ]
8
8
  digest = Digest::SHA1.new
9
9
  input = env['rack.input']
10
- if buf = input.read(rand(cap))
11
- begin
12
- raise "#{buf.size} > #{cap}" if buf.size > cap
13
- digest.update(buf)
14
- end while input.read(rand(cap), buf)
10
+ case env["PATH_INFO"]
11
+ when "/gets_read_mix"
12
+ warn "GETS_READ_MIX #{env['HTTP_TRANSFER_ENCODING'].inspect}"
13
+ if buf = input.gets
14
+ warn "input.rbuf: #{input.instance_variable_get(:@rbuf).inspect}"
15
+ begin
16
+ digest.update(buf)
17
+ warn "buf.size : #{buf.size}"
18
+ end while input.read(rand(cap), buf)
19
+ end
20
+ when "/each"
21
+ input.each { |buf| digest.update(buf) }
22
+ else
23
+ if buf = input.read(rand(cap))
24
+ begin
25
+ raise "#{buf.size} > #{cap}" if buf.size > cap
26
+ digest.update(buf)
27
+ end while input.read(rand(cap), buf)
28
+ end
15
29
  end
16
30
 
17
31
  [ 200, {'Content-Type' => 'text/plain'}, [ digest.hexdigest << "\n" ] ]
@@ -3,7 +3,7 @@
3
3
  test -r random_blob || die "random_blob required, run with 'make $0'"
4
4
  req_curl_chunked_upload_err_check
5
5
 
6
- t_plan 6 "rack.input client_max_body_size tiny"
6
+ t_plan 18 "rack.input client_max_body_size tiny"
7
7
 
8
8
  t_begin "setup and startup" && {
9
9
  rtmpfiles curl_out curl_err cmbs_config
@@ -21,6 +21,7 @@ t_begin "stops a regular request" && {
21
21
  http://$listen/ > $curl_out 2> $curl_err || > $ok
22
22
  dbgcat curl_err
23
23
  dbgcat curl_out
24
+ grep 413 $curl_err
24
25
  test -e $ok
25
26
  }
26
27
 
@@ -31,6 +32,7 @@ t_begin "stops a large chunked request" && {
31
32
  http://$listen/ > $curl_out 2> $curl_err || > $ok
32
33
  dbgcat curl_err
33
34
  dbgcat curl_out
35
+ grep 413 $curl_err
34
36
  test -e $ok
35
37
  }
36
38
 
@@ -56,6 +58,136 @@ t_begin "small size sha1 content-length ok" && {
56
58
  test "$(cat $curl_out)" = $blob_sha1
57
59
  }
58
60
 
61
+ t_begin "stops a regular request (gets_read_mix)" && {
62
+ rm -f $ok
63
+ dd if=/dev/zero bs=257 count=1 of=$tmp
64
+ curl -vsSf -T $tmp -H Expect: \
65
+ http://$listen/gets_read_mix > $curl_out 2> $curl_err || > $ok
66
+ dbgcat curl_err
67
+ dbgcat curl_out
68
+ grep 413 $curl_err
69
+ test -e $ok
70
+ }
71
+
72
+ t_begin "stops a large chunked request (gets_read_mix)" && {
73
+ rm -f $ok
74
+ dd if=/dev/zero bs=257 count=1 | \
75
+ curl -vsSf -T- -H Expect: \
76
+ http://$listen/gets_read_mix > $curl_out 2> $curl_err || > $ok
77
+ dbgcat curl_err
78
+ dbgcat curl_out
79
+ grep 413 $curl_err
80
+ test -e $ok
81
+ }
82
+
83
+ t_begin "stops a large line-based chunked request (gets_read_mix)" && {
84
+ rm -f $ok
85
+ </dev/null awk 'BEGIN{for(i=22;--i>=0;) print "hello world"}' | \
86
+ curl -vsSf -T- -H Expect: \
87
+ http://$listen/gets_read_mix > $curl_out 2> $curl_err || > $ok
88
+ dbgcat curl_err
89
+ dbgcat curl_out
90
+ grep 413 $curl_err
91
+ test -e $ok
92
+ }
93
+
94
+ t_begin "OK with line-based chunked request (gets_read_mix)" && {
95
+ rm -f $ok
96
+ </dev/null awk 'BEGIN{for(i=21;--i>=0;) print "hello world"}' | \
97
+ curl -vsSf -T- -H Expect: \
98
+ http://$listen/gets_read_mix > $curl_out 2> $curl_err
99
+ dbgcat curl_err
100
+ dbgcat curl_out
101
+ test x"$(cat $curl_out)" = x23eab3cebcbe22a0456c8462e3d3bb01ae761702
102
+ }
103
+
104
+ t_begin "small size sha1 chunked ok (gets_read_mix)" && {
105
+ blob_sha1=b376885ac8452b6cbf9ced81b1080bfd570d9b91
106
+ rm -f $ok
107
+ dd if=/dev/zero bs=256 count=1 | \
108
+ curl -vsSf -T- -H Expect: \
109
+ http://$listen/gets_read_mix > $curl_out 2> $curl_err
110
+ dbgcat curl_err
111
+ dbgcat curl_out
112
+ test "$(cat $curl_out)" = $blob_sha1
113
+ }
114
+
115
+ t_begin "small size sha1 content-length ok (gets_read_mix)" && {
116
+ blob_sha1=b376885ac8452b6cbf9ced81b1080bfd570d9b91
117
+ rm -f $ok
118
+ dd if=/dev/zero bs=256 count=1 of=$tmp
119
+ curl -vsSf -T $tmp -H Expect: \
120
+ http://$listen/gets_read_mix > $curl_out 2> $curl_err
121
+ dbgcat curl_err
122
+ dbgcat curl_out
123
+ test "$(cat $curl_out)" = $blob_sha1
124
+ }
125
+
126
+ t_begin "stops a regular request (each)" && {
127
+ rm -f $ok
128
+ dd if=/dev/zero bs=257 count=1 of=$tmp
129
+ curl -vsSf -T $tmp -H Expect: \
130
+ http://$listen/each > $curl_out 2> $curl_err || > $ok
131
+ dbgcat curl_err
132
+ dbgcat curl_out
133
+ grep 413 $curl_err
134
+ test -e $ok
135
+ }
136
+
137
+ t_begin "stops a large chunked request (each)" && {
138
+ rm -f $ok
139
+ dd if=/dev/zero bs=257 count=1 | \
140
+ curl -vsSf -T- -H Expect: \
141
+ http://$listen/each > $curl_out 2> $curl_err || > $ok
142
+ dbgcat curl_err
143
+ dbgcat curl_out
144
+ grep 413 $curl_err
145
+ test -e $ok
146
+ }
147
+
148
+ t_begin "small size sha1 chunked ok (each)" && {
149
+ blob_sha1=b376885ac8452b6cbf9ced81b1080bfd570d9b91
150
+ rm -f $ok
151
+ dd if=/dev/zero bs=256 count=1 | \
152
+ curl -vsSf -T- -H Expect: \
153
+ http://$listen/each > $curl_out 2> $curl_err
154
+ dbgcat curl_err
155
+ dbgcat curl_out
156
+ test "$(cat $curl_out)" = $blob_sha1
157
+ }
158
+
159
+ t_begin "small size sha1 content-length ok (each)" && {
160
+ blob_sha1=b376885ac8452b6cbf9ced81b1080bfd570d9b91
161
+ rm -f $ok
162
+ dd if=/dev/zero bs=256 count=1 of=$tmp
163
+ curl -vsSf -T $tmp -H Expect: \
164
+ http://$listen/each > $curl_out 2> $curl_err
165
+ dbgcat curl_err
166
+ dbgcat curl_out
167
+ test "$(cat $curl_out)" = $blob_sha1
168
+ }
169
+
170
+ t_begin "stops a large line-based chunked request (each)" && {
171
+ rm -f $ok
172
+ </dev/null awk 'BEGIN{for(i=22;--i>=0;) print "hello world"}' | \
173
+ curl -vsSf -T- -H Expect: \
174
+ http://$listen/each > $curl_out 2> $curl_err || > $ok
175
+ dbgcat curl_err
176
+ dbgcat curl_out
177
+ grep 413 $curl_err
178
+ test -e $ok
179
+ }
180
+
181
+ t_begin "OK with line-based chunked request (each)" && {
182
+ rm -f $ok
183
+ </dev/null awk 'BEGIN{for(i=21;--i>=0;) print "hello world"}' | \
184
+ curl -vsSf -T- -H Expect: \
185
+ http://$listen/each > $curl_out 2> $curl_err
186
+ dbgcat curl_err
187
+ dbgcat curl_out
188
+ test x"$(cat $curl_out)" = x23eab3cebcbe22a0456c8462e3d3bb01ae761702
189
+ }
190
+
59
191
  t_begin "shutdown" && {
60
192
  kill $rainbows_pid
61
193
  }
@@ -22,6 +22,7 @@ t_begin "stops a regular request" && {
22
22
  rm -f $tmp
23
23
  dbgcat curl_err
24
24
  dbgcat curl_out
25
+ grep 413 $curl_err
25
26
  test -e $ok
26
27
  }
27
28
 
@@ -32,6 +33,7 @@ t_begin "stops a large chunked request" && {
32
33
  http://$listen/ > $curl_out 2> $curl_err || > $ok
33
34
  dbgcat curl_err
34
35
  dbgcat curl_out
36
+ grep 413 $curl_err
35
37
  test -e $ok
36
38
  }
37
39
 
data/t/test_isolate.rb CHANGED
@@ -15,8 +15,8 @@ $stdout.reopen($stderr)
15
15
 
16
16
  Isolate.now!(opts) do
17
17
  gem 'rack', '1.1.0' # Cramp currently requires ~> 1.1.0
18
- gem 'kgio', '1.3.1'
19
- gem 'unicorn', '2.0.0'
18
+ gem 'kgio', '2.0.0'
19
+ gem 'unicorn', '3.0.0'
20
20
  gem 'kcar', '0.1.1'
21
21
 
22
22
  if engine == "ruby"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rainbows
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
- - 1
7
+ - 2
8
8
  - 0
9
9
  - 0
10
- version: 1.0.0
10
+ version: 2.0.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Rainbows! hackers
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-10-28 00:00:00 +00:00
18
+ date: 2010-11-20 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -41,12 +41,12 @@ dependencies:
41
41
  requirements:
42
42
  - - ~>
43
43
  - !ruby/object:Gem::Version
44
- hash: 15
44
+ hash: 7
45
45
  segments:
46
- - 2
46
+ - 3
47
47
  - 0
48
48
  - 0
49
- version: 2.0.0
49
+ version: 3.0.0
50
50
  type: :runtime
51
51
  version_requirements: *id002
52
52
  - !ruby/object:Gem::Dependency
@@ -112,11 +112,12 @@ extra_rdoc_files:
112
112
  - lib/rainbows/http_response.rb
113
113
  - lib/rainbows/http_server.rb
114
114
  - lib/rainbows/max_body.rb
115
+ - lib/rainbows/max_body/rewindable_wrapper.rb
116
+ - lib/rainbows/max_body/wrapper.rb
115
117
  - lib/rainbows/never_block.rb
116
118
  - lib/rainbows/never_block/event_machine.rb
117
119
  - lib/rainbows/process_client.rb
118
120
  - lib/rainbows/queue_pool.rb
119
- - lib/rainbows/read_timeout.rb
120
121
  - lib/rainbows/response.rb
121
122
  - lib/rainbows/response/body.rb
122
123
  - lib/rainbows/response/range.rb
@@ -138,10 +139,10 @@ extra_rdoc_files:
138
139
  - lib/rainbows/sendfile.rb
139
140
  - lib/rainbows/server_token.rb
140
141
  - lib/rainbows/stream_file.rb
141
- - lib/rainbows/tee_input.rb
142
142
  - lib/rainbows/thread_pool.rb
143
143
  - lib/rainbows/thread_spawn.rb
144
144
  - lib/rainbows/thread_timeout.rb
145
+ - lib/rainbows/timed_read.rb
145
146
  - lib/rainbows/writer_thread_pool.rb
146
147
  - lib/rainbows/writer_thread_spawn.rb
147
148
  - LICENSE
@@ -215,11 +216,12 @@ files:
215
216
  - lib/rainbows/http_response.rb
216
217
  - lib/rainbows/http_server.rb
217
218
  - lib/rainbows/max_body.rb
219
+ - lib/rainbows/max_body/rewindable_wrapper.rb
220
+ - lib/rainbows/max_body/wrapper.rb
218
221
  - lib/rainbows/never_block.rb
219
222
  - lib/rainbows/never_block/event_machine.rb
220
223
  - lib/rainbows/process_client.rb
221
224
  - lib/rainbows/queue_pool.rb
222
- - lib/rainbows/read_timeout.rb
223
225
  - lib/rainbows/response.rb
224
226
  - lib/rainbows/response/body.rb
225
227
  - lib/rainbows/response/range.rb
@@ -241,10 +243,10 @@ files:
241
243
  - lib/rainbows/sendfile.rb
242
244
  - lib/rainbows/server_token.rb
243
245
  - lib/rainbows/stream_file.rb
244
- - lib/rainbows/tee_input.rb
245
246
  - lib/rainbows/thread_pool.rb
246
247
  - lib/rainbows/thread_spawn.rb
247
248
  - lib/rainbows/thread_timeout.rb
249
+ - lib/rainbows/timed_read.rb
248
250
  - lib/rainbows/writer_thread_pool.rb
249
251
  - lib/rainbows/writer_thread_spawn.rb
250
252
  - local.mk.sample
@@ -1,18 +0,0 @@
1
- # -*- encoding: binary -*-
2
- # :enddoc:
3
- module Rainbows
4
-
5
- # acts like tee(1) on an input input to provide a input-like stream
6
- # while providing rewindable semantics through a File/StringIO
7
- # backing store. On the first pass, the input is only read on demand
8
- # so your Rack application can use input notification (upload progress
9
- # and like). This should fully conform to the Rack::InputWrapper
10
- # specification on the public API. This class is intended to be a
11
- # strict interpretation of Rack::InputWrapper functionality and will
12
- # not support any deviations from it.
13
- class TeeInput < Unicorn::TeeInput
14
-
15
- # empty class, this is to avoid unecessarily modifying Unicorn::TeeInput
16
- # when MaxBody::Limit is included
17
- end
18
- end