pakyow-realtime 0.11.3 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +5 -5
  2. data/{pakyow-realtime/CHANGELOG.md → CHANGELOG.md} +5 -0
  3. data/LICENSE +4 -0
  4. data/{pakyow-realtime/README.md → README.md} +1 -2
  5. data/lib/pakyow/environment/realtime/config.rb +29 -0
  6. data/lib/pakyow/realtime/actions/upgrader.rb +29 -0
  7. data/lib/pakyow/realtime/behavior/config.rb +42 -0
  8. data/lib/pakyow/realtime/behavior/rendering/install_websocket.rb +57 -0
  9. data/lib/pakyow/realtime/behavior/serialization.rb +42 -0
  10. data/lib/pakyow/realtime/behavior/server.rb +42 -0
  11. data/lib/pakyow/realtime/behavior/silencing.rb +25 -0
  12. data/lib/pakyow/realtime/channel.rb +23 -0
  13. data/lib/pakyow/realtime/context.rb +38 -0
  14. data/lib/pakyow/realtime/framework.rb +49 -0
  15. data/lib/pakyow/realtime/helpers/broadcasting.rb +13 -0
  16. data/lib/pakyow/realtime/helpers/socket.rb +13 -0
  17. data/lib/pakyow/realtime/helpers/subscriptions.rb +35 -0
  18. data/lib/pakyow/realtime/server/adapters/memory.rb +127 -0
  19. data/lib/pakyow/realtime/server/adapters/redis.rb +277 -0
  20. data/lib/pakyow/realtime/server.rb +152 -0
  21. data/lib/pakyow/realtime/websocket.rb +157 -0
  22. data/lib/pakyow/realtime.rb +13 -0
  23. metadata +73 -44
  24. data/pakyow-realtime/LICENSE +0 -20
  25. data/pakyow-realtime/lib/pakyow/realtime/config.rb +0 -20
  26. data/pakyow-realtime/lib/pakyow/realtime/connection.rb +0 -18
  27. data/pakyow-realtime/lib/pakyow/realtime/context.rb +0 -68
  28. data/pakyow-realtime/lib/pakyow/realtime/delegate.rb +0 -112
  29. data/pakyow-realtime/lib/pakyow/realtime/exceptions.rb +0 -6
  30. data/pakyow-realtime/lib/pakyow/realtime/ext/request.rb +0 -10
  31. data/pakyow-realtime/lib/pakyow/realtime/helpers.rb +0 -40
  32. data/pakyow-realtime/lib/pakyow/realtime/hooks.rb +0 -41
  33. data/pakyow-realtime/lib/pakyow/realtime/message_handler.rb +0 -57
  34. data/pakyow-realtime/lib/pakyow/realtime/message_handlers/call_route.rb +0 -34
  35. data/pakyow-realtime/lib/pakyow/realtime/message_handlers/ping.rb +0 -8
  36. data/pakyow-realtime/lib/pakyow/realtime/redis_subscription.rb +0 -61
  37. data/pakyow-realtime/lib/pakyow/realtime/registries/redis_registry.rb +0 -107
  38. data/pakyow-realtime/lib/pakyow/realtime/registries/simple_registry.rb +0 -40
  39. data/pakyow-realtime/lib/pakyow/realtime/websocket.rb +0 -209
  40. data/pakyow-realtime/lib/pakyow/realtime.rb +0 -19
  41. data/pakyow-realtime/lib/pakyow-realtime.rb +0 -1
@@ -1,61 +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
- 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
@@ -1,107 +0,0 @@
1
- require 'json'
2
- require 'redis'
3
- require 'singleton'
4
-
5
- require 'pakyow/realtime/redis_subscription'
6
- require 'pakyow/realtime/registries/simple_registry'
7
-
8
- module Pakyow
9
- module Realtime
10
- def self.redis
11
- $redis ||= Redis.new(Config.realtime.redis)
12
- end
13
- # Manages WebSocket connections and their subscriptions in Redis.
14
- #
15
- # This is the default registry in production systems and is required in
16
- # deployments with more than one app instance.
17
- #
18
- # @api private
19
- class RedisRegistry < SimpleRegistry
20
- attr_reader :subscriber
21
-
22
- def channels_for_key(key)
23
- value = Pakyow::Realtime.redis.hget(channel_key, key)
24
- value ? JSON.parse(value) : []
25
- end
26
-
27
- def unregister_key(key)
28
- super
29
-
30
- Pakyow::Realtime.redis.hdel(channel_key, key)
31
- resubscribe
32
- end
33
-
34
- def subscribe_to_channels_for_key(channels, key)
35
- channels << "pw:socket:#{key}"
36
- super
37
-
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)
42
- end
43
-
44
- def unsubscribe_to_channels_for_key(channels, key)
45
- super
46
-
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)
51
- end
52
-
53
- def propagates?
54
- true
55
- end
56
-
57
- def propagate(message, channels)
58
- message_json = message.to_json
59
-
60
- channels.each do |channel|
61
- Pakyow::Realtime.redis.publish(channel, message_json)
62
- end
63
- end
64
-
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
-
70
- resubscribe
71
- end
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
-
83
- private
84
-
85
- # Tells the subscription to unsubscribe from the current
86
- # list of channels, then subscribes it to the new list.
87
- def resubscribe
88
- channels = @channels.values.flatten.uniq
89
-
90
- if @subscriber
91
- Pakyow::Realtime.redis.publish(@subscriber.signal_channel, {
92
- 'signal' => RedisSubscription::SIGNAL_UNSUBSCRIBE,
93
- 'resubscribe_channels' => channels
94
- }.to_json)
95
- else
96
- @subscriber = RedisSubscription.new
97
- @subscriber.subscribe(channels)
98
- end
99
- end
100
-
101
- # Returns the key used to store channels.
102
- def channel_key
103
- Config.realtime.redis_key
104
- end
105
- end
106
- end
107
- end
@@ -1,40 +0,0 @@
1
- require 'singleton'
2
-
3
- module Pakyow
4
- module Realtime
5
- # Manages WebSocket connections and their subscriptions in memory.
6
- #
7
- # Intended only for use in development or single app-instance deployments.
8
- #
9
- # @api private
10
- class SimpleRegistry
11
- include Singleton
12
-
13
- def initialize
14
- @channels = {}
15
- end
16
-
17
- def channels_for_key(key)
18
- @channels.fetch(key, [])
19
- end
20
-
21
- def unregister_key(key)
22
- @channels.delete(key)
23
- end
24
-
25
- def subscribe_to_channels_for_key(channels, key)
26
- @channels[key] ||= []
27
- @channels[key].concat(Array.ensure(channels)).uniq!
28
- end
29
-
30
- def unsubscribe_to_channels_for_key(channels, key)
31
- @channels[key] ||= []
32
- @channels[key] = @channels[key] - Array.ensure(channels)
33
- end
34
-
35
- def propagates?
36
- false
37
- end
38
- end
39
- end
40
- end
@@ -1,209 +0,0 @@
1
- require 'concurrent'
2
- require 'websocket'
3
-
4
- require_relative 'connection'
5
-
6
- module Pakyow
7
- module Realtime
8
- # Hijacks a request, performs the handshake, and creates an async object
9
- # for handling incoming and outgoing messages in an asynchronous manner.
10
- #
11
- # @api private
12
- class Websocket < Connection
13
- attr_reader :parser, :socket, :key
14
-
15
- @event_handlers = {}
16
-
17
- def initialize(req, key)
18
- @req = req
19
- @key = key
20
-
21
- @socket = hijack(req)
22
- @server = handshake(req)
23
- setup
24
- end
25
-
26
- def registered
27
- handle_ws_join
28
- end
29
-
30
- def shutdown
31
- delegate.unregister(@key)
32
- self.class.handle_event(:leave, @req)
33
-
34
- @socket.close if @socket && !@socket.closed?
35
- @shutdown = true
36
-
37
- @reader = nil
38
- end
39
-
40
- def shutdown?
41
- @shutdown == true
42
- end
43
-
44
- def push(msg)
45
- json = JSON.pretty_generate(msg)
46
- logger.debug "(ws.#{@key}) sending message: #{json}\n"
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
60
- end
61
-
62
- def self.on(event, &block)
63
- (@event_handlers[event.to_sym] ||= []) << block
64
- end
65
-
66
- private
67
-
68
- def hijack(req)
69
- if req.env['rack.hijack']
70
- req.env['rack.hijack'].call
71
- return req.env['rack.hijack_io']
72
- else
73
- logger.info "there's no socket to hijack :("
74
- end
75
- end
76
-
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
84
- end
85
- data << "\r\n"
86
-
87
- server = WebSocket::Handshake::Server.new
88
- server << data
89
-
90
- fail HandshakeError, "(ws.#{@key}) error during handshake" unless server.valid?
91
- @socket.write(server.to_s)
92
-
93
- server
94
- end
95
-
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
103
- end
104
-
105
- def setup
106
- logger.info "(ws.#{@key}) client established connection"
107
-
108
- @parser = Parser.new(version: @server.version)
109
-
110
- @parser.on :message do |message|
111
- handle_ws_message(message)
112
- end
113
-
114
- @parser.on :error do |error|
115
- logger.error "(ws.#{@key}) encountered error #{error}"
116
- handle_ws_error(error)
117
- end
118
-
119
- @parser.on :close do |status, message|
120
- logger.info "(ws.#{@key}) client closed connection"
121
- handle_ws_close(status, message)
122
- end
123
-
124
- Concurrent::Future.execute {
125
- begin
126
- loop do
127
- break if shutdown?
128
- @parser << @socket.read_nonblock(1024)
129
- end
130
- rescue ::IO::WaitReadable
131
- IO.select([@socket])
132
- retry
133
- end
134
- }
135
- end
136
-
137
- def handle_ws_message(message)
138
- parsed = JSON.parse(message)
139
- logger.debug "(ws.#{@key}) received message: #{JSON.pretty_generate(parsed)}\n"
140
- push(MessageHandler.handle(parsed, @req.env['rack.session']))
141
- rescue StandardError => e
142
- logger.error "(#{@key}): WebSocket encountered an error:"
143
- logger.error e.message
144
-
145
- e.backtrace.each do |line|
146
- logger.error line
147
- end
148
- end
149
-
150
- def handle_ws_error(_error)
151
- shutdown
152
- end
153
-
154
- def handle_ws_join
155
- self.class.handle_event(:join, @req)
156
- end
157
-
158
- def handle_ws_close(_status, _message)
159
- shutdown
160
- end
161
-
162
- def self.handle_event(event, req)
163
- context = CallContext.new(req.env)
164
-
165
- event_handlers(event).each do |block|
166
- context.instance_exec(&block)
167
- end
168
- end
169
-
170
- def self.event_handlers(event = nil)
171
- @event_handlers.fetch(event, [])
172
- end
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
208
- end
209
- end
@@ -1,19 +0,0 @@
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 +0,0 @@
1
- require 'pakyow/realtime'