async_sinatra 0.2.1 → 0.2.2
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.
- 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
|