slanger 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of slanger might be problematic. Click here for more details.
- data/README.md +33 -5
- data/bin/slanger +17 -0
- data/lib/slanger/api_server.rb +4 -2
- data/lib/slanger/channel.rb +20 -0
- data/lib/slanger/config.rb +2 -0
- data/lib/slanger/handler.rb +81 -30
- data/lib/slanger/presence_channel.rb +22 -0
- data/lib/slanger/redis.rb +6 -2
- data/lib/slanger/web_socket_server.rb +13 -1
- metadata +34 -33
data/README.md
CHANGED
@@ -4,7 +4,7 @@ Slanger is an open source server implementation of the Pusher protocol written i
|
|
4
4
|
|
5
5
|
Presence channel state is shared using Redis. Channels are lazily instantiated internally within a given Slanger node when the first subscriber connects. When a presence channel is instantiated within a Slanger node, it queries Redis for the global state across all nodes within the system for that channel, and then copies that state internally. Afterwards, when subscribers connect or disconnect the node publishes a presence message to all interested nodes, i.e. all nodes with at least one subscriber interested in the given channel.
|
6
6
|
|
7
|
-
Slanger is smart enough to know if a new channel subscription belongs to the same user. It will not send presence messages to subscribers in this case. This happens when the user has multiple browser tabs open for example. Using a chat room backed by presence channels as a real example, one would not want "
|
7
|
+
Slanger is smart enough to know if a new channel subscription belongs to the same user. It will not send presence messages to subscribers in this case. This happens when the user has multiple browser tabs open for example. Using a chat room backed by presence channels as a real example, one would not want "Barrington" to show up N times in the presence roster because Barrington is a retard and has the chat room open in N browser tabs.
|
8
8
|
|
9
9
|
Slanger was designed to be highly available and partition tolerant with eventual consistency, which in practise is instantaneous.
|
10
10
|
|
@@ -12,7 +12,7 @@ Slanger was designed to be highly available and partition tolerant with eventual
|
|
12
12
|
|
13
13
|
## Requirements
|
14
14
|
|
15
|
-
- Ruby 1.9
|
15
|
+
- Ruby 1.9.2-p290 or greater
|
16
16
|
- Redis
|
17
17
|
|
18
18
|
## Starting the service
|
@@ -24,7 +24,7 @@ __IMPORTANT:__ Redis must be running where Slanger expects it to be (either on l
|
|
24
24
|
<pre>
|
25
25
|
$ gem install slanger
|
26
26
|
|
27
|
-
$ redis-server
|
27
|
+
$ redis-server &> /dev/null &
|
28
28
|
|
29
29
|
$ slanger --app_key 765ec374ae0a69f4ce44 --secret your-pusher-secret
|
30
30
|
</pre>
|
@@ -81,11 +81,39 @@ You will also need to do the same to the Pusher JavaScript client in your client
|
|
81
81
|
|
82
82
|
Of course you could proxy all requests to `ws.example.com` to port 8080 of your Slanger node and `api.example.com` to port 4567 of your Slanger node for example, that way you would only need to set the host property of the Pusher client.
|
83
83
|
|
84
|
+
# Configuration Options
|
85
|
+
|
86
|
+
Slanger supports several configuration options, which can be supplied as command line arguments at invocation.
|
87
|
+
|
88
|
+
<pre>
|
89
|
+
-k or --app_key This is the Pusher app key you want to use. This is a required argument
|
90
|
+
|
91
|
+
-s or --secret This is your Pusher secret. This is a required argument
|
92
|
+
|
93
|
+
-r or --redis_address An address where there is a Redis server running. This is an optional argument and defaults to redis://127.0.0.1:6379/0
|
94
|
+
|
95
|
+
-a or --api_host This is the address that Slanger will bind the HTTP REST API part of the service to. This is an optional argument and defaults to 0.0.0.0:4567
|
96
|
+
|
97
|
+
-w or --websocket_host This is the address that Slanger will bind the WebSocket part of the service to. This is an optional argument and defaults to 0.0.0.0:8080
|
98
|
+
|
99
|
+
-v or --[no-]verbose This makes Slanger run verbosely, meaning WebSocket frames will be echoed to STDOUT. Useful for debugging
|
100
|
+
</pre>
|
101
|
+
|
102
|
+
|
84
103
|
# Why use Slanger instead of Pusher?
|
85
104
|
|
86
|
-
There
|
105
|
+
There a few reasons you might want to use Slanger instead of Pusher, e.g.
|
87
106
|
|
88
107
|
- You operate in a heavily regulated industry and are worried about sending data to 3rd parties, and it is an organisational requirement that you own your own infrastructure.
|
89
|
-
- You might be travelling on an airplane without internet
|
108
|
+
- You might be travelling on an airplane without internet connectivity as I am right now. Airplane rides are very good times to get a lot done, unfortunately external services are also usually unreachable. Remove internet connectivity as a dependency of your development envirionment by running a local Slanger instance in development and Pusher in production.
|
109
|
+
- Remove the network dependency from your test suite.
|
90
110
|
- You want to extend the Pusher protocol or have some special requirement. If this applies to you, chances are you are out of luck as Pusher is unlikely to implement something to suit your special use case, and rightly so. With Slanger you are free to modify and extend its behavior anyway that suits your purpose.
|
91
111
|
|
112
|
+
# Why did you write Slanger
|
113
|
+
|
114
|
+
I wanted to write a non-trivial evented app. I also want to write a book on evented programming in Ruby as I feel there is scant good information available on the topic and this project is handy to show publishers.
|
115
|
+
|
116
|
+
Pusher is an awesome service, very reasonably priced, and run by an awesome crew. Give them a spin on your next project.
|
117
|
+
|
118
|
+
© 2011 a Stevie Graham joint.
|
119
|
+
|
data/bin/slanger
CHANGED
@@ -34,6 +34,16 @@ OptionParser.new do |opts|
|
|
34
34
|
options[:websocket_host], options[:websocket_port] = p.split(':')
|
35
35
|
end
|
36
36
|
|
37
|
+
opts.on '-p', '--private_key_file FILE', "Private key file for SSL transport" do |p|
|
38
|
+
options[:tls_options] ||= {}
|
39
|
+
options[:tls_options][:private_key_file] = p
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on '-c', '--cert_chain_file FILE', "Certificate chain file for SSL transport" do |p|
|
43
|
+
options[:tls_options] ||= {}
|
44
|
+
options[:tls_options][:cert_chain_file] = p
|
45
|
+
end
|
46
|
+
|
37
47
|
opts.on "-v", "--[no-]verbose", "Run verbosely" do |v|
|
38
48
|
options[:debug] = v
|
39
49
|
end
|
@@ -43,6 +53,13 @@ end.parse!
|
|
43
53
|
raise RuntimeError.new "--#{parameter} STRING is a required argument. Use your Pusher #{parameter}." unless options[parameter.to_sym]
|
44
54
|
end
|
45
55
|
|
56
|
+
if options[:tls_options]
|
57
|
+
[:cert_chain_file, :private_key_file].each do |param|
|
58
|
+
raise RuntimeError.new "--#{param} does not exist at `#{options[:tls_options][param]}`" unless File.exists? options[:tls_options][param]
|
59
|
+
raise RuntimeError.new "Both --cert_chain_file and --private_key_file need to be specified" unless options[:tls_options][param]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
46
63
|
EM.run do
|
47
64
|
File.tap { |f| require f.expand_path(f.join(f.dirname(__FILE__),'..', 'slanger.rb')) }
|
48
65
|
Slanger::Config.load options
|
data/lib/slanger/api_server.rb
CHANGED
@@ -18,12 +18,14 @@ module Slanger
|
|
18
18
|
error(Signature::AuthenticationError) { |c| halt 401, "401 UNAUTHORIZED\n" }
|
19
19
|
|
20
20
|
post '/apps/:app_id/channels/:channel_id/events' do
|
21
|
-
# authenticate request. exclude 'channel_id' and 'app_id'
|
22
|
-
#
|
21
|
+
# authenticate request. exclude 'channel_id' and 'app_id' included by sinatra but not sent by Pusher.
|
22
|
+
# Raises Signature::AuthenticationError if request does not authenticate.
|
23
23
|
Signature::Request.new('POST', env['PATH_INFO'], params.except('channel_id', 'app_id')).
|
24
24
|
authenticate { |key| Signature::Token.new key, Slanger::Config.secret }
|
25
25
|
|
26
26
|
f = Fiber.current
|
27
|
+
# Publish the event in Redis and translate the result into an HTTP
|
28
|
+
# status to return to the client.
|
27
29
|
Slanger::Redis.publish(params[:channel_id], payload).tap do |r|
|
28
30
|
r.callback { f.resume [202, {}, "202 ACCEPTED\n"] }
|
29
31
|
r.errback { f.resume [500, {}, "500 INTERNAL SERVER ERROR\n"] }
|
data/lib/slanger/channel.rb
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# Channel class.
|
2
|
+
#
|
3
|
+
# Uses an EventMachine channel to let clients interact with the
|
4
|
+
# Pusher channel. Relay events received from Redis into the
|
5
|
+
# EM channel.
|
6
|
+
#
|
7
|
+
|
1
8
|
require 'glamazon'
|
2
9
|
require 'eventmachine'
|
3
10
|
require 'forwardable'
|
@@ -18,8 +25,21 @@ module Slanger
|
|
18
25
|
@channel ||= EM::Channel.new
|
19
26
|
end
|
20
27
|
|
28
|
+
# Send a client event to the EventMachine channel.
|
29
|
+
# Only events to channels requiring authentication (private or presence)
|
30
|
+
# are accepted. Public channels only get events from the API.
|
31
|
+
def send_client_message(message)
|
32
|
+
push message.to_json if authenticated?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Send an event received from Redis to the EventMachine channel
|
36
|
+
# which will send it to subscribed clients.
|
21
37
|
def dispatch(message, channel)
|
22
38
|
push(message.to_json) unless channel =~ /^slanger:/
|
23
39
|
end
|
40
|
+
|
41
|
+
def authenticated?
|
42
|
+
channel_id =~ /^private-/ || channel_id =~ /^presence-/
|
43
|
+
end
|
24
44
|
end
|
25
45
|
end
|
data/lib/slanger/config.rb
CHANGED
data/lib/slanger/handler.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# Handler class.
|
2
|
+
# Handles a client connected via a websocket connection.
|
3
|
+
|
1
4
|
require 'active_support/json'
|
2
5
|
require 'active_support/core_ext/hash'
|
3
6
|
require 'securerandom'
|
@@ -7,52 +10,86 @@ require 'fiber'
|
|
7
10
|
module Slanger
|
8
11
|
class Handler
|
9
12
|
def initialize(socket)
|
10
|
-
@socket
|
13
|
+
@socket = socket
|
14
|
+
@subscriptions = {}
|
11
15
|
authenticate
|
12
16
|
end
|
13
17
|
|
14
18
|
# Dispatches message handling to method with same name as the event name
|
15
19
|
def onmessage(msg)
|
16
|
-
msg
|
17
|
-
|
20
|
+
msg = JSON.parse msg
|
21
|
+
event = msg['event'].gsub('pusher:', 'pusher_')
|
22
|
+
|
23
|
+
if event =~ /^pusher_/
|
24
|
+
# Pusher event, call method if it exists.
|
25
|
+
send(event, msg) if respond_to? event, true
|
26
|
+
elsif event =~ /^client-/
|
27
|
+
# Client event. Send it to the destination channel.
|
28
|
+
msg['socket_id'] = @socket_id
|
29
|
+
channel = find_channel msg['channel']
|
30
|
+
channel.try :send_client_message, msg
|
31
|
+
end
|
32
|
+
rescue Exception => e
|
33
|
+
handle_error(e)
|
18
34
|
end
|
19
35
|
|
20
|
-
# Unsubscribe this connection from the
|
36
|
+
# Unsubscribe this connection from all the channels on close.
|
21
37
|
def onclose
|
22
|
-
|
23
|
-
|
24
|
-
|
38
|
+
@subscriptions.each do |channel_id, subscription_id|
|
39
|
+
channel = find_channel channel_id
|
40
|
+
channel.try :unsubscribe, subscription_id
|
41
|
+
end
|
25
42
|
end
|
26
43
|
|
27
44
|
private
|
28
45
|
|
46
|
+
def find_channel(channel_id)
|
47
|
+
channel_class(channel_id).find_by_channel_id(channel_id)
|
48
|
+
end
|
49
|
+
|
50
|
+
def channel_class(channel_name)
|
51
|
+
channel_name =~ /^presence-/ ? PresenceChannel : Channel
|
52
|
+
end
|
53
|
+
|
29
54
|
# Verify app key. Send connection_established message to connection if it checks out. Send error message and disconnect if invalid.
|
30
55
|
def authenticate
|
31
56
|
app_key = @socket.request['path'].split(/\W/)[2]
|
32
57
|
if app_key == Slanger::Config.app_key
|
33
58
|
@socket_id = SecureRandom.uuid
|
34
|
-
@socket.send(payload 'pusher:connection_established', { socket_id: @socket_id })
|
59
|
+
@socket.send(payload nil, 'pusher:connection_established', { socket_id: @socket_id })
|
35
60
|
else
|
36
|
-
@socket.send(payload 'pusher:error', { code: '4001', message: "Could not find app by key #{app_key}" })
|
61
|
+
@socket.send(payload nil, 'pusher:error', { code: '4001', message: "Could not find app by key #{app_key}" })
|
37
62
|
@socket.close_websocket
|
38
63
|
end
|
39
64
|
end
|
40
65
|
|
41
66
|
# Dispatch to handler method if channel requires authentication, otherwise subscribe.
|
42
67
|
def pusher_subscribe(msg)
|
43
|
-
|
44
|
-
if match =
|
68
|
+
channel_id = msg['data']['channel']
|
69
|
+
subscription_id = if match = channel_id.match(/^((private)|(presence))-/)
|
45
70
|
send "handle_#{match.captures[0]}_subscription", msg
|
46
71
|
else
|
47
|
-
subscribe_channel
|
72
|
+
subscribe_channel channel_id
|
48
73
|
end
|
74
|
+
@subscriptions[channel_id] = subscription_id
|
49
75
|
end
|
50
76
|
|
77
|
+
def pusher_ping(msg)
|
78
|
+
@socket.send(payload nil, 'pusher:ping')
|
79
|
+
end
|
80
|
+
|
81
|
+
def pusher_pong msg; end
|
82
|
+
|
83
|
+
#TODO: think about moving all subscription stuff into channel classes
|
51
84
|
# Add connection to channel subscribers
|
52
|
-
def subscribe_channel
|
53
|
-
channel = Slanger::Channel.find_or_create_by_channel_id(
|
54
|
-
@
|
85
|
+
def subscribe_channel(channel_id)
|
86
|
+
channel = Slanger::Channel.find_or_create_by_channel_id(channel_id)
|
87
|
+
@socket.send(payload channel_id, 'pusher_internal:subscription_succeeded')
|
88
|
+
# Subscribe to the channel and have the events received from it
|
89
|
+
# sent to the client's socket.
|
90
|
+
subscription_id = channel.subscribe do |msg|
|
55
91
|
msg = JSON.parse(msg)
|
92
|
+
# Don't send the event if it was sent by the client
|
56
93
|
socket_id = msg.delete 'socket_id'
|
57
94
|
@socket.send msg.to_json unless socket_id == @socket_id
|
58
95
|
end
|
@@ -60,29 +97,31 @@ module Slanger
|
|
60
97
|
|
61
98
|
# Validate authentication token for private channel and add connection to channel subscribers if it checks out
|
62
99
|
def handle_private_subscription(msg)
|
63
|
-
|
64
|
-
|
65
|
-
|
100
|
+
channel = msg['data']['channel']
|
101
|
+
if msg['data']['auth'] && token(channel, msg['data']['channel_data']) != msg['data']['auth'].split(':')[1]
|
102
|
+
@socket.send(payload nil, 'pusher:error', {
|
103
|
+
message: "Invalid signature: Expected HMAC SHA256 hex digest of #{@socket_id}:#{channel}, but got #{msg['data']['auth']}"
|
66
104
|
})
|
67
105
|
else
|
68
|
-
subscribe_channel
|
106
|
+
subscribe_channel channel
|
69
107
|
end
|
70
108
|
end
|
71
109
|
|
72
110
|
# Validate authentication token and check channel_data. Add connection to channel subscribers if it checks out
|
73
111
|
def handle_presence_subscription(msg)
|
74
|
-
|
75
|
-
|
112
|
+
channel_id = msg['data']['channel']
|
113
|
+
if token(channel_id, msg['data']['channel_data']) != msg['data']['auth'].split(':')[1]
|
114
|
+
@socket.send(payload nil, 'pusher:error', {
|
76
115
|
message: "Invalid signature: Expected HMAC SHA256 hex digest of #{@socket_id}:#{msg['data']['channel']}, but got #{msg['data']['auth']}"
|
77
116
|
})
|
78
117
|
elsif !msg['data']['channel_data']
|
79
|
-
@socket.send(payload 'pusher:error', {
|
118
|
+
@socket.send(payload nil, 'pusher:error', {
|
80
119
|
message: "presence-channel is a presence channel and subscription must include channel_data"
|
81
120
|
})
|
82
121
|
else
|
83
|
-
channel = Slanger::PresenceChannel.find_or_create_by_channel_id(
|
122
|
+
channel = Slanger::PresenceChannel.find_or_create_by_channel_id(channel_id)
|
84
123
|
callback = Proc.new {
|
85
|
-
@socket.send(payload 'pusher_internal:subscription_succeeded', {
|
124
|
+
@socket.send(payload channel_id, 'pusher_internal:subscription_succeeded', {
|
86
125
|
presence: {
|
87
126
|
count: channel.subscribers.size,
|
88
127
|
ids: channel.ids,
|
@@ -90,24 +129,36 @@ module Slanger
|
|
90
129
|
}
|
91
130
|
})
|
92
131
|
}
|
93
|
-
|
132
|
+
# Subscribe to channel, call callback when done to send a
|
133
|
+
# subscription_succeeded event to the client.
|
134
|
+
channel.subscribe(msg, callback) do |msg|
|
135
|
+
# Send channel messages to the client, unless it is the
|
136
|
+
# sender of the event.
|
94
137
|
msg = JSON.parse(msg)
|
95
138
|
socket_id = msg.delete 'socket_id'
|
96
139
|
@socket.send msg.to_json unless socket_id == @socket_id
|
97
140
|
end
|
98
|
-
|
99
141
|
end
|
100
142
|
end
|
101
143
|
|
102
144
|
# Message helper method. Converts a hash into the Pusher JSON protocol
|
103
|
-
def payload(event_name, payload = {})
|
104
|
-
{ channel:
|
145
|
+
def payload(channel_id, event_name, payload = {})
|
146
|
+
{ channel: channel_id, event: event_name, data: payload }.to_json
|
105
147
|
end
|
106
148
|
|
107
149
|
# HMAC token validation
|
108
|
-
def token(params=nil)
|
109
|
-
string_to_sign = [@socket_id,
|
150
|
+
def token(channel_id, params=nil)
|
151
|
+
string_to_sign = [@socket_id, channel_id, params].compact.join ':'
|
110
152
|
HMAC::SHA256.hexdigest(Slanger::Config.secret, string_to_sign)
|
111
153
|
end
|
154
|
+
|
155
|
+
def handle_error(e)
|
156
|
+
case e
|
157
|
+
when JSON::ParserError
|
158
|
+
@socket.send(payload nil, 'pusher:error', { code: '5001', message: "Invalid JSON" })
|
159
|
+
else
|
160
|
+
@socket.send(payload nil, 'pusher:error', { code: '5000', message: "Internal Server error" })
|
161
|
+
end
|
162
|
+
end
|
112
163
|
end
|
113
164
|
end
|
@@ -1,3 +1,10 @@
|
|
1
|
+
# PresenceChannel class.
|
2
|
+
#
|
3
|
+
# Uses an EventMachine channel to let handlers interact with the
|
4
|
+
# Pusher channel. Relay events received from Redis into the
|
5
|
+
# EM channel. Keeps data on the subscribers to send it to clients.
|
6
|
+
#
|
7
|
+
|
1
8
|
require 'glamazon'
|
2
9
|
require 'eventmachine'
|
3
10
|
require 'forwardable'
|
@@ -7,8 +14,11 @@ module Slanger
|
|
7
14
|
class PresenceChannel < Channel
|
8
15
|
def_delegators :channel, :push
|
9
16
|
|
17
|
+
# Send an event received from Redis to the EventMachine channel
|
10
18
|
def dispatch(message, channel)
|
11
19
|
if channel =~ /^slanger:/
|
20
|
+
# Messages received from the Redis channel slanger:* carry info on
|
21
|
+
# subscriptions. Update our subscribers accordingly.
|
12
22
|
update_subscribers message
|
13
23
|
else
|
14
24
|
push message.to_json
|
@@ -17,6 +27,7 @@ module Slanger
|
|
17
27
|
|
18
28
|
def initialize(attrs)
|
19
29
|
super
|
30
|
+
# Also subscribe the slanger daemon to a Redis channel used for events concerning subscriptions.
|
20
31
|
Slanger::Redis.subscribe 'slanger:connection_notification'
|
21
32
|
end
|
22
33
|
|
@@ -24,15 +35,20 @@ module Slanger
|
|
24
35
|
channel_data = JSON.parse msg['data']['channel_data']
|
25
36
|
public_subscription_id = SecureRandom.uuid
|
26
37
|
|
38
|
+
# Send event about the new subscription to the Redis slanger:connection_notification Channel.
|
27
39
|
publisher = publish_connection_notification subscription_id: public_subscription_id, online: true,
|
28
40
|
channel_data: channel_data, channel: channel_id
|
29
41
|
|
42
|
+
# Associate the subscription data to the public id in Redis.
|
30
43
|
roster_add public_subscription_id, channel_data
|
31
44
|
|
32
45
|
# fuuuuuuuuuccccccck!
|
33
46
|
publisher.callback do
|
34
47
|
EM.next_tick do
|
48
|
+
# The Subscription event has been sent to Redis successfully.
|
49
|
+
# Call the provided callback.
|
35
50
|
callback.call
|
51
|
+
# Add the subscription to our table.
|
36
52
|
internal_subscription_table[public_subscription_id] = channel.subscribe &blk
|
37
53
|
end
|
38
54
|
end
|
@@ -51,6 +67,7 @@ module Slanger
|
|
51
67
|
def unsubscribe(public_subscription_id)
|
52
68
|
# Unsubcribe from EM::Channel
|
53
69
|
channel.unsubscribe(internal_subscription_table.delete(public_subscription_id)) # if internal_subscription_table[public_subscription_id]
|
70
|
+
# Remove subscription data from Redis
|
54
71
|
roster_remove public_subscription_id
|
55
72
|
# Notify all instances
|
56
73
|
publish_connection_notification subscription_id: public_subscription_id, online: false, channel: channel_id
|
@@ -59,6 +76,7 @@ module Slanger
|
|
59
76
|
private
|
60
77
|
|
61
78
|
def get_roster
|
79
|
+
# Read subscription infos from Redis.
|
62
80
|
Fiber.new do
|
63
81
|
f = Fiber.current
|
64
82
|
Slanger::Redis.hgetall(channel_id).
|
@@ -68,14 +86,18 @@ module Slanger
|
|
68
86
|
end
|
69
87
|
|
70
88
|
def roster_add(key, value)
|
89
|
+
# Add subscription info to Redis.
|
71
90
|
Slanger::Redis.hset(channel_id, key, value)
|
72
91
|
end
|
73
92
|
|
74
93
|
def roster_remove(key)
|
94
|
+
# Remove subscription info from Redis.
|
75
95
|
Slanger::Redis.hdel(channel_id, key)
|
76
96
|
end
|
77
97
|
|
78
98
|
def publish_connection_notification(payload, retry_count=0)
|
99
|
+
# Send a subscription notification to the global slanger:connection_notification
|
100
|
+
# channel.
|
79
101
|
Slanger::Redis.publish('slanger:connection_notification', payload.to_json).
|
80
102
|
tap { |r| r.errback { publish_connection_notification payload, retry_count.succ unless retry_count == 5 } }
|
81
103
|
end
|
data/lib/slanger/redis.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# Redis class.
|
2
|
+
# Interface with Redis.
|
3
|
+
|
1
4
|
require 'forwardable'
|
2
5
|
|
3
6
|
module Slanger
|
@@ -5,10 +8,11 @@ module Slanger
|
|
5
8
|
extend Forwardable
|
6
9
|
|
7
10
|
def self.extended base
|
11
|
+
# Dispatch messages received from Redis to their destination channel.
|
8
12
|
base.on(:message) do |channel, message|
|
9
13
|
message = JSON.parse message
|
10
|
-
|
11
|
-
|
14
|
+
klass = message['channel'][/^presence-/] ? PresenceChannel : Channel
|
15
|
+
klass.find_or_create_by_channel_id(message['channel']).dispatch message, channel
|
12
16
|
end
|
13
17
|
end
|
14
18
|
|
@@ -5,7 +5,19 @@ module Slanger
|
|
5
5
|
module WebSocketServer
|
6
6
|
def run
|
7
7
|
EM.run do
|
8
|
-
|
8
|
+
options = {
|
9
|
+
host: Slanger::Config[:websocket_host],
|
10
|
+
port: Slanger::Config[:websocket_port],
|
11
|
+
debug: Slanger::Config[:debug],
|
12
|
+
app_key: Slanger::Config[:app_key]
|
13
|
+
}
|
14
|
+
|
15
|
+
if Slanger::Config[:tls_options]
|
16
|
+
options.merge! secure: true,
|
17
|
+
tls_options: Slanger::Config[:tls_options]
|
18
|
+
end
|
19
|
+
|
20
|
+
EM::WebSocket.start options do |ws|
|
9
21
|
# Keep track of handler instance in instance of EM::Connection to ensure a unique handler instance is used per connection.
|
10
22
|
ws.class_eval { attr_accessor :connection_handler }
|
11
23
|
# Delegate connection management to handler instance.
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slanger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-04-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: eventmachine
|
16
|
-
requirement: &
|
16
|
+
requirement: &70260724123600 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 0.12.10
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70260724123600
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: em-hiredis
|
27
|
-
requirement: &
|
27
|
+
requirement: &70260724155460 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 0.1.0
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70260724155460
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: em-websocket
|
38
|
-
requirement: &
|
38
|
+
requirement: &70260724154320 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 0.3.5
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70260724154320
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rack
|
49
|
-
requirement: &
|
49
|
+
requirement: &70260724152840 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 1.3.3
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70260724152840
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rack-fiber_pool
|
60
|
-
requirement: &
|
60
|
+
requirement: &70260724149120 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - =
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: 0.9.1
|
66
66
|
type: :runtime
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70260724149120
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: signature
|
71
|
-
requirement: &
|
71
|
+
requirement: &70260724182220 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ~>
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: 0.1.2
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70260724182220
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: activesupport
|
82
|
-
requirement: &
|
82
|
+
requirement: &70260724178720 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ~>
|
@@ -87,10 +87,10 @@ dependencies:
|
|
87
87
|
version: 3.1.0
|
88
88
|
type: :runtime
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *70260724178720
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
92
|
name: glamazon
|
93
|
-
requirement: &
|
93
|
+
requirement: &70260724177880 !ruby/object:Gem::Requirement
|
94
94
|
none: false
|
95
95
|
requirements:
|
96
96
|
- - ~>
|
@@ -98,10 +98,10 @@ dependencies:
|
|
98
98
|
version: 0.3.1
|
99
99
|
type: :runtime
|
100
100
|
prerelease: false
|
101
|
-
version_requirements: *
|
101
|
+
version_requirements: *70260724177880
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
103
|
name: sinatra
|
104
|
-
requirement: &
|
104
|
+
requirement: &70260724206740 !ruby/object:Gem::Requirement
|
105
105
|
none: false
|
106
106
|
requirements:
|
107
107
|
- - ~>
|
@@ -109,10 +109,10 @@ dependencies:
|
|
109
109
|
version: 1.2.6
|
110
110
|
type: :runtime
|
111
111
|
prerelease: false
|
112
|
-
version_requirements: *
|
112
|
+
version_requirements: *70260724206740
|
113
113
|
- !ruby/object:Gem::Dependency
|
114
114
|
name: thin
|
115
|
-
requirement: &
|
115
|
+
requirement: &70260724205980 !ruby/object:Gem::Requirement
|
116
116
|
none: false
|
117
117
|
requirements:
|
118
118
|
- - ~>
|
@@ -120,10 +120,10 @@ dependencies:
|
|
120
120
|
version: 1.2.11
|
121
121
|
type: :runtime
|
122
122
|
prerelease: false
|
123
|
-
version_requirements: *
|
123
|
+
version_requirements: *70260724205980
|
124
124
|
- !ruby/object:Gem::Dependency
|
125
125
|
name: em-http-request
|
126
|
-
requirement: &
|
126
|
+
requirement: &70260724205260 !ruby/object:Gem::Requirement
|
127
127
|
none: false
|
128
128
|
requirements:
|
129
129
|
- - ~>
|
@@ -131,10 +131,10 @@ dependencies:
|
|
131
131
|
version: 0.3.0
|
132
132
|
type: :runtime
|
133
133
|
prerelease: false
|
134
|
-
version_requirements: *
|
134
|
+
version_requirements: *70260724205260
|
135
135
|
- !ruby/object:Gem::Dependency
|
136
136
|
name: rspec
|
137
|
-
requirement: &
|
137
|
+
requirement: &70260724203820 !ruby/object:Gem::Requirement
|
138
138
|
none: false
|
139
139
|
requirements:
|
140
140
|
- - ~>
|
@@ -142,10 +142,10 @@ dependencies:
|
|
142
142
|
version: 2.6.0
|
143
143
|
type: :development
|
144
144
|
prerelease: false
|
145
|
-
version_requirements: *
|
145
|
+
version_requirements: *70260724203820
|
146
146
|
- !ruby/object:Gem::Dependency
|
147
147
|
name: pusher
|
148
|
-
requirement: &
|
148
|
+
requirement: &70260724202560 !ruby/object:Gem::Requirement
|
149
149
|
none: false
|
150
150
|
requirements:
|
151
151
|
- - ~>
|
@@ -153,10 +153,10 @@ dependencies:
|
|
153
153
|
version: 0.8.2
|
154
154
|
type: :development
|
155
155
|
prerelease: false
|
156
|
-
version_requirements: *
|
156
|
+
version_requirements: *70260724202560
|
157
157
|
- !ruby/object:Gem::Dependency
|
158
158
|
name: haml
|
159
|
-
requirement: &
|
159
|
+
requirement: &70260724199480 !ruby/object:Gem::Requirement
|
160
160
|
none: false
|
161
161
|
requirements:
|
162
162
|
- - ~>
|
@@ -164,7 +164,7 @@ dependencies:
|
|
164
164
|
version: 3.1.2
|
165
165
|
type: :development
|
166
166
|
prerelease: false
|
167
|
-
version_requirements: *
|
167
|
+
version_requirements: *70260724199480
|
168
168
|
description: A websocket service compatible with Pusher libraries
|
169
169
|
email: sjtgraham@mac.com
|
170
170
|
executables:
|
@@ -183,8 +183,9 @@ files:
|
|
183
183
|
- lib/slanger/service.rb
|
184
184
|
- lib/slanger/web_socket_server.rb
|
185
185
|
- slanger.rb
|
186
|
-
-
|
187
|
-
|
186
|
+
- !binary |-
|
187
|
+
YmluL3NsYW5nZXI=
|
188
|
+
homepage: http://github.com/stevegraham/slanger
|
188
189
|
licenses: []
|
189
190
|
post_install_message:
|
190
191
|
rdoc_options: []
|
@@ -204,7 +205,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
204
205
|
version: '0'
|
205
206
|
requirements: []
|
206
207
|
rubyforge_project:
|
207
|
-
rubygems_version: 1.8.
|
208
|
+
rubygems_version: 1.8.16
|
208
209
|
signing_key:
|
209
210
|
specification_version: 3
|
210
211
|
summary: A websocket service compatible with Pusher libraries
|