rainbows 0.97.0 → 1.0.0pre1

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.
Files changed (55) hide show
  1. data/.manifest +14 -2
  2. data/ChangeLog +87 -118
  3. data/GIT-VERSION-FILE +1 -1
  4. data/GIT-VERSION-GEN +1 -1
  5. data/GNUmakefile +1 -1
  6. data/README +1 -1
  7. data/bin/rainbows +15 -20
  8. data/lib/rainbows/actor_spawn.rb +20 -22
  9. data/lib/rainbows/app_pool.rb +89 -93
  10. data/lib/rainbows/base.rb +4 -61
  11. data/lib/rainbows/client.rb +9 -0
  12. data/lib/rainbows/configurator.rb +37 -39
  13. data/lib/rainbows/const.rb +18 -18
  14. data/lib/rainbows/dev_fd_response.rb +2 -1
  15. data/lib/rainbows/error.rb +39 -37
  16. data/lib/rainbows/ev_core.rb +103 -109
  17. data/lib/rainbows/event_machine.rb +188 -196
  18. data/lib/rainbows/fiber/base.rb +69 -88
  19. data/lib/rainbows/fiber/io/compat.rb +13 -0
  20. data/lib/rainbows/fiber/io/methods.rb +49 -0
  21. data/lib/rainbows/fiber/io/pipe.rb +7 -0
  22. data/lib/rainbows/fiber/io/socket.rb +7 -0
  23. data/lib/rainbows/fiber/io.rb +125 -84
  24. data/lib/rainbows/fiber/rev/heartbeat.rb +8 -0
  25. data/lib/rainbows/fiber/rev/kato.rb +22 -0
  26. data/lib/rainbows/fiber/rev/methods.rb +55 -0
  27. data/lib/rainbows/fiber/rev/server.rb +32 -0
  28. data/lib/rainbows/fiber/rev/sleeper.rb +15 -0
  29. data/lib/rainbows/fiber/rev.rb +6 -164
  30. data/lib/rainbows/fiber.rb +23 -5
  31. data/lib/rainbows/fiber_pool.rb +31 -37
  32. data/lib/rainbows/fiber_spawn.rb +21 -28
  33. data/lib/rainbows/http_server.rb +80 -80
  34. data/lib/rainbows/max_body.rb +26 -28
  35. data/lib/rainbows/process_client.rb +61 -0
  36. data/lib/rainbows/queue_pool.rb +19 -22
  37. data/lib/rainbows/read_timeout.rb +28 -0
  38. data/lib/rainbows/rev/client.rb +10 -10
  39. data/lib/rainbows/rev/core.rb +2 -3
  40. data/lib/rainbows/rev/thread.rb +1 -1
  41. data/lib/rainbows/rev_fiber_spawn.rb +21 -24
  42. data/lib/rainbows/revactor.rb +18 -15
  43. data/lib/rainbows/thread_pool.rb +2 -4
  44. data/lib/rainbows/thread_spawn.rb +1 -2
  45. data/lib/rainbows/writer_thread_pool.rb +14 -4
  46. data/lib/rainbows/writer_thread_spawn.rb +14 -4
  47. data/lib/rainbows.rb +7 -15
  48. data/local.mk.sample +3 -11
  49. data/rainbows.gemspec +2 -4
  50. data/t/kgio-pipe-response.ru +10 -0
  51. data/t/t0035-kgio-pipe-response.sh +70 -0
  52. data/t/test_isolate.rb +2 -1
  53. metadata +46 -30
  54. data/lib/rainbows/acceptor.rb +0 -26
  55. data/lib/rainbows/byte_slice.rb +0 -17
@@ -4,168 +4,10 @@ require 'rev'
4
4
  require 'rainbows/fiber'
5
5
  require 'rainbows/fiber/io'
6
6
 
7
- module Rainbows::Fiber
8
- module Rev
9
- G = Rainbows::G
10
-
11
- # keep-alive timeout class
12
- class Kato < ::Rev::TimerWatcher
13
- def initialize
14
- @watch = []
15
- super(1, true)
16
- end
17
-
18
- def <<(fiber)
19
- @watch << fiber
20
- enable unless enabled?
21
- end
22
-
23
- def on_timer
24
- @watch.uniq!
25
- while f = @watch.shift
26
- f.resume if f.alive?
27
- end
28
- disable
29
- end
30
- end
31
-
32
- class Heartbeat < ::Rev::TimerWatcher
33
- def on_timer
34
- exit if (! G.tick && G.cur <= 0)
35
- end
36
- end
37
-
38
- class Sleeper < ::Rev::TimerWatcher
39
-
40
- def initialize(seconds)
41
- @f = ::Fiber.current
42
- super(seconds, false)
43
- attach(::Rev::Loop.default)
44
- ::Fiber.yield
45
- end
46
-
47
- def on_timer
48
- @f.resume
49
- end
50
- end
51
-
52
- class Server < ::Rev::IOWatcher
53
- include Unicorn
54
- include Rainbows
55
- include Rainbows::Const
56
- include Rainbows::Response
57
- include Rainbows::Acceptor
58
- FIO = Rainbows::Fiber::IO
59
-
60
- def to_io
61
- @io
62
- end
63
-
64
- def initialize(io)
65
- @io = io
66
- super(self, :r)
67
- end
68
-
69
- def close
70
- detach if attached?
71
- @io.close
72
- end
73
-
74
- def on_readable
75
- return if G.cur >= MAX
76
- c = accept(@io) and ::Fiber.new { process(c) }.resume
77
- end
78
-
79
- def process(io)
80
- G.cur += 1
81
- client = FIO.new(io, ::Fiber.current)
82
- buf = client.read_timeout or return
83
- hp = HttpParser.new
84
- env = {}
85
- remote_addr = Rainbows.addr(io)
86
-
87
- begin # loop
88
- buf << (client.read_timeout or return) until hp.headers(env, buf)
89
-
90
- env[CLIENT_IO] = client
91
- env[RACK_INPUT] = 0 == hp.content_length ?
92
- HttpRequest::NULL_IO : TeeInput.new(client, env, hp, buf)
93
- env[REMOTE_ADDR] = remote_addr
94
- status, headers, body = APP.call(env.update(RACK_DEFAULTS))
95
-
96
- if 100 == status.to_i
97
- client.write(EXPECT_100_RESPONSE)
98
- env.delete(HTTP_EXPECT)
99
- status, headers, body = APP.call(env)
100
- end
101
-
102
- if hp.headers?
103
- headers = HH.new(headers)
104
- range = make_range!(env, status, headers) and status = range.shift
105
- headers[CONNECTION] = if hp.keepalive? && G.alive
106
- KEEP_ALIVE
107
- else
108
- env = false
109
- CLOSE
110
- end
111
- client.write(response_header(status, headers))
112
- end
113
- write_body(client, body, range)
114
- end while env && env.clear && hp.reset.nil?
115
- rescue => e
116
- Error.write(io, e)
117
- ensure
118
- G.cur -= 1
119
- client.close
120
- end
121
- end
122
- end
123
-
124
- class IO # see rainbows/fiber/io for original definition
125
-
126
- class Watcher < ::Rev::IOWatcher
127
- def initialize(fio, flag)
128
- @fiber = fio.f
129
- super(fio, flag)
130
- attach(::Rev::Loop.default)
131
- end
132
-
133
- def on_readable
134
- @fiber.resume
135
- end
136
-
137
- alias on_writable on_readable
138
- end
139
-
140
- undef_method :wait_readable
141
- undef_method :wait_writable
142
- undef_method :close
143
-
144
- def initialize(*args)
145
- super
146
- @r = @w = false
147
- end
148
-
149
- def close
150
- @w.detach if @w
151
- @r.detach if @r
152
- @r = @w = false
153
- to_io.close unless to_io.closed?
154
- end
155
-
156
- def wait_writable
157
- @w ||= Watcher.new(self, :w)
158
- @w.enable unless @w.enabled?
159
- ::Fiber.yield
160
- @w.disable
161
- end
162
-
163
- def wait_readable
164
- @r ||= Watcher.new(self, :r)
165
- @r.enable unless @r.enabled?
166
- KATO << f
167
- ::Fiber.yield
168
- @r.disable
169
- end
170
- end
7
+ module Rainbows::Fiber::Rev
8
+ autoload :Heartbeat, 'rainbows/fiber/rev/heartbeat'
9
+ autoload :Kato, 'rainbows/fiber/rev/kato'
10
+ autoload :Server, 'rainbows/fiber/rev/server'
11
+ autoload :Sleeper, 'rainbows/fiber/rev/sleeper'
171
12
  end
13
+ require 'rainbows/fiber/rev/methods'
@@ -6,11 +6,29 @@ rescue LoadError
6
6
  defined?(NeverBlock) or raise
7
7
  end
8
8
 
9
- module Rainbows
9
+ # core module for all things that use Fibers in Rainbows!
10
+ module Rainbows::Fiber
10
11
 
11
- # core module for all things that use Fibers in Rainbows!
12
- module Fiber
13
- autoload :Base, 'rainbows/fiber/base'
14
- autoload :Queue, 'rainbows/fiber/queue'
12
+ # blocked readers (key: fileno, value: Rainbows::Fiber::IO object)
13
+ RD = []
14
+
15
+ # blocked writers (key: fileno, value: Rainbows::Fiber::IO object)
16
+ WR = []
17
+
18
+ # sleeping fibers go here (key: Fiber object, value: wakeup time)
19
+ ZZ = {}
20
+
21
+ # puts the current Fiber into uninterruptible sleep for at least
22
+ # +seconds+. Unlike Kernel#sleep, this it is not possible to sleep
23
+ # indefinitely to be woken up (nobody wants that in a web server,
24
+ # right?). Calling this directly is deprecated, use
25
+ # Rainbows.sleep(seconds) instead.
26
+ def self.sleep(seconds)
27
+ ZZ[::Fiber.current] = Time.now + seconds
28
+ ::Fiber.yield
15
29
  end
30
+
31
+ autoload :Base, 'rainbows/fiber/base'
32
+ autoload :Queue, 'rainbows/fiber/queue'
33
+ autoload :IO, 'rainbows/fiber/io'
16
34
  end
@@ -1,45 +1,39 @@
1
1
  # -*- encoding: binary -*-
2
2
  require 'rainbows/fiber'
3
3
 
4
- module Rainbows
4
+ # A Fiber-based concurrency model for Ruby 1.9. This uses a pool of
5
+ # Fibers to handle client IO to run the application and the root Fiber
6
+ # for scheduling and connection acceptance. The pool size is equal to
7
+ # the number of +worker_connections+. Compared to the ThreadPool
8
+ # model, Fibers are very cheap in terms of memory usage so you can
9
+ # have more active connections. This model supports a streaming
10
+ # "rack.input" with lightweight concurrency. Applications are
11
+ # strongly advised to wrap all slow IO objects (sockets, pipes) using
12
+ # the Rainbows::Fiber::IO class whenever possible.
13
+ module Rainbows::FiberPool
14
+ include Rainbows::Fiber::Base
5
15
 
6
- # A Fiber-based concurrency model for Ruby 1.9. This uses a pool of
7
- # Fibers to handle client IO to run the application and the root Fiber
8
- # for scheduling and connection acceptance. The pool size is equal to
9
- # the number of +worker_connections+. Compared to the ThreadPool
10
- # model, Fibers are very cheap in terms of memory usage so you can
11
- # have more active connections. This model supports a streaming
12
- # "rack.input" with lightweight concurrency. Applications are
13
- # strongly advised to wrap all slow IO objects (sockets, pipes) using
14
- # the Rainbows::Fiber::IO class whenever possible.
16
+ def worker_loop(worker) # :nodoc:
17
+ init_worker_process(worker)
18
+ pool = []
19
+ worker_connections.times {
20
+ Fiber.new {
21
+ process(Fiber.yield) while pool << Fiber.current
22
+ }.resume # resume to hit ::Fiber.yield so it waits on a client
23
+ }
24
+ Rainbows::Fiber::Base.setup(self.class, app)
15
25
 
16
- module FiberPool
17
- include Fiber::Base
18
- include Rainbows::Acceptor
19
-
20
- def worker_loop(worker) # :nodoc:
21
- init_worker_process(worker)
22
- pool = []
23
- worker_connections.times {
24
- ::Fiber.new {
25
- process_client(::Fiber.yield) while pool << ::Fiber.current
26
- }.resume # resume to hit ::Fiber.yield so it waits on a client
27
- }
28
- Fiber::Base.setup(self.class, app)
29
-
30
- begin
31
- schedule do |l|
32
- fib = pool.shift or break # let another worker process take it
33
- if io = accept(l)
34
- fib.resume(Fiber::IO.new(io, fib))
35
- else
36
- pool << fib
37
- end
26
+ begin
27
+ schedule do |l|
28
+ fib = pool.shift or break # let another worker process take it
29
+ if io = l.kgio_tryaccept
30
+ fib.resume(io)
31
+ else
32
+ pool << fib
38
33
  end
39
- rescue => e
40
- Error.listen_loop(e)
41
- end while G.alive || G.cur > 0
42
- end
43
-
34
+ end
35
+ rescue => e
36
+ Rainbows::Error.listen_loop(e)
37
+ end while G.alive || G.cur > 0
44
38
  end
45
39
  end
@@ -1,35 +1,28 @@
1
1
  # -*- encoding: binary -*-
2
2
  require 'rainbows/fiber'
3
3
 
4
- module Rainbows
4
+ # Simple Fiber-based concurrency model for 1.9. This spawns a new
5
+ # Fiber for every incoming client connection and the root Fiber for
6
+ # scheduling and connection acceptance. This exports a streaming
7
+ # "rack.input" with lightweight concurrency. Applications are
8
+ # strongly advised to wrap all slow IO objects (sockets, pipes) using
9
+ # the Rainbows::Fiber::IO class whenever possible.
10
+ module Rainbows::FiberSpawn
11
+ include Rainbows::Fiber::Base
5
12
 
6
- # Simple Fiber-based concurrency model for 1.9. This spawns a new
7
- # Fiber for every incoming client connection and the root Fiber for
8
- # scheduling and connection acceptance. This exports a streaming
9
- # "rack.input" with lightweight concurrency. Applications are
10
- # strongly advised to wrap all slow IO objects (sockets, pipes) using
11
- # the Rainbows::Fiber::IO class whenever possible.
12
-
13
- module FiberSpawn
14
- include Fiber::Base
15
- include Rainbows::Acceptor
16
-
17
- def worker_loop(worker) # :nodoc:
18
- init_worker_process(worker)
19
- Fiber::Base.setup(self.class, app)
20
- limit = worker_connections
21
- fio = Rainbows::Fiber::IO
22
-
23
- begin
24
- schedule do |l|
25
- break if G.cur >= limit
26
- io = accept(l) or next
27
- ::Fiber.new { process_client(fio.new(io, ::Fiber.current)) }.resume
28
- end
29
- rescue => e
30
- Error.listen_loop(e)
31
- end while G.alive || G.cur > 0
32
- end
13
+ def worker_loop(worker) # :nodoc:
14
+ init_worker_process(worker)
15
+ Rainbows::Fiber::Base.setup(self.class, app)
16
+ limit = worker_connections
33
17
 
18
+ begin
19
+ schedule do |l|
20
+ break if G.cur >= limit
21
+ io = l.kgio_tryaccept or next
22
+ Fiber.new { process(io) }.resume
23
+ end
24
+ rescue => e
25
+ Rainbows::Error.listen_loop(e)
26
+ end while G.alive || G.cur > 0
34
27
  end
35
28
  end
@@ -1,97 +1,97 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
- module Rainbows
4
3
 
5
- class HttpServer < ::Unicorn::HttpServer
6
- include Rainbows
4
+ class Rainbows::HttpServer < Unicorn::HttpServer
5
+ G = Rainbows::G
7
6
 
8
- class << self
9
- def setup(block)
10
- G.server.instance_eval(&block)
11
- end
12
- end
13
-
14
- def initialize(app, options)
15
- G.server = self
16
- rv = super(app, options)
17
- defined?(@use) or use(:Base)
18
- @worker_connections ||= MODEL_WORKER_CONNECTIONS[@use]
19
- end
7
+ def self.setup(block)
8
+ G.server.instance_eval(&block)
9
+ end
20
10
 
21
- def reopen_worker_logs(worker_nr)
22
- logger.info "worker=#{worker_nr} reopening logs..."
23
- Unicorn::Util.reopen_logs
24
- logger.info "worker=#{worker_nr} done reopening logs"
25
- rescue
26
- G.quit! # let the master reopen and refork us
27
- end
11
+ def initialize(app, options)
12
+ G.server = self
13
+ @logger = Unicorn::Configurator::DEFAULTS[:logger]
14
+ rv = super(app, options)
15
+ defined?(@use) or use(:Base)
16
+ @worker_connections ||= Rainbows::MODEL_WORKER_CONNECTIONS[@use]
17
+ end
28
18
 
29
- #:stopdoc:
30
- #
31
- # Add one second to the timeout since our fchmod heartbeat is less
32
- # precise (and must be more conservative) than Unicorn does. We
33
- # handle many clients per process and can't chmod on every
34
- # connection we accept without wasting cycles. That added to the
35
- # fact that we let clients keep idle connections open for long
36
- # periods of time means we have to chmod at a fixed interval.
37
- def timeout=(nr)
38
- super(nr + 1)
39
- end
40
- #:startdoc:
19
+ def reopen_worker_logs(worker_nr)
20
+ logger.info "worker=#{worker_nr} reopening logs..."
21
+ Unicorn::Util.reopen_logs
22
+ logger.info "worker=#{worker_nr} done reopening logs"
23
+ rescue
24
+ G.quit! # let the master reopen and refork us
25
+ end
41
26
 
42
- def use(*args)
43
- model = args.shift or return @use
44
- mod = begin
45
- Rainbows.const_get(model)
46
- rescue NameError
47
- raise ArgumentError, "concurrency model #{model.inspect} not supported"
48
- end
27
+ #:stopdoc:
28
+ #
29
+ # Add one second to the timeout since our fchmod heartbeat is less
30
+ # precise (and must be more conservative) than Unicorn does. We
31
+ # handle many clients per process and can't chmod on every
32
+ # connection we accept without wasting cycles. That added to the
33
+ # fact that we let clients keep idle connections open for long
34
+ # periods of time means we have to chmod at a fixed interval.
35
+ def timeout=(nr)
36
+ @timeout = nr + 1
37
+ end
38
+ #:startdoc:
49
39
 
50
- Module === mod or
51
- raise ArgumentError, "concurrency model #{model.inspect} not supported"
52
- extend(mod)
53
- args.each do |opt|
54
- case opt
55
- when Hash; O.update(opt)
56
- when Symbol; O[opt] = true
57
- else; raise ArgumentError, "can't handle option: #{opt.inspect}"
58
- end
59
- end
60
- mod.setup if mod.respond_to?(:setup)
61
- Const::RACK_DEFAULTS['rainbows.model'] = @use = model.to_sym
62
- Const::RACK_DEFAULTS['rack.multithread'] = !!(model.to_s =~ /Thread/)
40
+ def use(*args)
41
+ model = args.shift or return @use
42
+ mod = begin
43
+ Rainbows.const_get(model)
44
+ rescue NameError => e
45
+ logger.error "error loading #{model.inspect}: #{e}"
46
+ e.backtrace.each { |l| logger.error l }
47
+ raise ArgumentError, "concurrency model #{model.inspect} not supported"
48
+ end
63
49
 
64
- case @use
65
- when :Rev, :EventMachine, :NeverBlock
66
- Const::RACK_DEFAULTS['rainbows.autochunk'] = true
50
+ Module === mod or
51
+ raise ArgumentError, "concurrency model #{model.inspect} not supported"
52
+ extend(mod)
53
+ args.each do |opt|
54
+ case opt
55
+ when Hash; O.update(opt)
56
+ when Symbol; O[opt] = true
57
+ else; raise ArgumentError, "can't handle option: #{opt.inspect}"
67
58
  end
68
59
  end
69
-
70
- def worker_connections(*args)
71
- return @worker_connections if args.empty?
72
- nr = args[0]
73
- (Integer === nr && nr > 0) or
74
- raise ArgumentError, "worker_connections must be a positive Integer"
75
- @worker_connections = nr
60
+ mod.setup if mod.respond_to?(:setup)
61
+ new_defaults = {
62
+ 'rainbows.model' => (@use = model.to_sym),
63
+ 'rack.multithread' => !!(model.to_s =~ /Thread/),
64
+ }
65
+ case @use
66
+ when :Rev, :EventMachine, :NeverBlock
67
+ new_defaults['rainbows.autochunk'] = true
76
68
  end
69
+ Rainbows::Const::RACK_DEFAULTS.update(new_defaults)
70
+ end
77
71
 
78
- def keepalive_timeout(nr)
79
- (Integer === nr && nr >= 0) or
80
- raise ArgumentError, "keepalive must be a non-negative Integer"
81
- G.kato = nr
82
- end
72
+ def worker_connections(*args)
73
+ return @worker_connections if args.empty?
74
+ nr = args[0]
75
+ (Integer === nr && nr > 0) or
76
+ raise ArgumentError, "worker_connections must be a positive Integer"
77
+ @worker_connections = nr
78
+ end
83
79
 
84
- def client_max_body_size(nr)
85
- err = "client_max_body_size must be nil or a non-negative Integer"
86
- case nr
87
- when nil
88
- when Integer
89
- nr >= 0 or raise ArgumentError, err
90
- else
91
- raise ArgumentError, err
92
- end
93
- Rainbows.max_bytes = nr
94
- end
80
+ def keepalive_timeout(nr)
81
+ (Integer === nr && nr >= 0) or
82
+ raise ArgumentError, "keepalive must be a non-negative Integer"
83
+ G.kato = nr
95
84
  end
96
85
 
86
+ def client_max_body_size(nr)
87
+ err = "client_max_body_size must be nil or a non-negative Integer"
88
+ case nr
89
+ when nil
90
+ when Integer
91
+ nr >= 0 or raise ArgumentError, err
92
+ else
93
+ raise ArgumentError, err
94
+ end
95
+ Rainbows.max_bytes = nr
96
+ end
97
97
  end
@@ -1,42 +1,42 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
- module Rainbows
4
3
 
5
4
  # middleware used to enforce client_max_body_size for TeeInput users,
6
5
  # there is no need to configure this middleware manually, it will
7
6
  # automatically be configured for you based on the client_max_body_size
8
7
  # setting
9
- class MaxBody < Struct.new(:app)
8
+ class Rainbows::MaxBody < Struct.new(:app)
10
9
 
11
10
  # this is meant to be included in Rainbows::TeeInput (and derived
12
11
  # classes) to limit body sizes
13
12
  module Limit
14
- Util = Unicorn::Util
13
+ TmpIO = Unicorn::TmpIO
14
+ MAX_BODY = Rainbows::Const::MAX_BODY
15
15
 
16
- def initialize(socket, req, parser, buf)
17
- self.len = parser.content_length
16
+ def initialize(socket, request)
17
+ @parser = request
18
+ @buf = request.buf
19
+ @env = request.env
20
+ @len = request.content_length
18
21
 
19
22
  max = Rainbows.max_bytes # never nil, see MaxBody.setup
20
- if len && len > max
21
- socket.write(Const::ERROR_413_RESPONSE)
23
+ if @len && @len > max
24
+ socket.write(Rainbows::Const::ERROR_413_RESPONSE)
22
25
  socket.close
23
- raise IOError, "Content-Length too big: #{len} > #{max}", []
26
+ raise IOError, "Content-Length too big: #@len > #{max}", []
24
27
  end
25
28
 
26
- self.req = req
27
- self.parser = parser
28
- self.buf = buf
29
- self.socket = socket
30
- self.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", []
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
34
  end
35
- self.tmp = len && len < Const::MAX_BODY ? StringIO.new("") : Util.tmpio
36
- if buf2.size > 0
37
- tmp.write(buf2)
38
- tmp.seek(0)
39
- max -= buf2.size
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
40
  end
41
41
  @max_body = max
42
42
  end
@@ -46,7 +46,7 @@ class MaxBody < Struct.new(:app)
46
46
  if rv && ((@max_body -= rv.size) < 0)
47
47
  # make HttpParser#keepalive? => false to force an immediate disconnect
48
48
  # after we write
49
- parser.reset
49
+ @parser.reset
50
50
  throw :rainbows_EFBIG
51
51
  end
52
52
  rv
@@ -58,15 +58,15 @@ class MaxBody < Struct.new(:app)
58
58
  # if it's reconfigured
59
59
  def self.setup
60
60
  Rainbows.max_bytes or return
61
- case G.server.use
61
+ case Rainbows::G.server.use
62
62
  when :Rev, :EventMachine, :NeverBlock
63
63
  return
64
64
  end
65
65
 
66
- TeeInput.class_eval { include Limit }
66
+ Rainbows::TeeInput.__send__(:include, Limit)
67
67
 
68
68
  # force ourselves to the outermost middleware layer
69
- G.server.app = MaxBody.new(G.server.app)
69
+ Rainbows::G.server.app = self.new(Rainbows::G.server.app)
70
70
  end
71
71
 
72
72
  # Rack response returned when there's an error
@@ -78,6 +78,4 @@ class MaxBody < Struct.new(:app)
78
78
  def call(env)
79
79
  catch(:rainbows_EFBIG) { app.call(env) } || err(env)
80
80
  end
81
-
82
- end # class
83
- end # module
81
+ end
@@ -0,0 +1,61 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ module Rainbows::ProcessClient
4
+ G = Rainbows::G
5
+ include Rainbows::Response
6
+ HttpParser = Unicorn::HttpParser
7
+ NULL_IO = Unicorn::HttpRequest::NULL_IO
8
+ RACK_INPUT = Unicorn::HttpRequest::RACK_INPUT
9
+ TeeInput = Rainbows::TeeInput
10
+ include Rainbows::Const
11
+
12
+ def wait_headers_readable(client)
13
+ IO.select([client], nil, nil, G.kato)
14
+ end
15
+
16
+ # once a client is accepted, it is processed in its entirety here
17
+ # in 3 easy steps: read request, call app, write app response
18
+ # this is used by synchronous concurrency models
19
+ # Base, ThreadSpawn, ThreadPool
20
+ def process_client(client) # :nodoc:
21
+ hp = HttpParser.new
22
+ client.kgio_read!(16384, buf = hp.buf)
23
+ remote_addr = client.kgio_addr
24
+
25
+ begin # loop
26
+ until env = hp.parse
27
+ wait_headers_readable(client) or return
28
+ buf << client.kgio_read!(16384)
29
+ end
30
+
31
+ env[CLIENT_IO] = client
32
+ env[RACK_INPUT] = 0 == hp.content_length ?
33
+ NULL_IO : TeeInput.new(client, hp)
34
+ env[REMOTE_ADDR] = remote_addr
35
+ status, headers, body = APP.call(env.update(RACK_DEFAULTS))
36
+
37
+ if 100 == status.to_i
38
+ client.write(EXPECT_100_RESPONSE)
39
+ env.delete(HTTP_EXPECT)
40
+ status, headers, body = APP.call(env)
41
+ end
42
+
43
+ if hp.headers?
44
+ headers = HH.new(headers)
45
+ range = make_range!(env, status, headers) and status = range.shift
46
+ env = hp.keepalive? && G.alive
47
+ headers[CONNECTION] = env ? KEEP_ALIVE : CLOSE
48
+ client.write(response_header(status, headers))
49
+ end
50
+ write_body(client, body, range)
51
+ end while env && hp.reset.nil?
52
+ # if we get any error, try to write something back to the client
53
+ # assuming we haven't closed the socket, but don't get hung up
54
+ # if the socket is already closed or broken. We'll always ensure
55
+ # the socket is closed at the end of this function
56
+ rescue => e
57
+ Rainbows::Error.write(client, e)
58
+ ensure
59
+ client.close unless client.closed?
60
+ end
61
+ end