anycable 1.0.3 → 1.1.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +1 -3
- metadata +17 -144
- data/bin/anycable +0 -13
- data/bin/anycabled +0 -30
- data/bin/console +0 -7
- data/bin/setup +0 -6
- data/lib/anycable.rb +0 -101
- data/lib/anycable/broadcast_adapters.rb +0 -35
- data/lib/anycable/broadcast_adapters/base.rb +0 -29
- data/lib/anycable/broadcast_adapters/http.rb +0 -130
- data/lib/anycable/broadcast_adapters/redis.rb +0 -43
- data/lib/anycable/cli.rb +0 -353
- data/lib/anycable/config.rb +0 -135
- data/lib/anycable/exceptions_handling.rb +0 -35
- data/lib/anycable/health_server.rb +0 -73
- data/lib/anycable/middleware.rb +0 -27
- data/lib/anycable/middleware_chain.rb +0 -58
- data/lib/anycable/middlewares/check_version.rb +0 -24
- data/lib/anycable/rpc.rb +0 -84
- data/lib/anycable/rpc/rpc_pb.rb +0 -74
- data/lib/anycable/rpc/rpc_services_pb.rb +0 -24
- data/lib/anycable/rpc_handler.rb +0 -183
- data/lib/anycable/rspec.rb +0 -6
- data/lib/anycable/rspec/rpc_command_context.rb +0 -20
- data/lib/anycable/rspec/rpc_stub_context.rb +0 -13
- data/lib/anycable/rspec/with_grpc_server.rb +0 -16
- data/lib/anycable/server.rb +0 -96
- data/lib/anycable/socket.rb +0 -90
- data/lib/anycable/version.rb +0 -5
@@ -1,24 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
4
|
-
# Source: rpc.proto for package 'anycable'
|
5
|
-
|
6
|
-
require "grpc"
|
7
|
-
|
8
|
-
module AnyCable
|
9
|
-
module RPC
|
10
|
-
class Service
|
11
|
-
include GRPC::GenericService
|
12
|
-
|
13
|
-
self.marshal_class_method = :encode
|
14
|
-
self.unmarshal_class_method = :decode
|
15
|
-
self.service_name = "anycable.RPC"
|
16
|
-
|
17
|
-
rpc :Connect, ConnectionRequest, ConnectionResponse
|
18
|
-
rpc :Command, CommandMessage, CommandResponse
|
19
|
-
rpc :Disconnect, DisconnectRequest, DisconnectResponse
|
20
|
-
end
|
21
|
-
|
22
|
-
Stub = Service.rpc_stub_class
|
23
|
-
end
|
24
|
-
end
|
data/lib/anycable/rpc_handler.rb
DELETED
@@ -1,183 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "anycable/socket"
|
4
|
-
require "anycable/rpc"
|
5
|
-
|
6
|
-
# rubocop:disable Metrics/AbcSize
|
7
|
-
# rubocop:disable Metrics/MethodLength
|
8
|
-
# rubocop:disable Metrics/ClassLength
|
9
|
-
module AnyCable
|
10
|
-
# RPC service handler
|
11
|
-
class RPCHandler < AnyCable::RPC::Service
|
12
|
-
# Handle connection request from WebSocket server
|
13
|
-
def connect(request, _unused_call)
|
14
|
-
logger.debug("RPC Connect: #{request.inspect}")
|
15
|
-
|
16
|
-
socket = build_socket(env: rack_env(request.env))
|
17
|
-
|
18
|
-
connection = factory.call(socket)
|
19
|
-
|
20
|
-
connection.handle_open
|
21
|
-
|
22
|
-
if socket.closed?
|
23
|
-
AnyCable::ConnectionResponse.new(
|
24
|
-
status: AnyCable::Status::FAILURE,
|
25
|
-
transmissions: socket.transmissions
|
26
|
-
)
|
27
|
-
else
|
28
|
-
AnyCable::ConnectionResponse.new(
|
29
|
-
status: AnyCable::Status::SUCCESS,
|
30
|
-
identifiers: connection.identifiers_json,
|
31
|
-
transmissions: socket.transmissions,
|
32
|
-
env: build_env_response(socket)
|
33
|
-
)
|
34
|
-
end
|
35
|
-
rescue => exp
|
36
|
-
notify_exception(exp, :connect, request)
|
37
|
-
|
38
|
-
AnyCable::ConnectionResponse.new(
|
39
|
-
status: AnyCable::Status::ERROR,
|
40
|
-
error_msg: exp.message
|
41
|
-
)
|
42
|
-
end
|
43
|
-
|
44
|
-
def disconnect(request, _unused_call)
|
45
|
-
logger.debug("RPC Disconnect: #{request.inspect}")
|
46
|
-
|
47
|
-
socket = build_socket(env: rack_env(request.env))
|
48
|
-
|
49
|
-
connection = factory.call(
|
50
|
-
socket,
|
51
|
-
identifiers: request.identifiers,
|
52
|
-
subscriptions: request.subscriptions
|
53
|
-
)
|
54
|
-
|
55
|
-
if connection.handle_close
|
56
|
-
AnyCable::DisconnectResponse.new(status: AnyCable::Status::SUCCESS)
|
57
|
-
else
|
58
|
-
AnyCable::DisconnectResponse.new(status: AnyCable::Status::FAILURE)
|
59
|
-
end
|
60
|
-
rescue => exp
|
61
|
-
notify_exception(exp, :disconnect, request)
|
62
|
-
|
63
|
-
AnyCable::DisconnectResponse.new(
|
64
|
-
status: AnyCable::Status::ERROR,
|
65
|
-
error_msg: exp.message
|
66
|
-
)
|
67
|
-
end
|
68
|
-
|
69
|
-
def command(message, _unused_call)
|
70
|
-
logger.debug("RPC Command: #{message.inspect}")
|
71
|
-
|
72
|
-
socket = build_socket(env: rack_env(message.env))
|
73
|
-
|
74
|
-
connection = factory.call(
|
75
|
-
socket,
|
76
|
-
identifiers: message.connection_identifiers
|
77
|
-
)
|
78
|
-
|
79
|
-
result = connection.handle_channel_command(
|
80
|
-
message.identifier,
|
81
|
-
message.command,
|
82
|
-
message.data
|
83
|
-
)
|
84
|
-
|
85
|
-
AnyCable::CommandResponse.new(
|
86
|
-
status: result ? AnyCable::Status::SUCCESS : AnyCable::Status::FAILURE,
|
87
|
-
disconnect: socket.closed?,
|
88
|
-
stop_streams: socket.stop_streams?,
|
89
|
-
streams: socket.streams[:start],
|
90
|
-
stopped_streams: socket.streams[:stop],
|
91
|
-
transmissions: socket.transmissions,
|
92
|
-
env: build_env_response(socket)
|
93
|
-
)
|
94
|
-
rescue => exp
|
95
|
-
notify_exception(exp, :command, message)
|
96
|
-
|
97
|
-
AnyCable::CommandResponse.new(
|
98
|
-
status: AnyCable::Status::ERROR,
|
99
|
-
error_msg: exp.message
|
100
|
-
)
|
101
|
-
end
|
102
|
-
|
103
|
-
private
|
104
|
-
|
105
|
-
# Build Rack env from request
|
106
|
-
def rack_env(request_env)
|
107
|
-
uri = URI.parse(request_env.url)
|
108
|
-
|
109
|
-
env = base_rack_env
|
110
|
-
env.merge!({
|
111
|
-
"PATH_INFO" => uri.path,
|
112
|
-
"QUERY_STRING" => uri.query,
|
113
|
-
"SERVER_NAME" => uri.host,
|
114
|
-
"SERVER_PORT" => uri.port,
|
115
|
-
"HTTP_HOST" => uri.host,
|
116
|
-
"REMOTE_ADDR" => request_env.headers.delete("REMOTE_ADDR"),
|
117
|
-
"rack.url_scheme" => uri.scheme&.sub(/^ws/, "http"),
|
118
|
-
# AnyCable specific fields
|
119
|
-
"anycable.raw_cstate" => request_env.cstate&.to_h,
|
120
|
-
"anycable.raw_istate" => request_env.istate&.to_h
|
121
|
-
}.delete_if { |_k, v| v.nil? })
|
122
|
-
|
123
|
-
env.merge!(build_headers(request_env.headers))
|
124
|
-
end
|
125
|
-
|
126
|
-
def base_rack_env
|
127
|
-
# Minimum required variables according to Rack Spec
|
128
|
-
# (not all of them though, just those enough for Action Cable to work)
|
129
|
-
# See https://rubydoc.info/github/rack/rack/master/file/SPEC
|
130
|
-
# and https://github.com/rack/rack/blob/master/lib/rack/lint.rb
|
131
|
-
{
|
132
|
-
"REQUEST_METHOD" => "GET",
|
133
|
-
"SCRIPT_NAME" => "",
|
134
|
-
"PATH_INFO" => "/",
|
135
|
-
"QUERY_STRING" => "",
|
136
|
-
"SERVER_NAME" => "",
|
137
|
-
"SERVER_PORT" => "80",
|
138
|
-
"rack.url_scheme" => "http",
|
139
|
-
"rack.input" => StringIO.new("", "r").tap { |io| io.set_encoding(Encoding::ASCII_8BIT) },
|
140
|
-
"rack.version" => ::Rack::VERSION,
|
141
|
-
"rack.errors" => StringIO.new("").tap { |io| io.set_encoding(Encoding::ASCII_8BIT) },
|
142
|
-
"rack.multithread" => true,
|
143
|
-
"rack.multiprocess" => false,
|
144
|
-
"rack.run_once" => false,
|
145
|
-
"rack.hijack?" => false
|
146
|
-
}
|
147
|
-
end
|
148
|
-
|
149
|
-
def build_socket(**options)
|
150
|
-
AnyCable::Socket.new(**options)
|
151
|
-
end
|
152
|
-
|
153
|
-
def build_headers(headers)
|
154
|
-
headers.each_with_object({}) do |(k, v), obj|
|
155
|
-
k = k.upcase
|
156
|
-
k.tr!("-", "_")
|
157
|
-
obj["HTTP_#{k}"] = v
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def build_env_response(socket)
|
162
|
-
AnyCable::EnvResponse.new(
|
163
|
-
cstate: socket.cstate.changed_fields,
|
164
|
-
istate: socket.istate.changed_fields
|
165
|
-
)
|
166
|
-
end
|
167
|
-
|
168
|
-
def logger
|
169
|
-
AnyCable.logger
|
170
|
-
end
|
171
|
-
|
172
|
-
def factory
|
173
|
-
AnyCable.connection_factory
|
174
|
-
end
|
175
|
-
|
176
|
-
def notify_exception(exp, method_name, message)
|
177
|
-
AnyCable::ExceptionsHandling.notify(exp, method_name.to_s, message.to_h)
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
181
|
-
# rubocop:enable Metrics/AbcSize
|
182
|
-
# rubocop:enable Metrics/MethodLength
|
183
|
-
# rubocop:enable Metrics/ClassLength
|
data/lib/anycable/rspec.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.shared_context "anycable:rpc:command" do
|
4
|
-
include_context "anycable:rpc:stub"
|
5
|
-
|
6
|
-
let(:command) { "" }
|
7
|
-
let(:channel_id) { "" }
|
8
|
-
let(:identifiers) { {} }
|
9
|
-
let(:data) { {} }
|
10
|
-
|
11
|
-
let(:request) do
|
12
|
-
AnyCable::CommandMessage.new(
|
13
|
-
command: command,
|
14
|
-
identifier: channel_id,
|
15
|
-
connection_identifiers: identifiers.to_json,
|
16
|
-
data: data.to_json,
|
17
|
-
env: env
|
18
|
-
)
|
19
|
-
end
|
20
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.shared_context "anycable:rpc:stub" do
|
4
|
-
before(:all) do
|
5
|
-
@service = AnyCable::RPC::Stub.new(AnyCable.config.rpc_host, :this_channel_is_insecure)
|
6
|
-
end
|
7
|
-
|
8
|
-
let(:service) { @service }
|
9
|
-
|
10
|
-
let(:url) { "ws://example.anycable.com/cable" }
|
11
|
-
let(:headers) { {} }
|
12
|
-
let(:env) { AnyCable::Env.new(url: url, headers: headers) }
|
13
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.shared_context "anycable:rpc:server" do
|
4
|
-
before(:all) do
|
5
|
-
@server = AnyCable::Server.new(
|
6
|
-
host: AnyCable.config.rpc_host,
|
7
|
-
**AnyCable.config.to_grpc_params,
|
8
|
-
interceptors: AnyCable.middleware.to_a
|
9
|
-
)
|
10
|
-
|
11
|
-
@server.start
|
12
|
-
sleep 0.1
|
13
|
-
end
|
14
|
-
|
15
|
-
after(:all) { @server.stop }
|
16
|
-
end
|
data/lib/anycable/server.rb
DELETED
@@ -1,96 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "grpc"
|
4
|
-
require "grpc/health/checker"
|
5
|
-
require "grpc/health/v1/health_services_pb"
|
6
|
-
|
7
|
-
require "anycable/rpc_handler"
|
8
|
-
require "anycable/health_server"
|
9
|
-
|
10
|
-
module AnyCable
|
11
|
-
# Wrapper over gRPC server.
|
12
|
-
#
|
13
|
-
# Basic example:
|
14
|
-
#
|
15
|
-
# # create new server listening on the loopback interface with 50051 port
|
16
|
-
# server = AnyCable::Server.new(host: "127.0.0.1:50051")
|
17
|
-
#
|
18
|
-
# # run gRPC server in bakground
|
19
|
-
# server.start
|
20
|
-
#
|
21
|
-
# # stop server
|
22
|
-
# server.stop
|
23
|
-
class Server
|
24
|
-
attr_reader :grpc_server, :host
|
25
|
-
|
26
|
-
def initialize(host:, logger: nil, **options)
|
27
|
-
@logger = logger
|
28
|
-
@host = host
|
29
|
-
@grpc_server = build_server(options)
|
30
|
-
end
|
31
|
-
|
32
|
-
# Start gRPC server in background and
|
33
|
-
# wait untill it ready to accept connections
|
34
|
-
def start
|
35
|
-
return if running?
|
36
|
-
|
37
|
-
raise "Cannot re-start stopped server" if stopped?
|
38
|
-
|
39
|
-
logger.info "RPC server is starting..."
|
40
|
-
|
41
|
-
@start_thread = Thread.new { grpc_server.run }
|
42
|
-
|
43
|
-
grpc_server.wait_till_running
|
44
|
-
|
45
|
-
logger.info "RPC server is listening on #{host}"
|
46
|
-
end
|
47
|
-
|
48
|
-
def wait_till_terminated
|
49
|
-
raise "Server is not running" unless running?
|
50
|
-
|
51
|
-
start_thread.join
|
52
|
-
end
|
53
|
-
|
54
|
-
# Stop gRPC server if it's running
|
55
|
-
def stop
|
56
|
-
return unless running?
|
57
|
-
|
58
|
-
grpc_server.stop
|
59
|
-
|
60
|
-
logger.info "RPC server stopped"
|
61
|
-
end
|
62
|
-
|
63
|
-
def running?
|
64
|
-
grpc_server.running_state == :running
|
65
|
-
end
|
66
|
-
|
67
|
-
def stopped?
|
68
|
-
grpc_server.running_state == :stopped
|
69
|
-
end
|
70
|
-
|
71
|
-
private
|
72
|
-
|
73
|
-
attr_reader :start_thread
|
74
|
-
|
75
|
-
def logger
|
76
|
-
@logger ||= AnyCable.logger
|
77
|
-
end
|
78
|
-
|
79
|
-
def build_server(options)
|
80
|
-
GRPC::RpcServer.new(**options).tap do |server|
|
81
|
-
server.add_http2_port(host, :this_port_is_insecure)
|
82
|
-
server.handle(AnyCable::RPCHandler)
|
83
|
-
server.handle(build_health_checker)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def build_health_checker
|
88
|
-
health_checker = Grpc::Health::Checker.new
|
89
|
-
health_checker.add_status(
|
90
|
-
"anycable.RPC",
|
91
|
-
Grpc::Health::V1::HealthCheckResponse::ServingStatus::SERVING
|
92
|
-
)
|
93
|
-
health_checker
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
data/lib/anycable/socket.rb
DELETED
@@ -1,90 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AnyCable
|
4
|
-
# Socket mock to be used with application connection
|
5
|
-
class Socket
|
6
|
-
# Represents the per-connection store
|
7
|
-
# (for example, used to keep session beetween RPC calls)
|
8
|
-
class State
|
9
|
-
attr_reader :dirty_keys, :source
|
10
|
-
|
11
|
-
def initialize(from)
|
12
|
-
@source = from
|
13
|
-
@dirty_keys = nil
|
14
|
-
end
|
15
|
-
|
16
|
-
def read(key)
|
17
|
-
source&.[](key)
|
18
|
-
end
|
19
|
-
|
20
|
-
alias_method :[], :read
|
21
|
-
|
22
|
-
def write(key, val)
|
23
|
-
return if source&.[](key) == val
|
24
|
-
|
25
|
-
@source ||= {}
|
26
|
-
@dirty_keys ||= []
|
27
|
-
dirty_keys << key
|
28
|
-
source[key] = val
|
29
|
-
end
|
30
|
-
|
31
|
-
alias_method :[]=, :write
|
32
|
-
|
33
|
-
def changed_fields
|
34
|
-
return unless source && dirty_keys
|
35
|
-
source.slice(*dirty_keys)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
attr_reader :transmissions, :env, :cstate, :istate
|
40
|
-
|
41
|
-
def initialize(env: nil)
|
42
|
-
@transmissions = []
|
43
|
-
@env = env
|
44
|
-
@cstate = env["anycable.cstate"] = State.new(env["anycable.raw_cstate"])
|
45
|
-
@istate = env["anycable.istate"] = State.new(env["anycable.raw_istate"])
|
46
|
-
end
|
47
|
-
|
48
|
-
def transmit(websocket_message)
|
49
|
-
transmissions << websocket_message
|
50
|
-
end
|
51
|
-
|
52
|
-
def subscribe(_channel, broadcasting)
|
53
|
-
streams[:start] << broadcasting
|
54
|
-
end
|
55
|
-
|
56
|
-
def unsubscribe(_channel, broadcasting)
|
57
|
-
streams[:stop] << broadcasting
|
58
|
-
end
|
59
|
-
|
60
|
-
def unsubscribe_from_all(_channel)
|
61
|
-
@stop_all_streams = true
|
62
|
-
end
|
63
|
-
|
64
|
-
def streams
|
65
|
-
@streams ||= {start: [], stop: []}
|
66
|
-
end
|
67
|
-
|
68
|
-
def close
|
69
|
-
@closed = true
|
70
|
-
@streams&.clear
|
71
|
-
@stop_all_streams = true
|
72
|
-
end
|
73
|
-
|
74
|
-
def closed?
|
75
|
-
@closed == true
|
76
|
-
end
|
77
|
-
|
78
|
-
def stop_streams?
|
79
|
-
@stop_all_streams == true
|
80
|
-
end
|
81
|
-
|
82
|
-
def session
|
83
|
-
cstate.read(SESSION_KEY)
|
84
|
-
end
|
85
|
-
|
86
|
-
def session=(val)
|
87
|
-
cstate.write(SESSION_KEY, val)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|