rainbows 0.91.1 → 0.92.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.
Files changed (47) hide show
  1. data/.document +1 -1
  2. data/.gitignore +1 -0
  3. data/.manifest +13 -4
  4. data/ChangeLog +237 -219
  5. data/DEPLOY +13 -13
  6. data/Documentation/comparison.haml +1 -1
  7. data/GIT-VERSION-FILE +1 -1
  8. data/GIT-VERSION-GEN +1 -1
  9. data/GNUmakefile +18 -7
  10. data/NEWS +29 -0
  11. data/README +5 -4
  12. data/Rakefile +7 -0
  13. data/SIGNALS +5 -1
  14. data/TODO +2 -5
  15. data/config/.gitignore +1 -0
  16. data/config/isolate.rb +25 -0
  17. data/lib/rainbows/base.rb +32 -8
  18. data/lib/rainbows/const.rb +3 -2
  19. data/lib/rainbows/ev_core.rb +42 -2
  20. data/lib/rainbows/event_machine.rb +48 -5
  21. data/lib/rainbows/fiber/base.rb +4 -2
  22. data/lib/rainbows/fiber/rev.rb +1 -1
  23. data/lib/rainbows/http_response.rb +21 -20
  24. data/lib/rainbows/http_server.rb +13 -6
  25. data/lib/rainbows/max_body.rb +82 -0
  26. data/lib/rainbows/rev/deferred_response.rb +10 -11
  27. data/lib/rainbows/revactor.rb +38 -4
  28. data/lib/rainbows/tee_input.rb +17 -0
  29. data/lib/rainbows.rb +26 -2
  30. data/local.mk.sample +13 -23
  31. data/rainbows.gemspec +2 -2
  32. data/t/app_deferred.ru +23 -0
  33. data/t/async_examples/async_app.ru +8 -8
  34. data/t/rack-fiber_pool/app.ru +5 -0
  35. data/t/{t0100-rack-input-hammer.sh → t0100-rack-input-hammer-chunked.sh} +1 -1
  36. data/t/t0100-rack-input-hammer-content-length.sh +50 -0
  37. data/t/t0103-rack-input-limit.sh +60 -0
  38. data/t/t0104-rack-input-limit-tiny.sh +62 -0
  39. data/t/t0105-rack-input-limit-bigger.sh +105 -0
  40. data/t/t0401-em-async-tailer.sh +1 -1
  41. data/t/t0600-rack-fiber_pool.sh +49 -0
  42. data/t/t0700-app-deferred.sh +45 -0
  43. data/t/test-lib.sh +1 -0
  44. metadata +22 -10
  45. data/lib/rainbows/event_machine_defer.rb +0 -59
  46. data/lib/rainbows/revactor/tee_input.rb +0 -52
  47. data/t/simple-http_EventMachineDefer.ru +0 -11
@@ -274,7 +274,7 @@
274
274
  %td.devfd Yes
275
275
  %td.app_pool no-op
276
276
  %td.lock no-op
277
- %td.async async_sinatra
277
+ %td.async async_sinatra, Cramp, rack-fiber_pool
278
278
  %td.ws no
279
279
  %tr.comp_row
280
280
  %td.mod RevThreadSpawn
data/GIT-VERSION-FILE CHANGED
@@ -1 +1 @@
1
- GIT_VERSION = 0.91.1
1
+ GIT_VERSION = 0.92.0
data/GIT-VERSION-GEN CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v0.91.1.GIT
4
+ DEF_VER=v0.92.0.GIT
5
5
 
6
6
  LF='
7
7
  '
data/GNUmakefile CHANGED
@@ -3,6 +3,7 @@ all::
3
3
  RUBY = ruby
4
4
  RAKE = rake
5
5
  GIT_URL = git://git.bogomips.org/rainbows.git
6
+ ISOLATE_CONFIG = config/isolate.rb
6
7
 
7
8
  GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
8
9
  @./GIT-VERSION-GEN
@@ -15,8 +16,15 @@ ifeq ($(RUBY_VERSION),)
15
16
  RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
16
17
  endif
17
18
 
19
+ # rake takes forever to start
20
+ isolate: tmp/gems/$(RUBY_VERSION)/.isolate
21
+ tmp/gems/$(RUBY_VERSION)/.isolate: $(ISOLATE_CONFIG)
22
+ ISOLATE_CONFIG=$(ISOLATE_CONFIG) $(RAKE) isolate
23
+ > $@
24
+
18
25
  base_bins := rainbows
19
26
  bins := $(addprefix bin/, $(base_bins))
27
+ man1_rdoc := $(addsuffix _1, $(base_bins))
20
28
  man1_bins := $(addsuffix .1, $(base_bins))
21
29
  man1_paths := $(addprefix man/man1/, $(man1_bins))
22
30
 
@@ -58,7 +66,7 @@ NEWS: GIT-VERSION-FILE
58
66
  $(RAKE) -s news_rdoc > $@+
59
67
  mv $@+ $@
60
68
 
61
- SINCE = 0.90.0
69
+ SINCE = 0.91.1
62
70
  ChangeLog: LOG_VERSION = \
63
71
  $(shell git rev-parse -q "$(GIT_VERSION)" >/dev/null 2>&1 && \
64
72
  echo $(GIT_VERSION) || git describe)
@@ -74,18 +82,21 @@ cgit_atom := http://git.bogomips.org/cgit/rainbows.git/atom/?h=master
74
82
  atom = <link rel="alternate" title="Atom feed" href="$(1)" \
75
83
  type="application/atom+xml"/>
76
84
 
77
- # using rdoc 2.4.1+
85
+ # using rdoc 2.5.x+
78
86
  doc: .document NEWS ChangeLog
79
- for i in $(man1_bins); do > $$i; done
87
+ for i in $(man1_rdoc); do echo > $$i; done
80
88
  find bin lib -type f -name '*.rbc' -exec rm -f '{}' ';'
81
- rdoc -Na -t "$(shell sed -ne '1s/^= //p' README)"
89
+ rdoc -a -t "$(shell sed -ne '1s/^= //p' README)"
82
90
  install -m644 COPYING doc/COPYING
83
91
  install -m644 $(shell grep '^[A-Z]' .document) doc/
84
92
  $(MAKE) -C Documentation install-html install-man
85
93
  install -m644 $(man1_paths) doc/
86
94
  cd doc && for i in $(base_bins); do \
95
+ $(RM) 1.html $${i}.1.html; \
87
96
  sed -e '/"documentation">/r man1/'$$i'.1.html' \
88
- < $${i}_1.html > tmp && mv tmp $${i}_1.html; done
97
+ < $${i}_1.html > tmp && mv tmp $${i}_1.html; \
98
+ ln $${i}_1.html $${i}.1.html; \
99
+ done
89
100
  $(RUBY) -i -p -e \
90
101
  '$$_.gsub!("</title>",%q{\&$(call atom,$(cgit_atom))})' \
91
102
  doc/ChangeLog.html
@@ -99,7 +110,7 @@ doc: .document NEWS ChangeLog
99
110
  '$$_.gsub!(/INCLUDE/){File.read("Documentation/comparison.html")}' \
100
111
  doc/Summary.html
101
112
  cat Documentation/comparison.css >> doc/rdoc.css
102
- $(RM) $(man1_bins)
113
+ $(RM) $(man1_rdoc)
103
114
 
104
115
  ifneq ($(VERSION),)
105
116
  rfproject := rainbows
@@ -156,7 +167,7 @@ release: verify package $(release_notes) $(release_changes)
156
167
  # make tgz release on RubyForge
157
168
  rubyforge add_release -f -n $(release_notes) -a $(release_changes) \
158
169
  $(rfproject) $(rfpackage) $(VERSION) $(pkgtgz)
159
- # push gem to Gemcutter
170
+ # push gem to RubyGems.org
160
171
  gem push $(pkggem)
161
172
  # in case of gem downloads from RubyForge releases page
162
173
  -rubyforge add_file \
data/NEWS CHANGED
@@ -1,3 +1,32 @@
1
+ === 0.92.0 / 2010-05-04 21:58 UTC
2
+
3
+ Mostly internal cleanups and small improvements.
4
+
5
+ The only backwards incompatible change was the addition of the
6
+ "client_max_body_size" parameter to limit upload sizes to
7
+ prevent DoS. This defaults to one megabyte (same as nginx), so
8
+ any apps relying on the limit-less behavior of previous will
9
+ have to configure this in the Unicorn/Rainbows! config file:
10
+
11
+ Rainbows! do
12
+ # nil for unlimited, or any number in bytes
13
+ client_max_body_size nil
14
+ end
15
+
16
+ The ThreadSpawn and ThreadPool models are now optimized for serving
17
+ large static files under Ruby 1.9 using IO.copy_stream[1].
18
+
19
+ The EventMachine model has always had optimized static file
20
+ serving (using EM::Connection#stream_file_data[2]).
21
+
22
+ The EventMachine model (finally) gets conditionally deferred app
23
+ dispatch in a separate thread, as described by Ezra Zygmuntowicz
24
+ for Merb, Ebb and Thin[3].
25
+
26
+ [1] - http://euruko2008.csrug.cz/system/assets/documents/0000/0007/tanaka-IOcopy_stream-euruko2008.pdf
27
+ [2] - http://eventmachine.rubyforge.org/EventMachine/Connection.html#M000312
28
+ [3] - http://brainspl.at/articles/2008/04/18/deferred-requests-with-merb-ebb-and-thin
29
+
1
30
  === 0.91.1 / 2010-04-19 21:13 UTC
2
31
 
3
32
  This release fixes a denial-of-service vector for deployments
data/README CHANGED
@@ -1,6 +1,6 @@
1
- = Rainbows! Unicorn for sleepy apps and slow clients
1
+ = Rainbows! - Unicorn for sleepy apps and slow clients
2
2
 
3
- Rainbows! is an HTTP server for sleepy Rack applications. It is based on
3
+ \Rainbows! is an HTTP server for sleepy Rack applications. It is based on
4
4
  Unicorn, but designed to handle applications that expect long
5
5
  request/response times and/or slow clients. For Rack applications not
6
6
  heavily bound by slow external network dependencies, consider Unicorn
@@ -92,7 +92,7 @@ and run setup.rb after unpacking it:
92
92
 
93
93
  http://rubyforge.org/frs/?group_id=8977
94
94
 
95
- You may also install it via RubyGems on Gemcutter:
95
+ You may also install it via RubyGems on RubyGems.org:
96
96
 
97
97
  gem install rainbows
98
98
 
@@ -123,7 +123,8 @@ config file:
123
123
  worker_connections 100
124
124
  end
125
125
 
126
- See the {Rainbows! configuration documentation}[link:Rainbows.html#M000001]
126
+ See the {Rainbows! configuration}[link:Rainbows.html#method-i-Rainbows!]
127
+ {documentation}[link:Rainbows.html#method-i-Rainbows!]
127
128
  for more details.
128
129
 
129
130
  == Development
data/Rakefile CHANGED
@@ -183,3 +183,10 @@ task :fm_update do
183
183
  p http.post(uri.path, req, {'Content-Type'=>'application/json'})
184
184
  end
185
185
  end
186
+
187
+ desc 'isolate gems for development'
188
+ task :isolate do
189
+ require 'isolate'
190
+ Isolate.gems "tmp/gems/#{RUBY_VERSION}",
191
+ :file => ENV['ISOLATE_CONFIG']
192
+ end
data/SIGNALS CHANGED
@@ -14,7 +14,9 @@ between \Rainbows!, Unicorn and nginx.
14
14
  * INT/TERM - quick shutdown, kills all workers immediately
15
15
 
16
16
  * QUIT - graceful shutdown, waits for workers to finish their
17
- current request before finishing.
17
+ current request before finishing. This currently does not
18
+ wait for requests deferred to a separate thread when using
19
+ EventMachine (when app.deferred?(env) => true)
18
20
 
19
21
  * USR1 - reopen all logs owned by the master and all workers
20
22
  See Unicorn::Util.reopen_logs for what is considered a log.
@@ -43,6 +45,8 @@ automatically respawned.
43
45
  * QUIT - Gracefully exit after finishing the current request.
44
46
  Unless WINCH has been sent to the master (or the master is killed),
45
47
  the master process will respawn a worker to replace this one.
48
+ This currently does not wait for requests deferred to a separate
49
+ thread when using EventMachine (when app.deferred?(env) => true)
46
50
 
47
51
  * USR1 - Reopen all logs owned by the worker process.
48
52
  See Unicorn::Util.reopen_logs for what is considered a log.
data/TODO CHANGED
@@ -10,12 +10,9 @@ care about.
10
10
  unit tests, only integration tests that exercise externally
11
11
  visible parts.
12
12
 
13
- * EventMachine.spawn - should be like Revactor, maybe?
14
-
15
- * conditional app.deferred?(env) support
16
- Merb uses it, some other servers support it
17
-
18
13
  * EventMachine+Fibers+streaming input
14
+ (those who do not require streaming input can use
15
+ {rack-fiber_pool}[http://github.com/mperham/rack-fiber_pool])
19
16
 
20
17
  * RevFiberPool
21
18
 
data/config/.gitignore ADDED
@@ -0,0 +1 @@
1
+ /isolate_*.rb
data/config/isolate.rb ADDED
@@ -0,0 +1,25 @@
1
+ # this the default config file used by John Barnette's isolate gem
2
+ # you can create a config/isolate_local.rb file to override this
3
+ # See the corresponding tasks in Rakefile and GNUmakefile
4
+ # `rake isolate' or (faster in the unmodified case, `make isolate')
5
+
6
+ gem 'rack', '1.1.0'
7
+ gem 'unicorn', '0.97.1'
8
+
9
+ gem 'iobuffer', '0.1.3'
10
+ gem 'rev', '0.3.2'
11
+
12
+ gem 'eventmachine', '0.12.10'
13
+
14
+ gem 'sinatra', '0.9.4'
15
+ gem 'async_sinatra', '0.1.5'
16
+
17
+ gem 'neverblock', '0.1.6.2'
18
+
19
+ if defined?(::Fiber)
20
+ gem 'case', '0.5'
21
+ gem 'revactor', '0.1.5'
22
+ gem 'rack-fiber_pool', '0.9.0'
23
+ end
24
+
25
+ gem 'cramp', '0.10'
data/lib/rainbows/base.rb CHANGED
@@ -12,6 +12,7 @@ module Rainbows
12
12
 
13
13
  def init_worker_process(worker)
14
14
  super(worker)
15
+ MaxBody.setup
15
16
  G.tmp = worker.tmp
16
17
 
17
18
  # avoid spurious wakeups and blocking-accept() with 1.8 green threads
@@ -29,14 +30,35 @@ module Rainbows
29
30
  logger.info "Rainbows! #@use worker_connections=#@worker_connections"
30
31
  end
31
32
 
33
+ if IO.respond_to?(:copy_stream)
34
+ def write_body(client, body)
35
+ if body.respond_to?(:to_path)
36
+ io = body.respond_to?(:to_io) ? body.to_io : body.to_path
37
+ IO.copy_stream(io, client)
38
+ else
39
+ body.each { |chunk| client.write(chunk) }
40
+ end
41
+ ensure
42
+ body.respond_to?(:close) and body.close
43
+ end
44
+ else
45
+ def write_body(client, body)
46
+ body.each { |chunk| client.write(chunk) }
47
+ ensure
48
+ body.respond_to?(:close) and body.close
49
+ end
50
+ end
51
+
32
52
  # once a client is accepted, it is processed in its entirety here
33
53
  # in 3 easy steps: read request, call app, write app response
54
+ # this is used by synchronous concurrency models
55
+ # Base, ThreadSpawn, ThreadPool
34
56
  def process_client(client)
35
57
  buf = client.readpartial(CHUNK_SIZE) # accept filters protect us here
36
58
  hp = HttpParser.new
37
59
  env = {}
38
60
  alive = true
39
- remote_addr = TCPSocket === client ? client.peeraddr.last : LOCALHOST
61
+ remote_addr = Rainbows.addr(client)
40
62
 
41
63
  begin # loop
42
64
  while ! hp.headers(env, buf)
@@ -46,20 +68,22 @@ module Rainbows
46
68
 
47
69
  env[CLIENT_IO] = client
48
70
  env[RACK_INPUT] = 0 == hp.content_length ?
49
- HttpRequest::NULL_IO :
50
- Unicorn::TeeInput.new(client, env, hp, buf)
71
+ HttpRequest::NULL_IO : TeeInput.new(client, env, hp, buf)
51
72
  env[REMOTE_ADDR] = remote_addr
52
- response = app.call(env.update(RACK_DEFAULTS))
73
+ status, headers, body = app.call(env.update(RACK_DEFAULTS))
53
74
 
54
- if 100 == response.first.to_i
75
+ if 100 == status.to_i
55
76
  client.write(EXPECT_100_RESPONSE)
56
77
  env.delete(HTTP_EXPECT)
57
- response = app.call(env)
78
+ status, headers, body = app.call(env)
58
79
  end
59
80
 
60
81
  alive = hp.keepalive? && G.alive
61
- out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if hp.headers?
62
- HttpResponse.write(client, response, out)
82
+ if hp.headers?
83
+ out = [ alive ? CONN_ALIVE : CONN_CLOSE ]
84
+ client.write(HttpResponse.header_string(status, headers, out))
85
+ end
86
+ write_body(client, body)
63
87
  end while alive and hp.reset.nil? and env.clear
64
88
  # if we get any error, try to write something back to the client
65
89
  # assuming we haven't closed the socket, but don't get hung up
@@ -3,7 +3,7 @@
3
3
  module Rainbows
4
4
 
5
5
  module Const
6
- RAINBOWS_VERSION = '0.91.1'
6
+ RAINBOWS_VERSION = '0.92.0'
7
7
 
8
8
  include Unicorn::Const
9
9
 
@@ -17,7 +17,6 @@ module Rainbows
17
17
 
18
18
  CONN_CLOSE = "Connection: close\r\n"
19
19
  CONN_ALIVE = "Connection: keep-alive\r\n"
20
- LOCALHOST = Unicorn::HttpRequest::LOCALHOST
21
20
 
22
21
  # client IO object that supports reading and writing directly
23
22
  # without filtering it through the HTTP chunk parser.
@@ -25,5 +24,7 @@ module Rainbows
25
24
  # of the official spec, but for now it is "hack.io"
26
25
  CLIENT_IO = "hack.io".freeze
27
26
 
27
+ ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n"
28
+
28
29
  end
29
30
  end
@@ -14,7 +14,7 @@ module Rainbows
14
14
  ASYNC_CLOSE = "async.close".freeze
15
15
 
16
16
  def post_init
17
- @remote_addr = ::TCPSocket === @_io ? @_io.peeraddr.last : LOCALHOST
17
+ @remote_addr = Rainbows.addr(@_io)
18
18
  @env = {}
19
19
  @hp = HttpParser.new
20
20
  @state = :headers # [ :body [ :trailers ] ] :app_call :close
@@ -49,7 +49,7 @@ module Rainbows
49
49
  write(EXPECT_100_RESPONSE)
50
50
  @env.delete(HTTP_EXPECT)
51
51
  end
52
- @input = len && len <= MAX_BODY ? StringIO.new("") : Util.tmpio
52
+ @input = CapInput.new(len, self)
53
53
  @hp.filter_body(@buf2 = "", @buf)
54
54
  @input << @buf2
55
55
  on_read("")
@@ -73,5 +73,45 @@ module Rainbows
73
73
  handle_error(e)
74
74
  end
75
75
 
76
+ class CapInput < Struct.new(:io, :client, :bytes_left)
77
+ MAX_BODY = Unicorn::Const::MAX_BODY
78
+ Util = Unicorn::Util
79
+
80
+ def self.err(client, msg)
81
+ client.write(Const::ERROR_413_RESPONSE)
82
+ client.quit
83
+
84
+ # zip back up the stack
85
+ raise IOError, msg, []
86
+ end
87
+
88
+ def self.new(len, client)
89
+ max = Rainbows.max_bytes
90
+ if len
91
+ if max && (len > max)
92
+ err(client, "Content-Length too big: #{len} > #{max}")
93
+ end
94
+ len <= MAX_BODY ? StringIO.new("") : Util.tmpio
95
+ else
96
+ max ? super(Util.tmpio, client, max) : Util.tmpio
97
+ end
98
+ end
99
+
100
+ def <<(buf)
101
+ if (self.bytes_left -= buf.size) < 0
102
+ io.close
103
+ CapInput.err(client, "chunked request body too big")
104
+ end
105
+ io << buf
106
+ end
107
+
108
+ def gets; io.gets; end
109
+ def each(&block); io.each(&block); end
110
+ def size; io.size; end
111
+ def rewind; io.rewind; end
112
+ def read(*args); io.read(*args); end
113
+
114
+ end
115
+
76
116
  end
77
117
  end
@@ -8,11 +8,14 @@ module Rainbows
8
8
  # Implements a basic single-threaded event model with
9
9
  # {EventMachine}[http://rubyeventmachine.com/]. It is capable of
10
10
  # handling thousands of simultaneous client connections, but with only
11
- # a single-threaded app dispatch. It is suited for slow clients and
12
- # fast applications (applications that do not have slow network
13
- # dependencies) or applications that use DevFdResponse for deferrable
14
- # response bodies. It does not require your Rack application to be
15
- # thread-safe, reentrancy is only required for the DevFdResponse body
11
+ # a single-threaded app dispatch. It is suited for slow clients,
12
+ # and can work with slow applications via asynchronous libraries such as
13
+ # {async_sinatra}[http://github.com/raggi/async_sinatra],
14
+ # {Cramp}[http://m.onkey.org/2010/1/7/introducing-cramp],
15
+ # and {rack-fiber_pool}[http://github.com/mperham/rack-fiber_pool].
16
+ #
17
+ # It does not require your Rack application to be thread-safe,
18
+ # reentrancy is only required for the DevFdResponse body
16
19
  # generator.
17
20
  #
18
21
  # Compatibility: Whatever \EventMachine ~> 0.12.10 and Unicorn both
@@ -22,6 +25,20 @@ module Rainbows
22
25
  # environment such as
23
26
  # {async_sinatra}[http://github.com/raggi/async_sinatra].
24
27
  #
28
+ # For a complete asynchronous framework,
29
+ # {Cramp}[http://m.onkey.org/2010/1/7/introducing-cramp] is fully
30
+ # supported when using this concurrency model.
31
+ #
32
+ # This model is fully-compatible with
33
+ # {rack-fiber_pool}[http://github.com/mperham/rack-fiber_pool]
34
+ # which allows each request to run inside its own \Fiber after
35
+ # all request processing is complete.
36
+ #
37
+ # Merb (and other frameworks/apps) supporting +deferred?+ execution as
38
+ # documented at http://brainspl.at/articles/2008/04/18/deferred-requests-with-merb-ebb-and-thin
39
+ # will also get the ability to conditionally defer request processing
40
+ # to a separate thread.
41
+ #
25
42
  # This model does not implement as streaming "rack.input" which allows
26
43
  # the Rack application to process data as it arrives. This means
27
44
  # "rack.input" will be fully buffered in memory or to a temporary file
@@ -188,11 +205,37 @@ module Rainbows
188
205
  end
189
206
  end
190
207
 
208
+ # Middleware that will run the app dispatch in a separate thread.
209
+ # This middleware is automatically loaded by Rainbows! when using
210
+ # EventMachine and if the app responds to the +deferred?+ method.
211
+ class TryDefer < Struct.new(:app)
212
+
213
+ def initialize(app)
214
+ # the entire app becomes multithreaded, even the root (non-deferred)
215
+ # thread since any thread can share processes with others
216
+ Const::RACK_DEFAULTS['rack.multithread'] = true
217
+ super
218
+ end
219
+
220
+ def call(env)
221
+ if app.deferred?(env)
222
+ EM.defer(proc { catch(:async) { app.call(env) } },
223
+ env[EvCore::ASYNC_CALLBACK])
224
+ # all of the async/deferred stuff breaks Rack::Lint :<
225
+ nil
226
+ else
227
+ app.call(env)
228
+ end
229
+ end
230
+ end
231
+
191
232
  # runs inside each forked worker, this sits around and waits
192
233
  # for connections and doesn't die until the parent dies (or is
193
234
  # given a INT, QUIT, or TERM signal)
194
235
  def worker_loop(worker)
195
236
  init_worker_process(worker)
237
+ G.server.app.respond_to?(:deferred?) and
238
+ G.server.app = TryDefer[G.server.app]
196
239
 
197
240
  # enable them both, should be non-fatal if not supported
198
241
  EM.epoll
@@ -57,15 +57,17 @@ module Rainbows
57
57
  def schedule_sleepers
58
58
  max = nil
59
59
  now = Time.now
60
+ fibs = []
60
61
  ZZ.delete_if { |fib, time|
61
62
  if now >= time
62
- fib.resume
63
+ fibs << fib
63
64
  now = Time.now
64
65
  else
65
66
  max = time
66
67
  false
67
68
  end
68
69
  }
70
+ fibs.each { |fib| fib.resume }
69
71
  max.nil? || max > (now + 1) ? 1 : max - now
70
72
  end
71
73
 
@@ -76,7 +78,7 @@ module Rainbows
76
78
  hp = HttpParser.new
77
79
  env = {}
78
80
  alive = true
79
- remote_addr = TCPSocket === io ? io.peeraddr.last : LOCALHOST
81
+ remote_addr = Rainbows.addr(io)
80
82
 
81
83
  begin # loop
82
84
  while ! hp.headers(env, buf)
@@ -80,7 +80,7 @@ module Rainbows::Fiber
80
80
  hp = HttpParser.new
81
81
  env = {}
82
82
  alive = true
83
- remote_addr = TCPSocket === io ? io.peeraddr.last : LOCALHOST
83
+ remote_addr = Rainbows.addr(io)
84
84
 
85
85
  begin # loop
86
86
  buf << (client.read_timeout or return) until hp.headers(env, buf)
@@ -1,34 +1,35 @@
1
1
  # -*- encoding: binary -*-
2
2
  require 'time'
3
- require 'rainbows'
4
3
 
5
4
  module Rainbows
6
5
 
7
6
  class HttpResponse < ::Unicorn::HttpResponse
8
7
 
9
- def self.write(socket, rack_response, out = [])
10
- status, headers, body = rack_response
11
-
12
- if Array === out
13
- status = CODES[status.to_i] || status
8
+ def self.header_string(status, headers, out)
9
+ status = CODES[status.to_i] || status
14
10
 
15
- headers.each do |key, value|
16
- next if %r{\AX-Rainbows-}i =~ key
17
- next if SKIP.include?(key.downcase)
18
- if value =~ /\n/
19
- # avoiding blank, key-only cookies with /\n+/
20
- out.concat(value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" })
21
- else
22
- out << "#{key}: #{value}\r\n"
23
- end
11
+ headers.each do |key, value|
12
+ next if %r{\AX-Rainbows-}i =~ key
13
+ next if SKIP.include?(key.downcase)
14
+ if value =~ /\n/
15
+ # avoiding blank, key-only cookies with /\n+/
16
+ out.concat(value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" })
17
+ else
18
+ out << "#{key}: #{value}\r\n"
24
19
  end
25
-
26
- socket.write("HTTP/1.1 #{status}\r\n" \
27
- "Date: #{Time.now.httpdate}\r\n" \
28
- "Status: #{status}\r\n" \
29
- "#{out.join('')}\r\n")
30
20
  end
31
21
 
22
+ "HTTP/1.1 #{status}\r\n" \
23
+ "Date: #{Time.now.httpdate}\r\n" \
24
+ "Status: #{status}\r\n" \
25
+ "#{out.join('')}\r\n"
26
+ end
27
+
28
+ def self.write(socket, rack_response, out = [])
29
+ status, headers, body = rack_response
30
+ out.instance_of?(Array) and
31
+ socket.write(header_string(status, headers, out))
32
+
32
33
  body.each { |chunk| socket.write(chunk) }
33
34
  ensure
34
35
  body.respond_to?(:close) and body.close
@@ -1,5 +1,4 @@
1
1
  # -*- encoding: binary -*-
2
- require 'rainbows'
3
2
  module Rainbows
4
3
 
5
4
  class HttpServer < ::Unicorn::HttpServer
@@ -61,11 +60,7 @@ module Rainbows
61
60
  end
62
61
  mod.setup if mod.respond_to?(:setup)
63
62
  Const::RACK_DEFAULTS['rainbows.model'] = @use = model.to_sym
64
-
65
- Const::RACK_DEFAULTS['rack.multithread'] = case model.to_s
66
- when /Thread/, "EventMachineDefer"; true
67
- else false
68
- end
63
+ Const::RACK_DEFAULTS['rack.multithread'] = !!(model.to_s =~ /Thread/)
69
64
 
70
65
  case @use
71
66
  when :Rev, :EventMachine, :NeverBlock
@@ -86,6 +81,18 @@ module Rainbows
86
81
  raise ArgumentError, "keepalive must be a non-negative Integer"
87
82
  G.kato = nr
88
83
  end
84
+
85
+ def client_max_body_size(nr)
86
+ err = "client_max_body_size must be nil or a non-negative Integer"
87
+ case nr
88
+ when nil
89
+ when Integer
90
+ nr >= 0 or raise ArgumentError, err
91
+ else
92
+ raise ArgumentError, err
93
+ end
94
+ Rainbows.max_bytes = nr
95
+ end
89
96
  end
90
97
 
91
98
  end