anycable 0.6.0 → 1.0.0.preview1
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/CHANGELOG.md +93 -5
- data/MIT-LICENSE +1 -1
- data/README.md +13 -6
- data/bin/anycable +1 -1
- data/bin/anycabled +30 -0
- data/lib/anycable.rb +8 -13
- data/lib/anycable/broadcast_adapters.rb +3 -3
- data/lib/anycable/broadcast_adapters/redis.rb +2 -2
- data/lib/anycable/cli.rb +16 -6
- data/lib/anycable/config.rb +10 -5
- data/lib/anycable/exceptions_handling.rb +13 -9
- data/lib/anycable/health_server.rb +2 -3
- data/lib/anycable/middleware.rb +3 -0
- data/lib/anycable/middleware_chain.rb +2 -2
- data/lib/anycable/middlewares/check_version.rb +24 -0
- data/lib/anycable/rpc.rb +76 -0
- data/lib/anycable/rpc/rpc_pb.rb +54 -39
- data/lib/anycable/rpc/rpc_services_pb.rb +4 -3
- data/lib/anycable/rpc_handler.rb +77 -24
- data/lib/anycable/rspec.rb +6 -0
- data/lib/anycable/rspec/rpc_command_context.rb +20 -0
- data/lib/anycable/rspec/rpc_stub_context.rb +13 -0
- data/lib/anycable/rspec/with_grpc_server.rb +15 -0
- data/lib/anycable/server.rb +4 -48
- data/lib/anycable/socket.rb +39 -2
- data/lib/anycable/version.rb +1 -1
- metadata +34 -72
- data/.github/ISSUE_TEMPLATE.md +0 -25
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -31
- data/.gitignore +0 -40
- data/.hound.yml +0 -3
- data/.rubocop.yml +0 -71
- data/.travis.yml +0 -12
- data/Gemfile +0 -8
- data/Makefile +0 -5
- data/PITCHME.md +0 -139
- data/PITCHME.yaml +0 -1
- data/Rakefile +0 -8
- data/anycable.gemspec +0 -35
- data/assets/Memory3.png +0 -0
- data/assets/Memory5.png +0 -0
- data/assets/RTT3.png +0 -0
- data/assets/RTT5.png +0 -0
- data/assets/Scheme1.png +0 -0
- data/assets/Scheme2.png +0 -0
- data/assets/cpu_chart.gif +0 -0
- data/assets/cpu_chart2.gif +0 -0
- data/assets/evlms.png +0 -0
- data/benchmarks/.gitignore +0 -2
- data/benchmarks/2017-02-12.md +0 -308
- data/benchmarks/2018-03-04.md +0 -192
- data/benchmarks/2018-05-27-rpc-bench.md +0 -57
- data/benchmarks/2018-10-27.md +0 -181
- data/benchmarks/HowTo.md +0 -23
- data/benchmarks/ansible.cfg +0 -9
- data/benchmarks/assets/2018-10-27-action-cable-rss.png +0 -0
- data/benchmarks/assets/2018-10-27-action-cable-rtt.png +0 -0
- data/benchmarks/assets/2018-10-27-anycable-rss.png +0 -0
- data/benchmarks/assets/2018-10-27-anycable-rtt.png +0 -0
- data/benchmarks/assets/2018-10-27-async-rss.png +0 -0
- data/benchmarks/assets/2018-10-27-async-rtt.png +0 -0
- data/benchmarks/assets/2018-10-27-falcon-cable-rss.png +0 -0
- data/benchmarks/assets/2018-10-27-falcon-cable-rtt.png +0 -0
- data/benchmarks/assets/2018-10-27-iodine-cable-rss.png +0 -0
- data/benchmarks/assets/2018-10-27-iodine-cable-rtt.png +0 -0
- data/benchmarks/assets/2018-10-27-plezi-rss.png +0 -0
- data/benchmarks/assets/2018-10-27-plezi-rtt.png +0 -0
- data/benchmarks/bench.png +0 -0
- data/benchmarks/benchmark.yml +0 -69
- data/benchmarks/hosts +0 -5
- data/benchmarks/rtt_plot.py +0 -74
- data/benchmarks/rtt_plot_test.py +0 -16
- data/benchmarks/servers.yml +0 -58
- data/circle.yml +0 -8
- data/etc/bug_report_template.rb +0 -76
- data/lib/anycable/handler/capture_exceptions.rb +0 -39
- data/protos/rpc.proto +0 -55
@@ -1,8 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "webrick"
|
4
|
-
require "anycable/server"
|
5
|
-
|
6
3
|
module AnyCable
|
7
4
|
# Server for HTTP healthchecks.
|
8
5
|
#
|
@@ -54,6 +51,8 @@ module AnyCable
|
|
54
51
|
attr_reader :logger
|
55
52
|
|
56
53
|
def build_server
|
54
|
+
require "webrick"
|
55
|
+
|
57
56
|
WEBrick::HTTPServer.new(
|
58
57
|
Port: port,
|
59
58
|
Logger: logger,
|
data/lib/anycable/middleware.rb
CHANGED
@@ -7,6 +7,9 @@ module AnyCable
|
|
7
7
|
# for request/response calls
|
8
8
|
class Middleware < GRPC::Interceptor
|
9
9
|
def request_response(request: nil, call: nil, method: nil)
|
10
|
+
# Call middlewares only for AnyCable service
|
11
|
+
return yield unless method.receiver.is_a?(AnyCable::RPCHandler)
|
12
|
+
|
10
13
|
call(request, call, method) do
|
11
14
|
yield
|
12
15
|
end
|
@@ -48,8 +48,8 @@ module AnyCable
|
|
48
48
|
|
49
49
|
unless middleware.is_a?(AnyCable::Middleware)
|
50
50
|
raise ArgumentError,
|
51
|
-
|
52
|
-
|
51
|
+
"AnyCable middleware must be a subclass of AnyCable::Middleware, " \
|
52
|
+
"got #{middleware} instead"
|
53
53
|
end
|
54
54
|
|
55
55
|
middleware
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnyCable
|
4
|
+
module Middlewares
|
5
|
+
# Checks that RPC client version is compatibly with
|
6
|
+
# the current RPC proto version
|
7
|
+
class CheckVersion < Middleware
|
8
|
+
attr_reader :version
|
9
|
+
|
10
|
+
def initialize(version)
|
11
|
+
@version = version
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(_request, call, _method)
|
15
|
+
supported_versions = call.metadata["protov"]&.split(",")
|
16
|
+
return yield if supported_versions&.include?(version)
|
17
|
+
|
18
|
+
raise GRPC::Internal,
|
19
|
+
"Incompatible AnyCable RPC client.\nCurrent server version: #{version}.\n" \
|
20
|
+
"Client supported versions: #{call.metadata["protov"] || "unknown"}."
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/anycable/rpc.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "anycable/rpc/rpc_pb"
|
4
|
+
require "anycable/rpc/rpc_services_pb"
|
5
|
+
|
6
|
+
# Extend some PB auto-generated classes
|
7
|
+
module AnyCable
|
8
|
+
# Current RPC proto version (used for compatibility checks)
|
9
|
+
PROTO_VERSION = "v1"
|
10
|
+
SESSION_KEY = "_s_"
|
11
|
+
|
12
|
+
# Add setters/getter for cstate field
|
13
|
+
module WithConnectionState
|
14
|
+
def initialize(session: nil, **other)
|
15
|
+
if session
|
16
|
+
other[:cstate] ||= {}
|
17
|
+
other[:cstate][SESSION_KEY] = session
|
18
|
+
end
|
19
|
+
super(**other)
|
20
|
+
end
|
21
|
+
|
22
|
+
def session=(val)
|
23
|
+
self.cstate = {} unless cstate
|
24
|
+
cstate[SESSION_KEY] = val
|
25
|
+
end
|
26
|
+
|
27
|
+
def session
|
28
|
+
cstate[SESSION_KEY]
|
29
|
+
end
|
30
|
+
|
31
|
+
def cstate
|
32
|
+
env.cstate
|
33
|
+
end
|
34
|
+
|
35
|
+
def cstate=(val)
|
36
|
+
env.cstate = val
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Status predicates
|
41
|
+
module StatusPredicates
|
42
|
+
def success?
|
43
|
+
status == :SUCCESS
|
44
|
+
end
|
45
|
+
|
46
|
+
def failure?
|
47
|
+
status == :FAILURE
|
48
|
+
end
|
49
|
+
|
50
|
+
def error?
|
51
|
+
status == :ERROR
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class ConnectionResponse
|
56
|
+
prepend WithConnectionState
|
57
|
+
include StatusPredicates
|
58
|
+
end
|
59
|
+
|
60
|
+
class CommandMessage
|
61
|
+
prepend WithConnectionState
|
62
|
+
end
|
63
|
+
|
64
|
+
class CommandResponse
|
65
|
+
prepend WithConnectionState
|
66
|
+
include StatusPredicates
|
67
|
+
end
|
68
|
+
|
69
|
+
class DisconnectRequest
|
70
|
+
prepend WithConnectionState
|
71
|
+
end
|
72
|
+
|
73
|
+
class DisconnectResponse
|
74
|
+
include StatusPredicates
|
75
|
+
end
|
76
|
+
end
|
data/lib/anycable/rpc/rpc_pb.rb
CHANGED
@@ -1,51 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
4
|
# source: rpc.proto
|
3
5
|
|
4
|
-
require
|
6
|
+
require "google/protobuf"
|
5
7
|
|
6
8
|
Google::Protobuf::DescriptorPool.generated_pool.build do
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
9
|
+
add_file("rpc.proto", syntax: :proto3) do
|
10
|
+
add_message "anycable.Env" do
|
11
|
+
optional :url, :string, 1
|
12
|
+
map :headers, :string, :string, 2
|
13
|
+
map :cstate, :string, :string, 3
|
14
|
+
end
|
15
|
+
add_message "anycable.EnvResponse" do
|
16
|
+
map :cstate, :string, :string, 1
|
17
|
+
end
|
18
|
+
add_message "anycable.ConnectionRequest" do
|
19
|
+
optional :env, :message, 3, "anycable.Env"
|
20
|
+
end
|
21
|
+
add_message "anycable.ConnectionResponse" do
|
22
|
+
optional :status, :enum, 1, "anycable.Status"
|
23
|
+
optional :identifiers, :string, 2
|
24
|
+
repeated :transmissions, :string, 3
|
25
|
+
optional :error_msg, :string, 4
|
26
|
+
optional :env, :message, 5, "anycable.EnvResponse"
|
27
|
+
end
|
28
|
+
add_message "anycable.CommandMessage" do
|
29
|
+
optional :command, :string, 1
|
30
|
+
optional :identifier, :string, 2
|
31
|
+
optional :connection_identifiers, :string, 3
|
32
|
+
optional :data, :string, 4
|
33
|
+
optional :env, :message, 5, "anycable.Env"
|
34
|
+
end
|
35
|
+
add_message "anycable.CommandResponse" do
|
36
|
+
optional :status, :enum, 1, "anycable.Status"
|
37
|
+
optional :disconnect, :bool, 2
|
38
|
+
optional :stop_streams, :bool, 3
|
39
|
+
repeated :streams, :string, 4
|
40
|
+
repeated :transmissions, :string, 5
|
41
|
+
optional :error_msg, :string, 6
|
42
|
+
optional :env, :message, 7, "anycable.EnvResponse"
|
43
|
+
end
|
44
|
+
add_message "anycable.DisconnectRequest" do
|
45
|
+
optional :identifiers, :string, 1
|
46
|
+
repeated :subscriptions, :string, 2
|
47
|
+
optional :env, :message, 5, "anycable.Env"
|
48
|
+
end
|
49
|
+
add_message "anycable.DisconnectResponse" do
|
50
|
+
optional :status, :enum, 1, "anycable.Status"
|
51
|
+
optional :error_msg, :string, 2
|
52
|
+
end
|
53
|
+
add_enum "anycable.Status" do
|
54
|
+
value :ERROR, 0
|
55
|
+
value :SUCCESS, 1
|
56
|
+
value :FAILURE, 2
|
57
|
+
end
|
45
58
|
end
|
46
59
|
end
|
47
60
|
|
48
61
|
module AnyCable
|
62
|
+
Env = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.Env").msgclass
|
63
|
+
EnvResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.EnvResponse").msgclass
|
49
64
|
ConnectionRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.ConnectionRequest").msgclass
|
50
65
|
ConnectionResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.ConnectionResponse").msgclass
|
51
66
|
CommandMessage = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.CommandMessage").msgclass
|
@@ -1,17 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
4
|
# Source: rpc.proto for package 'anycable'
|
3
5
|
|
4
|
-
require
|
6
|
+
require "grpc"
|
5
7
|
|
6
8
|
module AnyCable
|
7
9
|
module RPC
|
8
10
|
class Service
|
9
|
-
|
10
11
|
include GRPC::GenericService
|
11
12
|
|
12
13
|
self.marshal_class_method = :encode
|
13
14
|
self.unmarshal_class_method = :decode
|
14
|
-
self.service_name =
|
15
|
+
self.service_name = "anycable.RPC"
|
15
16
|
|
16
17
|
rpc :Connect, ConnectionRequest, ConnectionResponse
|
17
18
|
rpc :Command, CommandMessage, CommandResponse
|
data/lib/anycable/rpc_handler.rb
CHANGED
@@ -1,43 +1,50 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "anycable/socket"
|
4
|
-
require "anycable/rpc
|
5
|
-
require "anycable/rpc/rpc_services_pb"
|
6
|
-
|
7
|
-
require "anycable/handler/capture_exceptions"
|
4
|
+
require "anycable/rpc"
|
8
5
|
|
9
6
|
# rubocop:disable Metrics/AbcSize
|
10
7
|
# rubocop:disable Metrics/MethodLength
|
8
|
+
# rubocop:disable Metrics/ClassLength
|
11
9
|
module AnyCable
|
12
10
|
# RPC service handler
|
13
11
|
class RPCHandler < AnyCable::RPC::Service
|
14
|
-
prepend AnyCable::Handler::CaptureExceptions
|
15
|
-
|
16
12
|
# Handle connection request from WebSocket server
|
17
13
|
def connect(request, _unused_call)
|
18
14
|
logger.debug("RPC Connect: #{request.inspect}")
|
19
15
|
|
20
|
-
socket = build_socket(env: rack_env(request))
|
16
|
+
socket = build_socket(env: rack_env(request.env))
|
21
17
|
|
22
18
|
connection = factory.call(socket)
|
23
19
|
|
24
20
|
connection.handle_open
|
25
21
|
|
26
22
|
if socket.closed?
|
27
|
-
AnyCable::ConnectionResponse.new(
|
23
|
+
AnyCable::ConnectionResponse.new(
|
24
|
+
status: AnyCable::Status::FAILURE,
|
25
|
+
transmissions: socket.transmissions
|
26
|
+
)
|
28
27
|
else
|
29
28
|
AnyCable::ConnectionResponse.new(
|
30
29
|
status: AnyCable::Status::SUCCESS,
|
31
30
|
identifiers: connection.identifiers_json,
|
32
|
-
transmissions: socket.transmissions
|
31
|
+
transmissions: socket.transmissions,
|
32
|
+
env: build_env_response(socket)
|
33
33
|
)
|
34
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
|
+
)
|
35
42
|
end
|
36
43
|
|
37
44
|
def disconnect(request, _unused_call)
|
38
45
|
logger.debug("RPC Disconnect: #{request.inspect}")
|
39
46
|
|
40
|
-
socket = build_socket(env: rack_env(request))
|
47
|
+
socket = build_socket(env: rack_env(request.env))
|
41
48
|
|
42
49
|
connection = factory.call(
|
43
50
|
socket,
|
@@ -50,12 +57,19 @@ module AnyCable
|
|
50
57
|
else
|
51
58
|
AnyCable::DisconnectResponse.new(status: AnyCable::Status::FAILURE)
|
52
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
|
+
)
|
53
67
|
end
|
54
68
|
|
55
69
|
def command(message, _unused_call)
|
56
70
|
logger.debug("RPC Command: #{message.inspect}")
|
57
71
|
|
58
|
-
socket = build_socket
|
72
|
+
socket = build_socket(env: rack_env(message.env))
|
59
73
|
|
60
74
|
connection = factory.call(
|
61
75
|
socket,
|
@@ -73,30 +87,58 @@ module AnyCable
|
|
73
87
|
disconnect: socket.closed?,
|
74
88
|
stop_streams: socket.stop_streams?,
|
75
89
|
streams: socket.streams,
|
76
|
-
transmissions: socket.transmissions
|
90
|
+
transmissions: socket.transmissions,
|
91
|
+
env: build_env_response(socket)
|
92
|
+
)
|
93
|
+
rescue => exp
|
94
|
+
notify_exception(exp, :command, message)
|
95
|
+
|
96
|
+
AnyCable::CommandResponse.new(
|
97
|
+
status: AnyCable::Status::ERROR,
|
98
|
+
error_msg: exp.message
|
77
99
|
)
|
78
100
|
end
|
79
101
|
|
80
102
|
private
|
81
103
|
|
82
|
-
# Build env from
|
83
|
-
def rack_env(
|
84
|
-
uri = URI.parse(
|
85
|
-
|
86
|
-
|
87
|
-
|
104
|
+
# Build Rack env from request
|
105
|
+
def rack_env(request_env)
|
106
|
+
uri = URI.parse(request_env.url)
|
107
|
+
|
108
|
+
env = base_rack_env
|
109
|
+
env.merge!(
|
88
110
|
"PATH_INFO" => uri.path,
|
111
|
+
"QUERY_STRING" => uri.query,
|
112
|
+
"SERVER_NAME" => uri.host,
|
89
113
|
"SERVER_PORT" => uri.port.to_s,
|
90
114
|
"HTTP_HOST" => uri.host,
|
91
|
-
|
92
|
-
"rack.
|
93
|
-
|
94
|
-
"
|
95
|
-
|
115
|
+
"REMOTE_ADDR" => request_env.headers.delete("REMOTE_ADDR"),
|
116
|
+
"rack.url_scheme" => uri.scheme,
|
117
|
+
# AnyCable specific fields
|
118
|
+
"anycable.raw_cstate" => request_env.cstate&.to_h
|
119
|
+
)
|
120
|
+
|
121
|
+
env.merge!(build_headers(request_env.headers))
|
122
|
+
end
|
123
|
+
|
124
|
+
def base_rack_env
|
125
|
+
# Minimum required variables according to Rack Spec
|
126
|
+
# (not all of them though, just those enough for Action Cable to work)
|
127
|
+
# See https://rubydoc.info/github/rack/rack/master/file/SPEC
|
128
|
+
{
|
129
|
+
"REQUEST_METHOD" => "GET",
|
130
|
+
"SCRIPT_NAME" => "",
|
131
|
+
"PATH_INFO" => "/",
|
132
|
+
"QUERY_STRING" => "",
|
133
|
+
"SERVER_NAME" => "",
|
134
|
+
"SERVER_PORT" => "80",
|
135
|
+
"rack.url_scheme" => "http",
|
136
|
+
"rack.input" => ""
|
137
|
+
}
|
96
138
|
end
|
97
139
|
|
98
140
|
def build_socket(**options)
|
99
|
-
AnyCable::Socket.new(options)
|
141
|
+
AnyCable::Socket.new(**options)
|
100
142
|
end
|
101
143
|
|
102
144
|
def build_headers(headers)
|
@@ -107,6 +149,12 @@ module AnyCable
|
|
107
149
|
end
|
108
150
|
end
|
109
151
|
|
152
|
+
def build_env_response(socket)
|
153
|
+
AnyCable::EnvResponse.new(
|
154
|
+
cstate: socket.cstate.changed_fields
|
155
|
+
)
|
156
|
+
end
|
157
|
+
|
110
158
|
def logger
|
111
159
|
AnyCable.logger
|
112
160
|
end
|
@@ -114,7 +162,12 @@ module AnyCable
|
|
114
162
|
def factory
|
115
163
|
AnyCable.connection_factory
|
116
164
|
end
|
165
|
+
|
166
|
+
def notify_exception(exp, method_name, message)
|
167
|
+
AnyCable::ExceptionsHandling.notify(exp, method_name.to_s, message.to_h)
|
168
|
+
end
|
117
169
|
end
|
118
170
|
end
|
119
171
|
# rubocop:enable Metrics/AbcSize
|
120
172
|
# rubocop:enable Metrics/MethodLength
|
173
|
+
# rubocop:enable Metrics/ClassLength
|