rainbows 0.91.1 → 0.92.0

Sign up to get free protection for your applications and to get access to all the features.
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