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