anycable-rack-server 0.0.1 → 0.3.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/LICENSE +1 -1
- data/README.md +101 -15
- data/lib/anycable-rack-server.rb +15 -1
- data/lib/anycable/rack/broadcast_subscribers/base_subscriber.rb +41 -0
- data/lib/anycable/rack/broadcast_subscribers/http_subscriber.rb +44 -0
- data/lib/anycable/rack/broadcast_subscribers/redis_subscriber.rb +57 -0
- data/lib/anycable/{rack-server → rack}/coders/json.rb +4 -2
- data/lib/anycable/rack/config.rb +21 -0
- data/lib/anycable/rack/connection.rb +197 -0
- data/lib/anycable/{rack-server → rack}/errors.rb +1 -2
- data/lib/anycable/{rack-server → rack}/hub.rb +42 -1
- data/lib/anycable/{rack-server → rack}/logging.rb +2 -2
- data/lib/anycable/rack/middleware.rb +95 -0
- data/lib/anycable/{rack-server → rack}/pinger.rb +4 -5
- data/lib/anycable/rack/railtie.rb +31 -0
- data/lib/anycable/rack/rpc/client.rb +70 -0
- data/lib/anycable/{rack-server → rack}/rpc/rpc.proto +18 -4
- data/lib/anycable/rack/server.rb +117 -0
- data/lib/anycable/{rack-server → rack}/socket.rb +21 -28
- data/lib/anycable/{rack-server → rack}/version.rb +2 -2
- metadata +106 -40
- data/lib/anycable/rack-server.rb +0 -107
- data/lib/anycable/rack-server/broadcast_subscribers/redis_subscriber.rb +0 -40
- data/lib/anycable/rack-server/connection.rb +0 -189
- data/lib/anycable/rack-server/middleware.rb +0 -82
- data/lib/anycable/rack-server/rpc/client.rb +0 -42
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'redis'
|
4
|
-
|
5
|
-
module AnyCable
|
6
|
-
module RackServer
|
7
|
-
module BroadcastSubscribers
|
8
|
-
class RedisSubscriber
|
9
|
-
attr_reader :hub, :coder, :redis_conn, :threads
|
10
|
-
|
11
|
-
def initialize(hub:, coder:, options:)
|
12
|
-
@hub = hub
|
13
|
-
@coder = coder
|
14
|
-
@redis_conn = ::Redis.new(options)
|
15
|
-
@threads = {}
|
16
|
-
end
|
17
|
-
|
18
|
-
def subscribe(channel)
|
19
|
-
@threads[channel] = Thread.new do
|
20
|
-
redis_conn.subscribe(channel) do |on|
|
21
|
-
on.message { |_channel, msg| handle_message(msg) }
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def unsubscribe(channel)
|
27
|
-
@threads[channel].terminate unless @threads[channel].nil?
|
28
|
-
@threads.delete(channel)
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
def handle_message(msg)
|
34
|
-
data = JSON.parse(msg)
|
35
|
-
hub.broadcast(data['stream'], data['data'], coder)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,189 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'anycable/rack-server/rpc/client'
|
4
|
-
require 'anycable/rack-server/logging'
|
5
|
-
require 'anycable/rack-server/errors'
|
6
|
-
|
7
|
-
module AnyCable
|
8
|
-
# rubocop:disable Metrics/LineLength
|
9
|
-
module RackServer
|
10
|
-
class Connection
|
11
|
-
# rubocop:enable Metrics/LineLength
|
12
|
-
include Logging
|
13
|
-
|
14
|
-
attr_reader :coder,
|
15
|
-
:header_names,
|
16
|
-
:hub,
|
17
|
-
:socket,
|
18
|
-
:rpc_client,
|
19
|
-
:server_id
|
20
|
-
|
21
|
-
def initialize(socket, hub, coder, host, header_names, server_id)
|
22
|
-
@socket = socket
|
23
|
-
@coder = coder
|
24
|
-
@hub = hub
|
25
|
-
@header_names = header_names
|
26
|
-
@server_id = server_id
|
27
|
-
|
28
|
-
@rpc_client = RPC::Client.new(host)
|
29
|
-
|
30
|
-
@_identifiers = '{}'
|
31
|
-
@_subscriptions = Set.new
|
32
|
-
end
|
33
|
-
|
34
|
-
def handle_open
|
35
|
-
response = rpc_connect
|
36
|
-
process_open(response)
|
37
|
-
end
|
38
|
-
|
39
|
-
def handle_close
|
40
|
-
response = rpc_disconnect
|
41
|
-
process_close(response)
|
42
|
-
reset_connection
|
43
|
-
end
|
44
|
-
|
45
|
-
def handle_command(websocket_message)
|
46
|
-
decoded = decode(websocket_message)
|
47
|
-
command = decoded.delete('command')
|
48
|
-
|
49
|
-
channel_identifier = decoded['identifier']
|
50
|
-
|
51
|
-
case command
|
52
|
-
when 'subscribe' then subscribe(channel_identifier)
|
53
|
-
when 'unsubscribe' then unsubscribe(channel_identifier)
|
54
|
-
when 'message' then send_message(channel_identifier, decoded['data'])
|
55
|
-
else
|
56
|
-
raise Errors::UnknownCommand, "Command not found #{command}"
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
def transmit(cable_message)
|
63
|
-
socket.transmit(encode(cable_message))
|
64
|
-
end
|
65
|
-
|
66
|
-
def close
|
67
|
-
socket.close
|
68
|
-
end
|
69
|
-
|
70
|
-
def request
|
71
|
-
socket.request
|
72
|
-
end
|
73
|
-
|
74
|
-
def request_path
|
75
|
-
request.fullpath
|
76
|
-
end
|
77
|
-
|
78
|
-
def rpc_connect
|
79
|
-
rpc_client.connect(headers: headers, path: request_path)
|
80
|
-
end
|
81
|
-
|
82
|
-
def rpc_disconnect
|
83
|
-
rpc_client.disconnect(
|
84
|
-
identifiers: @_identifiers,
|
85
|
-
subscriptions: @_subscriptions.to_a,
|
86
|
-
headers: headers,
|
87
|
-
path: request_path
|
88
|
-
)
|
89
|
-
end
|
90
|
-
|
91
|
-
def rpc_command(command, identifier, data = '')
|
92
|
-
rpc_client.command(
|
93
|
-
command: command,
|
94
|
-
identifier: identifier,
|
95
|
-
connection_identifiers: @_identifiers,
|
96
|
-
data: data
|
97
|
-
)
|
98
|
-
end
|
99
|
-
|
100
|
-
def subscribe(identifier)
|
101
|
-
response = rpc_command('subscribe', identifier)
|
102
|
-
if response.status == :SUCCESS
|
103
|
-
@_subscriptions.add(identifier)
|
104
|
-
else
|
105
|
-
log(:debug, log_fmt("RPC subscribe command failed: #{response.inspect}"))
|
106
|
-
end
|
107
|
-
process_command(response, identifier)
|
108
|
-
end
|
109
|
-
|
110
|
-
def unsubscribe(identifier)
|
111
|
-
response = rpc_command('unsubscribe', identifier)
|
112
|
-
if response.status == :SUCCESS
|
113
|
-
@_subscriptions.delete(identifier)
|
114
|
-
else
|
115
|
-
log(:debug, log_fmt("RPC unsubscribe command failed: #{response.inspect}"))
|
116
|
-
end
|
117
|
-
process_command(response, identifier)
|
118
|
-
end
|
119
|
-
|
120
|
-
def send_message(identifier, data)
|
121
|
-
response = rpc_command('message', identifier, data)
|
122
|
-
unless response.status == :SUCCESS
|
123
|
-
log(:debug, log_fmt("RPC message command failed: #{response.inspect}"))
|
124
|
-
end
|
125
|
-
process_command(response, identifier)
|
126
|
-
end
|
127
|
-
|
128
|
-
def headers
|
129
|
-
@headers ||= begin
|
130
|
-
header_names.inject({}) do |acc, name|
|
131
|
-
header_val = request.env["HTTP_#{name.gsub(/-/,'_').upcase}"]
|
132
|
-
acc[name] = header_val unless header_val.nil? || header_val.empty?
|
133
|
-
acc
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def process_command(response, identifier)
|
139
|
-
response.transmissions.each { |transmission| transmit(decode(transmission)) }
|
140
|
-
hub.remove_channel(socket, identifier) if response.stop_streams
|
141
|
-
response.streams.each { |stream| hub.add_subscriber(stream, socket, identifier) }
|
142
|
-
close_connection if response.disconnect
|
143
|
-
end
|
144
|
-
|
145
|
-
def process_open(response)
|
146
|
-
if response.status == :SUCCESS
|
147
|
-
@_identifiers = response.identifiers
|
148
|
-
response.transmissions.each { |transmission| transmit(decode(transmission)) }
|
149
|
-
log(:debug) { log_fmt('Opened') }
|
150
|
-
else
|
151
|
-
log(:error, log_fmt("RPC connection command failed: #{response.inspect}"))
|
152
|
-
close_connection
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
def process_close(response)
|
157
|
-
if response.status == :SUCCESS
|
158
|
-
log(:debug) { log_fmt('Closed') }
|
159
|
-
else
|
160
|
-
log(:error, log_fmt("RPC disconnection command failed: #{response.inspect}"))
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def reset_connection
|
165
|
-
@_identifiers = '{}'
|
166
|
-
@_subscriptions = []
|
167
|
-
|
168
|
-
hub.remove_socket(socket)
|
169
|
-
end
|
170
|
-
|
171
|
-
def close_connection
|
172
|
-
reset_connection
|
173
|
-
close
|
174
|
-
end
|
175
|
-
|
176
|
-
def encode(cable_message)
|
177
|
-
coder.encode(cable_message)
|
178
|
-
end
|
179
|
-
|
180
|
-
def decode(websocket_message)
|
181
|
-
coder.decode(websocket_message)
|
182
|
-
end
|
183
|
-
|
184
|
-
def log_fmt(msg)
|
185
|
-
"[connection:#{server_id}] #{msg}"
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
@@ -1,82 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'anycable/rack-server/connection'
|
4
|
-
require 'anycable/rack-server/errors'
|
5
|
-
require 'anycable/rack-server/socket'
|
6
|
-
|
7
|
-
module AnyCable
|
8
|
-
module RackServer
|
9
|
-
class Middleware
|
10
|
-
PROTOCOLS = ['actioncable-v1-json', 'actioncable-unsupported'].freeze
|
11
|
-
attr_reader :pinger,
|
12
|
-
:hub,
|
13
|
-
:coder,
|
14
|
-
:rpc_host,
|
15
|
-
:headers,
|
16
|
-
:server_id
|
17
|
-
|
18
|
-
def initialize(_app, pinger:, hub:, coder:, rpc_host:, headers:, server_id:)
|
19
|
-
@pinger = pinger
|
20
|
-
@hub = hub
|
21
|
-
@coder = coder
|
22
|
-
@rpc_host = rpc_host
|
23
|
-
@headers = headers
|
24
|
-
@server_id = server_id
|
25
|
-
end
|
26
|
-
|
27
|
-
def call(env)
|
28
|
-
return not_found unless websocket?(env)
|
29
|
-
|
30
|
-
rack_hijack(env)
|
31
|
-
listen_socket(env)
|
32
|
-
|
33
|
-
[-1, {}, []]
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def handshake
|
39
|
-
@handshake ||= WebSocket::Handshake::Server.new(protocols: PROTOCOLS)
|
40
|
-
end
|
41
|
-
|
42
|
-
def rack_hijack(env)
|
43
|
-
raise Errors::HijackNotAvailable unless env['rack.hijack']
|
44
|
-
|
45
|
-
env['rack.hijack'].call
|
46
|
-
send_handshake(env)
|
47
|
-
end
|
48
|
-
|
49
|
-
def send_handshake(env)
|
50
|
-
handshake.from_rack(env)
|
51
|
-
env['rack.hijack_io'].write(handshake.to_s)
|
52
|
-
end
|
53
|
-
|
54
|
-
def listen_socket(env)
|
55
|
-
socket = Socket.new(env, env['rack.hijack_io'], handshake.version)
|
56
|
-
init_connection(socket)
|
57
|
-
init_pinger(socket)
|
58
|
-
socket.listen
|
59
|
-
end
|
60
|
-
|
61
|
-
def not_found
|
62
|
-
[404, { 'Content-Type' => 'text/plain' }, ['Not Found']]
|
63
|
-
end
|
64
|
-
|
65
|
-
def websocket?(env)
|
66
|
-
env['HTTP_UPGRADE'] == 'websocket'
|
67
|
-
end
|
68
|
-
|
69
|
-
def init_connection(socket)
|
70
|
-
connection = Connection.new(socket, hub, coder, rpc_host, headers, server_id)
|
71
|
-
socket.onopen { connection.handle_open }
|
72
|
-
socket.onclose { connection.handle_close }
|
73
|
-
socket.onmessage { |data| connection.handle_command(data) }
|
74
|
-
end
|
75
|
-
|
76
|
-
def init_pinger(socket)
|
77
|
-
pinger.add(socket)
|
78
|
-
socket.onclose { pinger.remove(socket) }
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
@@ -1,42 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'grpc'
|
4
|
-
|
5
|
-
module AnyCable
|
6
|
-
module RackServer
|
7
|
-
module RPC
|
8
|
-
class Client
|
9
|
-
attr_reader :stub
|
10
|
-
|
11
|
-
def initialize(host)
|
12
|
-
@stub = AnyCable::RPC::Service.rpc_stub_class.new(host, :this_channel_is_insecure)
|
13
|
-
end
|
14
|
-
|
15
|
-
def connect(headers:, path:)
|
16
|
-
request = ConnectionRequest.new(headers: headers, path: path)
|
17
|
-
stub.connect(request)
|
18
|
-
end
|
19
|
-
|
20
|
-
def command(command:, identifier:, connection_identifiers:, data:)
|
21
|
-
message = CommandMessage.new(
|
22
|
-
command: command,
|
23
|
-
identifier: identifier,
|
24
|
-
connection_identifiers: connection_identifiers,
|
25
|
-
data: data
|
26
|
-
)
|
27
|
-
stub.command(message)
|
28
|
-
end
|
29
|
-
|
30
|
-
def disconnect(identifiers:, subscriptions:, headers:, path:)
|
31
|
-
request = DisconnectRequest.new(
|
32
|
-
identifiers: identifiers,
|
33
|
-
subscriptions: subscriptions,
|
34
|
-
headers: headers,
|
35
|
-
path: path
|
36
|
-
)
|
37
|
-
stub.disconnect(request)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|