pakyow-realtime 0.10.2 → 0.11.0
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.
- checksums.yaml +4 -4
- data/pakyow-realtime/CHANGELOG.md +14 -0
- data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/config.rb +4 -6
- data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/connection.rb +0 -0
- data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/context.rb +8 -0
- data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/delegate.rb +19 -2
- data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/exceptions.rb +0 -0
- data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/ext/request.rb +0 -0
- data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/helpers.rb +7 -0
- data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/hooks.rb +15 -4
- data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/message_handler.rb +0 -0
- data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/message_handlers/call_route.rb +7 -10
- data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/message_handlers/ping.rb +0 -0
- data/pakyow-realtime/lib/pakyow/realtime/redis_subscription.rb +61 -0
- data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/registries/redis_registry.rb +41 -28
- data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/registries/simple_registry.rb +3 -3
- data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/websocket.rb +84 -63
- data/pakyow-realtime/lib/pakyow/realtime.rb +19 -0
- data/pakyow-realtime/lib/pakyow-realtime.rb +1 -21
- metadata +26 -26
- data/pakyow-realtime/lib/pakyow-realtime/redis_subscription.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7de6d6caa97ff92eb8c07092eb9438e952ceb4c2
|
4
|
+
data.tar.gz: dc8d6705e39160414dea747275e53bf351ddfaa5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 562149cc4659759ae9a6154dfd5e6c5d02cab93723e50b15b188b7e240e85b24604011acceb40ac972e5a233d1a5bb82c352b1821cee86fe420ecbbf00a17072
|
7
|
+
data.tar.gz: ea8d69bf0efe8e29018d36835ab14c3d0b73b7e7707fd949a0e78d34eece2e3d013726b251e80c14b83b9de24b17be3ab72defaf478a8f9aefe42a2828617a71
|
@@ -1,3 +1,17 @@
|
|
1
|
+
# 0.11.0
|
2
|
+
|
3
|
+
* Gracefully shuts down WebSocket when something bad happens
|
4
|
+
* Fixes a bug calling the root route over a WebSocket
|
5
|
+
* Fixes a bug causing join events to be fired before the connection is registered
|
6
|
+
* Fixes a bug causing session to not be serialized in redis
|
7
|
+
* Fixes a bug causing redis subscriptions to never unsubscribe
|
8
|
+
* Properly disconnects from redis as necessary to avoid eating up connections
|
9
|
+
* Moves everything into the Pakyow namespace
|
10
|
+
* Improves subscriber management in production environments
|
11
|
+
* Fixes a memory leak caused by converting channel names to symbols
|
12
|
+
* Replaces `websocket_parser` gem with `websocket`
|
13
|
+
* Fixes a bug causing websocket failures in Internet Explorer
|
14
|
+
|
1
15
|
# 0.10.2 / 2015-11-15
|
2
16
|
|
3
17
|
* Adds ability to return only a partial in call-route response
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require_relative 'registries/simple_registry'
|
2
2
|
require_relative 'registries/redis_registry'
|
3
3
|
|
4
|
-
Pakyow::Config.register
|
4
|
+
Pakyow::Config.register :realtime do |config|
|
5
5
|
# The registry to use when keeping up with connections.
|
6
6
|
config.opt :registry, Pakyow::Realtime::SimpleRegistry
|
7
7
|
|
@@ -13,10 +13,8 @@ Pakyow::Config.register(:realtime) { |config|
|
|
13
13
|
|
14
14
|
# Whether or not realtime should be enabled.
|
15
15
|
config.opt :enabled, true
|
16
|
-
|
16
|
+
end.env :development do |opts|
|
17
17
|
opts.registry = Pakyow::Realtime::SimpleRegistry
|
18
|
-
|
18
|
+
end.env :production do |opts|
|
19
19
|
opts.registry = Pakyow::Realtime::RedisRegistry
|
20
|
-
|
21
|
-
opts.registry = Pakyow::Realtime::RedisRegistry
|
22
|
-
}
|
20
|
+
end
|
File without changes
|
@@ -49,6 +49,14 @@ module Pakyow
|
|
49
49
|
delegate.push(msg, channels)
|
50
50
|
end
|
51
51
|
|
52
|
+
# Push a message down a channel directed at a specific client,
|
53
|
+
# identified by key.
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
def push_message_to_socket_with_key(msg, channel, key, propagated = false)
|
57
|
+
delegate.push_message_to_socket_with_key(msg, channel, key, propagated)
|
58
|
+
end
|
59
|
+
|
52
60
|
# Returns an instance of the connection delegate.
|
53
61
|
#
|
54
62
|
# @api private
|
@@ -29,7 +29,7 @@ module Pakyow
|
|
29
29
|
@channels[channel] << connection
|
30
30
|
end
|
31
31
|
|
32
|
-
registry.subscribe_for_propagation(channels) if registry.propagates?
|
32
|
+
registry.subscribe_for_propagation(channels, key) if registry.propagates?
|
33
33
|
end
|
34
34
|
|
35
35
|
# Unregisters a connection by its key.
|
@@ -73,6 +73,23 @@ module Pakyow
|
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
+
# Pushes a message down a channel to a specific client (identified by key).
|
77
|
+
def push_message_to_socket_with_key(message, channel, key, propagated = false)
|
78
|
+
return if key.nil? || key.empty?
|
79
|
+
|
80
|
+
if registry.propagates? && !propagated
|
81
|
+
return registry.push_message_to_socket_with_key(message, channel, key)
|
82
|
+
else
|
83
|
+
connection = @connections.find { |_, connection|
|
84
|
+
connection and connection.key == key
|
85
|
+
}
|
86
|
+
|
87
|
+
if connection
|
88
|
+
connection[1].push(payload: message, channel: channel)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
76
93
|
private
|
77
94
|
|
78
95
|
def propagate(message, channels)
|
@@ -80,7 +97,7 @@ module Pakyow
|
|
80
97
|
end
|
81
98
|
|
82
99
|
def propagated?(message)
|
83
|
-
message.include?(:__propagated)
|
100
|
+
message.include?(:__propagated) || message.include?('__propagated')
|
84
101
|
end
|
85
102
|
|
86
103
|
def connections_for_channel(channel_query)
|
File without changes
|
File without changes
|
@@ -1,15 +1,24 @@
|
|
1
1
|
Pakyow::App.before :route do
|
2
2
|
# we want to hijack websocket requests
|
3
3
|
#
|
4
|
-
if req.env['HTTP_UPGRADE']
|
4
|
+
if req.env['HTTP_UPGRADE'] && req.env['HTTP_UPGRADE'].casecmp('websocket') == 0
|
5
5
|
if Pakyow::Config.realtime.enabled
|
6
6
|
socket_connection_id = params[:socket_connection_id]
|
7
7
|
socket_digest = socket_digest(socket_connection_id)
|
8
8
|
|
9
|
-
|
9
|
+
begin
|
10
|
+
conn = Pakyow::Realtime::Websocket.new(req, socket_digest)
|
11
|
+
rescue Pakyow::Realtime::HandshakeError => e
|
12
|
+
logger.error e.message
|
13
|
+
res.status = 400
|
14
|
+
halt
|
15
|
+
end
|
10
16
|
|
11
17
|
# register the connection with a unique key
|
12
18
|
Pakyow::Realtime::Delegate.instance.register(socket_digest, conn)
|
19
|
+
|
20
|
+
# tell the connection that it's registered
|
21
|
+
conn.registered
|
13
22
|
end
|
14
23
|
|
15
24
|
halt
|
@@ -20,8 +29,10 @@ Pakyow::App.after :process do
|
|
20
29
|
# mixin the socket connection id into the body tag
|
21
30
|
# this id is used by pakyow.js to idenfity itself with the server
|
22
31
|
#
|
23
|
-
if response.header['Content-Type']
|
24
|
-
|
32
|
+
if response.header['Content-Type'].include?('text/html') && Pakyow::Config.realtime.enabled
|
33
|
+
next if !response.body.is_a?(Array)
|
34
|
+
|
35
|
+
body = response.body.first
|
25
36
|
next if body.nil?
|
26
37
|
|
27
38
|
mixin = '<body data-socket-connection-id="' + socket_connection_id + '"'
|
File without changes
|
@@ -1,12 +1,7 @@
|
|
1
1
|
# Calls an app route and returns a response, just like an HTTP request!
|
2
2
|
#
|
3
3
|
Pakyow::Realtime.handler :'call-route' do |message, session, response|
|
4
|
-
|
5
|
-
path_parts = path.split('/')
|
6
|
-
path_parts[-1] += '.json'
|
7
|
-
uri = [path_parts.join('/'), qs].join('?')
|
8
|
-
|
9
|
-
env = Rack::MockRequest.env_for(uri, method: message['method'])
|
4
|
+
env = Rack::MockRequest.env_for(message['uri'], method: message['method'])
|
10
5
|
env['pakyow.socket'] = true
|
11
6
|
env['pakyow.data'] = message['input']
|
12
7
|
env['rack.session'] = session
|
@@ -14,13 +9,15 @@ Pakyow::Realtime.handler :'call-route' do |message, session, response|
|
|
14
9
|
# TODO: in production we want to push the message to a queue and
|
15
10
|
# let the next available app instance pick it up, rather than
|
16
11
|
# the current instance to handle all traffic on this socket
|
17
|
-
|
18
|
-
|
12
|
+
|
13
|
+
context = Pakyow::CallContext.new(env)
|
14
|
+
context.process
|
15
|
+
res = context.finish
|
19
16
|
|
20
17
|
container = message['container']
|
21
18
|
partial = message['partial']
|
22
19
|
|
23
|
-
composer =
|
20
|
+
composer = context.presenter.composer
|
24
21
|
|
25
22
|
if container
|
26
23
|
body = composer.container(container.to_sym).includes(composer.partials).to_s
|
@@ -32,6 +29,6 @@ Pakyow::Realtime.handler :'call-route' do |message, session, response|
|
|
32
29
|
|
33
30
|
response[:status] = res[0]
|
34
31
|
response[:headers] = res[1]
|
35
|
-
response[:body] = body
|
32
|
+
response[:body] = body.is_a?(StringIO) ? body.read : body
|
36
33
|
response
|
37
34
|
end
|
File without changes
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'concurrent'
|
3
|
+
|
4
|
+
module Pakyow
|
5
|
+
module Realtime
|
6
|
+
# Manages channel subscriptions for this application instance's WebSockets.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class RedisSubscription
|
10
|
+
SIGNAL_UNSUBSCRIBE = 'unsubscribe'
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@redis = ::Redis.new(Config.realtime.redis)
|
14
|
+
end
|
15
|
+
|
16
|
+
def subscribe(channels = [])
|
17
|
+
channels << signal_channel
|
18
|
+
|
19
|
+
Concurrent::Future.execute {
|
20
|
+
@redis.subscribe(*channels) do |on|
|
21
|
+
on.message do |channel, msg|
|
22
|
+
begin
|
23
|
+
msg = JSON.parse(msg)
|
24
|
+
|
25
|
+
if channel == signal_channel
|
26
|
+
if msg['signal'] == SIGNAL_UNSUBSCRIBE
|
27
|
+
@resubscribe_channels = msg['resubscribe_channels']
|
28
|
+
@redis.unsubscribe
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
if msg.is_a?(Hash)
|
33
|
+
msg[:__propagated] = true
|
34
|
+
elsif msg.is_a?(Array)
|
35
|
+
msg << :__propagated
|
36
|
+
end
|
37
|
+
|
38
|
+
context = Pakyow::Realtime::Context.new(Pakyow.app)
|
39
|
+
|
40
|
+
if msg.key?('key')
|
41
|
+
context.push_message_to_socket_with_key(msg['message'], msg['channel'], msg['key'], true)
|
42
|
+
else
|
43
|
+
context.push(msg, channel)
|
44
|
+
end
|
45
|
+
rescue StandardError => e
|
46
|
+
Pakyow.logger.error "RedisSubscription encountered a fatal error:"
|
47
|
+
Pakyow.logger.error e.message
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
subscribe(@resubscribe_channels) if @resubscribe_channels
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def signal_channel
|
57
|
+
"pw:#{object_id}:signal"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -2,7 +2,8 @@ require 'json'
|
|
2
2
|
require 'redis'
|
3
3
|
require 'singleton'
|
4
4
|
|
5
|
-
|
5
|
+
require 'pakyow/realtime/redis_subscription'
|
6
|
+
require 'pakyow/realtime/registries/simple_registry'
|
6
7
|
|
7
8
|
module Pakyow
|
8
9
|
module Realtime
|
@@ -15,37 +16,38 @@ module Pakyow
|
|
15
16
|
# deployments with more than one app instance.
|
16
17
|
#
|
17
18
|
# @api private
|
18
|
-
class RedisRegistry
|
19
|
-
include Singleton
|
20
|
-
|
19
|
+
class RedisRegistry < SimpleRegistry
|
21
20
|
attr_reader :subscriber
|
22
21
|
|
23
|
-
def initialize
|
24
|
-
@channels = []
|
25
|
-
end
|
26
|
-
|
27
22
|
def channels_for_key(key)
|
28
|
-
|
23
|
+
value = Pakyow::Realtime.redis.hget(channel_key, key)
|
24
|
+
value ? JSON.parse(value) : []
|
29
25
|
end
|
30
26
|
|
31
27
|
def unregister_key(key)
|
28
|
+
super
|
29
|
+
|
32
30
|
Pakyow::Realtime.redis.hdel(channel_key, key)
|
31
|
+
resubscribe
|
33
32
|
end
|
34
33
|
|
35
34
|
def subscribe_to_channels_for_key(channels, key)
|
36
|
-
|
37
|
-
|
35
|
+
channels << "pw:socket:#{key}"
|
36
|
+
super
|
38
37
|
|
39
|
-
@channels.concat(channels).uniq!
|
40
38
|
resubscribe
|
39
|
+
|
40
|
+
new_channels = channels_for_key(key).concat(Array.ensure(channels)).uniq
|
41
|
+
Pakyow::Realtime.redis.hset(channel_key, key, new_channels.to_json)
|
41
42
|
end
|
42
43
|
|
43
44
|
def unsubscribe_to_channels_for_key(channels, key)
|
44
|
-
|
45
|
-
Pakyow::Realtime.redis.hset(channel_key, key, new_channels.to_json)
|
45
|
+
super
|
46
46
|
|
47
|
-
channels.each { |channel| @channels.delete(channel) }
|
48
47
|
resubscribe
|
48
|
+
|
49
|
+
new_channels = channels_for_key(key) - Array.ensure(channels)
|
50
|
+
Pakyow::Realtime.redis.hset(channel_key, key, new_channels.to_json)
|
49
51
|
end
|
50
52
|
|
51
53
|
def propagates?
|
@@ -60,35 +62,46 @@ module Pakyow
|
|
60
62
|
end
|
61
63
|
end
|
62
64
|
|
63
|
-
def subscribe_for_propagation(channels)
|
64
|
-
@channels
|
65
|
+
def subscribe_for_propagation(channels, key)
|
66
|
+
@channels[key] ||= []
|
67
|
+
@channels[key].concat(Array.ensure(channels)) << "pw:socket:#{key}"
|
68
|
+
@channels[key].uniq!
|
69
|
+
|
65
70
|
resubscribe
|
66
71
|
end
|
67
72
|
|
73
|
+
def push_message_to_socket_with_key(message, channel, key)
|
74
|
+
propagate({
|
75
|
+
key: key,
|
76
|
+
channel: channel,
|
77
|
+
message: message
|
78
|
+
},
|
79
|
+
|
80
|
+
["pw:socket:#{key}"])
|
81
|
+
end
|
82
|
+
|
68
83
|
private
|
69
84
|
|
70
|
-
#
|
71
|
-
#
|
85
|
+
# Tells the subscription to unsubscribe from the current
|
86
|
+
# list of channels, then subscribes it to the new list.
|
72
87
|
def resubscribe
|
88
|
+
channels = @channels.values.flatten.uniq
|
89
|
+
|
73
90
|
if @subscriber
|
74
|
-
Pakyow::Realtime.redis.publish(@subscriber.signal_channel,
|
91
|
+
Pakyow::Realtime.redis.publish(@subscriber.signal_channel, {
|
92
|
+
'signal' => RedisSubscription::SIGNAL_UNSUBSCRIBE,
|
93
|
+
'resubscribe_channels' => channels
|
94
|
+
}.to_json)
|
75
95
|
else
|
76
96
|
@subscriber = RedisSubscription.new
|
97
|
+
@subscriber.subscribe(channels)
|
77
98
|
end
|
78
|
-
|
79
|
-
@subscriber.subscribe(@channels)
|
80
99
|
end
|
81
100
|
|
82
101
|
# Returns the key used to store channels.
|
83
102
|
def channel_key
|
84
103
|
Config.realtime.redis_key
|
85
104
|
end
|
86
|
-
|
87
|
-
# Returns the channels for a specific key, or all channels.
|
88
|
-
def channels(key)
|
89
|
-
value = Pakyow::Realtime.redis.hget(channel_key, key)
|
90
|
-
(value ? JSON.parse(value) : []).map(&:to_sym)
|
91
|
-
end
|
92
105
|
end
|
93
106
|
end
|
94
107
|
end
|
@@ -24,14 +24,14 @@ module Pakyow
|
|
24
24
|
|
25
25
|
def subscribe_to_channels_for_key(channels, key)
|
26
26
|
@channels[key] ||= []
|
27
|
-
@channels[key].concat(Array.ensure(channels
|
27
|
+
@channels[key].concat(Array.ensure(channels)).uniq!
|
28
28
|
end
|
29
29
|
|
30
30
|
def unsubscribe_to_channels_for_key(channels, key)
|
31
31
|
@channels[key] ||= []
|
32
|
-
@channels[key] = @channels[key] - Array.ensure(channels
|
32
|
+
@channels[key] = @channels[key] - Array.ensure(channels)
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
def propagates?
|
36
36
|
false
|
37
37
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'concurrent'
|
2
|
-
require '
|
2
|
+
require 'websocket'
|
3
3
|
|
4
4
|
require_relative 'connection'
|
5
5
|
|
@@ -18,10 +18,13 @@ module Pakyow
|
|
18
18
|
@req = req
|
19
19
|
@key = key
|
20
20
|
|
21
|
-
@
|
22
|
-
@
|
21
|
+
@socket = hijack(req)
|
22
|
+
@server = handshake(req)
|
23
|
+
setup
|
24
|
+
end
|
23
25
|
|
24
|
-
|
26
|
+
def registered
|
27
|
+
handle_ws_join
|
25
28
|
end
|
26
29
|
|
27
30
|
def shutdown
|
@@ -41,7 +44,19 @@ module Pakyow
|
|
41
44
|
def push(msg)
|
42
45
|
json = JSON.pretty_generate(msg)
|
43
46
|
logger.debug "(ws.#{@key}) sending message: #{json}\n"
|
44
|
-
|
47
|
+
|
48
|
+
frame = WebSocket::Frame::Outgoing::Server.new(
|
49
|
+
version: @server.version,
|
50
|
+
data: json,
|
51
|
+
type: :text)
|
52
|
+
@socket.write(frame.to_s)
|
53
|
+
rescue StandardError => e
|
54
|
+
logger.error "(#{@key}): WebSocket encountered a fatal error:"
|
55
|
+
logger.error e.message
|
56
|
+
|
57
|
+
# something went wrong (like a broken pipe); shutdown
|
58
|
+
# and let the socket reconnect if it's still around
|
59
|
+
shutdown
|
45
60
|
end
|
46
61
|
|
47
62
|
def self.on(event, &block)
|
@@ -50,11 +65,7 @@ module Pakyow
|
|
50
65
|
|
51
66
|
private
|
52
67
|
|
53
|
-
def
|
54
|
-
WebSocket::ClientHandshake.new(:get, req.url, handshake_headers(req))
|
55
|
-
end
|
56
|
-
|
57
|
-
def hijack!(req)
|
68
|
+
def hijack(req)
|
58
69
|
if req.env['rack.hijack']
|
59
70
|
req.env['rack.hijack'].call
|
60
71
|
return req.env['rack.hijack_io']
|
@@ -63,75 +74,62 @@ module Pakyow
|
|
63
74
|
end
|
64
75
|
end
|
65
76
|
|
66
|
-
def
|
67
|
-
{
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
def handle_handshake
|
75
|
-
return if @socket.nil?
|
76
|
-
|
77
|
-
if @handshake.valid?
|
78
|
-
accept_handshake
|
79
|
-
setup
|
80
|
-
else
|
81
|
-
fail_handshake
|
77
|
+
def handshake(req)
|
78
|
+
data = "#{req.env['REQUEST_METHOD']} #{req.env['REQUEST_URI']} #{req.env['SERVER_PROTOCOL']}\r\n"
|
79
|
+
req.env.each_pair do |key, val|
|
80
|
+
if key =~ /^HTTP_(.*)/
|
81
|
+
name = rack_env_key_to_http_header_name($1)
|
82
|
+
data << "#{name}: #{val}\r\n"
|
83
|
+
end
|
82
84
|
end
|
83
|
-
|
85
|
+
data << "\r\n"
|
84
86
|
|
85
|
-
|
86
|
-
|
87
|
-
response.render(@socket)
|
88
|
-
end
|
87
|
+
server = WebSocket::Handshake::Server.new
|
88
|
+
server << data
|
89
89
|
|
90
|
-
|
91
|
-
|
90
|
+
fail HandshakeError, "(ws.#{@key}) error during handshake" unless server.valid?
|
91
|
+
@socket.write(server.to_s)
|
92
92
|
|
93
|
-
|
94
|
-
|
93
|
+
server
|
94
|
+
end
|
95
95
|
|
96
|
-
|
96
|
+
def rack_env_key_to_http_header_name(key)
|
97
|
+
name = key.downcase.gsub('_', '-')
|
98
|
+
name[0] = name[0].upcase
|
99
|
+
name.gsub!(/-(.)/) do |chr|
|
100
|
+
chr.upcase
|
101
|
+
end
|
102
|
+
name
|
97
103
|
end
|
98
104
|
|
99
105
|
def setup
|
100
106
|
logger.info "(ws.#{@key}) client established connection"
|
101
|
-
handle_ws_join
|
102
107
|
|
103
|
-
@parser =
|
108
|
+
@parser = Parser.new(version: @server.version)
|
104
109
|
|
105
|
-
@parser.
|
110
|
+
@parser.on :message do |message|
|
106
111
|
handle_ws_message(message)
|
107
112
|
end
|
108
113
|
|
109
|
-
@parser.
|
114
|
+
@parser.on :error do |error|
|
110
115
|
logger.error "(ws.#{@key}) encountered error #{error}"
|
111
116
|
handle_ws_error(error)
|
112
117
|
end
|
113
118
|
|
114
|
-
@parser.
|
119
|
+
@parser.on :close do |status, message|
|
115
120
|
logger.info "(ws.#{@key}) client closed connection"
|
116
121
|
handle_ws_close(status, message)
|
117
122
|
end
|
118
123
|
|
119
|
-
|
120
|
-
handle_ws_ping(payload)
|
121
|
-
end
|
122
|
-
|
123
|
-
@reader = Concurrent::Future.execute {
|
124
|
+
Concurrent::Future.execute {
|
124
125
|
begin
|
125
126
|
loop do
|
126
127
|
break if shutdown?
|
127
|
-
@parser << @socket.read_nonblock(
|
128
|
+
@parser << @socket.read_nonblock(1024)
|
128
129
|
end
|
129
130
|
rescue ::IO::WaitReadable
|
130
131
|
IO.select([@socket])
|
131
132
|
retry
|
132
|
-
rescue EOFError
|
133
|
-
@parent.delegate.unregister(@key)
|
134
|
-
@parent.shutdown
|
135
133
|
end
|
136
134
|
}
|
137
135
|
end
|
@@ -158,25 +156,14 @@ module Pakyow
|
|
158
156
|
end
|
159
157
|
|
160
158
|
def handle_ws_close(_status, _message)
|
161
|
-
@socket << WebSocket::Message.close.to_data
|
162
159
|
shutdown
|
163
160
|
end
|
164
161
|
|
165
|
-
def handle_ws_ping(payload)
|
166
|
-
@socket << WebSocket::Message.pong(payload).to_data
|
167
|
-
end
|
168
|
-
|
169
162
|
def self.handle_event(event, req)
|
170
|
-
|
171
|
-
app = Pakyow.app.dup
|
172
|
-
app.context = AppContext.new(req)
|
173
|
-
|
174
|
-
ui = Pakyow.app.instance_variable_get(:@ui)
|
175
|
-
app.context.ui = ui.dup if ui
|
176
|
-
end
|
163
|
+
context = CallContext.new(req.env)
|
177
164
|
|
178
165
|
event_handlers(event).each do |block|
|
179
|
-
|
166
|
+
context.instance_exec(&block)
|
180
167
|
end
|
181
168
|
end
|
182
169
|
|
@@ -184,5 +171,39 @@ module Pakyow
|
|
184
171
|
@event_handlers.fetch(event, [])
|
185
172
|
end
|
186
173
|
end
|
174
|
+
|
175
|
+
class Parser
|
176
|
+
def initialize(version: nil)
|
177
|
+
@parser = WebSocket::Frame::Incoming::Server.new(version: version)
|
178
|
+
@handlers = {}
|
179
|
+
end
|
180
|
+
|
181
|
+
def on(event, &block)
|
182
|
+
@handlers[event] = block
|
183
|
+
end
|
184
|
+
|
185
|
+
def <<(data)
|
186
|
+
@parser << data
|
187
|
+
process
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
|
192
|
+
def process
|
193
|
+
while (frame = @parser.next)
|
194
|
+
case frame.type
|
195
|
+
when :text
|
196
|
+
handle :message, frame
|
197
|
+
when :close
|
198
|
+
handle :close, frame
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def handle(event, frame)
|
204
|
+
return unless @handlers.keys.include?(event)
|
205
|
+
@handlers[event].call(frame.data)
|
206
|
+
end
|
207
|
+
end
|
187
208
|
end
|
188
209
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require 'pakyow/support'
|
5
|
+
require 'pakyow/core'
|
6
|
+
require 'pakyow/realtime/helpers'
|
7
|
+
require 'pakyow/realtime/hooks'
|
8
|
+
require 'pakyow/realtime/context'
|
9
|
+
require 'pakyow/realtime/delegate'
|
10
|
+
require 'pakyow/realtime/registries/simple_registry'
|
11
|
+
require 'pakyow/realtime/registries/redis_registry'
|
12
|
+
require 'pakyow/realtime/redis_subscription'
|
13
|
+
require 'pakyow/realtime/websocket'
|
14
|
+
require 'pakyow/realtime/config'
|
15
|
+
require 'pakyow/realtime/exceptions'
|
16
|
+
require 'pakyow/realtime/message_handler'
|
17
|
+
require 'pakyow/realtime/message_handlers/call_route'
|
18
|
+
require 'pakyow/realtime/message_handlers/ping'
|
19
|
+
require 'pakyow/realtime/ext/request'
|
@@ -1,21 +1 @@
|
|
1
|
-
require '
|
2
|
-
require 'json'
|
3
|
-
|
4
|
-
require 'pakyow-support'
|
5
|
-
require 'pakyow-core'
|
6
|
-
|
7
|
-
require_relative 'pakyow-realtime/helpers'
|
8
|
-
require_relative 'pakyow-realtime/hooks'
|
9
|
-
require_relative 'pakyow-realtime/context'
|
10
|
-
require_relative 'pakyow-realtime/delegate'
|
11
|
-
require_relative 'pakyow-realtime/registries/simple_registry'
|
12
|
-
require_relative 'pakyow-realtime/registries/redis_registry'
|
13
|
-
require_relative 'pakyow-realtime/redis_subscription'
|
14
|
-
require_relative 'pakyow-realtime/websocket'
|
15
|
-
require_relative 'pakyow-realtime/config'
|
16
|
-
require_relative 'pakyow-realtime/exceptions'
|
17
|
-
require_relative 'pakyow-realtime/message_handler'
|
18
|
-
require_relative 'pakyow-realtime/message_handlers/call_route'
|
19
|
-
require_relative 'pakyow-realtime/message_handlers/ping'
|
20
|
-
|
21
|
-
require_relative 'pakyow-realtime/ext/request'
|
1
|
+
require 'pakyow/realtime'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pakyow-realtime
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bryan Powell
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-04-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pakyow-support
|
@@ -16,42 +16,42 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.11.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.11.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: pakyow-core
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - '='
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: 0.11.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - '='
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
40
|
+
version: 0.11.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: websocket
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '1.
|
47
|
+
version: '1.2'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '1.
|
54
|
+
version: '1.2'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: redis
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -90,21 +90,22 @@ files:
|
|
90
90
|
- pakyow-realtime/LICENSE
|
91
91
|
- pakyow-realtime/README.md
|
92
92
|
- pakyow-realtime/lib/pakyow-realtime.rb
|
93
|
-
- pakyow-realtime/lib/pakyow
|
94
|
-
- pakyow-realtime/lib/pakyow
|
95
|
-
- pakyow-realtime/lib/pakyow
|
96
|
-
- pakyow-realtime/lib/pakyow
|
97
|
-
- pakyow-realtime/lib/pakyow
|
98
|
-
- pakyow-realtime/lib/pakyow
|
99
|
-
- pakyow-realtime/lib/pakyow
|
100
|
-
- pakyow-realtime/lib/pakyow
|
101
|
-
- pakyow-realtime/lib/pakyow
|
102
|
-
- pakyow-realtime/lib/pakyow
|
103
|
-
- pakyow-realtime/lib/pakyow
|
104
|
-
- pakyow-realtime/lib/pakyow
|
105
|
-
- pakyow-realtime/lib/pakyow
|
106
|
-
- pakyow-realtime/lib/pakyow
|
107
|
-
- pakyow-realtime/lib/pakyow
|
93
|
+
- pakyow-realtime/lib/pakyow/realtime.rb
|
94
|
+
- pakyow-realtime/lib/pakyow/realtime/config.rb
|
95
|
+
- pakyow-realtime/lib/pakyow/realtime/connection.rb
|
96
|
+
- pakyow-realtime/lib/pakyow/realtime/context.rb
|
97
|
+
- pakyow-realtime/lib/pakyow/realtime/delegate.rb
|
98
|
+
- pakyow-realtime/lib/pakyow/realtime/exceptions.rb
|
99
|
+
- pakyow-realtime/lib/pakyow/realtime/ext/request.rb
|
100
|
+
- pakyow-realtime/lib/pakyow/realtime/helpers.rb
|
101
|
+
- pakyow-realtime/lib/pakyow/realtime/hooks.rb
|
102
|
+
- pakyow-realtime/lib/pakyow/realtime/message_handler.rb
|
103
|
+
- pakyow-realtime/lib/pakyow/realtime/message_handlers/call_route.rb
|
104
|
+
- pakyow-realtime/lib/pakyow/realtime/message_handlers/ping.rb
|
105
|
+
- pakyow-realtime/lib/pakyow/realtime/redis_subscription.rb
|
106
|
+
- pakyow-realtime/lib/pakyow/realtime/registries/redis_registry.rb
|
107
|
+
- pakyow-realtime/lib/pakyow/realtime/registries/simple_registry.rb
|
108
|
+
- pakyow-realtime/lib/pakyow/realtime/websocket.rb
|
108
109
|
homepage: http://pakyow.org
|
109
110
|
licenses:
|
110
111
|
- MIT
|
@@ -125,9 +126,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
126
|
version: '0'
|
126
127
|
requirements: []
|
127
128
|
rubyforge_project:
|
128
|
-
rubygems_version: 2.
|
129
|
+
rubygems_version: 2.5.1
|
129
130
|
signing_key:
|
130
131
|
specification_version: 4
|
131
132
|
summary: Pakyow Realtime
|
132
133
|
test_files: []
|
133
|
-
has_rdoc:
|
@@ -1,49 +0,0 @@
|
|
1
|
-
require 'redis'
|
2
|
-
require 'concurrent'
|
3
|
-
|
4
|
-
module Pakyow
|
5
|
-
module Realtime
|
6
|
-
# Manages channel subscriptions for this application instance's WebSockets.
|
7
|
-
#
|
8
|
-
# @api private
|
9
|
-
class RedisSubscription
|
10
|
-
SIGNAL_UNSUBSCRIBE = :unsubscribe
|
11
|
-
|
12
|
-
def initialize
|
13
|
-
@redis = ::Redis.new(Config.realtime.redis)
|
14
|
-
end
|
15
|
-
|
16
|
-
def subscribe(channels = [])
|
17
|
-
channels << signal_channel
|
18
|
-
|
19
|
-
Concurrent::Future.execute {
|
20
|
-
@redis.subscribe(*channels) do |on|
|
21
|
-
on.message do |channel, msg|
|
22
|
-
if channel == signal_channel
|
23
|
-
if msg == SIGNAL_UNSUBSCRIBE
|
24
|
-
@redis.unsubscribe
|
25
|
-
return
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
msg = JSON.parse(msg)
|
30
|
-
|
31
|
-
if msg.is_a?(Hash)
|
32
|
-
msg[:__propagated] = true
|
33
|
-
elsif msg.is_a?(Array)
|
34
|
-
msg << :__propagated
|
35
|
-
end
|
36
|
-
|
37
|
-
context = Pakyow::Realtime::Context.new(Pakyow.app)
|
38
|
-
context.push(msg, channel)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
}
|
42
|
-
end
|
43
|
-
|
44
|
-
def signal_channel
|
45
|
-
"pw:#{object_id}:signal"
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|