rainbows 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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