anycable 0.6.5 → 1.0.0.preview1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -5
- data/README.md +5 -5
- data/lib/anycable.rb +2 -11
- data/lib/anycable/broadcast_adapters.rb +2 -2
- data/lib/anycable/broadcast_adapters/redis.rb +1 -1
- data/lib/anycable/cli.rb +13 -3
- data/lib/anycable/config.rb +9 -6
- data/lib/anycable/exceptions_handling.rb +5 -7
- 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 +28 -18
- 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 +0 -60
- data/lib/anycable/socket.rb +39 -2
- data/lib/anycable/version.rb +1 -1
- metadata +21 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65d07fe802d850f89351ed8c054d2cba9c393b36f7a5aa318183362f09d53e58
|
4
|
+
data.tar.gz: b4167ba77ce930ddcb77a9e38d205215fb3ec085a93bfd3876f2bf8a5e669373
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 456af4dd28de6d32a8436ce8702ddb3ba5523b25b531bce8dac00c5bf6cd181e79c57e4d4a89d1a2c1ff3e21eda1108be3f023c65edcf1bbf5790594216ae971
|
7
|
+
data.tar.gz: b25fa2a291a6cd6bbf4734c9c59dcf991dead19c0e25c004ac696ec524e6de63bb5ea4251f8d06ce1d21699a44e178a7e445bfedcd4d70179535c6648ed67854
|
data/CHANGELOG.md
CHANGED
@@ -1,14 +1,30 @@
|
|
1
1
|
# Change log
|
2
2
|
|
3
|
-
##
|
3
|
+
## 🚧 1.0.0 (_coming soon_)
|
4
4
|
|
5
|
-
|
5
|
+
- **RPC schema has changed**. ([@palkan][])
|
6
6
|
|
7
|
-
|
7
|
+
Using `anycable-go` v1.x is required.
|
8
|
+
|
9
|
+
- **Ruby 2.5+ is required**. ([@palkan][])
|
10
|
+
|
11
|
+
- Added RPC proto version check. ([@palkan][])
|
12
|
+
|
13
|
+
Server must sent `protov` metadata with the supported versions (comma-separated list). If there is no matching version an exception is raised.
|
14
|
+
|
15
|
+
Current RPC proto version is **v1**.
|
16
|
+
|
17
|
+
- Added `request` support to channels. ([@palkan][])
|
18
|
+
|
19
|
+
Now you can access `request` object in channels, too (e.g., to read headers/cookies/URL/etc).
|
20
|
+
|
21
|
+
- Change default server address from `[::]:50051` to `127.0.0.1:50051`. ([@palkan][])
|
22
|
+
|
23
|
+
See [#71](https://github.com/anycable/anycable/pull/71).
|
8
24
|
|
9
25
|
## 0.6.4 (2020-01-24)
|
10
26
|
|
11
|
-
- Fix Ruby 2.7 warnings. ([@palkan]
|
27
|
+
- Fix Ruby 2.7 warnings. ([@palkan])
|
12
28
|
|
13
29
|
– Add `REMOTE_ADDR` socket env variable using a synthetic header passed from a websocket
|
14
30
|
server. ([@sponomarev][])
|
@@ -84,7 +100,7 @@ AnyCable allows you to use custom broadcasting adapters (Redis is used by defaul
|
|
84
100
|
```ruby
|
85
101
|
# Specify by name (tries to load `AnyCable::BroadcastAdapters::MyAdapter` from
|
86
102
|
# "anycable/broadcast_adapters/my_adapter")
|
87
|
-
AnyCable.broadcast_adapter = :my_adapter, {
|
103
|
+
AnyCable.broadcast_adapter = :my_adapter, {option: "value"}
|
88
104
|
# or provide an instance (should respond_to #broadcast)
|
89
105
|
AnyCable.broadcast_adapter = MyAdapter.new
|
90
106
|
```
|
data/README.md
CHANGED
@@ -16,7 +16,7 @@ AnyCable uses the same protocol as ActionCable, so you can use its [JavaScript c
|
|
16
16
|
|
17
17
|
## Requirements
|
18
18
|
|
19
|
-
- Ruby >= 2.
|
19
|
+
- Ruby >= 2.5
|
20
20
|
- Redis (for broadcasting, [discuss other options](https://github.com/anycable/anycable/issues/2) with us!)
|
21
21
|
|
22
22
|
## Usage
|
@@ -52,27 +52,27 @@ Check out our 📑 [Documentation](https://docs.anycable.io).
|
|
52
52
|
|
53
53
|
- Install required GRPC gems:
|
54
54
|
|
55
|
-
```
|
55
|
+
```sh
|
56
56
|
gem install grpc
|
57
57
|
gem install grpc-tools
|
58
58
|
```
|
59
59
|
|
60
60
|
- Re-generate GRPC files (if necessary):
|
61
61
|
|
62
|
-
```
|
62
|
+
```sh
|
63
63
|
make
|
64
64
|
```
|
65
65
|
|
66
66
|
## Contributing
|
67
67
|
|
68
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/anycable/anycable.
|
68
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/anycable/anycable](https://github.com/anycable/anycable).
|
69
69
|
|
70
70
|
Please, provide reproduction script (using [this template](https://github.com/anycable/anycable/blob/master/etc/bug_report_template.rb)) when submitting bugs if possible.
|
71
71
|
|
72
72
|
## License
|
73
|
+
|
73
74
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
74
75
|
|
75
76
|
## Security Contact
|
76
77
|
|
77
78
|
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
|
78
|
-
|
data/lib/anycable.rb
CHANGED
@@ -56,14 +56,6 @@ module AnyCable
|
|
56
56
|
ExceptionsHandling << block
|
57
57
|
end
|
58
58
|
|
59
|
-
def error_handlers
|
60
|
-
warn <<~DEPRECATION
|
61
|
-
Using `AnyCable.error_handlers` is deprecated!
|
62
|
-
Please, use `AnyCable.capture_exception` instead.
|
63
|
-
DEPRECATION
|
64
|
-
ExceptionsHandling
|
65
|
-
end
|
66
|
-
|
67
59
|
# Register a callback to be invoked before
|
68
60
|
# the server starts
|
69
61
|
def configure_server(&block)
|
@@ -103,8 +95,7 @@ module AnyCable
|
|
103
95
|
attr_writer :middleware
|
104
96
|
end
|
105
97
|
|
98
|
+
require "anycable/middlewares/check_version"
|
99
|
+
|
106
100
|
self.middleware = MiddlewareChain.new
|
107
101
|
end
|
108
|
-
|
109
|
-
# Backward compatibility
|
110
|
-
Anycable = AnyCable
|
@@ -17,11 +17,11 @@ module AnyCable
|
|
17
17
|
# We couldn't require the adapter itself.
|
18
18
|
if e.path == path_to_adapter
|
19
19
|
raise e.class, "Couldn't load the '#{adapter}' broadcast adapter for AnyCable",
|
20
|
-
|
20
|
+
e.backtrace
|
21
21
|
# Bubbled up from the adapter require.
|
22
22
|
else
|
23
23
|
raise e.class, "Error loading the '#{adapter}' broadcast adapter for AnyCable",
|
24
|
-
|
24
|
+
e.backtrace
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
data/lib/anycable/cli.rb
CHANGED
@@ -36,11 +36,11 @@ module AnyCable
|
|
36
36
|
|
37
37
|
configure_server!
|
38
38
|
|
39
|
-
logger.info "Starting AnyCable gRPC server (pid: #{Process.pid})"
|
39
|
+
logger.info "Starting AnyCable gRPC server (pid: #{Process.pid}, workers_num: #{config.rpc_pool_size})"
|
40
40
|
|
41
41
|
print_versions!
|
42
42
|
|
43
|
-
logger.info "Serving #{defined?(::Rails) ?
|
43
|
+
logger.info "Serving #{defined?(::Rails) ? "Rails " : ""}application from #{boot_file}"
|
44
44
|
|
45
45
|
verify_connection_factory!
|
46
46
|
|
@@ -48,6 +48,8 @@ module AnyCable
|
|
48
48
|
|
49
49
|
log_errors!
|
50
50
|
|
51
|
+
use_version_check! if config.version_check_enabled?
|
52
|
+
|
51
53
|
@server = AnyCable::Server.new(
|
52
54
|
host: config.rpc_host,
|
53
55
|
**config.to_grpc_params,
|
@@ -124,7 +126,7 @@ module AnyCable
|
|
124
126
|
end
|
125
127
|
|
126
128
|
def print_versions!
|
127
|
-
logger.info "AnyCable version: #{AnyCable::VERSION}"
|
129
|
+
logger.info "AnyCable version: #{AnyCable::VERSION} (proto_version: #{AnyCable::PROTO_VERSION})"
|
128
130
|
logger.info "gRPC version: #{GRPC::VERSION}"
|
129
131
|
end
|
130
132
|
|
@@ -162,6 +164,14 @@ module AnyCable
|
|
162
164
|
AnyCable.server_callbacks.each(&:call)
|
163
165
|
end
|
164
166
|
|
167
|
+
def use_version_check!
|
168
|
+
require "anycable/middlewares/check_version"
|
169
|
+
|
170
|
+
AnyCable.middleware.use(
|
171
|
+
AnyCable::Middlewares::CheckVersion.new(AnyCable::PROTO_VERSION)
|
172
|
+
)
|
173
|
+
end
|
174
|
+
|
165
175
|
def start_health_server!
|
166
176
|
@health_server = AnyCable::HealthServer.new(
|
167
177
|
server,
|
data/lib/anycable/config.rb
CHANGED
@@ -8,11 +8,9 @@ module AnyCable
|
|
8
8
|
class Config < Anyway::Config
|
9
9
|
config_name :anycable
|
10
10
|
|
11
|
-
DefaultHostWrapper = Class.new(String)
|
12
|
-
|
13
11
|
attr_config(
|
14
12
|
### gRPC options
|
15
|
-
rpc_host:
|
13
|
+
rpc_host: "127.0.0.1:50051",
|
16
14
|
# For defaults see https://github.com/grpc/grpc/blob/51f0d35509bcdaba572d422c4f856208162022de/src/ruby/lib/grpc/generic/rpc_server.rb#L186-L216
|
17
15
|
rpc_pool_size: GRPC::RpcServer::DEFAULT_POOL_SIZE,
|
18
16
|
rpc_max_waiting_requests: GRPC::RpcServer::DEFAULT_MAX_WAITING_REQUESTS,
|
@@ -34,9 +32,14 @@ module AnyCable
|
|
34
32
|
|
35
33
|
### Health check options
|
36
34
|
http_health_port: nil,
|
37
|
-
http_health_path: "/health"
|
35
|
+
http_health_path: "/health",
|
36
|
+
|
37
|
+
### Misc options
|
38
|
+
version_check_enabled: true
|
38
39
|
)
|
39
40
|
|
41
|
+
alias version_check_enabled? version_check_enabled
|
42
|
+
|
40
43
|
ignore_options :rpc_server_args
|
41
44
|
flag_options :log_grpc, :debug
|
42
45
|
|
@@ -71,7 +74,7 @@ module AnyCable
|
|
71
74
|
|
72
75
|
# Build Redis parameters
|
73
76
|
def to_redis_params
|
74
|
-
{
|
77
|
+
{url: redis_url}.tap do |params|
|
75
78
|
next if redis_sentinels.nil?
|
76
79
|
|
77
80
|
raise ArgumentError, "redis_sentinels must be an array; got #{redis_sentinels}" unless
|
@@ -102,7 +105,7 @@ module AnyCable
|
|
102
105
|
|
103
106
|
raise ArgumentError, "Invalid Sentinel value: #{sentinel}" if matches.nil?
|
104
107
|
|
105
|
-
{
|
108
|
+
{"host" => matches[1], "port" => matches[2].to_i}
|
106
109
|
end
|
107
110
|
end
|
108
111
|
end
|
@@ -11,13 +11,11 @@ module AnyCable
|
|
11
11
|
|
12
12
|
def notify(exp, method_name, message)
|
13
13
|
handlers.each do |handler|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
AnyCable.logger.error exp.backtrace.join("\n") unless exp.backtrace.nil?
|
20
|
-
end
|
14
|
+
handler.call(exp, method_name, message)
|
15
|
+
rescue => exp
|
16
|
+
AnyCable.logger.error "!!! EXCEPTION HANDLER THREW AN ERROR !!!"
|
17
|
+
AnyCable.logger.error exp
|
18
|
+
AnyCable.logger.error exp.backtrace.join("\n") unless exp.backtrace.nil?
|
21
19
|
end
|
22
20
|
end
|
23
21
|
|
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,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "anycable/socket"
|
4
|
-
require "anycable/rpc
|
5
|
-
require "anycable/rpc/rpc_services_pb"
|
4
|
+
require "anycable/rpc"
|
6
5
|
|
7
6
|
# rubocop:disable Metrics/AbcSize
|
8
7
|
# rubocop:disable Metrics/MethodLength
|
@@ -14,22 +13,26 @@ module AnyCable
|
|
14
13
|
def connect(request, _unused_call)
|
15
14
|
logger.debug("RPC Connect: #{request.inspect}")
|
16
15
|
|
17
|
-
socket = build_socket(env: rack_env(request))
|
16
|
+
socket = build_socket(env: rack_env(request.env))
|
18
17
|
|
19
18
|
connection = factory.call(socket)
|
20
19
|
|
21
20
|
connection.handle_open
|
22
21
|
|
23
22
|
if socket.closed?
|
24
|
-
AnyCable::ConnectionResponse.new(
|
23
|
+
AnyCable::ConnectionResponse.new(
|
24
|
+
status: AnyCable::Status::FAILURE,
|
25
|
+
transmissions: socket.transmissions
|
26
|
+
)
|
25
27
|
else
|
26
28
|
AnyCable::ConnectionResponse.new(
|
27
29
|
status: AnyCable::Status::SUCCESS,
|
28
30
|
identifiers: connection.identifiers_json,
|
29
|
-
transmissions: socket.transmissions
|
31
|
+
transmissions: socket.transmissions,
|
32
|
+
env: build_env_response(socket)
|
30
33
|
)
|
31
34
|
end
|
32
|
-
rescue
|
35
|
+
rescue => exp
|
33
36
|
notify_exception(exp, :connect, request)
|
34
37
|
|
35
38
|
AnyCable::ConnectionResponse.new(
|
@@ -41,7 +44,7 @@ module AnyCable
|
|
41
44
|
def disconnect(request, _unused_call)
|
42
45
|
logger.debug("RPC Disconnect: #{request.inspect}")
|
43
46
|
|
44
|
-
socket = build_socket(env: rack_env(request))
|
47
|
+
socket = build_socket(env: rack_env(request.env))
|
45
48
|
|
46
49
|
connection = factory.call(
|
47
50
|
socket,
|
@@ -54,7 +57,7 @@ module AnyCable
|
|
54
57
|
else
|
55
58
|
AnyCable::DisconnectResponse.new(status: AnyCable::Status::FAILURE)
|
56
59
|
end
|
57
|
-
rescue
|
60
|
+
rescue => exp
|
58
61
|
notify_exception(exp, :disconnect, request)
|
59
62
|
|
60
63
|
AnyCable::DisconnectResponse.new(
|
@@ -66,9 +69,7 @@ module AnyCable
|
|
66
69
|
def command(message, _unused_call)
|
67
70
|
logger.debug("RPC Command: #{message.inspect}")
|
68
71
|
|
69
|
-
|
70
|
-
# but we still want `connection.env` to work
|
71
|
-
socket = build_socket(env: base_rack_env)
|
72
|
+
socket = build_socket(env: rack_env(message.env))
|
72
73
|
|
73
74
|
connection = factory.call(
|
74
75
|
socket,
|
@@ -86,9 +87,10 @@ module AnyCable
|
|
86
87
|
disconnect: socket.closed?,
|
87
88
|
stop_streams: socket.stop_streams?,
|
88
89
|
streams: socket.streams,
|
89
|
-
transmissions: socket.transmissions
|
90
|
+
transmissions: socket.transmissions,
|
91
|
+
env: build_env_response(socket)
|
90
92
|
)
|
91
|
-
rescue
|
93
|
+
rescue => exp
|
92
94
|
notify_exception(exp, :command, message)
|
93
95
|
|
94
96
|
AnyCable::CommandResponse.new(
|
@@ -100,8 +102,8 @@ module AnyCable
|
|
100
102
|
private
|
101
103
|
|
102
104
|
# Build Rack env from request
|
103
|
-
def rack_env(
|
104
|
-
uri = URI.parse(
|
105
|
+
def rack_env(request_env)
|
106
|
+
uri = URI.parse(request_env.url)
|
105
107
|
|
106
108
|
env = base_rack_env
|
107
109
|
env.merge!(
|
@@ -110,11 +112,13 @@ module AnyCable
|
|
110
112
|
"SERVER_NAME" => uri.host,
|
111
113
|
"SERVER_PORT" => uri.port.to_s,
|
112
114
|
"HTTP_HOST" => uri.host,
|
113
|
-
"REMOTE_ADDR" =>
|
114
|
-
"rack.url_scheme" => uri.scheme
|
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
|
115
119
|
)
|
116
120
|
|
117
|
-
env.merge!(build_headers(
|
121
|
+
env.merge!(build_headers(request_env.headers))
|
118
122
|
end
|
119
123
|
|
120
124
|
def base_rack_env
|
@@ -145,6 +149,12 @@ module AnyCable
|
|
145
149
|
end
|
146
150
|
end
|
147
151
|
|
152
|
+
def build_env_response(socket)
|
153
|
+
AnyCable::EnvResponse.new(
|
154
|
+
cstate: socket.cstate.changed_fields
|
155
|
+
)
|
156
|
+
end
|
157
|
+
|
148
158
|
def logger
|
149
159
|
AnyCable.logger
|
150
160
|
end
|
@@ -0,0 +1,20 @@
|
|
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
|
@@ -0,0 +1,13 @@
|
|
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) { "example.com/cable" }
|
11
|
+
let(:headers) { {} }
|
12
|
+
let(:env) { AnyCable::Env.new(url: url, headers: headers) }
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
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
|
+
end
|
13
|
+
|
14
|
+
after(:all) { @server.stop }
|
15
|
+
end
|
data/lib/anycable/server.rb
CHANGED
@@ -21,48 +21,6 @@ module AnyCable
|
|
21
21
|
# # stop server
|
22
22
|
# server.stop
|
23
23
|
class Server
|
24
|
-
class << self
|
25
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
26
|
-
def start(**options)
|
27
|
-
warn <<~DEPRECATION
|
28
|
-
DEPRECATION WARNING: Using AnyCable::Server.start is deprecated!
|
29
|
-
Please, use anycable CLI instead.
|
30
|
-
|
31
|
-
See https://docs.anycable.io/#upgrade_to_0_6_0
|
32
|
-
DEPRECATION
|
33
|
-
|
34
|
-
AnyCable.server_callbacks.each(&:call)
|
35
|
-
|
36
|
-
server = new(
|
37
|
-
host: AnyCable.config.rpc_host,
|
38
|
-
**AnyCable.config.to_grpc_params,
|
39
|
-
interceptors: AnyCable.middleware.to_a,
|
40
|
-
**options
|
41
|
-
)
|
42
|
-
|
43
|
-
AnyCable.middleware.freeze
|
44
|
-
|
45
|
-
if AnyCable.config.http_health_port_provided?
|
46
|
-
health_server = AnyCable::HealthServer.new(
|
47
|
-
server,
|
48
|
-
**AnyCable.config.to_http_health_params
|
49
|
-
)
|
50
|
-
health_server.start
|
51
|
-
end
|
52
|
-
|
53
|
-
at_exit do
|
54
|
-
server.stop
|
55
|
-
health_server&.stop
|
56
|
-
end
|
57
|
-
|
58
|
-
AnyCable.logger.info "Broadcasting Redis channel: #{AnyCable.config.redis_channel}"
|
59
|
-
|
60
|
-
server.start
|
61
|
-
server.wait_till_terminated
|
62
|
-
end
|
63
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
64
|
-
end
|
65
|
-
|
66
24
|
attr_reader :grpc_server, :host
|
67
25
|
|
68
26
|
def initialize(host:, logger: AnyCable.logger, **options)
|
@@ -78,8 +36,6 @@ module AnyCable
|
|
78
36
|
|
79
37
|
raise "Cannot re-start stopped server" if stopped?
|
80
38
|
|
81
|
-
check_default_host
|
82
|
-
|
83
39
|
logger.info "RPC server is starting..."
|
84
40
|
|
85
41
|
@start_thread = Thread.new { grpc_server.run }
|
@@ -132,21 +88,5 @@ module AnyCable
|
|
132
88
|
)
|
133
89
|
health_checker
|
134
90
|
end
|
135
|
-
|
136
|
-
def check_default_host
|
137
|
-
return unless host.is_a?(Anycable::Config::DefaultHostWrapper)
|
138
|
-
|
139
|
-
warn <<~DEPRECATION
|
140
|
-
DEPRECATION WARNING: You're using default rpc_host configuration which starts AnyCable RPC
|
141
|
-
server on all available interfaces including external IPv4 and IPv6.
|
142
|
-
This is about to be changed to loopback interface only in future versions.
|
143
|
-
|
144
|
-
Please, consider switching to the loopback interface or set "[::]:50051"
|
145
|
-
explicitly in your configuration, if you want to continue with the current
|
146
|
-
behavior and supress this message.
|
147
|
-
|
148
|
-
See https://docs.anycable.io/#/configuration
|
149
|
-
DEPRECATION
|
150
|
-
end
|
151
91
|
end
|
152
92
|
end
|
data/lib/anycable/socket.rb
CHANGED
@@ -3,11 +3,41 @@
|
|
3
3
|
module AnyCable
|
4
4
|
# Socket mock to be used with application connection
|
5
5
|
class Socket
|
6
|
-
|
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
|
+
def write(key, val)
|
21
|
+
return if source&.[](key) == val
|
22
|
+
|
23
|
+
@source ||= {}
|
24
|
+
@dirty_keys ||= []
|
25
|
+
dirty_keys << key
|
26
|
+
source[key] = val
|
27
|
+
end
|
28
|
+
|
29
|
+
def changed_fields
|
30
|
+
return unless source && dirty_keys
|
31
|
+
source.slice(*dirty_keys)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :transmissions, :env, :cstate
|
7
36
|
|
8
37
|
def initialize(env: nil)
|
9
38
|
@transmissions = []
|
10
39
|
@env = env
|
40
|
+
@cstate = env["anycable.cstate"] = State.new(env["anycable.raw_cstate"])
|
11
41
|
end
|
12
42
|
|
13
43
|
def transmit(websocket_message)
|
@@ -32,7 +62,6 @@ module AnyCable
|
|
32
62
|
|
33
63
|
def close
|
34
64
|
@closed = true
|
35
|
-
@transmissions.clear
|
36
65
|
@streams&.clear
|
37
66
|
@stop_all_streams = true
|
38
67
|
end
|
@@ -44,5 +73,13 @@ module AnyCable
|
|
44
73
|
def stop_streams?
|
45
74
|
@stop_all_streams == true
|
46
75
|
end
|
76
|
+
|
77
|
+
def session
|
78
|
+
cstate.read(SESSION_KEY)
|
79
|
+
end
|
80
|
+
|
81
|
+
def session=(val)
|
82
|
+
cstate.write(SESSION_KEY, val)
|
83
|
+
end
|
47
84
|
end
|
48
85
|
end
|
data/lib/anycable/version.rb
CHANGED
metadata
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: anycable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0.preview1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- palkan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-02-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: anyway_config
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 1.4.2
|
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
26
|
version: 1.4.2
|
27
27
|
- !ruby/object:Gem::Dependency
|
@@ -109,19 +109,19 @@ dependencies:
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '3.5'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name: rubocop
|
112
|
+
name: rubocop-md
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: 0.
|
117
|
+
version: '0.3'
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: 0.
|
124
|
+
version: '0.3'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: simplecov
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -137,19 +137,19 @@ dependencies:
|
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: 0.3.8
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
|
-
name:
|
140
|
+
name: standard
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
|
-
- - "
|
143
|
+
- - "~>"
|
144
144
|
- !ruby/object:Gem::Version
|
145
|
-
version:
|
145
|
+
version: 0.1.7
|
146
146
|
type: :development
|
147
147
|
prerelease: false
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
|
-
- - "
|
150
|
+
- - "~>"
|
151
151
|
- !ruby/object:Gem::Version
|
152
|
-
version:
|
152
|
+
version: 0.1.7
|
153
153
|
description: AnyCable is a polyglot replacement for ActionCable-compatible servers
|
154
154
|
email:
|
155
155
|
- dementiev.vm@gmail.com
|
@@ -175,9 +175,15 @@ files:
|
|
175
175
|
- lib/anycable/health_server.rb
|
176
176
|
- lib/anycable/middleware.rb
|
177
177
|
- lib/anycable/middleware_chain.rb
|
178
|
+
- lib/anycable/middlewares/check_version.rb
|
179
|
+
- lib/anycable/rpc.rb
|
178
180
|
- lib/anycable/rpc/rpc_pb.rb
|
179
181
|
- lib/anycable/rpc/rpc_services_pb.rb
|
180
182
|
- lib/anycable/rpc_handler.rb
|
183
|
+
- lib/anycable/rspec.rb
|
184
|
+
- lib/anycable/rspec/rpc_command_context.rb
|
185
|
+
- lib/anycable/rspec/rpc_stub_context.rb
|
186
|
+
- lib/anycable/rspec/with_grpc_server.rb
|
181
187
|
- lib/anycable/server.rb
|
182
188
|
- lib/anycable/socket.rb
|
183
189
|
- lib/anycable/version.rb
|
@@ -198,12 +204,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
198
204
|
requirements:
|
199
205
|
- - ">="
|
200
206
|
- !ruby/object:Gem::Version
|
201
|
-
version: 2.
|
207
|
+
version: 2.5.0
|
202
208
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
203
209
|
requirements:
|
204
|
-
- - "
|
210
|
+
- - ">"
|
205
211
|
- !ruby/object:Gem::Version
|
206
|
-
version:
|
212
|
+
version: 1.3.1
|
207
213
|
requirements: []
|
208
214
|
rubygems_version: 3.0.6
|
209
215
|
signing_key:
|