em-breakout 0.0.1
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/.gitignore +8 -0
- data/Gemfile +7 -0
- data/README.md +19 -0
- data/_LICENSE +20 -0
- data/em-breakout.gemspec +26 -0
- data/examples/breakout.rb +10 -0
- data/features/api.feature +101 -0
- data/features/api_format.feature +38 -0
- data/features/connect.feature +54 -0
- data/features/notify.feature +19 -0
- data/features/performance.feature +8 -0
- data/features/ping.feature +21 -0
- data/features/security.feature +47 -0
- data/features/serialize.feature +17 -0
- data/features/step_definitions/common_steps.rb +155 -0
- data/features/step_definitions/performance_steps.rb +46 -0
- data/features/support/em_control +35 -0
- data/features/support/env.rb +13 -0
- data/features/support/server.rb +18 -0
- data/features/support/socket.rb +7 -0
- data/lib/em-breakout/browser.rb +94 -0
- data/lib/em-breakout/connection.rb +28 -0
- data/lib/em-breakout/exception.rb +10 -0
- data/lib/em-breakout/grid.rb +44 -0
- data/lib/em-breakout/server.rb +29 -0
- data/lib/em-breakout/version.rb +5 -0
- data/lib/em-breakout/worker.rb +136 -0
- data/lib/em-breakout.rb +11 -0
- metadata +97 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# EM-Breakout
|
2
|
+
|
3
|
+
Breakout is a light framework for routing messages among web browsers and workers using WebSockets.
|
4
|
+
|
5
|
+
EM-Breakout uses [EM-WebSocket](https://github.com/igrigorik/em-websocket) to implement a standalone server process that accepts connections from both browsers and workers.
|
6
|
+
Whenever a browser sends a message, it will be put on a queue to be read by the next available worker.
|
7
|
+
A simple API lets workers send messages to browsers, disconnect a browser, and be notified when a browser connects or disconnects.
|
8
|
+
|
9
|
+
The [breakout](https://github.com/steve9001/breakout) gem provides a module to help create workers along with some example workers and JavaScripts.
|
10
|
+
|
11
|
+
## Getting started
|
12
|
+
|
13
|
+
Clone the repository and change to the directory. Use [bundler](http://gembundler.com) to install the dependencies, and run cucumber. If that is successful, you can change into the examples directory and run the server script (possibly with 'bundle exec').
|
14
|
+
|
15
|
+
Your em-breakout server is now ready to accept connections. Visit the [breakout](https://github.com/steve9001/breakout) page and pick up from there!
|
16
|
+
|
17
|
+
## Copyright
|
18
|
+
|
19
|
+
Copyright (c) 2011 Steve Masterman. See LICENSE for details.
|
data/_LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Steve Masterman
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/em-breakout.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "em-breakout/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "em-breakout"
|
7
|
+
s.version = EventMachine::Breakout::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Steve Masterman"]
|
10
|
+
s.email = ["steve@vermonster.com"]
|
11
|
+
s.homepage = "https://github.com/steve9001/em-breakout"
|
12
|
+
s.summary = %q{Breakout routes messages among web browsers and workers using WebSockets.}
|
13
|
+
s.description = %q{Breakout routes messages among web browsers and workers using WebSockets.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "em-breakout"
|
16
|
+
|
17
|
+
s.add_dependency 'json', '>=1.4.6'
|
18
|
+
s.add_dependency 'em-websocket', '>=0.2.1'
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
|
@@ -0,0 +1,101 @@
|
|
1
|
+
Feature: server api
|
2
|
+
|
3
|
+
A worker can send commands to the server
|
4
|
+
|
5
|
+
Scenario: disconnect
|
6
|
+
Given worker 1 opens a url for work
|
7
|
+
And browser 1 opens a url
|
8
|
+
Then browser 1 should be connected
|
9
|
+
When worker 1 sends disconnect for browser 1
|
10
|
+
Then browser 1 should not be connected
|
11
|
+
And worker 1 should be connected
|
12
|
+
|
13
|
+
Scenario: done work
|
14
|
+
Given worker 1 opens a url for work
|
15
|
+
And browser 1 opens a url
|
16
|
+
When browser 1 sends "one"
|
17
|
+
And browser 1 sends "two"
|
18
|
+
And worker 1 receives a payload
|
19
|
+
Then worker 1 should not have a payload
|
20
|
+
When worker 1 sends done_work
|
21
|
+
And worker 1 receives a payload
|
22
|
+
Then worker 1's payload message should be "two"
|
23
|
+
|
24
|
+
Scenario: done work no requeue
|
25
|
+
Given worker 1 opens a url for work
|
26
|
+
And browser 1 opens a url
|
27
|
+
When browser 1 sends "one"
|
28
|
+
And browser 1 sends "two"
|
29
|
+
And worker 1 receives a payload
|
30
|
+
Then worker 1 should not have a payload
|
31
|
+
When worker 1 sends done_work no requeue
|
32
|
+
Then worker 1 should not have a payload
|
33
|
+
When worker 1 sends done_work
|
34
|
+
And worker 1 receives a payload
|
35
|
+
Then worker 1's payload message should be "two"
|
36
|
+
|
37
|
+
Scenario: done work no requeue
|
38
|
+
Given worker 1 opens a url for work
|
39
|
+
And worker 2 opens a url for work
|
40
|
+
When browser 1 opens a url
|
41
|
+
And browser 1 sends "one"
|
42
|
+
And worker 1 receives a payload
|
43
|
+
And worker 1 sends done_work
|
44
|
+
And browser 1 sends "two"
|
45
|
+
And browser 1 sends "three"
|
46
|
+
And worker 2 receives a payload
|
47
|
+
Then worker 1 should not have a payload
|
48
|
+
When worker 2 sends done_work no requeue
|
49
|
+
And worker 1 receives a payload
|
50
|
+
Then worker 1's payload message should be "three"
|
51
|
+
|
52
|
+
Scenario: done work disconnected browser
|
53
|
+
Given worker 1 opens a url for work
|
54
|
+
And worker 2 opens a url for work
|
55
|
+
And browser 1 opens a url with notify
|
56
|
+
And worker 1 receives a payload
|
57
|
+
Then worker 1's payload message should be "/open"
|
58
|
+
And browser 1 disconnects
|
59
|
+
Then worker 2 should not have a payload
|
60
|
+
When worker 1 sends done_work
|
61
|
+
And worker 2 receives a payload
|
62
|
+
Then worker 2's payload message should be "/close"
|
63
|
+
|
64
|
+
Scenario: done work disconnected browser
|
65
|
+
Given worker 1 opens a url for work
|
66
|
+
And browser 1 opens a url with notify
|
67
|
+
When browser 1 sends "one"
|
68
|
+
And browser 1 sends "two"
|
69
|
+
And worker 1 receives a payload
|
70
|
+
Then worker 1's payload message should be "/open"
|
71
|
+
And browser 1 disconnects
|
72
|
+
When worker 1 sends done_work
|
73
|
+
And worker 1 receives a payload
|
74
|
+
Then worker 1's payload message should be "/close"
|
75
|
+
|
76
|
+
Scenario: done work disconnected browser
|
77
|
+
Given worker 1 opens a url for work
|
78
|
+
And browser 1 opens a url with notify
|
79
|
+
When browser 1 sends "one"
|
80
|
+
And browser 1 sends "two"
|
81
|
+
And worker 1 receives a payload
|
82
|
+
Then worker 1's payload message should be "/open"
|
83
|
+
When worker 1 sends done_work
|
84
|
+
And worker 1 receives a payload
|
85
|
+
And browser 1 disconnects
|
86
|
+
When worker 1 sends done_work
|
87
|
+
And worker 1 receives a payload
|
88
|
+
Then worker 1's payload message should be "/close"
|
89
|
+
|
90
|
+
Scenario: done work disconnected browser no requeue
|
91
|
+
Given worker 1 opens a url for work
|
92
|
+
And browser 1 opens a url with notify
|
93
|
+
And worker 1 receives a payload
|
94
|
+
Then worker 1's payload message should be "/open"
|
95
|
+
When browser 1 disconnects
|
96
|
+
And worker 2 opens a url for work
|
97
|
+
Then worker 1 should not have a payload
|
98
|
+
And worker 2 should not have a payload
|
99
|
+
When worker 1 sends done_work no requeue
|
100
|
+
And worker 2 receives a payload
|
101
|
+
Then worker 2's payload message should be "/close"
|
@@ -0,0 +1,38 @@
|
|
1
|
+
Feature: server api disconnects on bad message
|
2
|
+
|
3
|
+
Workers must send a json hash
|
4
|
+
Send messages requires
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given worker 1 opens a url
|
8
|
+
Then worker 1 should be connected
|
9
|
+
|
10
|
+
Scenario: not json
|
11
|
+
When worker 1 sends_eval "nil"
|
12
|
+
When worker 1 receives a payload
|
13
|
+
Then worker 1's payload should be "message must be JSON encoded"
|
14
|
+
Then worker 1 should not be connected
|
15
|
+
|
16
|
+
Scenario: not dictionary
|
17
|
+
When worker 1 sends_eval "Array.new"
|
18
|
+
When worker 1 receives a payload
|
19
|
+
Then worker 1's payload should be "message must be dictionary"
|
20
|
+
Then worker 1 should not be connected
|
21
|
+
|
22
|
+
Scenario: send_messages not hash
|
23
|
+
When worker 1 sends_eval "{ :send_messages => Array.new }"
|
24
|
+
When worker 1 receives a payload
|
25
|
+
Then worker 1's payload should be "send_messages must be dictionary"
|
26
|
+
Then worker 1 should not be connected
|
27
|
+
|
28
|
+
Scenario: send_messages keys must be strings
|
29
|
+
When worker 1 sends_eval "{ :send_messages => { 3 => nil } }"
|
30
|
+
When worker 1 receives a payload
|
31
|
+
Then worker 1's payload should be "send_messages keys must be strings and values must be arrays"
|
32
|
+
Then worker 1 should not be connected
|
33
|
+
|
34
|
+
Scenario: send_messages values must be arrays
|
35
|
+
When worker 1 sends_eval "{ :send_messages => { '3' => nil } }"
|
36
|
+
When worker 1 receives a payload
|
37
|
+
Then worker 1's payload should be "send_messages keys must be strings and values must be arrays"
|
38
|
+
Then worker 1 should not be connected
|
@@ -0,0 +1,54 @@
|
|
1
|
+
Feature: worker and browser connect
|
2
|
+
|
3
|
+
Workers can connect, disconnect and reconnect with worker url
|
4
|
+
Browsers can connect, disconnect with browser url
|
5
|
+
|
6
|
+
Scenario: worker connects
|
7
|
+
When worker 1 opens a url
|
8
|
+
Then worker 1 should be connected
|
9
|
+
|
10
|
+
Scenario: browser connects to unknown grid
|
11
|
+
When browser 1 opens a url
|
12
|
+
Then browser 1 should not be connected
|
13
|
+
|
14
|
+
Scenario: worker and browser connect
|
15
|
+
When worker 1 opens a url
|
16
|
+
And browser 1 opens a url
|
17
|
+
Then browser 1 should be connected
|
18
|
+
And worker 1 should be connected
|
19
|
+
|
20
|
+
Scenario: worker reconnects
|
21
|
+
When worker 1 opens a url
|
22
|
+
Then worker 1 should be connected
|
23
|
+
When worker 1 disconnects
|
24
|
+
And worker 1 opens a url
|
25
|
+
Then worker 1 should be connected
|
26
|
+
|
27
|
+
Scenario: browser disconnected when worker d/c'd
|
28
|
+
When worker 1 opens a url
|
29
|
+
And browser 1 opens a url
|
30
|
+
Then browser 1 should be connected
|
31
|
+
When worker 1 disconnects
|
32
|
+
Then browser 1 should not be connected
|
33
|
+
|
34
|
+
Scenario: queued browser work is removed when browser d/c's
|
35
|
+
When worker 1 opens a url
|
36
|
+
And browser 1 opens a url
|
37
|
+
And browser 1 sends "one"
|
38
|
+
And browser 1 disconnects
|
39
|
+
When worker 1 sends done_work
|
40
|
+
Then worker 1 should not have a payload
|
41
|
+
When browser 2 opens a url
|
42
|
+
And browser 2 sends "two"
|
43
|
+
And worker 1 receives a payload
|
44
|
+
Then worker 1's payload message should be "two"
|
45
|
+
|
46
|
+
Scenario: browser disconnected when workers d/c'd
|
47
|
+
When worker 1 opens a url
|
48
|
+
And browser 1 opens a url
|
49
|
+
And worker 2 opens a url
|
50
|
+
Then browser 1 should be connected
|
51
|
+
When worker 1 disconnects
|
52
|
+
Then browser 1 should be connected
|
53
|
+
When worker 2 disconnects
|
54
|
+
Then browser 1 should not be connected
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Feature: notify
|
2
|
+
In order to know when browsers connect and disconnect
|
3
|
+
The url can include notify=true
|
4
|
+
The server will queue a message "from" the browser
|
5
|
+
The message is either "/open" or "/close"
|
6
|
+
|
7
|
+
Scenario: notify on connect and disconnect
|
8
|
+
Given worker 1 opens a url for work
|
9
|
+
When browser 1 opens a url with notify
|
10
|
+
And worker 1 receives a payload
|
11
|
+
Then worker 1's payload route should be "test"
|
12
|
+
Then worker 1's payload bid should be "1"
|
13
|
+
And worker 1's payload message should be "/open"
|
14
|
+
Given worker 1 sends done_work
|
15
|
+
When browser 1 disconnects
|
16
|
+
And worker 1 receives a payload
|
17
|
+
Then worker 1's payload route should be "test"
|
18
|
+
Then worker 1's payload bid should be "1"
|
19
|
+
Then worker 1's payload message should be "/close"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Feature: two-way asynchronous message passing
|
2
|
+
|
3
|
+
A worker connects to the em-breakout server and receives messages sent from the server
|
4
|
+
The messages always originate from a specific browser identified by bid
|
5
|
+
The worker interacts with browsers through a server api, which includes
|
6
|
+
Sending a message to one or more browsers identified by bid
|
7
|
+
Disconnecting one or more browsers from the server
|
8
|
+
The api also includes a the done_work command to inform the server that the browser message is done being processed
|
9
|
+
|
10
|
+
Scenario: browser pings worker
|
11
|
+
When worker 1 opens a url for work
|
12
|
+
And browser 1 opens a url for "ping"
|
13
|
+
When browser 1 sends "ping"
|
14
|
+
And browser 1 sends "ping 2"
|
15
|
+
And worker 1 receives a payload
|
16
|
+
Then worker 1's payload route should be "ping"
|
17
|
+
And worker 1's payload message should be "ping"
|
18
|
+
When worker 1 sends message "pong" to browser 1
|
19
|
+
And browser 1 receives a payload
|
20
|
+
Then browser 1's payload should be "pong"
|
21
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
Feature: security
|
2
|
+
In order to control access to my workers
|
3
|
+
As a browser url publisher
|
4
|
+
I want my urls to be tamper-proof
|
5
|
+
|
6
|
+
Scenario: unknown grid
|
7
|
+
When browser 1 opens a url
|
8
|
+
And browser 1 receives a payload
|
9
|
+
Then browser 1's payload should be "unknown grid"
|
10
|
+
And browser 1 should be disconnected
|
11
|
+
|
12
|
+
Scenario: bid in use
|
13
|
+
Given worker 1 opens a url
|
14
|
+
When browser 1 opens a url with bid "foo"
|
15
|
+
And browser 2 opens a url with bid "foo"
|
16
|
+
And browser 2 receives a payload
|
17
|
+
Then browser 2's payload should be "bid in use"
|
18
|
+
And browser 2 should be disconnected
|
19
|
+
And browser 1 should be connected
|
20
|
+
|
21
|
+
Scenario: expired link
|
22
|
+
Given worker 1 opens a url
|
23
|
+
When browser 1 opens an expired url
|
24
|
+
And browser 1 receives a payload
|
25
|
+
Then browser 1's payload should be "expired"
|
26
|
+
|
27
|
+
Scenario: Tamper with route
|
28
|
+
Given worker 1 opens a url
|
29
|
+
When browser 1 opens a tampered url
|
30
|
+
And browser 1 receives a payload
|
31
|
+
Then browser 1's payload should be "invalid url"
|
32
|
+
|
33
|
+
Scenario: invalid worker url
|
34
|
+
When worker 1 opens a url without grid
|
35
|
+
And worker 1 receives a payload
|
36
|
+
Then worker 1's payload should be "invalid grid"
|
37
|
+
And worker 1 should be disconnected
|
38
|
+
|
39
|
+
Scenario: grid is taken / wrong grid key
|
40
|
+
When worker 1 opens a url
|
41
|
+
And the grid_key config is changed
|
42
|
+
When worker 2 opens a url
|
43
|
+
And worker 2 receives a payload
|
44
|
+
Then worker 2's payload should be "invalid grid_key"
|
45
|
+
And worker 2 should be disconnected
|
46
|
+
And worker 1 should be connected
|
47
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Feature: two workers will not get messages from the same browser at the same time
|
2
|
+
In order to keep it simple
|
3
|
+
Messages from a single browser will be queued up
|
4
|
+
And the next message will not be sent to any worker
|
5
|
+
Until the worker receiving the previous message sends done_work
|
6
|
+
|
7
|
+
Scenario: browser sends two messages
|
8
|
+
Given worker 1 opens a url for work
|
9
|
+
And worker 2 opens a url for work
|
10
|
+
When browser 1 opens a url
|
11
|
+
And browser 1 sends "one"
|
12
|
+
And browser 1 sends "two"
|
13
|
+
And worker 1 receives a payload
|
14
|
+
Then worker 2 should not have a payload
|
15
|
+
When worker 1 sends done_work
|
16
|
+
And worker 2 receives a payload
|
17
|
+
Then worker 2's payload message should be "two"
|
@@ -0,0 +1,155 @@
|
|
1
|
+
When /^worker (\d+) opens a url$/ do |id|
|
2
|
+
@worker_by_id[id] = Breakout::Socket.new(Breakout.worker_url)
|
3
|
+
end
|
4
|
+
|
5
|
+
When /^browser (\d+) opens a url for "([^"]*)"$/ do |id, route|
|
6
|
+
@browser_by_id[id] = Breakout::Socket.new(Breakout.browser_url(route, :bid => id))
|
7
|
+
end
|
8
|
+
|
9
|
+
When /^browser (\d+) opens a url with bid "([^"]*)"$/ do |id, bid|
|
10
|
+
@browser_by_id[id] = Breakout::Socket.new(Breakout.browser_url("test", :bid => bid))
|
11
|
+
end
|
12
|
+
|
13
|
+
When /^browser (\d+) opens a url with notify$/ do |id|
|
14
|
+
@browser_by_id[id] = Breakout::Socket.new(Breakout.browser_url("test", :bid => id, :notify => true))
|
15
|
+
end
|
16
|
+
|
17
|
+
When /^worker (\d+) opens a url for work$/ do |id|
|
18
|
+
@worker_by_id[id] = Breakout::Socket.new(Breakout.worker_url)
|
19
|
+
@worker_by_id[id].send :done_work => true
|
20
|
+
end
|
21
|
+
|
22
|
+
When /^browser (\d+) opens a url$/ do |id|
|
23
|
+
When %|browser #{id} opens a url for "test"|
|
24
|
+
end
|
25
|
+
|
26
|
+
When /^browser (\d+) sends "([^"]*)"$/ do |id, msg|
|
27
|
+
@browser_by_id[id].send(msg)
|
28
|
+
end
|
29
|
+
|
30
|
+
When /^worker (\d+) receives a payload$/ do |id|
|
31
|
+
Timeout::timeout(0.1) { @worker_payload_by_id[id] = @worker_by_id[id].receive }
|
32
|
+
end
|
33
|
+
|
34
|
+
Then /^worker (\d+) should not have a payload$/ do |id|
|
35
|
+
->() do
|
36
|
+
When %|worker #{id} receives a payload|
|
37
|
+
end.should raise_error(Timeout::Error)
|
38
|
+
end
|
39
|
+
|
40
|
+
Then /^worker (\d+)'s payload route should be "([^"]*)"$/ do |id, route|
|
41
|
+
@worker_payload_by_id[id].split("\n", 3)[0].should == route
|
42
|
+
end
|
43
|
+
|
44
|
+
Then /^worker (\d+)'s payload message should be "([^"]*)"$/ do |id, msg|
|
45
|
+
@worker_payload_by_id[id].split("\n", 3)[2].should == msg
|
46
|
+
end
|
47
|
+
|
48
|
+
Then /^worker (\d+)'s payload bid should be "([^"]*)"$/ do |id, msg|
|
49
|
+
@worker_payload_by_id[id].split("\n", 3)[1].should == msg
|
50
|
+
end
|
51
|
+
|
52
|
+
Then /^worker (\d+)'s payload should be "([^"]*)"$/ do |id, msg|
|
53
|
+
@worker_payload_by_id[id].should == msg
|
54
|
+
end
|
55
|
+
|
56
|
+
When /^worker (\d+) sends done_work$/ do |id|
|
57
|
+
@worker_by_id[id].send :done_work => true
|
58
|
+
end
|
59
|
+
|
60
|
+
When /^worker (\d+) sends done_work no requeue$/ do |id|
|
61
|
+
@worker_by_id[id].send :done_work => false
|
62
|
+
end
|
63
|
+
|
64
|
+
When /^worker (\d+) sends disconnect for browser (\d+)$/ do |id, bid|
|
65
|
+
@worker_by_id[id].send :disconnect => bid
|
66
|
+
end
|
67
|
+
|
68
|
+
When /^worker (\d+) sends message "([^"]*)" to browser (\d+)$/ do |id, msg, bid|
|
69
|
+
@worker_by_id[id].send :send_messages => { msg => [ bid ] }
|
70
|
+
end
|
71
|
+
|
72
|
+
When /^browser (\d+) receives a payload$/ do |id|
|
73
|
+
Timeout::timeout(1) { @browser_payload_by_id[id] = @browser_by_id[id].receive }
|
74
|
+
end
|
75
|
+
|
76
|
+
Then /^browser (\d+)'s payload should be "([^"]*)"$/ do |id, msg|
|
77
|
+
@browser_payload_by_id[id].should == msg
|
78
|
+
end
|
79
|
+
|
80
|
+
Then /^worker (\d+) should be connected$/ do |id|
|
81
|
+
->() do
|
82
|
+
Timeout::timeout(0.1) { @worker_by_id[id].receive }
|
83
|
+
end.should raise_error(Timeout::Error)
|
84
|
+
end
|
85
|
+
|
86
|
+
Then /^browser (\d+) should be connected$/ do |id|
|
87
|
+
->() do
|
88
|
+
Timeout::timeout(0.1) { @browser_by_id[id].receive }
|
89
|
+
end.should raise_error(Timeout::Error)
|
90
|
+
end
|
91
|
+
|
92
|
+
Then /^browser (\d+) should not be connected$/ do |id|
|
93
|
+
->() do
|
94
|
+
Timeout::timeout(0.1) { @browser_by_id[id].receive }
|
95
|
+
end.should_not raise_error(Timeout::Error)
|
96
|
+
end
|
97
|
+
|
98
|
+
Then /^worker (\d+) should not be connected$/ do |arg1|
|
99
|
+
->() do
|
100
|
+
Timeout::timeout(0.1) { @worker_by_id[id].receive }
|
101
|
+
end.should_not raise_error(Timeout::Error)
|
102
|
+
end
|
103
|
+
|
104
|
+
When /^browser (\d+) disconnects$/ do |id|
|
105
|
+
@browser_by_id[id].close
|
106
|
+
end
|
107
|
+
|
108
|
+
When /^worker (\d+) disconnects$/ do |id|
|
109
|
+
@worker_by_id[id].close
|
110
|
+
end
|
111
|
+
|
112
|
+
Then /^browser (\d+) should be disconnected$/ do |id|
|
113
|
+
Then "browser #{id} should not be connected"
|
114
|
+
end
|
115
|
+
|
116
|
+
Then /^worker (\d+) should be disconnected$/ do |id|
|
117
|
+
Then "worker #{id} should not be connected"
|
118
|
+
end
|
119
|
+
|
120
|
+
When /^browser (\d+) opens an expired url$/ do |id|
|
121
|
+
url = Breakout.browser_url('test', :e => (Time.now - 1).to_i)
|
122
|
+
@browser_by_id[id] = Breakout::Socket.new(url)
|
123
|
+
end
|
124
|
+
|
125
|
+
When /^browser (\d+) opens a tampered url$/ do |id|
|
126
|
+
url = Breakout.browser_url('REALROUTE').gsub(/REALROUTE/, 'TAMPEREDROUTE')
|
127
|
+
@browser_by_id[id] = Breakout::Socket.new(url)
|
128
|
+
end
|
129
|
+
|
130
|
+
When /^worker (\d+) opens a url without grid$/ do |id|
|
131
|
+
url = "ws://#{Breakout::CONFIG[:breakout_host]}:#{Breakout::CONFIG[:worker_port]}/?grid_key=#{Breakout::CONFIG[:grid_key]}"
|
132
|
+
@worker_by_id[id] = Breakout::Socket.new(url)
|
133
|
+
end
|
134
|
+
|
135
|
+
When /^worker (\d+) sends_eval "([^"]*)"$/ do |id, rb|
|
136
|
+
@worker_by_id[id].send eval(rb)
|
137
|
+
end
|
138
|
+
|
139
|
+
When /^worker (\d+) sends not json$/ do |id|
|
140
|
+
@worker_by_id[id].send nil
|
141
|
+
end
|
142
|
+
|
143
|
+
When /^worker (\d+) sends not dictionary$/ do |id|
|
144
|
+
@worker_by_id[id].send Array.new
|
145
|
+
end
|
146
|
+
|
147
|
+
When /^the ([\w]*) config is changed$/ do |option|
|
148
|
+
option = option.to_sym
|
149
|
+
raise option unless Breakout::CONFIG.has_key? option
|
150
|
+
Breakout.config(option => Breakout.random_config[option])
|
151
|
+
end
|
152
|
+
|
153
|
+
Given /^the server is restarted$/ do
|
154
|
+
restart_server
|
155
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
When /^(\d+) workers connect$/ do |total|
|
2
|
+
total = [1, total.to_i].max
|
3
|
+
i = 0
|
4
|
+
while i < total
|
5
|
+
i += 1
|
6
|
+
@worker_by_id[i] = Breakout::Socket.new(Breakout.worker_url)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
When /^(\d+) browsers connect$/ do |total|
|
11
|
+
total = [1, total.to_i].max
|
12
|
+
i = 0
|
13
|
+
while i < total
|
14
|
+
i += 1
|
15
|
+
@browser_by_id[total] = Breakout::Socket.new(Breakout.browser_url('test'))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
When /^a browser sends (\d+) messages$/ do |total|
|
20
|
+
total = [1, total.to_i].max
|
21
|
+
i = 0
|
22
|
+
@worker_by_id[total] = Breakout::Socket.new(Breakout.worker_url)
|
23
|
+
@browser_by_id[total] = Breakout::Socket.new(Breakout.browser_url('test'))
|
24
|
+
while i < total
|
25
|
+
i += 1
|
26
|
+
@browser_by_id[total].send(i)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
When /^(\d+) grids connect$/ do |total|
|
31
|
+
total = [1, total.to_i].max
|
32
|
+
i = 0
|
33
|
+
while i < total
|
34
|
+
i += 1
|
35
|
+
|
36
|
+
Breakout.config(:grid => i)
|
37
|
+
@worker_by_id[i] = Breakout::Socket.new(Breakout.worker_url)
|
38
|
+
@browser_by_id[i] = Breakout::Socket.new(Breakout.browser_url("test", :bid => i))
|
39
|
+
@browser_by_id[i].send(i)
|
40
|
+
@worker_by_id[i].receive
|
41
|
+
@worker_by_id[i].send :send_messages => { i => [ i.to_s ] }
|
42
|
+
@browser_by_id[i].receive
|
43
|
+
end
|
44
|
+
|
45
|
+
#sleep(10)
|
46
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'daemons'
|
6
|
+
require File.expand_path("lib/em-breakout")
|
7
|
+
|
8
|
+
options = {
|
9
|
+
:dir_mode => :normal,
|
10
|
+
:dir => File.expand_path('../', __FILE__),
|
11
|
+
:log_output => :true,
|
12
|
+
:app_name => 'em-breakout'
|
13
|
+
}
|
14
|
+
|
15
|
+
|
16
|
+
breakout_opts = {
|
17
|
+
:worker_port => 8001,
|
18
|
+
:browser_port => 8002,
|
19
|
+
:debug => ENV['EMDEBUG'],
|
20
|
+
:bdebug => ENV['BDEBUG']
|
21
|
+
}
|
22
|
+
|
23
|
+
Daemons.run_proc('em-breakout', options) do
|
24
|
+
|
25
|
+
if ENV['EMPROFILE']
|
26
|
+
require 'ruby-prof'
|
27
|
+
result = RubyProf.profile { EventMachine::Breakout.start_server(breakout_opts) }
|
28
|
+
printer = RubyProf::GraphHtmlPrinter.new(result)
|
29
|
+
printer.print(File.open(File.expand_path('../stats.html', __FILE__),'w'), :min_percent => 5)
|
30
|
+
else
|
31
|
+
EventMachine::Breakout.start_server(breakout_opts)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
`rm -f #{File.expand_path('../em-breakout.output', __FILE__)}`
|
2
|
+
CONTROL_SCRIPT = File.expand_path('../em_control', __FILE__)
|
3
|
+
|
4
|
+
module ServerHelper
|
5
|
+
def restart_server
|
6
|
+
#`#{::CONTROL_SCRIPT} stop ; EMDEBUG=true #{::CONTROL_SCRIPT} start`
|
7
|
+
#`#{::CONTROL_SCRIPT} stop ; BDEBUG=true #{::CONTROL_SCRIPT} start`
|
8
|
+
`#{::CONTROL_SCRIPT} stop ; #{::CONTROL_SCRIPT} start`
|
9
|
+
end
|
10
|
+
module_function :restart_server
|
11
|
+
end
|
12
|
+
|
13
|
+
World(ServerHelper)
|
14
|
+
|
15
|
+
ServerHelper.restart_server
|
16
|
+
at_exit do
|
17
|
+
`#{CONTROL_SCRIPT} stop`
|
18
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module Breakout
|
3
|
+
class Browser < Connection
|
4
|
+
|
5
|
+
attr_accessor :wip #true from when a worker gets sent a message until the worker sends :done_work, false all other times
|
6
|
+
attr_reader :bid, #browser id
|
7
|
+
:route, #handler to which worker should route this browser's messages
|
8
|
+
:notify, #whether to send notification to route handler for browser open/close
|
9
|
+
:message_queue #for incoming browser messages waiting to be pulled by a worker
|
10
|
+
|
11
|
+
def breakout(debug=false)
|
12
|
+
|
13
|
+
onopen do
|
14
|
+
@grid_name = request["Path"].split('?').first.gsub('/','')
|
15
|
+
@grid = GRIDS[@grid_name]
|
16
|
+
@route = request["Query"]["route"]
|
17
|
+
@bid = request["Query"]["bid"]
|
18
|
+
@notify = request["Query"]["notify"] == "true" ? true : false
|
19
|
+
@e = request["Query"]["e"].to_i
|
20
|
+
@gat = request["Query"]["gat"]
|
21
|
+
|
22
|
+
log(%|grid_name: #{@grid_name}\nroute: #{@route}\nbid: #{@bid}\ne: #{@e}\n| +
|
23
|
+
%|gat: #{@gat}\nnotify: #{@notify}|) if debug
|
24
|
+
|
25
|
+
catch :break do
|
26
|
+
if !@grid
|
27
|
+
close_websocket "unknown grid"
|
28
|
+
throw :break
|
29
|
+
end
|
30
|
+
|
31
|
+
if @grid.browsers.has_key?(@bid)
|
32
|
+
close_websocket "bid in use"
|
33
|
+
throw :break
|
34
|
+
end
|
35
|
+
|
36
|
+
if @e < Time.now.to_i
|
37
|
+
close_websocket "expired"
|
38
|
+
throw :break
|
39
|
+
end
|
40
|
+
|
41
|
+
unless @gat == ::Breakout.grid_access_token(@route, @bid, @e, @notify, @grid.grid_key)
|
42
|
+
close_websocket "invalid url"
|
43
|
+
throw :break
|
44
|
+
end
|
45
|
+
|
46
|
+
@message_queue = []
|
47
|
+
@grid.browsers[@bid] = self
|
48
|
+
if @notify
|
49
|
+
@message_queue << "/open"
|
50
|
+
@grid.work_queue[self] = true
|
51
|
+
EventMachine.next_tick { @grid.try_work }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
onmessage do |msg|
|
57
|
+
log("msg: #{msg}") if debug
|
58
|
+
|
59
|
+
unless @is_closing
|
60
|
+
|
61
|
+
if @message_queue.empty? && !@wip
|
62
|
+
@grid.work_queue[self] = true
|
63
|
+
EventMachine.next_tick { @grid.try_work }
|
64
|
+
end
|
65
|
+
|
66
|
+
@message_queue << msg
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
onclose do
|
71
|
+
log("closing") if debug
|
72
|
+
|
73
|
+
if @message_queue
|
74
|
+
|
75
|
+
if @notify
|
76
|
+
@grid.disconnected_browsers[@bid] = self
|
77
|
+
@message_queue = ["/close"]
|
78
|
+
unless @wip
|
79
|
+
@grid.work_queue[self] = true
|
80
|
+
EventMachine.next_tick { @grid.try_work }
|
81
|
+
end
|
82
|
+
else
|
83
|
+
@grid.work_queue.delete self
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
onerror do |reason|
|
89
|
+
log reason.pretty
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module Breakout
|
3
|
+
class Connection < EventMachine::WebSocket::Connection
|
4
|
+
@@connection_counter = 0
|
5
|
+
|
6
|
+
attr_reader :grid_name, #public name of worker-browser group
|
7
|
+
:grid, #Grid instance
|
8
|
+
:is_closing #close_websocket has been invoked
|
9
|
+
|
10
|
+
def cid
|
11
|
+
@cid ||= @@connection_counter += 1
|
12
|
+
end
|
13
|
+
|
14
|
+
def close_websocket(msg=nil)
|
15
|
+
unless @is_closing
|
16
|
+
@is_closing = true
|
17
|
+
send(msg) if msg
|
18
|
+
super()
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def log(msg)
|
23
|
+
puts "** #{self.class.name.split('::').last} #{cid} ********** \n#{msg}\n\n\n"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module Breakout
|
3
|
+
|
4
|
+
GRIDS = Hash.new # grid_name => grid
|
5
|
+
|
6
|
+
class Grid
|
7
|
+
|
8
|
+
attr_reader :grid_key, :workers, :browsers, :disconnected_browsers, :work_queue, :worker_queue
|
9
|
+
|
10
|
+
def initialize(worker)
|
11
|
+
@browsers = Hash.new # bid => browser
|
12
|
+
@disconnected_browsers = Hash.new # bid => browser
|
13
|
+
@workers = Hash.new # worker => true
|
14
|
+
@work_queue = Hash.new # browser => true
|
15
|
+
@worker_queue = Hash.new # worker => true
|
16
|
+
@grid_key = worker.request["Query"]["grid_key"]
|
17
|
+
@name = worker.grid_name
|
18
|
+
GRIDS[@name] = self
|
19
|
+
end
|
20
|
+
|
21
|
+
def release
|
22
|
+
GRIDS.delete @name
|
23
|
+
@browsers.each_value { |browser| browser.close_websocket }
|
24
|
+
@worker_queue = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def try_work
|
28
|
+
return if @work_queue.empty?
|
29
|
+
return if @worker_queue.empty?
|
30
|
+
|
31
|
+
browser = @work_queue.shift.first
|
32
|
+
|
33
|
+
msg = browser.message_queue.shift
|
34
|
+
return unless msg
|
35
|
+
|
36
|
+
worker = @worker_queue.shift.first
|
37
|
+
|
38
|
+
browser.wip = true
|
39
|
+
worker.browser = browser
|
40
|
+
worker.send("#{browser.route}\n#{browser.bid}\n#{msg}")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# adapted from https://github.com/igrigorik/em-websocket/blob/866290409c35b1557017fac3c12b701af71f8d2d/lib/em-websocket/websocket.rb
|
2
|
+
module EventMachine
|
3
|
+
module Breakout
|
4
|
+
|
5
|
+
def self.start_server(opts={})
|
6
|
+
browser_port = opts[:browser_port] || 9002
|
7
|
+
worker_port = opts[:worker_port] || 9001
|
8
|
+
debug = opts[:bdebug]
|
9
|
+
|
10
|
+
EM.epoll
|
11
|
+
|
12
|
+
EventMachine::run do
|
13
|
+
|
14
|
+
trap("TERM") { EventMachine.stop }
|
15
|
+
trap("INT") { EventMachine.stop }
|
16
|
+
|
17
|
+
EventMachine::start_server('0.0.0.0', browser_port, Browser, opts) do |browser|
|
18
|
+
browser.breakout debug
|
19
|
+
end
|
20
|
+
|
21
|
+
EventMachine::start_server('0.0.0.0', worker_port, Worker, opts) do |worker|
|
22
|
+
worker.breakout debug
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module Breakout
|
3
|
+
class Worker < Connection
|
4
|
+
|
5
|
+
attr_accessor :browser
|
6
|
+
|
7
|
+
def breakout(debug=false)
|
8
|
+
|
9
|
+
onopen do
|
10
|
+
@grid_name = request["Path"].split('?').first.gsub('/','')
|
11
|
+
@grid_key = request["Query"]["grid_key"]
|
12
|
+
|
13
|
+
log(%|grid: #{@grid_name}\ngrid_key: #{@grid_key}|) if debug
|
14
|
+
|
15
|
+
catch :break do
|
16
|
+
unless @grid_name && @grid_name.length > 0 && @grid_key && @grid_key.length > 0
|
17
|
+
close_websocket "invalid grid"
|
18
|
+
throw :break
|
19
|
+
end
|
20
|
+
|
21
|
+
if @grid = GRIDS[@grid_name]
|
22
|
+
unless @grid.grid_key == @grid_key
|
23
|
+
close_websocket "invalid grid_key"
|
24
|
+
throw :break
|
25
|
+
end
|
26
|
+
else
|
27
|
+
@grid = Grid.new(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
@grid.workers[self] = true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
onmessage do |msg|
|
35
|
+
log("msg: #{msg}") if debug
|
36
|
+
|
37
|
+
catch :break do
|
38
|
+
throw :break if @is_closing
|
39
|
+
|
40
|
+
begin
|
41
|
+
payload = JSON.parse(msg)
|
42
|
+
rescue JSON::ParserError
|
43
|
+
close_websocket "message must be JSON encoded"
|
44
|
+
throw :break
|
45
|
+
end
|
46
|
+
|
47
|
+
unless payload.is_a? Hash
|
48
|
+
close_websocket "message must be dictionary"
|
49
|
+
throw :break
|
50
|
+
end
|
51
|
+
|
52
|
+
send_messages = payload['send_messages']
|
53
|
+
if send_messages
|
54
|
+
unless send_messages.is_a? Hash
|
55
|
+
close_websocket "send_messages must be dictionary"
|
56
|
+
throw :break
|
57
|
+
end
|
58
|
+
|
59
|
+
send_messages.each_pair do |message, bids|
|
60
|
+
unless message.is_a?(String) && bids.is_a?(Array)
|
61
|
+
close_websocket "send_messages keys must be strings and values must be arrays"
|
62
|
+
throw :break
|
63
|
+
end
|
64
|
+
message_string = "#{message}"
|
65
|
+
bids.each do |bid|
|
66
|
+
next unless b = @grid.browsers[bid]
|
67
|
+
b.send(message_string) unless b.is_closing
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
disconnect = payload['disconnect']
|
73
|
+
if disconnect && b = @grid.browsers[disconnect]
|
74
|
+
b.close_websocket
|
75
|
+
end
|
76
|
+
|
77
|
+
requeue = payload['done_work']
|
78
|
+
unless requeue.nil?
|
79
|
+
next_tick = false
|
80
|
+
if @browser
|
81
|
+
@browser.wip = false
|
82
|
+
if @grid.browsers.include?(@browser.bid)
|
83
|
+
unless @browser.message_queue.empty? or @browser.is_closing
|
84
|
+
@grid.work_queue[@browser] = true
|
85
|
+
EventMachine.next_tick { @grid.try_work }
|
86
|
+
next_tick = true
|
87
|
+
end
|
88
|
+
elsif @grid.disconnected_browsers.include?(@browser.bid)
|
89
|
+
if @browser.message_queue.any?
|
90
|
+
@grid.work_queue[@browser] = true
|
91
|
+
unless next_tick
|
92
|
+
EventMachine.next_tick { @grid.try_work }
|
93
|
+
next_tick = true
|
94
|
+
end
|
95
|
+
else
|
96
|
+
@grid.disconnected_browsers.delete @browser.bid
|
97
|
+
end
|
98
|
+
end
|
99
|
+
@browser = nil
|
100
|
+
end
|
101
|
+
if requeue
|
102
|
+
@grid.worker_queue[self] = true
|
103
|
+
EventMachine.next_tick { @grid.try_work } unless next_tick
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
onclose do
|
110
|
+
log("closing") if debug
|
111
|
+
|
112
|
+
catch :break do
|
113
|
+
throw :break unless @grid
|
114
|
+
throw :break unless @grid.workers.delete self
|
115
|
+
|
116
|
+
if @grid.workers.empty?
|
117
|
+
@grid.release
|
118
|
+
throw :break
|
119
|
+
end
|
120
|
+
|
121
|
+
@grid.worker_queue.delete self
|
122
|
+
if b = @grid.browsers[@browser]
|
123
|
+
b.close_websocket
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
onerror do |reason|
|
130
|
+
log reason.pretty
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/lib/em-breakout.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'json'
|
3
|
+
require 'breakout'
|
4
|
+
require 'em-websocket'
|
5
|
+
require 'em-breakout/exception'
|
6
|
+
require 'em-breakout/connection'
|
7
|
+
require 'em-breakout/browser'
|
8
|
+
require 'em-breakout/worker'
|
9
|
+
require 'em-breakout/grid'
|
10
|
+
require 'em-breakout/server'
|
11
|
+
require 'em-breakout/version'
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-breakout
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Steve Masterman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-05-01 00:00:00.000000000 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: json
|
17
|
+
requirement: &81638730 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.4.6
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *81638730
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: em-websocket
|
28
|
+
requirement: &81638310 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.2.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *81638310
|
37
|
+
description: Breakout routes messages among web browsers and workers using WebSockets.
|
38
|
+
email:
|
39
|
+
- steve@vermonster.com
|
40
|
+
executables: []
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- Gemfile
|
46
|
+
- README.md
|
47
|
+
- _LICENSE
|
48
|
+
- em-breakout.gemspec
|
49
|
+
- examples/breakout.rb
|
50
|
+
- features/api.feature
|
51
|
+
- features/api_format.feature
|
52
|
+
- features/connect.feature
|
53
|
+
- features/notify.feature
|
54
|
+
- features/performance.feature
|
55
|
+
- features/ping.feature
|
56
|
+
- features/security.feature
|
57
|
+
- features/serialize.feature
|
58
|
+
- features/step_definitions/common_steps.rb
|
59
|
+
- features/step_definitions/performance_steps.rb
|
60
|
+
- features/support/em_control
|
61
|
+
- features/support/env.rb
|
62
|
+
- features/support/server.rb
|
63
|
+
- features/support/socket.rb
|
64
|
+
- lib/em-breakout.rb
|
65
|
+
- lib/em-breakout/browser.rb
|
66
|
+
- lib/em-breakout/connection.rb
|
67
|
+
- lib/em-breakout/exception.rb
|
68
|
+
- lib/em-breakout/grid.rb
|
69
|
+
- lib/em-breakout/server.rb
|
70
|
+
- lib/em-breakout/version.rb
|
71
|
+
- lib/em-breakout/worker.rb
|
72
|
+
has_rdoc: true
|
73
|
+
homepage: https://github.com/steve9001/em-breakout
|
74
|
+
licenses: []
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project: em-breakout
|
93
|
+
rubygems_version: 1.6.2
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: Breakout routes messages among web browsers and workers using WebSockets.
|
97
|
+
test_files: []
|