rainbows 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/GIT-VERSION-GEN +1 -1
  2. data/GNUmakefile +12 -6
  3. data/README +3 -2
  4. data/Rakefile +3 -3
  5. data/TODO +2 -9
  6. data/lib/rainbows.rb +1 -0
  7. data/lib/rainbows/app_pool.rb +10 -6
  8. data/lib/rainbows/base.rb +1 -1
  9. data/lib/rainbows/const.rb +1 -1
  10. data/lib/rainbows/ev_core.rb +88 -0
  11. data/lib/rainbows/event_machine.rb +218 -0
  12. data/lib/rainbows/http_server.rb +4 -1
  13. data/lib/rainbows/rev.rb +21 -89
  14. data/lib/rainbows/revactor.rb +4 -10
  15. data/local.mk.sample +13 -7
  16. data/rainbows.gemspec +17 -2
  17. data/t/.gitignore +1 -0
  18. data/t/GNUmakefile +63 -40
  19. data/t/README +6 -2
  20. data/t/async_sinatra.ru +13 -0
  21. data/t/bin/unused_listen +1 -1
  22. data/t/large-file-response.ru +1 -0
  23. data/t/my-tap-lib.sh +200 -0
  24. data/t/simple-http_Base.ru +3 -0
  25. data/t/simple-http_EventMachine.ru +9 -0
  26. data/t/simple-http_Rev.ru +9 -0
  27. data/t/simple-http_Revactor.ru +10 -0
  28. data/t/simple-http_ThreadPool.ru +10 -0
  29. data/t/simple-http_ThreadSpawn.ru +10 -0
  30. data/t/t0000-simple-http.sh +142 -0
  31. data/t/t0001-unix-http.sh +103 -0
  32. data/t/t0002-graceful.sh +32 -0
  33. data/t/t0002-parser-error.sh +31 -0
  34. data/t/t0003-reopen-logs.sh +97 -0
  35. data/t/t0005-large-file-response.sh +83 -0
  36. data/t/t0100-rack-input-hammer.sh +45 -0
  37. data/t/t0101-rack-input-trailer.sh +68 -0
  38. data/t/t0200-async-response.sh +66 -0
  39. data/t/t0201-async-response-no-autochunk.sh +3 -0
  40. data/t/t0300-async_sinatra.sh +65 -0
  41. data/t/t9000-rack-app-pool.sh +45 -33
  42. data/t/test-lib.sh +67 -56
  43. metadata +26 -56
  44. data/t/lib-async-response-no-autochunk.sh +0 -6
  45. data/t/lib-async-response.sh +0 -45
  46. data/t/lib-graceful.sh +0 -40
  47. data/t/lib-input-trailer.sh +0 -63
  48. data/t/lib-large-file-response.sh +0 -45
  49. data/t/lib-parser-error.sh +0 -29
  50. data/t/lib-rack-input-hammer.sh +0 -38
  51. data/t/lib-reopen-logs.sh +0 -60
  52. data/t/lib-simple-http.sh +0 -92
  53. data/t/t0000-basic.sh +0 -2
  54. data/t/t1000-thread-pool-basic.sh +0 -2
  55. data/t/t1002-thread-pool-graceful.sh +0 -2
  56. data/t/t1003-thread-pool-reopen-logs.sh +0 -2
  57. data/t/t1004-thread-pool-async-response.sh +0 -45
  58. data/t/t1005-thread-pool-large-file-response.sh +0 -45
  59. data/t/t1006-thread-pool-async-response-no-autochunk.sh +0 -6
  60. data/t/t1100-thread-pool-rack-input.sh +0 -2
  61. data/t/t1101-thread-pool-input-trailer.sh +0 -2
  62. data/t/t2000-thread-spawn-basic.sh +0 -2
  63. data/t/t2002-thread-spawn-graceful.sh +0 -2
  64. data/t/t2003-thread-spawn-reopen-logs.sh +0 -2
  65. data/t/t2004-thread-spawn-async-response.sh +0 -45
  66. data/t/t2005-thread-spawn-large-file-response.sh +0 -45
  67. data/t/t2006-thread-spawn-async-response-no-autochunk.sh +0 -6
  68. data/t/t2100-thread-spawn-rack-input.sh +0 -2
  69. data/t/t2101-thread-spawn-input-trailer.sh +0 -2
  70. data/t/t3000-revactor-basic.sh +0 -2
  71. data/t/t3002-revactor-graceful.sh +0 -2
  72. data/t/t3003-revactor-reopen-logs.sh +0 -2
  73. data/t/t3004-revactor-async-response.sh +0 -45
  74. data/t/t3005-revactor-large-file-response.sh +0 -2
  75. data/t/t3006-revactor-async-response-no-autochunk.sh +0 -6
  76. data/t/t3100-revactor-rack-input.sh +0 -2
  77. data/t/t3101-revactor-rack-input-trailer.sh +0 -2
  78. data/t/t4000-rev-basic.sh +0 -2
  79. data/t/t4002-rev-graceful.sh +0 -2
  80. data/t/t4003-rev-parser-error.sh +0 -2
  81. data/t/t4003-rev-reopen-logs.sh +0 -2
  82. data/t/t4004-rev-async-response.sh +0 -45
  83. data/t/t4005-rev-large-file-response.sh +0 -2
  84. data/t/t4006-rev-async-response-no-autochunk.sh +0 -6
  85. data/t/t4100-rev-rack-input.sh +0 -2
  86. data/t/t4101-rev-rack-input-trailer.sh +0 -2
@@ -33,7 +33,10 @@ module Rainbows
33
33
  extend(mod)
34
34
  Const::RACK_DEFAULTS['rainbows.model'] = @use = model
35
35
  Const::RACK_DEFAULTS['rack.multithread'] = !!(/Thread/ =~ model.to_s)
36
- Const::RACK_DEFAULTS['rainbows.autochunk'] = (model.to_s == "Rev")
36
+ case model
37
+ when :Rev, :EventMachine
38
+ Const::RACK_DEFAULTS['rainbows.autochunk'] = true
39
+ end
37
40
  end
38
41
 
39
42
  def worker_connections(*args)
data/lib/rainbows/rev.rb CHANGED
@@ -1,9 +1,7 @@
1
1
  # -*- encoding: binary -*-
2
2
  require 'rev'
3
-
4
- # workaround revactor 0.1.4 still using the old Rev::Buffer
5
- # ref: http://rubyforge.org/pipermail/revactor-talk/2009-October/000034.html
6
- defined?(Rev::Buffer) or Rev::Buffer = IO::Buffer
3
+ Rev::VERSION >= '0.3.0' or abort 'rev >= 0.3.0 is required'
4
+ require 'rainbows/ev_core'
7
5
 
8
6
  module Rainbows
9
7
 
@@ -30,46 +28,22 @@ module Rainbows
30
28
  include Base
31
29
 
32
30
  class Client < ::Rev::IO
33
- include Unicorn
34
- include Rainbows::Const
31
+ include Rainbows::EvCore
35
32
  G = Rainbows::G
36
33
 
37
- # queued, optional response bodies, it should only be unpollable "fast"
38
- # devices where read(2) is uninterruptable. Unfortunately, NFS and ilk
39
- # are also part of this. We'll also stick DeferredResponse bodies in
40
- # here to prevent connections from being closed on us.
41
- attr_reader :deferred_bodies
42
-
43
34
  def initialize(io)
44
35
  G.cur += 1
45
36
  super(io)
46
- @remote_addr = ::TCPSocket === io ? io.peeraddr.last : LOCALHOST
47
- @env = {}
48
- @hp = HttpParser.new
49
- @state = :headers # [ :body [ :trailers ] ] :app_call :close
50
- @buf = ""
51
- @deferred_bodies = [] # for (fast) regular files only
52
- end
53
-
54
- # graceful exit, like SIGQUIT
55
- def quit
56
- @deferred_bodies.clear
57
- @state = :close
37
+ post_init
58
38
  end
59
39
 
60
- def handle_error(e)
61
- quit
62
- msg = case e
63
- when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
64
- ERROR_500_RESPONSE
65
- when HttpParserError # try to tell the client they're bad
66
- ERROR_400_RESPONSE
67
- else
68
- G.logger.error "Read error: #{e.inspect}"
69
- G.logger.error e.backtrace.join("\n")
70
- ERROR_500_RESPONSE
71
- end
72
- write(msg)
40
+ # queued, optional response bodies, it should only be unpollable "fast"
41
+ # devices where read(2) is uninterruptable. Unfortunately, NFS and ilk
42
+ # are also part of this. We'll also stick DeferredResponse bodies in
43
+ # here to prevent connections from being closed on us.
44
+ def defer_body(io)
45
+ @deferred_bodies << io
46
+ on_write_complete unless @hp.headers? # triggers a write
73
47
  end
74
48
 
75
49
  def app_call
@@ -89,7 +63,7 @@ module Rainbows
89
63
  # keepalive requests are always body-less, so @input is unchanged
90
64
  @hp.headers(@env, @buf) and next
91
65
  else
92
- @state = :close
66
+ quit
93
67
  end
94
68
  return
95
69
  end while true
@@ -104,6 +78,7 @@ module Rainbows
104
78
  rescue EOFError # expected at file EOF
105
79
  @deferred_bodies.shift
106
80
  body.close
81
+ close if :close == @state && @deferred_bodies.empty?
107
82
  end
108
83
  rescue Object => e
109
84
  handle_error(e)
@@ -116,53 +91,6 @@ module Rainbows
116
91
  def on_close
117
92
  G.cur -= 1
118
93
  end
119
-
120
- def tmpio
121
- io = Util.tmpio
122
- def io.size
123
- # already sync=true at creation, so no need to flush before stat
124
- stat.size
125
- end
126
- io
127
- end
128
-
129
- # TeeInput doesn't map too well to this right now...
130
- def on_read(data)
131
- case @state
132
- when :headers
133
- @hp.headers(@env, @buf << data) or return
134
- @state = :body
135
- len = @hp.content_length
136
- if len == 0
137
- @input = HttpRequest::NULL_IO
138
- app_call # common case
139
- else # nil or len > 0
140
- # since we don't do streaming input, we have no choice but
141
- # to take over 100-continue handling from the Rack application
142
- if @env[HTTP_EXPECT] =~ /\A100-continue\z/i
143
- write(EXPECT_100_RESPONSE)
144
- @env.delete(HTTP_EXPECT)
145
- end
146
- @input = len && len <= MAX_BODY ? StringIO.new("") : tmpio
147
- @hp.filter_body(@buf2 = @buf.dup, @buf)
148
- @input << @buf2
149
- on_read("")
150
- end
151
- when :body
152
- if @hp.body_eof?
153
- @state = :trailers
154
- on_read(data)
155
- elsif data.size > 0
156
- @hp.filter_body(@buf2, @buf << data)
157
- @input << @buf2
158
- on_read("")
159
- end
160
- when :trailers
161
- @hp.trailers(@env, @buf << data) and app_call
162
- end
163
- rescue Object => e
164
- handle_error(e)
165
- end
166
94
  end
167
95
 
168
96
  class Server < ::Rev::IO
@@ -172,7 +100,7 @@ module Rainbows
172
100
  return if G.cur >= G.max
173
101
  begin
174
102
  Client.new(@_io.accept_nonblock).attach(::Rev::Loop.default)
175
- rescue Errno::EAGAIN, Errno::ECONNBORTED
103
+ rescue Errno::EAGAIN, Errno::ECONNABORTED
176
104
  end
177
105
  end
178
106
 
@@ -191,7 +119,7 @@ module Rainbows
191
119
  # here since we can't get here without checking to_path first
192
120
  io = body.to_io if body.respond_to?(:to_io)
193
121
  io ||= ::IO.new($1.to_i) if body.to_path =~ %r{\A/dev/fd/(\d+)\z}
194
- io ||= File.open(File.expand_path(body.to_path), 'rb')
122
+ io ||= File.open(body.to_path, 'rb')
195
123
  st = io.stat
196
124
 
197
125
  if st.socket? || st.pipe?
@@ -199,7 +127,11 @@ module Rainbows
199
127
  do_chunk = false if headers.delete('X-Rainbows-Autochunk') == 'no'
200
128
  # too tricky to support keepalive/pipelining when a response can
201
129
  # take an indeterminate amount of time here.
202
- out[0] = CONN_CLOSE
130
+ if out.nil?
131
+ do_chunk = false
132
+ else
133
+ out[0] = CONN_CLOSE
134
+ end
203
135
 
204
136
  io = new(io, client, do_chunk, body).attach(::Rev::Loop.default)
205
137
  elsif st.file?
@@ -208,7 +140,7 @@ module Rainbows
208
140
  else # char/block device, directory, whatever... nobody cares
209
141
  return response
210
142
  end
211
- client.deferred_bodies << io
143
+ client.defer_body(io)
212
144
  [ response.first, headers.to_hash, [] ]
213
145
  end
214
146
 
@@ -1,9 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
  require 'revactor'
3
-
4
- # workaround revactor 0.1.4 still using the old Rev::Buffer
5
- # ref: http://rubyforge.org/pipermail/revactor-talk/2009-October/000034.html
6
- defined?(Rev::Buffer) or Rev::Buffer = IO::Buffer
3
+ Revactor::VERSION >= '0.1.5' or abort 'revactor 0.1.5 is required'
7
4
 
8
5
  module Rainbows
9
6
 
@@ -137,16 +134,13 @@ module Rainbows
137
134
 
138
135
  def revactorize_listeners!
139
136
  LISTENERS.map! do |s|
140
- if TCPServer === s
137
+ case s
138
+ when TCPServer
141
139
  ::Revactor::TCP.listen(s, nil)
142
- elsif defined?(::Revactor::UNIX) && UNIXServer === s
140
+ when UNIXServer
143
141
  ::Revactor::UNIX.listen(s)
144
- else
145
- logger.error "your version of Revactor can't handle #{s.inspect}"
146
- nil
147
142
  end
148
143
  end
149
- LISTENERS.compact!
150
144
  end
151
145
 
152
146
  end
data/local.mk.sample CHANGED
@@ -5,18 +5,24 @@
5
5
  # This is depends on a bunch of GNU-isms from bash, sed, touch.
6
6
 
7
7
  DLEXT := so
8
- gems := rev-0.3.1 rack-1.0.0 iobuffer-0.1.1
8
+ gems := rack-1.0.1
9
+ # gems += unicorn-0.93.3 # installed via setup.rb
10
+ gems += rev-0.3.1 iobuffer-0.1.1
11
+ gems += eventmachine-0.12.10
12
+ gems += async_sinatra-0.1.5 sinatra-0.9.4
9
13
 
10
14
  # Avoid loading rubygems to speed up tests because gmake is
11
15
  # fork+exec heavy with Ruby.
16
+ prefix = $(HOME)
12
17
  ifeq ($(r19),)
13
- ruby := $(HOME)/bin/ruby
14
- gem_paths := $(addprefix $(HOME)/lib/ruby/gems/1.8/gems/,$(gems))
18
+ RUBY := $(prefix)/bin/ruby
19
+ gem_paths := $(addprefix $(prefix)/lib/ruby/gems/1.8/gems/,$(gems))
15
20
  else
16
- export PATH := $(HOME)/ruby-1.9/bin:$(PATH)
17
- ruby := $(HOME)/ruby-1.9/bin/ruby --disable-gems
18
- gems := $(gems) case-0.5 revactor-0.1.4
19
- gem_paths := $(addprefix $(HOME)/ruby-1.9/lib/ruby/gems/1.9.1/gems/,$(gems))
21
+ prefix := $(prefix)/ruby-1.9
22
+ export PATH := $(prefix)/bin:$(PATH)
23
+ RUBY := $(prefix)/bin/ruby --disable-gems
24
+ gems += case-0.5 revactor-0.1.5
25
+ gem_paths := $(addprefix $(prefix)/lib/ruby/gems/1.9.1/gems/,$(gems))
20
26
  endif
21
27
 
22
28
  ifdef gem_paths
data/rainbows.gemspec CHANGED
@@ -40,8 +40,23 @@ Gem::Specification.new do |s|
40
40
 
41
41
  s.test_files = test_files
42
42
 
43
- s.add_dependency(%q<rack>)
44
- s.add_dependency(%q<unicorn>, ["~> 0.93.1"])
43
+ # we need Unicorn for the HTTP parser and process management
44
+ s.add_dependency(%q<unicorn>, ["~> 0.93.4"])
45
+
46
+ # Unicorn already depends on Rack
47
+ # s.add_dependency(%q<rack>)
48
+
49
+ # optional runtime dependencies depending on configuration
50
+ # see local.mk.sample for the exact versions we've tested with
51
+ #
52
+ # Revactor >= 0.1.5 includes UNIX domain socket support
53
+ # s.add_dependency(%q<revactor>, [">= 0.1.5"])
54
+ #
55
+ # Revactor depends on Rev, too, 0.3.0 got the ability to attach IOs
56
+ # s.add_dependency(%q<rev>, [">= 0.3.0"])
57
+ #
58
+ # We use the new EM::attach/watch API in 0.12.10
59
+ # s.add_dependency(%q<eventmachine>, ["~> 0.12.10"])
45
60
 
46
61
  # s.licenses = %w(GPLv2 Ruby) # accessor not compatible with older Rubygems
47
62
  end
data/t/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  /test-results-*
2
2
  /test-bin-*
3
3
  /random_blob
4
+ /.dep+*
data/t/GNUmakefile CHANGED
@@ -2,11 +2,13 @@
2
2
 
3
3
  all::
4
4
 
5
- ruby = ruby
5
+ pid := $(shell echo $$PPID)
6
+
7
+ RUBY = $(ruby)
6
8
  rainbows_lib := $(shell cd ../lib && pwd)
7
9
  -include ../local.mk
8
10
  ifeq ($(RUBY_VERSION),)
9
- RUBY_VERSION := $(shell $(ruby) -e 'puts RUBY_VERSION')
11
+ RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
10
12
  endif
11
13
 
12
14
  ifeq ($(RUBYLIB),)
@@ -16,60 +18,81 @@ else
16
18
  endif
17
19
  export RUBYLIB RUBY_VERSION
18
20
 
21
+ models := ThreadPool ThreadSpawn Rev EventMachine
22
+ ifeq ($(RUBY_VERSION),1.9.1) # 1.9.2-preview1 was broken
23
+ models += Revactor
24
+ endif
25
+ all_models := $(models) Base
26
+
19
27
  T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
20
28
 
29
+ MODEL_T := $(foreach m,$(all_models),$(addprefix $(m).,$(T)))
30
+ $(T): MODELS = $(models)
31
+
32
+ # some tests can be run with all models
33
+ t0000-simple-http.sh: MODELS = $(all_models)
34
+ t0001-unix-http.sh: MODELS = $(all_models)
35
+ t0002-graceful.sh: MODELS = $(all_models)
36
+ t0002-parser-error.sh: MODELS = $(all_models)
37
+ t0003-reopen-logs.sh: MODELS = $(all_models)
38
+
39
+ # this test is not compatible with non-Thread models yet
40
+ t9000-rack-app-pool.sh: MODELS = ThreadPool ThreadSpawn
41
+
42
+ # recursively run per-model tests
43
+ # haven't figured out a good way to make make non-recursive here, yet...
44
+ $(T):
45
+ $(MAKE) $(foreach m,$(MODELS),$(addprefix $(m).,$@))
46
+
47
+ $(all_models):
48
+ $(MAKE) $(filter $@.%,$(MODEL_T))
49
+
21
50
  all:: $(T)
22
51
 
23
52
  # can't rely on "set -o pipefail" since we don't require bash or ksh93 :<
24
53
  t_pfx = trash/$@-$(RUBY_VERSION)
25
- t_code = $(t_pfx).code
26
- t_log = $(t_pfx).log
54
+ TEST_OPTS =
27
55
  # TRACER = strace -f -o $(t_pfx).strace -s 100000
28
56
  # TRACER = /usr/bin/time -o $(t_pfx).time
29
- t_run = $(TRACER) $(SHELL) $(SH_TEST_OPTS) $@
30
-
31
- # prefix stdout messages with ':', and stderr messages with '!'
32
- t_wrap = ( ( ( echo 42 > $(t_code); \
33
- $(t_run); \
34
- echo $$? > $(t_code) ) \
35
- | sed 's/^/$(pfx):/' 1>&3 ) 2>&1 \
36
- | sed 's/^/$(pfx)!/' 1>&2 ) 3>&1
37
-
38
- ifndef V
39
- quiet_pre = @echo '* $@';
40
- quiet_post = > $(t_log) 2>&1; exit $$(cat $(t_code))
41
- pfx =
42
- else
57
+
58
+ ifdef V
43
59
  ifeq ($(V),2)
44
- SH_TEST_OPTS += -x
60
+ TEST_OPTS += --trace
61
+ else
62
+ TEST_OPTS += --verbose
45
63
  endif
46
- quiet_pre = @echo '* $@';
47
- quiet_post = 2>&1 | ./bin/utee $(t_log); exit $$(cat $(t_code))
48
- pfx = $@
49
64
  endif
50
65
 
51
- run_test = $(quiet_pre) ( $(t_wrap) ) $(quiet_post)
52
-
53
- test-bin-$(RUBY_VERSION)/rainbows: ruby_bin = $(shell which $(ruby))
66
+ test-bin-$(RUBY_VERSION)/rainbows: ruby_bin = $(shell which $(RUBY))
54
67
  test-bin-$(RUBY_VERSION)/rainbows: ../bin/rainbows
55
68
  mkdir -p $(@D)
56
- install -m 755 $^ $@+
57
- $(ruby) -i -p -e '$$_.gsub!(%r{^#!.*$$},"#!$(ruby_bin)")' $@+
58
- cmp $@+ $@ 2>/dev/null || mv $@+ $@
59
- $(RM) $@+
69
+ install -m 755 $^ $@.$(pid)
70
+ $(RUBY) -i -p -e '$$_.gsub!(%r{^#!.*$$},"#!$(ruby_bin)")' $@.$(pid)
71
+ mv $@.$(pid) $@
60
72
 
61
- req_random_blob := $(wildcard t?1??-*.sh)
62
73
  random_blob:
63
- dd if=/dev/urandom bs=1M count=10 of=$@+
64
- mv $@+ $@
65
-
66
- $(req_random_blob): random_blob
67
-
68
- $(T): trash/.gitignore
69
- $(T): export ruby := $(ruby)
70
- $(T): export PATH := $(CURDIR)/test-bin-$(RUBY_VERSION):$(PATH)
71
- $(T): test-bin-$(RUBY_VERSION)/rainbows
72
- $(run_test)
74
+ dd if=/dev/urandom bs=1M count=30 of=$@.$(pid)
75
+ mv $@.$(pid) $@
76
+
77
+ $(T): random_blob
78
+
79
+ dependencies := socat curl
80
+ deps := $(addprefix .dep+,$(dependencies))
81
+ $(deps): dep_bin = $(lastword $(subst +, ,$@))
82
+ $(deps):
83
+ @which $(dep_bin) > $@.$(pid) 2>/dev/null || :
84
+ @test -s $@.$(pid) || \
85
+ { echo >&2 "E `$(dep_bin)' not found in PATH=$(PATH)"; exit 1; }
86
+ @mv $@.$(pid) $@
87
+ dep: $(deps)
88
+
89
+ $(MODEL_T): export model = $(firstword $(subst ., ,$@))
90
+ $(MODEL_T): script = $(subst $(model).,,$@)
91
+ $(MODEL_T): trash/.gitignore
92
+ $(MODEL_T): export RUBY := $(RUBY)
93
+ $(MODEL_T): export PATH := $(CURDIR)/test-bin-$(RUBY_VERSION):$(PATH)
94
+ $(MODEL_T): test-bin-$(RUBY_VERSION)/rainbows dep
95
+ $(TRACER) $(SHELL) $(SH_TEST_OPTS) $(script) $(TEST_OPTS)
73
96
 
74
97
  trash/.gitignore:
75
98
  mkdir -p $(@D)
data/t/README CHANGED
@@ -28,9 +28,13 @@ To run the entire test suite with 8 tests running at once:
28
28
 
29
29
  make -j8
30
30
 
31
- To run one individual test:
31
+ To run one individual test for all concurrency models:
32
32
 
33
- make t0000-basic.sh
33
+ make t0000-simple-http.sh
34
+
35
+ To run one individual test for one concurrency model:
36
+
37
+ make Revactor.t0000-simple-http.sh
34
38
 
35
39
  You may also increase verbosity by setting the "V" variable for
36
40
  GNU make. To disable trapping of stdout/stderr:
@@ -0,0 +1,13 @@
1
+ # See http://github.com/raggi/async_sinatra
2
+ # gem install async_sinatra -v0.1.5
3
+ require 'sinatra/async'
4
+
5
+ class AsyncTest < Sinatra::Base
6
+ register Sinatra::Async
7
+
8
+ aget '/:n' do |n|
9
+ EM.add_timer(n.to_i) { body { "delayed for #{n} seconds\n" } }
10
+ end
11
+ end
12
+
13
+ run AsyncTest.new