anycable-core 1.2.5 → 1.3.1
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 +29 -2
- data/MIT-LICENSE +1 -1
- data/README.md +5 -2
- data/lib/anycable/broadcast_adapters/http.rb +1 -1
- data/lib/anycable/broadcast_adapters/nats.rb +14 -0
- data/lib/anycable/broadcast_adapters/redis.rb +2 -2
- data/lib/anycable/broadcast_adapters.rb +1 -1
- data/lib/anycable/cli.rb +1 -1
- data/lib/anycable/config.rb +71 -16
- data/lib/anycable/grpc/config.rb +14 -7
- data/lib/anycable/grpc/server.rb +1 -1
- data/lib/anycable/grpc_kit/health_checker.rb +72 -0
- data/lib/anycable/grpc_kit/health_pb.rb +30 -0
- data/lib/anycable/grpc_kit/health_services_pb.rb +41 -0
- data/lib/anycable/grpc_kit/server.rb +151 -0
- data/lib/anycable/grpc_kit.rb +22 -0
- data/lib/anycable/version.rb +1 -1
- data/lib/anycable.rb +19 -7
- data/sig/anycable/broadcast_adapters/nats.rbs +4 -0
- data/sig/anycable/config.rbs +19 -1
- data/sig/anycable/grpc/config.rbs +17 -9
- data/sig/anycable/grpc/server.rbs +1 -1
- data/sig/anycable/health_server.rbs +1 -1
- data/sig/anycable/middleware.rbs +1 -1
- data/sig/anycable/rpc.rbs +6 -0
- metadata +41 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5eaf888f4c9312a81bbadf980e5279efa1a0403760569b0465a537e6bd2ed291
|
4
|
+
data.tar.gz: 85206aa6fb8aac5faa0641c889deb9f4c61d40a27211feffb20ab23590313469
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8eb1685bd731c6904126e0ea75a5f4489f268e97f080df60a6eec9ce3ae79cf7c3477ff5cf4add6541a7b09c21455c1d97ababcc1ad818f119e79ac6faf97e56
|
7
|
+
data.tar.gz: 9d5c9882b1eb73901e253f64915c3647c38453357560d9ec836a29163aa3b6f24a10b485b1007751e3600f81a4e4a9cb8eb0a37204af35d87cc216934b7530c2
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,34 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 1.3.1 (2023-05-12)
|
6
|
+
|
7
|
+
- Fix gRPC health check response for an empty service. ([@palkan][])
|
8
|
+
|
9
|
+
As per [docs](https://github.com/grpc/grpc/blob/master/doc/health-checking.md), an empty service is used as the key for server's overall health status. So, if we serve any services, we should return `SERVING` status.
|
10
|
+
|
11
|
+
- Add gRPC health check when using grpc_kit. ([@palkan][])
|
12
|
+
|
13
|
+
## 1.3.0 (2023-02-28)
|
14
|
+
|
15
|
+
- Add configuration presets. ([@palkan][])
|
16
|
+
|
17
|
+
Provide sensible defaults matching current platform (e.g., Fly.io).
|
18
|
+
|
19
|
+
- Require Ruby 2.7+ and Anyway Config 2.2+.
|
20
|
+
|
21
|
+
- Add `rpc_max_connection_age` option (if favour of `rpc_server_args.max_connection_age_ms`) and configured its **default value to be 300 (5 minutes)**. ([@palkan][])
|
22
|
+
|
23
|
+
- (_Experimental_) Add support for [grpc_kit](https://github.com/cookpad/grpc_kit) as an alternative gRPC implementation. ([@palkan][], [@cylon-v][])
|
24
|
+
|
25
|
+
Add `grpc_kit` to your Gemfile and specify `ANYCABLE_GRPC_IMPL=grpc_kit` env var to use it.
|
26
|
+
|
27
|
+
- Setting the redis driver to ruby specified. ([@smasry][])
|
28
|
+
|
29
|
+
- Add mutual TLS support for connections to Redis. ([@Envek][])
|
30
|
+
|
31
|
+
`ANYCABLE_REDIS_TLS_CLIENT_CERT_PATH` and `ANYCABLE_REDIS_TLS_CLIENT_KEY_PATH` settings to specify client certificate and key when connecting to Redis server that requires clients to authenticate themselves.
|
32
|
+
|
5
33
|
## 1.2.5 (2022-12-01)
|
6
34
|
|
7
35
|
- Add `ANYCABLE_REDIS_TLS_VERIFY` setting to disable validation of Redis server TLS certificate. ([@Envek][])
|
@@ -132,7 +160,6 @@ See [#71](https://github.com/anycable/anycable/pull/71).
|
|
132
160
|
See [Changelog](https://github.com/anycable/anycable/blob/0-6-stable/CHANGELOG.md) for versions <1.0.0.
|
133
161
|
|
134
162
|
[@palkan]: https://github.com/palkan
|
135
|
-
[@sponomarev]: https://github.com/sponomarev
|
136
|
-
[@bibendi]: https://github.com/bibendi
|
137
163
|
[@smasry]: https://github.com/smasry
|
138
164
|
[@Envek]: https://github.com/Envek
|
165
|
+
[@cylon-v]: https://github.com/cylon-v
|
data/MIT-LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
[](https://rubygems.org/gems/anycable)
|
2
2
|
[](https://github.com/anycable/anycable/actions)
|
3
|
+
[](https://coveralls.io/github/anycable/anycable?branch=master)
|
3
4
|
[](https://docs.anycable.io/v1)
|
4
5
|
|
5
6
|
# AnyCable
|
@@ -18,8 +19,8 @@ AnyCable uses the same protocol as ActionCable, so you can use its [JavaScript c
|
|
18
19
|
|
19
20
|
## Requirements
|
20
21
|
|
21
|
-
- Ruby >= 2.
|
22
|
-
- Redis (for broadcasting **in production**, [discuss other options](https://github.com/anycable/anycable/issues/2) with us!)
|
22
|
+
- Ruby >= 2.7
|
23
|
+
- Redis or NATS (for broadcasting **in production**, [discuss other options](https://github.com/anycable/anycable/issues/2) with us!)
|
23
24
|
|
24
25
|
## Usage
|
25
26
|
|
@@ -27,6 +28,8 @@ Check out our 📑 [Documentation](https://docs.anycable.io/v1).
|
|
27
28
|
|
28
29
|
## Links
|
29
30
|
|
31
|
+
- [AnyCable off Rails: connecting Twilio streams with Hanami](https://evilmartians.com/chronicles/anycable-goes-off-rails-connecting-twilio-streams-with-hanami)
|
32
|
+
|
30
33
|
- [AnyCable 1.0: Four years of real-time web with Ruby and Go](https://evilmartians.com/chronicles/anycable-1-0-four-years-of-real-time-web-with-ruby-and-go)
|
31
34
|
|
32
35
|
- [AnyCable: Action Cable on steroids!](https://evilmartians.com/chronicles/anycable-actioncable-on-steroids)
|
@@ -118,7 +118,7 @@ module AnyCable
|
|
118
118
|
yield http
|
119
119
|
rescue Timeout::Error, *RECOVERABLE_EXCEPTIONS => e
|
120
120
|
retry_count += 1
|
121
|
-
if
|
121
|
+
if retry_count > MAX_ATTEMPTS
|
122
122
|
logger.error("Broadcast request failed: #{e.message}")
|
123
123
|
return
|
124
124
|
end
|
@@ -31,6 +31,7 @@ module AnyCable
|
|
31
31
|
)
|
32
32
|
options = AnyCable.config.to_nats_params.merge(options)
|
33
33
|
@nats_conn = ::NATS.connect(nil, options)
|
34
|
+
setup_listeners(nats_conn)
|
34
35
|
@channel = channel
|
35
36
|
end
|
36
37
|
|
@@ -41,6 +42,19 @@ module AnyCable
|
|
41
42
|
def announce!
|
42
43
|
logger.info "Broadcasting NATS channel: #{channel}"
|
43
44
|
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def setup_listeners(nats_client)
|
49
|
+
nats_client.on_disconnect { logger.info "NATS client disconnected" }
|
50
|
+
nats_client.on_reconnect do
|
51
|
+
info = nats_client.server_info
|
52
|
+
logger.info "NATS client reconnected: host=#{info[:host]}:#{info[:port]} cluster=#{info[:cluster]}"
|
53
|
+
end
|
54
|
+
nats_client.on_error do |err|
|
55
|
+
logger.warn "NATS client error: #{err.message}"
|
56
|
+
end
|
57
|
+
end
|
44
58
|
end
|
45
59
|
end
|
46
60
|
end
|
@@ -21,7 +21,7 @@ module AnyCable
|
|
21
21
|
#
|
22
22
|
# You can override these params:
|
23
23
|
#
|
24
|
-
# AnyCable.broadcast_adapter = :redis, url: "redis://my_redis", channel: "_any_cable_"
|
24
|
+
# AnyCable.broadcast_adapter = :redis, { url: "redis://my_redis", channel: "_any_cable_" }
|
25
25
|
class Redis < Base
|
26
26
|
attr_reader :redis_conn, :channel
|
27
27
|
|
@@ -30,7 +30,7 @@ module AnyCable
|
|
30
30
|
**options
|
31
31
|
)
|
32
32
|
options = AnyCable.config.to_redis_params.merge(options)
|
33
|
-
options[:driver]
|
33
|
+
options[:driver] ||= :ruby
|
34
34
|
@redis_conn = ::Redis.new(**options)
|
35
35
|
@channel = channel
|
36
36
|
end
|
data/lib/anycable/cli.rb
CHANGED
@@ -333,7 +333,7 @@ module AnyCable
|
|
333
333
|
$ anycable [options]
|
334
334
|
|
335
335
|
CLI
|
336
|
-
-r, --require=path Location of application file to require, default:
|
336
|
+
-r, --require=path Location of application file to require, default candidates: #{APP_CANDIDATES.join(", ")}
|
337
337
|
--server-command=command Command to run WebSocket server
|
338
338
|
-v, --version Print version and exit
|
339
339
|
-h, --help Show this help
|
data/lib/anycable/config.rb
CHANGED
@@ -21,6 +21,8 @@ module AnyCable
|
|
21
21
|
config_name :anycable
|
22
22
|
|
23
23
|
attr_config(
|
24
|
+
presets: "",
|
25
|
+
|
24
26
|
## PubSub
|
25
27
|
broadcast_adapter: :redis,
|
26
28
|
|
@@ -29,6 +31,8 @@ module AnyCable
|
|
29
31
|
redis_sentinels: nil,
|
30
32
|
redis_channel: "__anycable__",
|
31
33
|
redis_tls_verify: false,
|
34
|
+
redis_tls_client_cert_path: nil,
|
35
|
+
redis_tls_client_key_path: nil,
|
32
36
|
|
33
37
|
### NATS options
|
34
38
|
nats_servers: "nats://localhost:4222",
|
@@ -54,23 +58,21 @@ module AnyCable
|
|
54
58
|
sid_header_enabled: true
|
55
59
|
)
|
56
60
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
61
|
+
coerce_types(
|
62
|
+
presets: {type: nil, array: true},
|
63
|
+
redis_sentinels: {type: nil, array: true},
|
64
|
+
nats_servers: {type: nil, array: true},
|
65
|
+
redis_tls_verify: :boolean,
|
66
|
+
nats_dont_randomize_servers: :boolean,
|
67
|
+
debug: :boolean,
|
68
|
+
version_check_enabled: :boolean
|
69
|
+
)
|
67
70
|
|
68
71
|
flag_options :debug, :nats_dont_randomize_servers
|
69
72
|
ignore_options :nats_options
|
70
73
|
|
71
|
-
|
72
|
-
|
73
|
-
self.debug = debug != false
|
74
|
+
def load(*)
|
75
|
+
super.tap { load_presets }
|
74
76
|
end
|
75
77
|
|
76
78
|
def log_level
|
@@ -97,6 +99,8 @@ module AnyCable
|
|
97
99
|
--redis-channel=name Redis channel for broadcasting, default: "__anycable__"
|
98
100
|
--redis-sentinels=<...hosts> Redis Sentinel followers addresses (as a comma-separated list), default: nil
|
99
101
|
--redis-tls-verify=yes|no Whether to perform server certificate check in case of rediss:// protocol. Default: yes
|
102
|
+
--redis-tls-client_cert-path=path Default: nil
|
103
|
+
--redis-tls-client_key-path=path Default: nil
|
100
104
|
|
101
105
|
NATS PUB/SUB
|
102
106
|
--nats-servers=<...addresses> NATS servers for pub/sub, default: "nats://localhost:4222"
|
@@ -122,9 +126,23 @@ module AnyCable
|
|
122
126
|
|
123
127
|
params[:sentinels] = sentinels.map { |sentinel| parse_sentinel(sentinel) }
|
124
128
|
end.tap do |params|
|
125
|
-
next unless redis_url.match?(/rediss:\/\//)
|
126
|
-
|
127
|
-
|
129
|
+
next unless redis_url.match?(/rediss:\/\//)
|
130
|
+
|
131
|
+
if !!redis_tls_client_cert_path ^ !!redis_tls_client_key_path
|
132
|
+
raise_validation_error "Both Redis TLS client certificate and private key must be specified (or none of them)"
|
133
|
+
end
|
134
|
+
|
135
|
+
if !redis_tls_verify?
|
136
|
+
params[:ssl_params] = {verify_mode: OpenSSL::SSL::VERIFY_NONE}
|
137
|
+
else
|
138
|
+
cert_path, key_path = redis_tls_client_cert_path, redis_tls_client_key_path
|
139
|
+
if cert_path && key_path
|
140
|
+
params[:ssl_params] = {
|
141
|
+
cert: OpenSSL::X509::Certificate.new(File.read(cert_path)),
|
142
|
+
key: OpenSSL::PKey.read(File.read(key_path))
|
143
|
+
}
|
144
|
+
end
|
145
|
+
end
|
128
146
|
end
|
129
147
|
end
|
130
148
|
|
@@ -155,5 +173,42 @@ module AnyCable
|
|
155
173
|
opts[:password] = uri.password if uri.password
|
156
174
|
end
|
157
175
|
end
|
176
|
+
|
177
|
+
def load_presets
|
178
|
+
if presets.nil? || presets.empty?
|
179
|
+
self.presets = detect_presets
|
180
|
+
__trace__&.record_value(presets, :presets, type: :env)
|
181
|
+
end
|
182
|
+
|
183
|
+
return if presets.empty?
|
184
|
+
|
185
|
+
presets.each { send(:"load_#{_1}_presets") if respond_to?(:"load_#{_1}_presets", true) }
|
186
|
+
end
|
187
|
+
|
188
|
+
def detect_presets
|
189
|
+
[].tap do
|
190
|
+
_1 << "fly" if ENV.key?("FLY_APP_NAME") && ENV.key?("FLY_ALLOC_ID") && ENV.key?("FLY_REGION")
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def load_fly_presets
|
195
|
+
write_preset(:rpc_host, "0.0.0.0:50051", preset: "fly")
|
196
|
+
|
197
|
+
ws_app_name = ENV["ANYCABLE_FLY_WS_APP_NAME"]
|
198
|
+
return unless ws_app_name
|
199
|
+
|
200
|
+
region = ENV.fetch("FLY_REGION")
|
201
|
+
|
202
|
+
write_preset(:http_broadcast_url, "http://#{region}.#{ws_app_name}.internal:8090/_broadcast", preset: "fly")
|
203
|
+
write_preset(:nats_servers, "nats://#{region}.#{ws_app_name}.internal:4222", preset: "fly")
|
204
|
+
end
|
205
|
+
|
206
|
+
def write_preset(key, value, preset:)
|
207
|
+
# do not override explicitly provided values
|
208
|
+
return unless __trace__&.dig(key.to_s)&.source&.dig(:type) == :defaults
|
209
|
+
|
210
|
+
write_config_attr(key, value)
|
211
|
+
__trace__&.record_value(value, key, type: :preset, preset: preset)
|
212
|
+
end
|
158
213
|
end
|
159
214
|
end
|
data/lib/anycable/grpc/config.rb
CHANGED
@@ -3,13 +3,12 @@
|
|
3
3
|
AnyCable::Config.attr_config(
|
4
4
|
### gRPC options
|
5
5
|
rpc_host: "127.0.0.1:50051",
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
rpc_pool_keep_alive: ::GRPC::Pool::DEFAULT_KEEP_ALIVE,
|
11
|
-
# https://github.com/grpc/grpc/blob/f526602bff029b8db50a8d57134d72da33d8a752/include/grpc/impl/codegen/grpc_types.h#L141-L351
|
6
|
+
rpc_pool_size: 30,
|
7
|
+
rpc_max_waiting_requests: 20,
|
8
|
+
rpc_poll_period: 1,
|
9
|
+
rpc_pool_keep_alive: 0.25,
|
12
10
|
rpc_server_args: {},
|
11
|
+
rpc_max_connection_age: 300,
|
13
12
|
log_grpc: false
|
14
13
|
)
|
15
14
|
|
@@ -33,7 +32,7 @@ module AnyCable
|
|
33
32
|
max_waiting_requests: rpc_max_waiting_requests,
|
34
33
|
poll_period: rpc_poll_period,
|
35
34
|
pool_keep_alive: rpc_pool_keep_alive,
|
36
|
-
server_args: normalized_grpc_server_args
|
35
|
+
server_args: enhance_grpc_server_args(normalized_grpc_server_args)
|
37
36
|
}
|
38
37
|
end
|
39
38
|
|
@@ -46,6 +45,14 @@ module AnyCable
|
|
46
45
|
skey.start_with?("grpc.") ? skey : "grpc.#{skey}"
|
47
46
|
end
|
48
47
|
end
|
48
|
+
|
49
|
+
def enhance_grpc_server_args(opts)
|
50
|
+
return opts if opts.key?("grpc.max_connection_age_ms")
|
51
|
+
return opts unless rpc_max_connection_age.to_i > 0
|
52
|
+
|
53
|
+
opts["grpc.max_connection_age_ms"] = rpc_max_connection_age.to_i * 1000
|
54
|
+
opts
|
55
|
+
end
|
49
56
|
end
|
50
57
|
end
|
51
58
|
end
|
data/lib/anycable/grpc/server.rb
CHANGED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Copyright 2015 gRPC authors.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module Grpc
|
16
|
+
# Health contains classes and modules that support providing a health check
|
17
|
+
# service.
|
18
|
+
module Health
|
19
|
+
# Checker is implementation of the schema-specified health checking service.
|
20
|
+
class Checker < V1::Health::Service
|
21
|
+
StatusCodes = GRPC::Core::StatusCodes
|
22
|
+
HealthCheckResponse = V1::HealthCheckResponse
|
23
|
+
|
24
|
+
# Initializes the statuses of participating services
|
25
|
+
def initialize
|
26
|
+
@statuses = {}
|
27
|
+
@status_mutex = Mutex.new # guards access to @statuses
|
28
|
+
end
|
29
|
+
|
30
|
+
# Implements the rpc IDL API method
|
31
|
+
def check(req, _call)
|
32
|
+
status = nil
|
33
|
+
@status_mutex.synchronize do
|
34
|
+
status = @statuses["#{req.service}"]
|
35
|
+
end
|
36
|
+
if status.nil?
|
37
|
+
fail GRPC::NotFound.new("Service is not found: #{req.service}")
|
38
|
+
end
|
39
|
+
HealthCheckResponse.new(status: status)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Adds the health status for a given service.
|
43
|
+
def add_status(service, status)
|
44
|
+
@status_mutex.synchronize { @statuses["#{service}"] = status }
|
45
|
+
end
|
46
|
+
|
47
|
+
# Adds given health status for all given services
|
48
|
+
def set_status_for_services(status, *services)
|
49
|
+
@status_mutex.synchronize do
|
50
|
+
services.each { |service| @statuses["#{service}"] = status }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Adds health status for each service given within hash
|
55
|
+
def add_statuses(service_statuses = {})
|
56
|
+
@status_mutex.synchronize do
|
57
|
+
service_statuses.each_pair { |service, status| @statuses["#{service}"] = status }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Clears the status for the given service.
|
62
|
+
def clear_status(service)
|
63
|
+
@status_mutex.synchronize { @statuses.delete("#{service}") }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Clears alls the statuses.
|
67
|
+
def clear_all
|
68
|
+
@status_mutex.synchronize { @statuses = {} }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
4
|
+
# source: grpc/health/v1/health.proto
|
5
|
+
|
6
|
+
require 'google/protobuf'
|
7
|
+
|
8
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
9
|
+
add_message "grpc.health.v1.HealthCheckRequest" do
|
10
|
+
optional :service, :string, 1
|
11
|
+
end
|
12
|
+
add_message "grpc.health.v1.HealthCheckResponse" do
|
13
|
+
optional :status, :enum, 1, "grpc.health.v1.HealthCheckResponse.ServingStatus"
|
14
|
+
end
|
15
|
+
add_enum "grpc.health.v1.HealthCheckResponse.ServingStatus" do
|
16
|
+
value :UNKNOWN, 0
|
17
|
+
value :SERVING, 1
|
18
|
+
value :NOT_SERVING, 2
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Grpc
|
23
|
+
module Health
|
24
|
+
module V1
|
25
|
+
HealthCheckRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.health.v1.HealthCheckRequest").msgclass
|
26
|
+
HealthCheckResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.health.v1.HealthCheckResponse").msgclass
|
27
|
+
HealthCheckResponse::ServingStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.health.v1.HealthCheckResponse.ServingStatus").enummodule
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# Source: grpc/health/v1/health.proto for package 'grpc.health.v1'
|
3
|
+
# Original file comments:
|
4
|
+
# Copyright 2015 The gRPC Authors
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
# The canonical version of this proto can be found at
|
19
|
+
# https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto
|
20
|
+
#
|
21
|
+
|
22
|
+
module Grpc
|
23
|
+
module Health
|
24
|
+
module V1
|
25
|
+
module Health
|
26
|
+
class Service
|
27
|
+
|
28
|
+
include GRPC::GenericService
|
29
|
+
|
30
|
+
self.marshal_class_method = :encode
|
31
|
+
self.unmarshal_class_method = :decode
|
32
|
+
self.service_name = 'grpc.health.v1.Health'
|
33
|
+
|
34
|
+
rpc :Check, HealthCheckRequest, HealthCheckResponse
|
35
|
+
end
|
36
|
+
|
37
|
+
Stub = Service.rpc_stub_class
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "anycable/grpc/handler"
|
4
|
+
|
5
|
+
require_relative "./health_pb"
|
6
|
+
require_relative "./health_services_pb"
|
7
|
+
require_relative "./health_checker"
|
8
|
+
|
9
|
+
module AnyCable
|
10
|
+
module GRPC
|
11
|
+
raise LoadError, "AnyCable::GRPC::Server has been already loaded!" if defined?(AnyCable::GRPC::Server)
|
12
|
+
|
13
|
+
using(Module.new do
|
14
|
+
refine ::GrpcKit::Server do
|
15
|
+
attr_reader :max_pool_size
|
16
|
+
|
17
|
+
def stopped?
|
18
|
+
@stopping
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end)
|
22
|
+
|
23
|
+
# Wrapper over gRPC kit server.
|
24
|
+
#
|
25
|
+
# Basic example:
|
26
|
+
#
|
27
|
+
# # create new server listening on the loopback interface with 50051 port
|
28
|
+
# server = AnyCable::GrpcKit::Server.new(host: "127.0.0.1:50051")
|
29
|
+
#
|
30
|
+
# # run gRPC server in bakground
|
31
|
+
# server.start
|
32
|
+
#
|
33
|
+
# # stop server
|
34
|
+
# server.stop
|
35
|
+
class Server
|
36
|
+
attr_reader :grpc_server, :host, :hostname, :port, :sock
|
37
|
+
|
38
|
+
def initialize(host:, logger: nil, **options)
|
39
|
+
@logger = logger
|
40
|
+
@host = host
|
41
|
+
|
42
|
+
host_parts = host.match(/\A(?<hostname>.+):(?<port>\d{2,5})\z/)
|
43
|
+
|
44
|
+
@hostname = host_parts[:hostname]
|
45
|
+
@port = host_parts[:port].to_i
|
46
|
+
|
47
|
+
@grpc_server = build_server(**options)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Start gRPC server in background and
|
51
|
+
# wait untill it ready to accept connections
|
52
|
+
def start
|
53
|
+
return if running?
|
54
|
+
|
55
|
+
raise "Cannot re-start stopped server" if stopped?
|
56
|
+
|
57
|
+
logger.info "RPC server (grpc_kit) is starting..."
|
58
|
+
|
59
|
+
@sock = TCPServer.new(hostname, port)
|
60
|
+
|
61
|
+
server = grpc_server
|
62
|
+
|
63
|
+
@start_thread = Thread.new do
|
64
|
+
loop do
|
65
|
+
conn = @sock.accept
|
66
|
+
server.run(conn)
|
67
|
+
rescue IOError
|
68
|
+
# ignore broken connections
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
wait_till_running
|
73
|
+
|
74
|
+
logger.info "RPC server is listening on #{host} (workers_num: #{grpc_server.max_pool_size})"
|
75
|
+
end
|
76
|
+
|
77
|
+
def wait_till_running
|
78
|
+
raise "Server is not running" unless running?
|
79
|
+
|
80
|
+
timeout = 5
|
81
|
+
|
82
|
+
loop do
|
83
|
+
sock = TCPSocket.new(hostname, port, connect_timeout: 1)
|
84
|
+
stub = ::Grpc::Health::V1::Health::Stub.new(sock)
|
85
|
+
stub.check(::Grpc::Health::V1::HealthCheckRequest.new)
|
86
|
+
sock.close
|
87
|
+
break
|
88
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError
|
89
|
+
timeout -= 1
|
90
|
+
raise "Server is not responding" if timeout.zero?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def wait_till_terminated
|
95
|
+
raise "Server is not running" unless running?
|
96
|
+
|
97
|
+
start_thread.join
|
98
|
+
end
|
99
|
+
|
100
|
+
# Stop gRPC server if it's running
|
101
|
+
def stop
|
102
|
+
return unless running?
|
103
|
+
|
104
|
+
return if stopped?
|
105
|
+
|
106
|
+
grpc_server.graceful_shutdown
|
107
|
+
sock.close
|
108
|
+
|
109
|
+
logger.info "RPC server stopped"
|
110
|
+
end
|
111
|
+
|
112
|
+
def running?
|
113
|
+
!!sock
|
114
|
+
end
|
115
|
+
|
116
|
+
def stopped?
|
117
|
+
grpc_server.stopped?
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
attr_reader :start_thread
|
123
|
+
|
124
|
+
def logger
|
125
|
+
@logger ||= AnyCable.logger
|
126
|
+
end
|
127
|
+
|
128
|
+
def build_server(**options)
|
129
|
+
pool_size = options[:pool_size]
|
130
|
+
|
131
|
+
::GrpcKit::Server.new(min_pool_size: pool_size, max_pool_size: pool_size).tap do |server|
|
132
|
+
server.handle(AnyCable::GRPC::Handler)
|
133
|
+
server.handle(build_health_checker)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def build_health_checker
|
138
|
+
health_checker = ::Grpc::Health::Checker.new
|
139
|
+
health_checker.add_status(
|
140
|
+
"anycable.RPC",
|
141
|
+
::Grpc::Health::V1::HealthCheckResponse::ServingStatus::SERVING
|
142
|
+
)
|
143
|
+
health_checker.add_status(
|
144
|
+
"",
|
145
|
+
::Grpc::Health::V1::HealthCheckResponse::ServingStatus::SERVING
|
146
|
+
)
|
147
|
+
health_checker
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "grpc_kit"
|
4
|
+
|
5
|
+
module AnyCable
|
6
|
+
module GRPC
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require "anycable/grpc/config"
|
11
|
+
require "anycable/grpc_kit/server"
|
12
|
+
|
13
|
+
AnyCable.server_builder = ->(config) {
|
14
|
+
AnyCable.logger.info "gRPC Kit version: #{::GrpcKit::VERSION}"
|
15
|
+
|
16
|
+
::GrpcKit.loglevel = :fatal
|
17
|
+
::GrpcKit.logger = AnyCable.logger if config.log_grpc?
|
18
|
+
|
19
|
+
params = config.to_grpc_params
|
20
|
+
|
21
|
+
AnyCable::GRPC::Server.new(**params, host: config.rpc_host)
|
22
|
+
}
|
data/lib/anycable/version.rb
CHANGED
data/lib/anycable.rb
CHANGED
@@ -109,11 +109,23 @@ module AnyCable
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
112
|
+
# Try loading a gRPC implementation
|
113
|
+
impl = ENV.fetch("ANYCABLE_GRPC_IMPL", "grpc")
|
114
|
+
|
115
|
+
case impl
|
116
|
+
when "grpc"
|
117
|
+
begin
|
118
|
+
require "grpc/version"
|
119
|
+
require "anycable/grpc"
|
120
|
+
rescue LoadError => e
|
121
|
+
# Re-raise an exception if we failed to load grpc .so files
|
122
|
+
# (e.g., on Alpine Linux)
|
123
|
+
raise if /(error loading shared library|incompatible architecture)/i.match?(e.message)
|
124
|
+
end
|
125
|
+
when "grpc_kit"
|
126
|
+
begin
|
127
|
+
require "grpc_kit/version"
|
128
|
+
require "anycable/grpc_kit"
|
129
|
+
rescue LoadError
|
130
|
+
end
|
119
131
|
end
|
data/sig/anycable/config.rbs
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module AnyCable
|
2
2
|
interface _Config
|
3
|
+
def presets: () -> Array[String]
|
4
|
+
def presets=: (Array[String]) -> void
|
3
5
|
def broadcast_adapter: () -> Symbol
|
4
6
|
def broadcast_adapter=: (Symbol) -> void
|
5
7
|
def redis_url: () -> String
|
@@ -9,14 +11,19 @@ module AnyCable
|
|
9
11
|
def redis_channel: () -> String
|
10
12
|
def redis_channel=: (String) -> void
|
11
13
|
def redis_tls_verify: () -> bool
|
12
|
-
def redis_tls_verify?: () -> bool
|
13
14
|
def redis_tls_verify=: (bool) -> void
|
15
|
+
def redis_tls_verify?: () -> bool
|
16
|
+
def redis_tls_client_cert_path: () -> String?
|
17
|
+
def redis_tls_client_cert_path=: (String) -> void
|
18
|
+
def redis_tls_client_key_path: () -> String?
|
19
|
+
def redis_tls_client_key_path=: (String) -> void
|
14
20
|
def nats_servers: () -> Array[String]
|
15
21
|
def nats_servers=: (Array[String]) -> void
|
16
22
|
def nats_channel: () -> String
|
17
23
|
def nats_channel=: (String) -> void
|
18
24
|
def nats_dont_randomize_servers: () -> bool
|
19
25
|
def nats_dont_randomize_servers=: (bool) -> void
|
26
|
+
def nats_dont_randomize_servers?: () -> bool
|
20
27
|
def nats_options: () -> Hash[untyped, untyped]
|
21
28
|
def nats_options=: (Hash[untyped, untyped]) -> void
|
22
29
|
def http_broadcast_url: () -> String
|
@@ -29,12 +36,17 @@ module AnyCable
|
|
29
36
|
def log_level=: (String) -> void
|
30
37
|
def debug: () -> bool
|
31
38
|
def debug=: (bool) -> void
|
39
|
+
def debug?: () -> bool
|
32
40
|
def http_health_port: () -> Integer?
|
33
41
|
def http_health_port=: (Integer) -> void
|
34
42
|
def http_health_path: () -> String
|
35
43
|
def http_health_path=: (String) -> void
|
36
44
|
def version_check_enabled: () -> bool
|
37
45
|
def version_check_enabled=: (bool) -> void
|
46
|
+
def version_check_enabled?: () -> bool
|
47
|
+
def sid_header_enabled: () -> bool
|
48
|
+
def sid_header_enabled=: (bool) -> void
|
49
|
+
def sid_header_enabled?: () -> bool
|
38
50
|
end
|
39
51
|
|
40
52
|
class Config < Anyway::Config
|
@@ -46,6 +58,7 @@ module AnyCable
|
|
46
58
|
alias debug? debug
|
47
59
|
alias version_check_enabled? version_check_enabled
|
48
60
|
|
61
|
+
def load: (*untyped) -> void
|
49
62
|
def http_health_port_provided?: () -> bool
|
50
63
|
def to_redis_params: () -> { url: String, sentinels: Array[untyped]?, ssl_params: Hash[Symbol, untyped]? }
|
51
64
|
def to_http_health_params: () -> { port: Integer?, path: String }
|
@@ -54,5 +67,10 @@ module AnyCable
|
|
54
67
|
private
|
55
68
|
|
56
69
|
def parse_sentinel: ((String | Hash[untyped, untyped]) sentinel) -> Hash[Symbol, untyped]
|
70
|
+
def load_presets: () -> void
|
71
|
+
def detect_presets: () -> Array[String]
|
72
|
+
def write_preset: (Symbol, untyped, preset: String) -> void
|
73
|
+
def write_config_attr: (Symbol, untyped) -> void
|
74
|
+
def __trace__: () -> untyped
|
57
75
|
end
|
58
76
|
end
|
@@ -7,30 +7,38 @@ module AnyCable
|
|
7
7
|
def rpc_pool_size=: (Integer) -> void
|
8
8
|
def rpc_max_waiting_requests: () -> Integer
|
9
9
|
def rpc_max_waiting_requests=: (Integer) -> void
|
10
|
-
def rpc_poll_period: () ->
|
11
|
-
def rpc_poll_period=: (
|
12
|
-
def rpc_pool_keep_alive: () ->
|
13
|
-
def rpc_pool_keep_alive=: (
|
10
|
+
def rpc_poll_period: () -> Numeric
|
11
|
+
def rpc_poll_period=: (Numeric) -> void
|
12
|
+
def rpc_pool_keep_alive: () -> Numeric
|
13
|
+
def rpc_pool_keep_alive=: (Numeric) -> void
|
14
|
+
def rpc_max_connection_age: () -> Integer
|
15
|
+
def rpc_max_connection_age=: (Integer) -> void
|
14
16
|
def rpc_server_args: () -> Hash[Symbol | String, untyped]?
|
15
17
|
def rpc_server_args=: (Hash[Symbol | String, untyped]) -> void
|
16
18
|
def log_grpc: () -> bool
|
17
19
|
def log_grpc=: (bool) -> void
|
20
|
+
def log_grpc?: () -> bool
|
18
21
|
end
|
19
22
|
|
20
|
-
module Config : AnyCable::
|
23
|
+
module Config : AnyCable::_Config
|
21
24
|
include _Config
|
22
25
|
|
23
|
-
alias log_grpc? log_grpc
|
24
|
-
|
25
26
|
def to_grpc_params: () -> {
|
26
27
|
pool_size: Integer,
|
27
28
|
max_waiting_requests: Integer,
|
28
|
-
poll_period:
|
29
|
-
pool_keep_alive:
|
29
|
+
poll_period: Numeric,
|
30
|
+
pool_keep_alive: Numeric,
|
30
31
|
server_args: Hash[String, untyped]
|
31
32
|
}
|
32
33
|
|
33
34
|
def normalized_grpc_server_args: () -> Hash[String, untyped]
|
35
|
+
def enhance_grpc_server_args: (Hash[String, untyped]) -> Hash[String, untyped]
|
34
36
|
end
|
35
37
|
end
|
36
38
|
end
|
39
|
+
|
40
|
+
module AnyCable
|
41
|
+
class Config
|
42
|
+
include GRPC::Config
|
43
|
+
end
|
44
|
+
end
|
@@ -12,7 +12,7 @@ module AnyCable
|
|
12
12
|
attr_reader path: String
|
13
13
|
attr_reader http_server: untyped
|
14
14
|
|
15
|
-
def initialize: (_Runnable server, port: Integer port, ?logger: Logger logger, ?path: String path) -> void
|
15
|
+
def initialize: (_Runnable server, port: Integer port, ?logger: Logger? logger, ?path: String path) -> void
|
16
16
|
def start: () -> void
|
17
17
|
def stop: () -> void
|
18
18
|
def running?: () -> bool
|
data/sig/anycable/middleware.rbs
CHANGED
data/sig/anycable/rpc.rbs
CHANGED
@@ -12,6 +12,12 @@ module AnyCable
|
|
12
12
|
type protoMap = _ProtobufMap
|
13
13
|
type rpcMetadata = Hash[String, String]
|
14
14
|
|
15
|
+
module Status
|
16
|
+
SUCCESS: 0
|
17
|
+
FAILURE: 1
|
18
|
+
ERROR: 2
|
19
|
+
end
|
20
|
+
|
15
21
|
interface _WithEnvState
|
16
22
|
def cstate: () -> protoMap?
|
17
23
|
def cstate=: (protoMap) -> void
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: anycable-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- palkan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-05-12 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
|
-
version: 2.
|
19
|
+
version: '2.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
|
-
version: 2.
|
26
|
+
version: '2.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: google-protobuf
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +122,34 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '3.5'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: simplecov
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: simplecov-lcov
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
125
153
|
- !ruby/object:Gem::Dependency
|
126
154
|
name: webmock
|
127
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -181,6 +209,11 @@ files:
|
|
181
209
|
- lib/anycable/grpc/handler.rb
|
182
210
|
- lib/anycable/grpc/rpc_services_pb.rb
|
183
211
|
- lib/anycable/grpc/server.rb
|
212
|
+
- lib/anycable/grpc_kit.rb
|
213
|
+
- lib/anycable/grpc_kit/health_checker.rb
|
214
|
+
- lib/anycable/grpc_kit/health_pb.rb
|
215
|
+
- lib/anycable/grpc_kit/health_services_pb.rb
|
216
|
+
- lib/anycable/grpc_kit/server.rb
|
184
217
|
- lib/anycable/health_server.rb
|
185
218
|
- lib/anycable/middleware.rb
|
186
219
|
- lib/anycable/middleware_chain.rb
|
@@ -241,14 +274,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
241
274
|
requirements:
|
242
275
|
- - ">="
|
243
276
|
- !ruby/object:Gem::Version
|
244
|
-
version: 2.
|
277
|
+
version: 2.7.0
|
245
278
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
246
279
|
requirements:
|
247
280
|
- - ">="
|
248
281
|
- !ruby/object:Gem::Version
|
249
282
|
version: '0'
|
250
283
|
requirements: []
|
251
|
-
rubygems_version: 3.
|
284
|
+
rubygems_version: 3.4.8
|
252
285
|
signing_key:
|
253
286
|
specification_version: 4
|
254
287
|
summary: AnyCable core RPC implementation
|