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