angelo 0.1.2 → 0.1.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile +1 -0
- data/lib/angelo/responder/websocket.rb +11 -3
- data/lib/angelo/rspec/helpers.rb +58 -35
- data/lib/angelo/server.rb +1 -0
- data/lib/angelo/version.rb +1 -1
- data/spec/angelo/mustermann_spec.rb +1 -0
- data/spec/angelo/static_spec.rb +13 -0
- data/spec/angelo/websocket_spec.rb +151 -49
- data/spec/spec_helper.rb +74 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb21b9dcec8959a4845e5e6063ae0051b71589c4
|
4
|
+
data.tar.gz: 88e2ca4bd79df106deaa35c7509d22b237e5918a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a468e2ad3a12a7b3a7fab94816d13141f25675651d456651470c287b1773ef9e766b3a3eba7e45cb6310159caeef32b222026cc2853bded2c7b78ce6d6ccd4ad
|
7
|
+
data.tar.gz: f844a3b8410c35392490058ee699202b7a29f4b9df923635ec8500485789832db69f470db62f39e4001842cde0581b178207ab9d4bd80bff6ca5175bf85c3014
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
changelog
|
2
2
|
=========
|
3
3
|
|
4
|
+
### 0.1.3 24 mar 2014
|
5
|
+
|
6
|
+
* better testing of websockets
|
7
|
+
* slightly better handling of socket errors
|
8
|
+
* static file content type fix
|
9
|
+
|
4
10
|
### 0.1.2 5 mar 2014 burnout, denial
|
5
11
|
|
6
12
|
* basic static file support (i.e. /public files)
|
data/Gemfile
CHANGED
@@ -25,15 +25,23 @@ module Angelo
|
|
25
25
|
end
|
26
26
|
rescue IOError => ioe
|
27
27
|
warn "#{ioe.class} - #{ioe.message}"
|
28
|
-
|
29
|
-
@base.websockets.delete @websocket
|
28
|
+
close_websocket
|
30
29
|
rescue => e
|
31
30
|
error e.message
|
32
31
|
::STDERR.puts e.backtrace
|
33
|
-
|
32
|
+
begin
|
33
|
+
@connection.close
|
34
|
+
rescue Reel::Connection::StateError => rcse
|
35
|
+
close_websocket
|
36
|
+
end
|
34
37
|
end
|
35
38
|
end
|
36
39
|
|
40
|
+
def close_websocket
|
41
|
+
@websocket.close
|
42
|
+
@base.websockets.delete @websocket
|
43
|
+
end
|
44
|
+
|
37
45
|
end
|
38
46
|
|
39
47
|
end
|
data/lib/angelo/rspec/helpers.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'websocket/driver'
|
2
|
+
|
1
3
|
module Angelo
|
2
4
|
module RSpec
|
3
5
|
|
@@ -15,6 +17,7 @@ module Angelo
|
|
15
17
|
app.class_eval { content_type :html } # reset
|
16
18
|
app.class_eval &block
|
17
19
|
@server = Angelo::Server.new app
|
20
|
+
$reactor = Reactor.new unless $reactor.alive?
|
18
21
|
end
|
19
22
|
|
20
23
|
after do
|
@@ -42,31 +45,19 @@ module Angelo
|
|
42
45
|
end
|
43
46
|
end
|
44
47
|
|
45
|
-
def socket path, params = {}
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
"Upgrade" => "websocket",
|
59
|
-
"Connection" => "Upgrade",
|
60
|
-
"Sec-WebSocket-Key" => "dGhlIHNhbXBsZSBub25jZQ==",
|
61
|
-
"Origin" => "http://example.com",
|
62
|
-
"Sec-WebSocket-Protocol" => "chat, superchat",
|
63
|
-
"Sec-WebSocket-Version" => "13"
|
64
|
-
}
|
65
|
-
|
66
|
-
client << handshake.to_data
|
67
|
-
yield WebsocketHelper.new client
|
68
|
-
ensure
|
69
|
-
client.close
|
48
|
+
def socket path, params = {}
|
49
|
+
params = params.keys.reduce([]) {|a,k|
|
50
|
+
a << CGI.escape(k) + '=' + CGI.escape(params[k])
|
51
|
+
a
|
52
|
+
}.join('&')
|
53
|
+
|
54
|
+
path += "?#{params}" unless params.empty?
|
55
|
+
wsh = WebsocketHelper.new DEFAULT_ADDR, DEFAULT_PORT, path
|
56
|
+
if block_given?
|
57
|
+
yield wsh
|
58
|
+
wsh.close
|
59
|
+
else
|
60
|
+
return wsh
|
70
61
|
end
|
71
62
|
end
|
72
63
|
|
@@ -85,23 +76,55 @@ module Angelo
|
|
85
76
|
end
|
86
77
|
|
87
78
|
class WebsocketHelper
|
79
|
+
include Celluloid::Logger
|
80
|
+
|
81
|
+
extend Forwardable
|
82
|
+
def_delegator :@socket, :write
|
83
|
+
def_delegators :@driver, :binary, :close, :text
|
84
|
+
|
85
|
+
attr_reader :driver, :socket
|
86
|
+
attr_writer :addr, :port, :path, :on_close, :on_message, :on_open
|
87
|
+
|
88
|
+
def initialize addr, port, path
|
89
|
+
@addr, @port, @path = addr, port, path
|
90
|
+
end
|
91
|
+
|
92
|
+
def init
|
93
|
+
init_socket
|
94
|
+
init_driver
|
95
|
+
end
|
88
96
|
|
89
|
-
def
|
90
|
-
|
91
|
-
@
|
97
|
+
def init_socket
|
98
|
+
ip = @addr
|
99
|
+
ip = Socket.getaddrinfo(@addr, 'http')[0][3] unless @addr =~ /\d+\.\d+\.\d+\.\d+/
|
100
|
+
@socket = Celluloid::IO::TCPSocket.new ip, @port
|
92
101
|
end
|
93
102
|
|
94
|
-
def
|
95
|
-
@
|
103
|
+
def init_driver
|
104
|
+
@driver = WebSocket::Driver.client self
|
105
|
+
|
106
|
+
@driver.on :open do |e|
|
107
|
+
@on_open.call(e) if Proc === @on_open
|
108
|
+
end
|
109
|
+
|
110
|
+
@driver.on :message do |e|
|
111
|
+
@on_message.call(e) if Proc === @on_message
|
112
|
+
end
|
113
|
+
|
114
|
+
@driver.on :close do |e|
|
115
|
+
@on_close.call(e) if Proc === @on_close
|
116
|
+
end
|
96
117
|
end
|
97
118
|
|
98
|
-
def
|
99
|
-
@
|
119
|
+
def url
|
120
|
+
WS_URL % [@addr, @port] + @path
|
100
121
|
end
|
101
122
|
|
102
|
-
def
|
103
|
-
|
104
|
-
msg
|
123
|
+
def go
|
124
|
+
@driver.start
|
125
|
+
while msg = @socket.readpartial(4096)
|
126
|
+
@driver.parse msg
|
127
|
+
end
|
105
128
|
end
|
106
129
|
|
107
130
|
end
|
data/lib/angelo/server.rb
CHANGED
data/lib/angelo/version.rb
CHANGED
data/spec/angelo/static_spec.rb
CHANGED
@@ -54,6 +54,19 @@ describe Angelo::Server do
|
|
54
54
|
expect(last_response.status).to be(304)
|
55
55
|
end
|
56
56
|
|
57
|
+
it 'serves proper content-types' do
|
58
|
+
{ 'test.js' => 'application/javascript',
|
59
|
+
'test.html' => 'text/html',
|
60
|
+
'test.css' => 'text/css',
|
61
|
+
'what.png' => 'image/png' }.each do |k,v|
|
62
|
+
|
63
|
+
get "/#{k}"
|
64
|
+
expect(last_response.status).to be(200)
|
65
|
+
expect(last_response.headers['Content-Type']).to eq(v)
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
57
70
|
end
|
58
71
|
|
59
72
|
end
|
@@ -1,14 +1,32 @@
|
|
1
1
|
require_relative '../spec_helper'
|
2
2
|
|
3
|
-
CK = 'ANGELO_CONCURRENCY' # concurrency key
|
4
|
-
DC = 5 # default concurrency
|
5
|
-
|
6
3
|
describe Angelo::WebsocketResponder do
|
7
4
|
|
8
|
-
|
5
|
+
def socket_wait_for path, latch, expectation, key = :swf, &block
|
6
|
+
Reactor.testers[key] = Array.new(CONCURRENCY).map do
|
7
|
+
wsh = socket path
|
8
|
+
wsh.on_message = ->(e) {
|
9
|
+
expectation[e] if Proc === expectation
|
10
|
+
latch.count_down
|
11
|
+
}
|
12
|
+
wsh.init
|
13
|
+
wsh
|
14
|
+
end
|
15
|
+
action = (key.to_s + '_go').to_sym
|
16
|
+
Reactor.define_action action do |n|
|
17
|
+
every(0.01){ terminate if Reactor.stop? }
|
18
|
+
Reactor.testers[key][n].go
|
19
|
+
end
|
20
|
+
Reactor.unstop!
|
21
|
+
CONCURRENCY.times {|n| $reactor.async.__send__(action, n)}
|
22
|
+
|
23
|
+
sleep 0.01 * CONCURRENCY
|
24
|
+
yield
|
9
25
|
|
10
|
-
|
11
|
-
|
26
|
+
Reactor.testers[key].map &:close
|
27
|
+
Reactor.stop!
|
28
|
+
Reactor.testers.delete key
|
29
|
+
Reactor.remove_action action
|
12
30
|
end
|
13
31
|
|
14
32
|
describe 'basics' do
|
@@ -22,25 +40,66 @@ describe Angelo::WebsocketResponder do
|
|
22
40
|
end
|
23
41
|
|
24
42
|
it 'responds on websockets properly' do
|
25
|
-
socket '/' do |
|
26
|
-
|
27
|
-
|
28
|
-
|
43
|
+
socket '/' do |wsh|
|
44
|
+
latch = CountDownLatch.new 500
|
45
|
+
|
46
|
+
wsh.on_message = ->(e) {
|
47
|
+
expect(e.data).to match(/hi there \d/)
|
48
|
+
latch.count_down
|
29
49
|
}
|
50
|
+
|
51
|
+
wsh.init
|
52
|
+
Reactor.testers[:tester] = wsh
|
53
|
+
Reactor.define_action :go do
|
54
|
+
every(0.01){ terminate if Reactor.stop? }
|
55
|
+
Reactor.testers[:tester].go
|
56
|
+
end
|
57
|
+
Reactor.unstop!
|
58
|
+
$reactor.async.go
|
59
|
+
|
60
|
+
500.times {|n| wsh.text "hi there #{n}"}
|
61
|
+
latch.wait
|
62
|
+
|
63
|
+
Reactor.stop!
|
64
|
+
Reactor.testers.delete :tester
|
65
|
+
Reactor.remove_action :go
|
30
66
|
end
|
31
67
|
end
|
32
68
|
|
33
69
|
it 'responds on multiple websockets properly' do
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
70
|
+
latch = CountDownLatch.new CONCURRENCY * 500
|
71
|
+
|
72
|
+
Reactor.testers[:wshs] = Array.new(CONCURRENCY).map do
|
73
|
+
wsh = socket '/'
|
74
|
+
wsh.on_message = ->(e) {
|
75
|
+
expect(e.data).to match(/hi there \d/)
|
76
|
+
latch.count_down
|
77
|
+
}
|
78
|
+
wsh.init
|
79
|
+
wsh
|
80
|
+
end
|
81
|
+
|
82
|
+
Reactor.define_action :go do |n|
|
83
|
+
every(0.01){ terminate if Reactor.stop? }
|
84
|
+
Reactor.testers[:wshs][n].go
|
85
|
+
end
|
86
|
+
Reactor.unstop!
|
87
|
+
CONCURRENCY.times {|n| $reactor.async.go n}
|
88
|
+
|
89
|
+
sleep 0.01 * CONCURRENCY
|
90
|
+
|
91
|
+
ActorPool.define_action :go do |n|
|
92
|
+
500.times {|x| Reactor.testers[:wshs][n].text "hi there #{x}"}
|
43
93
|
end
|
94
|
+
CONCURRENCY.times {|n| $pool.async.go n}
|
95
|
+
latch.wait
|
96
|
+
|
97
|
+
Reactor.testers[:wshs].map &:close
|
98
|
+
Reactor.stop!
|
99
|
+
Reactor.testers.delete :wshs
|
100
|
+
Reactor.remove_action :go
|
101
|
+
|
102
|
+
ActorPool.remove_action :go
|
44
103
|
end
|
45
104
|
|
46
105
|
end
|
@@ -67,15 +126,16 @@ describe Angelo::WebsocketResponder do
|
|
67
126
|
|
68
127
|
it 'works with http requests' do
|
69
128
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
129
|
+
latch = CountDownLatch.new CONCURRENCY * Angelo::HTTPABLE.length
|
130
|
+
|
131
|
+
expectation = ->(e){
|
132
|
+
expect(e.data).to match(/from http (#{Angelo::HTTPABLE.map(&:to_s).join('|')})/)
|
133
|
+
}
|
75
134
|
|
76
|
-
|
77
|
-
|
78
|
-
|
135
|
+
socket_wait_for '/concur', latch, expectation do
|
136
|
+
Angelo::HTTPABLE.each {|m| __send__ m, '/concur', foo: 'http'}
|
137
|
+
latch.wait
|
138
|
+
end
|
79
139
|
|
80
140
|
end
|
81
141
|
|
@@ -83,7 +143,7 @@ describe Angelo::WebsocketResponder do
|
|
83
143
|
|
84
144
|
describe 'helper contexts' do
|
85
145
|
let(:obj){ {'foo' => 'bar'} }
|
86
|
-
let(:wait_for_block){ ->(
|
146
|
+
let(:wait_for_block){ ->(e){ expect(JSON.parse(e.data)).to eq(obj) }}
|
87
147
|
|
88
148
|
define_app do
|
89
149
|
|
@@ -126,38 +186,80 @@ describe Angelo::WebsocketResponder do
|
|
126
186
|
end
|
127
187
|
|
128
188
|
it 'handles single context' do
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
189
|
+
latch = CountDownLatch.new CONCURRENCY
|
190
|
+
socket_wait_for '/', latch, wait_for_block do
|
191
|
+
post '/', obj
|
192
|
+
latch.wait
|
193
|
+
end
|
133
194
|
end
|
134
195
|
|
135
196
|
it 'handles multiple contexts' do
|
136
|
-
ts = socket_wait_for '/', &wait_for_block
|
137
|
-
one_ts = socket_wait_for '/one', &wait_for_block
|
138
|
-
other_ts = socket_wait_for '/other', &wait_for_block
|
139
197
|
|
140
|
-
|
141
|
-
|
198
|
+
latch = CountDownLatch.new CONCURRENCY
|
199
|
+
|
200
|
+
Reactor.testers[:hmc] = Array.new(CONCURRENCY).map do
|
201
|
+
wsh = socket '/'
|
202
|
+
wsh.on_message = ->(e) {
|
203
|
+
wait_for_block[e]
|
204
|
+
latch.count_down
|
205
|
+
}
|
206
|
+
wsh.init
|
207
|
+
wsh
|
208
|
+
end
|
142
209
|
|
143
|
-
|
144
|
-
one_ts.each &:join
|
145
|
-
other_ts.each {|t| t.should be_alive}
|
210
|
+
one_latch = CountDownLatch.new CONCURRENCY
|
146
211
|
|
147
|
-
|
148
|
-
|
212
|
+
Reactor.testers[:hmc_one] = Array.new(CONCURRENCY).map do
|
213
|
+
wsh = socket '/one'
|
214
|
+
wsh.on_message = ->(e) {
|
215
|
+
wait_for_block[e]
|
216
|
+
one_latch.count_down
|
217
|
+
}
|
218
|
+
wsh.init
|
219
|
+
wsh
|
220
|
+
end
|
149
221
|
|
150
|
-
|
151
|
-
one_ts.each {|t| t.should_not be_alive}
|
152
|
-
other_ts.each &:join
|
222
|
+
other_latch = CountDownLatch.new CONCURRENCY
|
153
223
|
|
154
|
-
|
224
|
+
Reactor.testers[:hmc_other] = Array.new(CONCURRENCY).map do
|
225
|
+
wsh = socket '/other'
|
226
|
+
wsh.on_message = ->(e) {
|
227
|
+
wait_for_block[e]
|
228
|
+
other_latch.count_down
|
229
|
+
}
|
230
|
+
wsh.init
|
231
|
+
wsh
|
232
|
+
end
|
233
|
+
|
234
|
+
Reactor.define_action :go do |k, n|
|
235
|
+
Reactor.testers[k][n].go
|
236
|
+
end
|
237
|
+
Reactor.unstop!
|
238
|
+
CONCURRENCY.times do |n|
|
239
|
+
[:hmc, :hmc_one, :hmc_other].each do |k|
|
240
|
+
$reactor.async.go k, n
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
sleep 0.01 * CONCURRENCY
|
245
|
+
|
246
|
+
post '/one', obj
|
247
|
+
one_latch.wait
|
155
248
|
post '/', obj
|
249
|
+
latch.wait
|
250
|
+
post '/other', obj
|
251
|
+
other_latch.wait
|
156
252
|
|
157
|
-
|
158
|
-
|
159
|
-
|
253
|
+
[:hmc, :hmc_one, :hmc_other].each do |k|
|
254
|
+
Reactor.testers[k].map &:close
|
255
|
+
end
|
256
|
+
Reactor.stop!
|
257
|
+
[:hmc, :hmc_one, :hmc_other].each do |k|
|
258
|
+
Reactor.testers.delete k
|
259
|
+
end
|
260
|
+
Reactor.remove_action :go
|
160
261
|
end
|
161
262
|
|
162
263
|
end
|
264
|
+
|
163
265
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -8,3 +8,77 @@ Celluloid.logger.level = ::Logger::ERROR
|
|
8
8
|
include Angelo::RSpec::Helpers
|
9
9
|
|
10
10
|
TEST_APP_ROOT = File.expand_path '../test_app_root', __FILE__
|
11
|
+
|
12
|
+
CK = 'ANGELO_CONCURRENCY' # concurrency key
|
13
|
+
DC = 5 # default concurrency
|
14
|
+
CONCURRENCY = ENV.key?(CK) ? ENV[CK].to_i : DC
|
15
|
+
|
16
|
+
# https://gist.github.com/tkareine/739662
|
17
|
+
#
|
18
|
+
class CountDownLatch
|
19
|
+
attr_reader :count
|
20
|
+
|
21
|
+
def initialize(to)
|
22
|
+
@count = to.to_i
|
23
|
+
raise ArgumentError, "cannot count down from negative integer" unless @count >= 0
|
24
|
+
@lock = Mutex.new
|
25
|
+
@condition = ConditionVariable.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def count_down
|
29
|
+
@lock.synchronize do
|
30
|
+
@count -= 1 if @count > 0
|
31
|
+
@condition.broadcast if @count == 0
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def wait
|
36
|
+
@lock.synchronize do
|
37
|
+
@condition.wait(@lock) while @count > 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
module Cellper
|
44
|
+
|
45
|
+
@@stop = false
|
46
|
+
@@testers = {}
|
47
|
+
|
48
|
+
def define_action sym, &block
|
49
|
+
define_method sym, &block
|
50
|
+
end
|
51
|
+
|
52
|
+
def remove_action sym
|
53
|
+
remove_method sym
|
54
|
+
end
|
55
|
+
|
56
|
+
def unstop!
|
57
|
+
@@stop = false
|
58
|
+
end
|
59
|
+
|
60
|
+
def stop!
|
61
|
+
@@stop = true
|
62
|
+
end
|
63
|
+
|
64
|
+
def stop?
|
65
|
+
@@stop
|
66
|
+
end
|
67
|
+
|
68
|
+
def testers; @@testers; end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
class Reactor
|
73
|
+
include Celluloid::IO
|
74
|
+
extend Cellper
|
75
|
+
end
|
76
|
+
|
77
|
+
$reactor = Reactor.new
|
78
|
+
|
79
|
+
class ActorPool
|
80
|
+
include Celluloid
|
81
|
+
extend Cellper
|
82
|
+
end
|
83
|
+
|
84
|
+
$pool = ActorPool.pool size: CONCURRENCY
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: angelo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kenichi Nakamura
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-03-
|
11
|
+
date: 2014-03-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: reel
|