async_sinatra 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +1 -1
- data/lib/sinatra/async.rb +12 -1
- data/lib/sinatra/async/test.rb +50 -9
- data/test/test_async.rb +41 -0
- metadata +7 -9
- data/test/borked_test_crohr.rb +0 -161
data/README.rdoc
CHANGED
data/lib/sinatra/async.rb
CHANGED
@@ -73,7 +73,10 @@ module Sinatra #:nodoc:
|
|
73
73
|
# request.
|
74
74
|
def body(*args)
|
75
75
|
if @async_running
|
76
|
-
block_given? ? async_handle_exception { super
|
76
|
+
block_given? ? async_handle_exception { super(yield) } : super
|
77
|
+
if response.body.respond_to?(:call)
|
78
|
+
response.body = Array(async_handle_exception {response.body.call})
|
79
|
+
end
|
77
80
|
request.env['async.callback'][
|
78
81
|
[response.status, response.headers, response.body]
|
79
82
|
]
|
@@ -133,6 +136,14 @@ module Sinatra #:nodoc:
|
|
133
136
|
invoke { error_block! response.status }
|
134
137
|
body response.body
|
135
138
|
end
|
139
|
+
|
140
|
+
# The given block will be executed if the user closes the connection
|
141
|
+
# prematurely (before we've sent a response). This is good for
|
142
|
+
# deregistering callbacks that would otherwise send the body (for
|
143
|
+
# example channel subscriptions).
|
144
|
+
def on_close(&blk)
|
145
|
+
env['async.close'].callback(&blk)
|
146
|
+
end
|
136
147
|
end
|
137
148
|
|
138
149
|
def self.registered(app) #:nodoc:
|
data/lib/sinatra/async/test.rb
CHANGED
@@ -8,10 +8,31 @@ class Rack::MockResponse
|
|
8
8
|
end
|
9
9
|
|
10
10
|
class Sinatra::Async::Test
|
11
|
+
|
11
12
|
class AsyncSession < Rack::MockSession
|
13
|
+
class AsyncCloser
|
14
|
+
def initialize
|
15
|
+
@callbacks, @errbacks = [], []
|
16
|
+
end
|
17
|
+
def callback(&b)
|
18
|
+
@callbacks << b
|
19
|
+
end
|
20
|
+
def errback(&b)
|
21
|
+
@errbacks << b
|
22
|
+
end
|
23
|
+
def fail
|
24
|
+
@errbacks.each { |cb| cb.call }
|
25
|
+
@errbacks.clear
|
26
|
+
end
|
27
|
+
def succeed
|
28
|
+
@callbacks.each { |cb| cb.call }
|
29
|
+
@callbacks.clear
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
12
33
|
def request(uri, env)
|
13
34
|
env['async.callback'] = lambda { |r| s,h,b = *r; handle_last_response(uri, env, s,h,b) }
|
14
|
-
env['async.close'] =
|
35
|
+
env['async.close'] = AsyncCloser.new
|
15
36
|
catch(:async) { super }
|
16
37
|
@last_response ||= Rack::MockResponse.new(-1, {}, [], env["rack.errors"].flush)
|
17
38
|
end
|
@@ -29,14 +50,14 @@ class Sinatra::Async::Test
|
|
29
50
|
|
30
51
|
module Methods
|
31
52
|
include Rack::Test::Methods
|
32
|
-
|
53
|
+
|
33
54
|
%w(get put post delete head).each do |m|
|
34
55
|
eval <<-RUBY, binding, __FILE__, __LINE__ + 1
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
56
|
+
def a#{m}(*args)
|
57
|
+
#{m}(*args)
|
58
|
+
assert_async
|
59
|
+
async_continue
|
60
|
+
end
|
40
61
|
RUBY
|
41
62
|
end
|
42
63
|
|
@@ -48,20 +69,40 @@ class Sinatra::Async::Test
|
|
48
69
|
assert last_response.async?
|
49
70
|
end
|
50
71
|
|
72
|
+
# Simulate a user closing the connection before a response is sent.
|
73
|
+
def async_close
|
74
|
+
raise ArgumentError, 'please make a request first' unless last_request
|
75
|
+
current_session.last_request.env['async.close'].succeed
|
76
|
+
end
|
77
|
+
|
78
|
+
# Executes the pending asynchronous blocks, required for the
|
79
|
+
# aget/apost/etc blocks to run.
|
51
80
|
def async_continue
|
52
81
|
while b = app.options.async_schedules.shift
|
53
82
|
b.call
|
54
83
|
end
|
55
84
|
end
|
56
85
|
|
86
|
+
# Crank the eventmachine loop until a response is made, or timeout after a
|
87
|
+
# particular period, by default 10s. If the timeout is nil, no timeout
|
88
|
+
# will occur.
|
57
89
|
def em_async_continue(timeout = 10)
|
58
90
|
timed = false
|
59
91
|
EM.run do
|
60
92
|
async_continue
|
61
|
-
|
62
|
-
EM.add_timer(timeout) { timed = true; EM.stop }
|
93
|
+
em_hard_loop { EM.stop unless last_response.async? }
|
94
|
+
EM.add_timer(timeout) { timed = true; EM.stop } if timeout
|
63
95
|
end
|
64
96
|
assert !timed, "asynchronous timeout after #{timeout} seconds"
|
65
97
|
end
|
98
|
+
|
99
|
+
# Uses EM.tick_loop or a periodic timer to check for changes
|
100
|
+
def em_hard_loop
|
101
|
+
if EM.respond_to?(:tick_loop)
|
102
|
+
EM.tick_loop { yield }
|
103
|
+
else
|
104
|
+
EM.periodic_timer(0.0001) { yield }
|
105
|
+
end
|
106
|
+
end
|
66
107
|
end
|
67
108
|
end
|
data/test/test_async.rb
CHANGED
@@ -12,6 +12,12 @@ class TestSinatraAsync < Test::Unit::TestCase
|
|
12
12
|
set :environment, :test
|
13
13
|
register Sinatra::Async
|
14
14
|
|
15
|
+
# Hack for storing some global data accessible in tests (normally you
|
16
|
+
# shouldn't need to do this!)
|
17
|
+
def self.singletons
|
18
|
+
@singletons ||= []
|
19
|
+
end
|
20
|
+
|
15
21
|
error 401 do
|
16
22
|
'401'
|
17
23
|
end
|
@@ -47,6 +53,22 @@ class TestSinatraAsync < Test::Unit::TestCase
|
|
47
53
|
aget '/a401' do
|
48
54
|
ahalt 401
|
49
55
|
end
|
56
|
+
|
57
|
+
aget '/async_close' do
|
58
|
+
# don't call body here, the 'user' is going to 'disconnect' before we do
|
59
|
+
env['async.close'].callback { self.class.singletons << 'async_closed' }
|
60
|
+
end
|
61
|
+
|
62
|
+
aget '/on_close' do
|
63
|
+
# sugared version of the above
|
64
|
+
on_close do
|
65
|
+
self.class.singletons << 'async_close_cleaned_up'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
aget '/redirect' do
|
70
|
+
redirect '/'
|
71
|
+
end
|
50
72
|
end
|
51
73
|
|
52
74
|
def app
|
@@ -113,4 +135,23 @@ class TestSinatraAsync < Test::Unit::TestCase
|
|
113
135
|
assert_equal 401, last_response.status
|
114
136
|
assert_equal '401', last_response.body
|
115
137
|
end
|
138
|
+
|
139
|
+
def test_async_close
|
140
|
+
aget '/async_close'
|
141
|
+
async_close
|
142
|
+
assert_equal 'async_closed', TestApp.singletons.shift
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_on_close
|
146
|
+
aget '/on_close'
|
147
|
+
async_close
|
148
|
+
assert_equal 'async_close_cleaned_up', TestApp.singletons.shift
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_redirect
|
152
|
+
aget '/redirect'
|
153
|
+
assert last_response.redirect?
|
154
|
+
assert_equal 302, last_response.status
|
155
|
+
assert_equal '/', last_response.location
|
156
|
+
end
|
116
157
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async_sinatra
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 19
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 2
|
10
|
+
version: 0.2.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- James Tucker
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-09-07 00:00:00 -03:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -26,12 +26,11 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
29
|
+
hash: 15
|
30
30
|
segments:
|
31
|
-
- 0
|
32
|
-
- 9
|
33
31
|
- 1
|
34
|
-
|
32
|
+
- 0
|
33
|
+
version: "1.0"
|
35
34
|
type: :runtime
|
36
35
|
version_requirements: *id001
|
37
36
|
- !ruby/object:Gem::Dependency
|
@@ -80,7 +79,6 @@ files:
|
|
80
79
|
- examples/basic.ru
|
81
80
|
- lib/sinatra/async.rb
|
82
81
|
- lib/sinatra/async/test.rb
|
83
|
-
- test/borked_test_crohr.rb
|
84
82
|
- test/test_async.rb
|
85
83
|
has_rdoc: true
|
86
84
|
homepage: http://libraggi.rubyforge.org/async_sinatra
|
data/test/borked_test_crohr.rb
DELETED
@@ -1,161 +0,0 @@
|
|
1
|
-
require 'eventmachine'
|
2
|
-
require "sinatra/async/test"
|
3
|
-
|
4
|
-
require 'spec/autorun'
|
5
|
-
require 'spec/interop/test'
|
6
|
-
|
7
|
-
require 'em-http'
|
8
|
-
|
9
|
-
Spec::Runner.configure { |c| }
|
10
|
-
|
11
|
-
def server(base=Sinatra::Base, &block)
|
12
|
-
s = Sinatra.new(base, &block).new
|
13
|
-
s.options.set :environment, :test
|
14
|
-
s
|
15
|
-
end
|
16
|
-
|
17
|
-
def app
|
18
|
-
@app
|
19
|
-
end
|
20
|
-
|
21
|
-
include Sinatra::Async::Test::Methods
|
22
|
-
|
23
|
-
describe "Asynchronous routes" do
|
24
|
-
it "should still work as usual" do
|
25
|
-
@app = server do
|
26
|
-
register Sinatra::Async
|
27
|
-
disable :raise_errors, :show_exceptions
|
28
|
-
|
29
|
-
aget '/' do
|
30
|
-
body "hello async"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
aget '/'
|
34
|
-
last_response.status.should_equal 200
|
35
|
-
last_response.body.should == "hello async"
|
36
|
-
end
|
37
|
-
|
38
|
-
it "should correctly deal with raised exceptions" do
|
39
|
-
@app = server do
|
40
|
-
register Sinatra::Async
|
41
|
-
disable :raise_errors, :show_exceptions
|
42
|
-
aget '/' do
|
43
|
-
raise "boom"
|
44
|
-
body "hello async"
|
45
|
-
end
|
46
|
-
error Exception do
|
47
|
-
e = request.env['sinatra.error']
|
48
|
-
"problem: #{e.class.name} #{e.message}"
|
49
|
-
end
|
50
|
-
end
|
51
|
-
aget '/'
|
52
|
-
last_response.status.should == 500
|
53
|
-
last_response.body.should == "problem: RuntimeError boom"
|
54
|
-
end
|
55
|
-
|
56
|
-
it "should correctly deal with halts" do
|
57
|
-
@app = server do
|
58
|
-
register Sinatra::Async
|
59
|
-
disable :raise_errors, :show_exceptions
|
60
|
-
aget '/' do
|
61
|
-
halt 406, "Format not supported"
|
62
|
-
body "never called"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
aget '/'
|
67
|
-
last_response.status.should == 406
|
68
|
-
last_response.body.should == "Format not supported"
|
69
|
-
end
|
70
|
-
|
71
|
-
it "should correctly deal with halts and pass it to the defined error blocks if any" do
|
72
|
-
@app = server do
|
73
|
-
register Sinatra::Async
|
74
|
-
disable :raise_errors, :show_exceptions
|
75
|
-
aget '/' do
|
76
|
-
halt 406, "Format not supported"
|
77
|
-
body "never called"
|
78
|
-
end
|
79
|
-
error 406 do
|
80
|
-
response['Content-Type'] = "text/plain"
|
81
|
-
"problem: #{response.body.to_s}"
|
82
|
-
end
|
83
|
-
end
|
84
|
-
aget '/'
|
85
|
-
last_response.status.should == 406
|
86
|
-
last_response.headers['Content-Type'].should == "text/plain"
|
87
|
-
last_response.body.should == "problem: Format not supported"
|
88
|
-
end
|
89
|
-
|
90
|
-
describe "using EM libraries inside route block" do
|
91
|
-
it "should still work as usual" do
|
92
|
-
@app = server do
|
93
|
-
register Sinatra::Async
|
94
|
-
disable :raise_errors, :show_exceptions
|
95
|
-
aget '/' do
|
96
|
-
url = "http://ruby.activeventure.com/programmingruby/book/tut_exceptions.html"
|
97
|
-
http = EM::HttpRequest.new(url).get
|
98
|
-
http.callback {
|
99
|
-
status http.response_header.status
|
100
|
-
body "ok"
|
101
|
-
}
|
102
|
-
http.errback {
|
103
|
-
body "nok"
|
104
|
-
}
|
105
|
-
end
|
106
|
-
end
|
107
|
-
EM.run { EM.add_timer(1) { EM.stop }; aget '/' }
|
108
|
-
last_response.status.should == 200
|
109
|
-
last_response.body.should == "ok"
|
110
|
-
end
|
111
|
-
|
112
|
-
it "should correctly deal with exceptions raised from within EM callbacks" do
|
113
|
-
@app = server do
|
114
|
-
register Sinatra::Async
|
115
|
-
disable :raise_errors, :show_exceptions
|
116
|
-
aget '/' do
|
117
|
-
url = "http://doesnotexist.local/whatever"
|
118
|
-
http = EM::HttpRequest.new(url).get
|
119
|
-
http.callback {
|
120
|
-
status http.response_header.status
|
121
|
-
body "ok"
|
122
|
-
}
|
123
|
-
http.errback {
|
124
|
-
raise "boom"
|
125
|
-
}
|
126
|
-
end
|
127
|
-
error Exception do
|
128
|
-
e = request.env['sinatra.error']
|
129
|
-
"#{e.class.name}: #{e.message}"
|
130
|
-
end
|
131
|
-
end
|
132
|
-
EM.run { EM.add_timer(1) { EM.stop }; aget '/' }
|
133
|
-
last_response.status.should == 500
|
134
|
-
last_response.body.should == "RuntimeError: boom"
|
135
|
-
end
|
136
|
-
|
137
|
-
it "should correctly deal with halts thrown from within EM callbacks" do
|
138
|
-
@app = server do
|
139
|
-
register Sinatra::Async
|
140
|
-
disable :raise_errors, :show_exceptions
|
141
|
-
aget '/' do
|
142
|
-
url = "http://doesnotexist.local/whatever"
|
143
|
-
http = EM::HttpRequest.new(url).get
|
144
|
-
http.callback {
|
145
|
-
status http.response_header.status
|
146
|
-
body "ok"
|
147
|
-
}
|
148
|
-
http.errback {
|
149
|
-
halt 503, "error: #{http.errors.inspect}"
|
150
|
-
}
|
151
|
-
end
|
152
|
-
error 503 do
|
153
|
-
"503: #{response.body.to_s}"
|
154
|
-
end
|
155
|
-
end
|
156
|
-
EM.run { EM.add_timer(1) { EM.stop }; aget '/' }
|
157
|
-
last_response.status.should == 503
|
158
|
-
last_response.body.should == "503: error: \"unable to resolve server address\""
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|