pakyow-realtime 0.10.2 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (21) hide show
  1. checksums.yaml +4 -4
  2. data/pakyow-realtime/CHANGELOG.md +14 -0
  3. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/config.rb +4 -6
  4. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/connection.rb +0 -0
  5. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/context.rb +8 -0
  6. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/delegate.rb +19 -2
  7. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/exceptions.rb +0 -0
  8. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/ext/request.rb +0 -0
  9. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/helpers.rb +7 -0
  10. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/hooks.rb +15 -4
  11. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/message_handler.rb +0 -0
  12. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/message_handlers/call_route.rb +7 -10
  13. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/message_handlers/ping.rb +0 -0
  14. data/pakyow-realtime/lib/pakyow/realtime/redis_subscription.rb +61 -0
  15. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/registries/redis_registry.rb +41 -28
  16. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/registries/simple_registry.rb +3 -3
  17. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/websocket.rb +84 -63
  18. data/pakyow-realtime/lib/pakyow/realtime.rb +19 -0
  19. data/pakyow-realtime/lib/pakyow-realtime.rb +1 -21
  20. metadata +26 -26
  21. 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: d3af56bba7d1aa3e3b121eb2b542d0dc4f85d35d
4
- data.tar.gz: 33f0af0198c31f0b2b728d07a63635a2f85ad79a
3
+ metadata.gz: 7de6d6caa97ff92eb8c07092eb9438e952ceb4c2
4
+ data.tar.gz: dc8d6705e39160414dea747275e53bf351ddfaa5
5
5
  SHA512:
6
- metadata.gz: bf7e58a486cdef598237a63744c06069c9df16bf70c39d206d9bd18e2272e8a2035007ac3cd50d7dec232488ad89deb122d1b369a1230a229b2e6f09818dc968
7
- data.tar.gz: 2ab01fe491a66614a021fefea6e674f68cf614fb970742b57ba2a3adca12c4dfae3716dc0c25c51ff3e3b147985730e0f657cd586fa784fdcd9da4e59aba7520
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(:realtime) { |config|
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
- }.env(:development) { |opts|
16
+ end.env :development do |opts|
17
17
  opts.registry = Pakyow::Realtime::SimpleRegistry
18
- }.env(:staging) { |opts|
18
+ end.env :production do |opts|
19
19
  opts.registry = Pakyow::Realtime::RedisRegistry
20
- }.env(:production) { |opts|
21
- opts.registry = Pakyow::Realtime::RedisRegistry
22
- }
20
+ end
@@ -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)
@@ -29,5 +29,12 @@ module Pakyow
29
29
  def socket_digest(socket_connection_id)
30
30
  Digest::SHA1.hexdigest("--#{socket_key}--#{socket_connection_id}--")
31
31
  end
32
+
33
+ module App
34
+ # @api private
35
+ def socket
36
+ Realtime::Context.new(self)
37
+ end
38
+ end
32
39
  end
33
40
  end
@@ -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'] == 'websocket'
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
- conn = Pakyow::Realtime::Websocket.new(req, socket_digest)
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'] == 'text/html' && Pakyow::Config.realtime.enabled
24
- body = response.body[0]
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 + '"'
@@ -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
- path, qs = message['uri'].split('?')
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
- app = Pakyow.app.dup
18
- res = app.process(env)
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 = app.presenter.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
@@ -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
- require_relative '../redis_subscription'
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
- channels(key)
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
- new_channels = channels(key).concat(Array.ensure(channels)).uniq
37
- Pakyow::Realtime.redis.hset(channel_key, key, new_channels.to_json)
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
- new_channels = channels(key) - Array.ensure(channels)
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.concat(channels).uniq!
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
- # Terminates the current subscriber and creates a new
71
- # subscriber with the current channels.
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, RedisSubscription::SIGNAL_UNSUBSCRIBE)
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.map(&:to_sym))).uniq!
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.map(&:to_sym))
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 'websocket_parser'
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
- @handshake = handshake!(req)
22
- @socket = hijack!(req)
21
+ @socket = hijack(req)
22
+ @server = handshake(req)
23
+ setup
24
+ end
23
25
 
24
- handle_handshake
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
- WebSocket::Message.new(json).write(@socket)
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 handshake!(req)
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 handshake_headers(req)
67
- {
68
- 'Upgrade' => req.env['HTTP_UPGRADE'],
69
- 'Sec-WebSocket-Version' => req.env['HTTP_SEC_WEBSOCKET_VERSION'],
70
- 'Sec-Websocket-Key' => req.env['HTTP_SEC_WEBSOCKET_KEY']
71
- }
72
- end
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
- end
85
+ data << "\r\n"
84
86
 
85
- def accept_handshake
86
- response = @handshake.accept_response
87
- response.render(@socket)
88
- end
87
+ server = WebSocket::Handshake::Server.new
88
+ server << data
89
89
 
90
- def fail_handshake
91
- error = @handshake.errors.first
90
+ fail HandshakeError, "(ws.#{@key}) error during handshake" unless server.valid?
91
+ @socket.write(server.to_s)
92
92
 
93
- response = Rack::Response.new(400)
94
- response.render(@socket)
93
+ server
94
+ end
95
95
 
96
- fail HandshakeError, "(ws.#{@key}) error during handshake: #{error}"
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 = WebSocket::Parser.new
108
+ @parser = Parser.new(version: @server.version)
104
109
 
105
- @parser.on_message do |message|
110
+ @parser.on :message do |message|
106
111
  handle_ws_message(message)
107
112
  end
108
113
 
109
- @parser.on_error do |error|
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.on_close do |status, message|
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
- @parser.on_ping do |payload|
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(16_384)
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
- if Pakyow.app
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
- app.instance_exec(&block)
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 'securerandom'
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.10.2
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: 2015-11-16 00:00:00.000000000 Z
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.10.2
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.10.2
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.10.2
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.10.2
40
+ version: 0.11.0
41
41
  - !ruby/object:Gem::Dependency
42
- name: websocket_parser
42
+ name: websocket
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.0'
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.0'
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-realtime/config.rb
94
- - pakyow-realtime/lib/pakyow-realtime/connection.rb
95
- - pakyow-realtime/lib/pakyow-realtime/context.rb
96
- - pakyow-realtime/lib/pakyow-realtime/delegate.rb
97
- - pakyow-realtime/lib/pakyow-realtime/exceptions.rb
98
- - pakyow-realtime/lib/pakyow-realtime/ext/request.rb
99
- - pakyow-realtime/lib/pakyow-realtime/helpers.rb
100
- - pakyow-realtime/lib/pakyow-realtime/hooks.rb
101
- - pakyow-realtime/lib/pakyow-realtime/message_handler.rb
102
- - pakyow-realtime/lib/pakyow-realtime/message_handlers/call_route.rb
103
- - pakyow-realtime/lib/pakyow-realtime/message_handlers/ping.rb
104
- - pakyow-realtime/lib/pakyow-realtime/redis_subscription.rb
105
- - pakyow-realtime/lib/pakyow-realtime/registries/redis_registry.rb
106
- - pakyow-realtime/lib/pakyow-realtime/registries/simple_registry.rb
107
- - pakyow-realtime/lib/pakyow-realtime/websocket.rb
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.4.5
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