kosmonaut 0.3.0 → 0.3.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/NEWS +25 -1
- data/README +1 -1
- data/Rakefile +7 -9
- data/lib/kosmonaut.rb +10 -0
- data/lib/kosmonaut/client.rb +94 -13
- data/lib/kosmonaut/errors.rb +10 -0
- data/lib/kosmonaut/socket.rb +64 -15
- data/lib/kosmonaut/version.rb +11 -1
- data/lib/kosmonaut/worker.rb +108 -15
- data/test/test_kosmonaut_worker.rb +3 -3
- metadata +7 -7
data/NEWS
CHANGED
@@ -1,6 +1,30 @@
|
|
1
1
|
Releases
|
2
2
|
========
|
3
3
|
|
4
|
+
v0.3.1
|
5
|
+
------
|
6
|
+
* Documentation.
|
7
|
+
* Listener doesn't try to reconnect when unauthenticated.
|
8
|
+
|
9
|
+
v0.3.0
|
10
|
+
------
|
11
|
+
* Kosmonaut::Client#request_single_access token gets 2 arguments now - uid and
|
12
|
+
permission - according to new spec and implementation of WebRocket.
|
13
|
+
|
14
|
+
v0.2.4
|
15
|
+
------
|
16
|
+
* Handled broken pipe error.
|
17
|
+
* Timeouts switched to raw socket options.
|
18
|
+
|
19
|
+
v0.2.3
|
20
|
+
------
|
21
|
+
* Worker's listener tweaks and optimization.
|
22
|
+
|
23
|
+
v0.2.2
|
24
|
+
------
|
25
|
+
* Optimized timeouts.
|
26
|
+
* Fixed bug in opening new channel.
|
27
|
+
|
4
28
|
v0.2.1
|
5
29
|
------
|
6
30
|
* Fixed behaviour of the worker's listener.
|
@@ -8,4 +32,4 @@ v0.2.1
|
|
8
32
|
v0.2.0
|
9
33
|
------
|
10
34
|
* Pure ruby implementation of the client and worker using majordomo pattern.
|
11
|
-
* Tests for the API
|
35
|
+
* Tests for the API
|
data/README
CHANGED
@@ -85,7 +85,7 @@ or email one of the maintainers.
|
|
85
85
|
Sponsors
|
86
86
|
--------
|
87
87
|
All the work on the project is sponsored and supported by Cubox - an
|
88
|
-
awesome dev shop from Uruguay <http://
|
88
|
+
awesome dev shop from Uruguay <http://cuboxlabs.com>.
|
89
89
|
|
90
90
|
Copyright
|
91
91
|
---------
|
data/Rakefile
CHANGED
@@ -1,14 +1,12 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
rdoc.
|
7
|
-
rdoc.
|
8
|
-
rdoc.rdoc_files.include('
|
9
|
-
|
10
|
-
end
|
11
|
-
=end
|
3
|
+
#require 'rdoc/task'
|
4
|
+
#Rake::RDocTask.new do |rdoc|
|
5
|
+
# rdoc.rdoc_dir = 'rdoc'
|
6
|
+
# rdoc.title = "Kosmonaut - The WebRocket backend client"
|
7
|
+
# rdoc.rdoc_files.include('README*')
|
8
|
+
# rdoc.rdoc_files.include('lib/**/*.rb')
|
9
|
+
#end
|
12
10
|
|
13
11
|
require 'rake/testtask'
|
14
12
|
Rake::TestTask.new do |t|
|
data/lib/kosmonaut.rb
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
# Copyright (C) 2012 Krzysztof Kowalik <chris@nu7hat.ch> and folks at Cubox
|
2
|
+
#
|
3
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
4
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
5
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
6
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
7
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
8
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
9
|
+
# SOFTWARE.
|
10
|
+
|
1
11
|
require 'kosmonaut/errors'
|
2
12
|
require 'kosmonaut/socket'
|
3
13
|
require 'kosmonaut/worker'
|
data/lib/kosmonaut/client.rb
CHANGED
@@ -1,25 +1,45 @@
|
|
1
|
+
# Copyright (C) 2012 Krzysztof Kowalik <chris@nu7hat.ch> and folks at Cubox
|
2
|
+
#
|
3
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
4
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
5
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
6
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
7
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
8
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
9
|
+
# SOFTWARE.
|
10
|
+
|
1
11
|
require 'json'
|
2
12
|
require 'thread'
|
3
13
|
|
4
14
|
module Kosmonaut
|
15
|
+
# Public: Client is an implementation REQ-REP type socket which handles
|
16
|
+
# communication between backend application and WebRocket backend endpoint.
|
17
|
+
#
|
18
|
+
# Client is used to synchronously request operations from the server.
|
19
|
+
# Synchronous operations are used to provide consistency for the backed
|
20
|
+
# generated events.
|
21
|
+
#
|
22
|
+
# Examples
|
23
|
+
#
|
24
|
+
# c = Kosmonaut::Client.new("wr://token@127.0.0.1:8081/vhost")
|
25
|
+
# c.open_channel("comments")
|
26
|
+
#
|
27
|
+
# @comment = User.new(params[:comment])
|
28
|
+
#
|
29
|
+
# if @comment.save
|
30
|
+
# c.broadcast("comments", "comment_added", @comment.to_json)
|
31
|
+
# # ...
|
32
|
+
# end
|
33
|
+
#
|
5
34
|
class Client < Socket
|
6
35
|
# Maximum number of seconds to wait for the request to being processed.
|
7
36
|
REQUEST_TIMEOUT = 5.0
|
8
37
|
|
9
38
|
# Public: The Client constructor. Pre-configures the client instance. See
|
10
|
-
# also the Kosmonaut::Socket#initialize
|
39
|
+
# also the Kosmonaut::Socket#initialize to get more information.
|
11
40
|
#
|
12
41
|
# url - The WebRocket backend endpoint URL to connect to.
|
13
42
|
#
|
14
|
-
# The endpoint's URL must have the following format:
|
15
|
-
#
|
16
|
-
# [scheme]://[secret]@[host]:[port]/[vhost]
|
17
|
-
#
|
18
|
-
# Examples
|
19
|
-
#
|
20
|
-
# c = Kosmonaut::Client.new("wr://f343...fa4a@myhost.com:8080/hello")
|
21
|
-
# c.broadcast("room", "status", {"message" => "is going to the beach!"})
|
22
|
-
#
|
23
43
|
def initialize(url)
|
24
44
|
super(url)
|
25
45
|
@mtx = Mutex.new
|
@@ -38,32 +58,81 @@ module Kosmonaut
|
|
38
58
|
# c.broadcast("room". "message", {"content" => "Hello World!"})
|
39
59
|
# c.broadcast("room". "status", {"message" => "is saying hello!"})
|
40
60
|
#
|
61
|
+
# Returns 0 if succeed.
|
62
|
+
# Raises one of the Kosmonaut::Error inherited exceptions.
|
41
63
|
def broadcast(channel, event, data)
|
42
64
|
payload = ["BC", channel, event, data.to_json]
|
43
65
|
perform_request(payload)
|
44
66
|
end
|
45
|
-
|
67
|
+
|
68
|
+
# Public: Opens specified channel. If channel already exists, then ok
|
69
|
+
# response will be received anyway. If channel name is starts with the
|
70
|
+
# `presence-` or `private-` prefix, then appropriate type of the channel
|
71
|
+
# will be created.
|
72
|
+
#
|
73
|
+
# name - A name of the channel to be created.
|
74
|
+
#
|
75
|
+
# Examples
|
76
|
+
#
|
77
|
+
# c.open_channel("room")
|
78
|
+
# c.open_channel("presence-room")
|
79
|
+
# c.open_channel("private-room")
|
80
|
+
#
|
81
|
+
# Returns 0 if succeed.
|
82
|
+
# Raises one of the Kosmonaut::Error inherited exceptions.
|
46
83
|
def open_channel(name)
|
47
84
|
payload = ["OC", name]
|
48
85
|
perform_request(payload)
|
49
86
|
end
|
50
87
|
|
88
|
+
# Public: Closes specified channel. If channel doesn't exist then an
|
89
|
+
# error will be thrown.
|
90
|
+
#
|
91
|
+
# name - A name of the channel to be deleted.
|
92
|
+
#
|
93
|
+
# Examples
|
94
|
+
#
|
95
|
+
# c.close_channel("hello")
|
96
|
+
# c.close_channel("presence-room")
|
97
|
+
#
|
98
|
+
# Returns 0 if succeed.
|
99
|
+
# Raises one of the Kosmonaut::Error inherited exceptions.
|
51
100
|
def close_channel(name)
|
52
101
|
payload = ["CC", name]
|
53
102
|
perform_request(payload)
|
54
103
|
end
|
55
104
|
|
105
|
+
# Public: Sends a request to generate a single access token for given
|
106
|
+
# user with specified permissions.
|
107
|
+
#
|
108
|
+
# uid - An user defined unique ID.
|
109
|
+
# permission - A permissions regexp to match against the channels.
|
110
|
+
#
|
111
|
+
# Examples
|
112
|
+
#
|
113
|
+
# @current_user = User.find(params[:id])
|
114
|
+
# c.request_single_access_token(@current_user.username, ".*")
|
115
|
+
#
|
116
|
+
#
|
117
|
+
# Returns generated access token string if succeed.
|
118
|
+
# Raises one of the Kosmonaut::Error inherited exceptions.
|
56
119
|
def request_single_access_token(uid, permission)
|
57
120
|
payload = ["AT", uid, permission]
|
58
121
|
perform_request(payload)
|
59
122
|
end
|
60
123
|
|
124
|
+
private
|
125
|
+
|
126
|
+
# Internal: Returns abbreviation of the socket type.
|
61
127
|
def socket_type
|
62
128
|
"req"
|
63
129
|
end
|
64
130
|
|
65
|
-
|
66
|
-
|
131
|
+
# Internal: Performs request with specified payload and waits for the
|
132
|
+
# response with it's result.
|
133
|
+
#
|
134
|
+
# Returns response result if succeed.
|
135
|
+
# Raises one of the Kosmonaut::Error inherited exceptions.
|
67
136
|
def perform_request(payload)
|
68
137
|
@mtx.synchronize {
|
69
138
|
response = []
|
@@ -77,6 +146,18 @@ module Kosmonaut
|
|
77
146
|
}
|
78
147
|
end
|
79
148
|
|
149
|
+
# Internal: Parses given response and discovers it's result according
|
150
|
+
# to the WebRocket Backend Protocol specification.
|
151
|
+
#
|
152
|
+
# Response format
|
153
|
+
#
|
154
|
+
# 0x01 | command \n |
|
155
|
+
# 0x02 | payload... \n | *
|
156
|
+
# 0x.. | ... \n | *
|
157
|
+
# | \r\n\r\n |
|
158
|
+
#
|
159
|
+
# Returns response result if succeed.
|
160
|
+
# Raises one of the Kosmonaut::Error inherited exceptions.
|
80
161
|
def parse_response(response)
|
81
162
|
cmd = response[0].to_s
|
82
163
|
Kosmonaut.log("Client/RES : #{response.join("\n").inspect}")
|
data/lib/kosmonaut/errors.rb
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
# Copyright (C) 2012 Krzysztof Kowalik <chris@nu7hat.ch> and folks at Cubox
|
2
|
+
#
|
3
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
4
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
5
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
6
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
7
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
8
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
9
|
+
# SOFTWARE.
|
10
|
+
|
1
11
|
module Kosmonaut
|
2
12
|
class Error < StandardError
|
3
13
|
end
|
data/lib/kosmonaut/socket.rb
CHANGED
@@ -1,33 +1,77 @@
|
|
1
|
+
# Copyright (C) 2012 Krzysztof Kowalik <chris@nu7hat.ch> and folks at Cubox
|
2
|
+
#
|
3
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
4
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
5
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
6
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
7
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
8
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
9
|
+
# SOFTWARE.
|
10
|
+
|
1
11
|
require 'uri'
|
2
12
|
require 'socket'
|
3
13
|
require 'securerandom'
|
4
14
|
|
5
15
|
module Kosmonaut
|
16
|
+
# Internal: Socket is a base class defining tools and helpers used by
|
17
|
+
# Client and Worker implementations.
|
6
18
|
class Socket
|
19
|
+
# Public: A parsed URL of the WebRocket backend endpoint.
|
7
20
|
attr_reader :uri
|
8
21
|
|
22
|
+
# Internal: The Socket constructor.
|
23
|
+
#
|
24
|
+
# The endpoint's URL must have the following format:
|
25
|
+
#
|
26
|
+
# [scheme]://[secret]@[host]:[port]/[vhost]
|
27
|
+
#
|
9
28
|
def initialize(url)
|
10
29
|
@uri = URI.parse(url)
|
11
30
|
@addr = ::Socket.getaddrinfo(@uri.host, nil)
|
12
31
|
end
|
13
32
|
|
33
|
+
protected
|
34
|
+
|
35
|
+
# Internal: Connect creates new connection with the backend endpoint.
|
36
|
+
#
|
37
|
+
# timeout - A value of the maximum execution time (float).
|
38
|
+
#
|
39
|
+
# Returns configured and connected socket instance.
|
14
40
|
def connect(timeout)
|
15
|
-
secs
|
16
|
-
usecs
|
41
|
+
secs = timeout.to_i
|
42
|
+
usecs = ((timeout - secs) * 1_000_000).to_i
|
17
43
|
optval = [secs, usecs].pack("l_2")
|
18
44
|
|
45
|
+
# Raw timeouts are way way way faster than Timeout module or SystemTimer
|
46
|
+
# and doesn't need external dependencies.
|
19
47
|
s = ::Socket.new(::Socket.const_get(@addr[0][0]), ::Socket::SOCK_STREAM, 0)
|
20
48
|
s.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
|
21
49
|
s.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVTIMEO, optval)
|
22
50
|
s.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDTIMEO, optval)
|
23
51
|
s.connect(::Socket.pack_sockaddr_in(@uri.port, @addr[0][3]))
|
24
52
|
|
25
|
-
generate_identity
|
53
|
+
generate_identity!
|
26
54
|
return s
|
27
55
|
end
|
28
56
|
|
29
|
-
|
30
|
-
|
57
|
+
# Internal: Pack converts given payload into single packet in format
|
58
|
+
# defined by WebRocket Backend Protocol.
|
59
|
+
#
|
60
|
+
# Packet format
|
61
|
+
#
|
62
|
+
# 0x01 | identity \n | *
|
63
|
+
# 0x02 | \n | *
|
64
|
+
# 0x03 | command \n |
|
65
|
+
# 0x04 | payload... \n | *
|
66
|
+
# 0x.. | ... \n | *
|
67
|
+
# | \r\n\r\n |
|
68
|
+
#
|
69
|
+
# * - optional field
|
70
|
+
#
|
71
|
+
# payload - The data to be packed.
|
72
|
+
# with_identity - Whether identity should be prepend to the packet.
|
73
|
+
#
|
74
|
+
# Returns packed data.
|
31
75
|
def pack(payload=[], with_identity=true)
|
32
76
|
if with_identity
|
33
77
|
payload.unshift("")
|
@@ -36,11 +80,16 @@ module Kosmonaut
|
|
36
80
|
payload.join("\n") + "\n\r\n\r\n"
|
37
81
|
end
|
38
82
|
|
39
|
-
|
83
|
+
# Internal: Reads one packet from given socket instance.
|
84
|
+
#
|
85
|
+
# sock - The socket to receive from.
|
86
|
+
#
|
87
|
+
# Returns received data (frames) as an array of strings.
|
88
|
+
def recv(sock)
|
40
89
|
data = []
|
41
90
|
possible_eom = false # possible end of message
|
42
|
-
while !
|
43
|
-
line =
|
91
|
+
while !sock.eof?
|
92
|
+
line = sock.gets
|
44
93
|
if line == "\r\n"
|
45
94
|
break if possible_eom
|
46
95
|
possible_eom = true
|
@@ -54,13 +103,13 @@ module Kosmonaut
|
|
54
103
|
|
55
104
|
private
|
56
105
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
@identity =
|
106
|
+
# Internal: Generates unique identity for the socket connection.
|
107
|
+
# Identity is composed from the following parts:
|
108
|
+
#
|
109
|
+
# [socket-type]:[vhost]:[vhost-token]:[uuid]
|
110
|
+
#
|
111
|
+
def generate_identity!
|
112
|
+
@identity = [socket_type, @uri.path, @uri.user, SecureRandom.uuid].join(":")
|
64
113
|
end
|
65
114
|
end
|
66
115
|
end
|
data/lib/kosmonaut/version.rb
CHANGED
@@ -1,8 +1,18 @@
|
|
1
|
+
# Copyright (C) 2012 Krzysztof Kowalik <chris@nu7hat.ch> and folks at Cubox
|
2
|
+
#
|
3
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
4
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
5
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
6
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
7
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
8
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
9
|
+
# SOFTWARE.
|
10
|
+
|
1
11
|
module Kosmonaut
|
2
12
|
module Version
|
3
13
|
MAJOR = 0
|
4
14
|
MINOR = 3
|
5
|
-
PATCH =
|
15
|
+
PATCH = 1
|
6
16
|
|
7
17
|
def self.to_s
|
8
18
|
[MAJOR, MINOR, PATCH].join('.')
|
data/lib/kosmonaut/worker.rb
CHANGED
@@ -1,11 +1,62 @@
|
|
1
|
+
# Copyright (C) 2012 Krzysztof Kowalik <chris@nu7hat.ch> and folks at Cubox
|
2
|
+
#
|
3
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
4
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
5
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
6
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
7
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
8
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
9
|
+
# SOFTWARE.
|
10
|
+
|
1
11
|
require 'json'
|
2
12
|
require 'thread'
|
3
13
|
|
4
14
|
module Kosmonaut
|
15
|
+
# Public: Worker is an implementation of SUBSCRIBER socket which handles
|
16
|
+
# events incoming from the WebRocket backend endpoint.
|
17
|
+
#
|
18
|
+
# The communication here is asynchronous, and Worker can't send other
|
19
|
+
# messages than ready state, heartbeat, or quit notification.
|
20
|
+
#
|
21
|
+
# Worker shouldn't be called directly, it should be used as a base class
|
22
|
+
# for implementing your own workers.
|
23
|
+
#
|
24
|
+
# Examples
|
25
|
+
#
|
26
|
+
# class MyWorker < Kosmonaut::Worker
|
27
|
+
# def on_message(event, data)
|
28
|
+
# if event == "append_chat_history"
|
29
|
+
# @room = Room.find(data[:room_id])
|
30
|
+
# @room.messages.build(data[:message])
|
31
|
+
# @room.save!
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# def on_error(err)
|
36
|
+
# puts "WEBROCKET ERROR: #{err.to_s}"
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# def on_exception(err)
|
40
|
+
# puts "INTERNAL ERROR: #{err.to_s}"
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# w = MyWorker.new("wr://token@127.0.0.1:8081/vhost")
|
45
|
+
# w.listen
|
46
|
+
#
|
5
47
|
class Worker < Socket
|
6
|
-
|
7
|
-
|
48
|
+
# Number of milliseconds after which client should retry to reconnect
|
49
|
+
# to the bakcend endpoint.
|
50
|
+
RECONNECT_DELAY = 1000
|
8
51
|
|
52
|
+
# Number of milliseconds between next heartbeat message.
|
53
|
+
HEARTBEAT_INTERVAL = 500
|
54
|
+
|
55
|
+
# Public: The Worker constructor. Pre-configures the worker instance. See
|
56
|
+
# also the Kosmonaut::Socket#initialize to get more information.
|
57
|
+
#
|
58
|
+
# url - The WebRocket backend endpoint URL to connect to.
|
59
|
+
#
|
9
60
|
def initialize(url)
|
10
61
|
super(url)
|
11
62
|
@mtx = Mutex.new
|
@@ -16,6 +67,10 @@ module Kosmonaut
|
|
16
67
|
@heartbeat_at = 0
|
17
68
|
end
|
18
69
|
|
70
|
+
# Public: Listen starts a listener's loop for the worker. Listener implements
|
71
|
+
# the Majordomo pattern to manage connection with the backend.
|
72
|
+
#
|
73
|
+
# Raises Kosmonaut::UnauthorizedError if worker's credentials are invalid.
|
19
74
|
def listen
|
20
75
|
@alive = true
|
21
76
|
reconnect
|
@@ -31,7 +86,7 @@ module Kosmonaut
|
|
31
86
|
end
|
32
87
|
|
33
88
|
msg = recv(@sock)
|
34
|
-
raise Errno::ECONNRESET if
|
89
|
+
raise Errno::ECONNRESET if msg.empty?
|
35
90
|
Kosmonaut.log("Worker/RECV : #{msg.join("\n").inspect}")
|
36
91
|
cmd = msg.shift
|
37
92
|
|
@@ -46,6 +101,8 @@ module Kosmonaut
|
|
46
101
|
when "ER"
|
47
102
|
error_handler(msg.size < 1 ? 597 : msg[0])
|
48
103
|
end
|
104
|
+
|
105
|
+
raise Errno::ECONNRESET if @sock.eof?
|
49
106
|
rescue Errno::EAGAIN, Errno::ECONNRESET, Errno::ECONNREFUSED, IOError => err
|
50
107
|
Kosmonaut.log("Worker/RECONNECT: " + err.to_s)
|
51
108
|
sleep(@reconnect_delay.to_f / 1000.0)
|
@@ -59,33 +116,49 @@ module Kosmonaut
|
|
59
116
|
end
|
60
117
|
end
|
61
118
|
|
119
|
+
# Public: Breaks the listener's loop and stops execution of
|
120
|
+
# the worker.
|
62
121
|
def stop
|
63
122
|
@mtx.synchronize { @alive = false }
|
64
123
|
end
|
65
124
|
|
125
|
+
# Public: Returns whether this worker's event loop is running.
|
66
126
|
def alive?
|
67
127
|
@mtx.synchronize { @alive }
|
68
128
|
end
|
69
129
|
|
130
|
+
private
|
131
|
+
|
132
|
+
# Internal: Returns abbreviation of the socket type.
|
70
133
|
def socket_type
|
71
134
|
"dlr"
|
72
135
|
end
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
136
|
+
|
137
|
+
# Internal: Packs given payload and writes it to the specified socket.
|
138
|
+
#
|
139
|
+
# sock - The socket to write to.
|
140
|
+
# payload - The payload to be packed and sent.
|
141
|
+
# with_identity - Whether identity should be prepend to the packet.
|
142
|
+
#
|
143
|
+
def send(sock, payload, with_identity=false)
|
144
|
+
return unless sock
|
78
145
|
packet = pack(payload, with_identity)
|
79
|
-
|
146
|
+
sock.write(packet)
|
80
147
|
Kosmonaut.log("Worker/SENT : #{packet.inspect}")
|
148
|
+
nil
|
81
149
|
rescue Errno::EPIPE
|
82
150
|
end
|
83
|
-
|
151
|
+
|
152
|
+
# Internal: Disconnects and cleans up the socket if connected.
|
84
153
|
def disconnect
|
85
|
-
@sock.close if @sock
|
154
|
+
@sock.close if @sock
|
155
|
+
@sock = nil
|
86
156
|
rescue IOError
|
87
157
|
end
|
88
|
-
|
158
|
+
|
159
|
+
# Internal: Sets up new connection with the backend endpoint and sends
|
160
|
+
# information that it's ready to work. Also initializes heartbeat
|
161
|
+
# scheduling.
|
89
162
|
def reconnect
|
90
163
|
@sock = connect(((@heartbeat_ivl * 2).to_f / 1000.0).to_i + 1)
|
91
164
|
send(@sock, ["RD"], true)
|
@@ -93,6 +166,11 @@ module Kosmonaut
|
|
93
166
|
rescue Errno::ECONNREFUSED
|
94
167
|
end
|
95
168
|
|
169
|
+
# Intenral: Message handler routes received data to user defined
|
170
|
+
# 'on_message' method (if exists) and handles it's exceptions.
|
171
|
+
#
|
172
|
+
# data - The data to be handled.
|
173
|
+
#
|
96
174
|
def message_handler(data)
|
97
175
|
if respond_to?(:on_message)
|
98
176
|
payload = JSON.parse(data.to_s)
|
@@ -102,15 +180,30 @@ module Kosmonaut
|
|
102
180
|
exception_handler(err)
|
103
181
|
end
|
104
182
|
|
183
|
+
# Internal: Handles given WebRocket error.
|
184
|
+
#
|
185
|
+
# errcode - A code of the received error.
|
186
|
+
#
|
187
|
+
# Raises Kosmonaut::UnauthorizerError if error 402 has been received.
|
105
188
|
def error_handler(errcode)
|
106
189
|
if respond_to?(:on_error)
|
107
190
|
err = ERRORS[errcode.to_i]
|
108
|
-
|
191
|
+
if err == UnauthorizedError
|
192
|
+
raise err.new
|
193
|
+
end
|
194
|
+
begin
|
195
|
+
on_error((err ? err : UnknownServerError).new)
|
196
|
+
rescue => err
|
197
|
+
exception_handler(err)
|
198
|
+
end
|
109
199
|
end
|
110
|
-
rescue => err
|
111
|
-
exception_handler(err)
|
112
200
|
end
|
113
201
|
|
202
|
+
# Internal: Exception handler passes given error to user defined
|
203
|
+
# exception handler or raises error if such not exists.
|
204
|
+
#
|
205
|
+
# err - The exception to be handled.
|
206
|
+
#
|
114
207
|
def exception_handler(err)
|
115
208
|
if respond_to?(:on_exception)
|
116
209
|
on_exception(err)
|
@@ -2,15 +2,15 @@ require File.expand_path("../helper", __FILE__)
|
|
2
2
|
|
3
3
|
class MyWorker < Kosmonaut::Worker
|
4
4
|
def on_message(event, data)
|
5
|
-
puts "MSG
|
5
|
+
puts "MSG : #{event} : #{data.inspect}"
|
6
6
|
end
|
7
7
|
|
8
8
|
def on_error(err)
|
9
|
-
puts "ERR
|
9
|
+
puts "ERR : #{err.to_s}"
|
10
10
|
end
|
11
11
|
|
12
12
|
def on_exception(err)
|
13
|
-
puts "EXC
|
13
|
+
puts "EXC : #{err.to_s}"
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: kosmonaut
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.3.
|
5
|
+
version: 0.3.1
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Krzysztof Kowalik
|
@@ -11,7 +11,7 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date: 2012-01-
|
14
|
+
date: 2012-01-28 00:00:00 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: json
|
@@ -44,12 +44,12 @@ extensions: []
|
|
44
44
|
extra_rdoc_files: []
|
45
45
|
|
46
46
|
files:
|
47
|
-
- lib/kosmonaut.rb
|
47
|
+
- lib/kosmonaut/client.rb
|
48
|
+
- lib/kosmonaut/errors.rb
|
48
49
|
- lib/kosmonaut/socket.rb
|
49
|
-
- lib/kosmonaut/worker.rb
|
50
50
|
- lib/kosmonaut/version.rb
|
51
|
-
- lib/kosmonaut/
|
52
|
-
- lib/kosmonaut
|
51
|
+
- lib/kosmonaut/worker.rb
|
52
|
+
- lib/kosmonaut.rb
|
53
53
|
- test/helper.rb
|
54
54
|
- test/test_kosmonaut_client.rb
|
55
55
|
- test/test_kosmonaut_worker.rb
|
@@ -82,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
82
|
requirements: []
|
83
83
|
|
84
84
|
rubyforge_project:
|
85
|
-
rubygems_version: 1.8.
|
85
|
+
rubygems_version: 1.8.10
|
86
86
|
signing_key:
|
87
87
|
specification_version: 3
|
88
88
|
summary: Ruby client for the WebRocket backend
|