anycable 1.0.0.preview2 → 1.0.2
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 +28 -255
- data/MIT-LICENSE +1 -1
- data/README.md +10 -9
- data/lib/anycable.rb +2 -2
- data/lib/anycable/broadcast_adapters.rb +2 -0
- data/lib/anycable/broadcast_adapters/base.rb +29 -0
- data/lib/anycable/broadcast_adapters/http.rb +130 -0
- data/lib/anycable/broadcast_adapters/redis.rb +7 -6
- data/lib/anycable/cli.rb +23 -9
- data/lib/anycable/config.rb +24 -14
- data/lib/anycable/exceptions_handling.rb +1 -1
- data/lib/anycable/health_server.rb +4 -2
- data/lib/anycable/middleware.rb +6 -1
- data/lib/anycable/middlewares/check_version.rb +1 -1
- data/lib/anycable/rpc.rb +8 -0
- data/lib/anycable/rpc/rpc_pb.rb +3 -0
- data/lib/anycable/rpc_handler.rb +7 -4
- data/lib/anycable/rspec/with_grpc_server.rb +1 -0
- data/lib/anycable/server.rb +6 -2
- data/lib/anycable/socket.rb +10 -5
- data/lib/anycable/version.rb +1 -1
- metadata +20 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd11b4e0403da1e162132375055bc2815c48ee57c3554a04ed72683d91fb0fde
|
4
|
+
data.tar.gz: '0568d54349b5e0a452f00119678b37964bb8c0102f512010e015c0b836a2ef6f'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 22ded17aa347fe2c26d0d0aa6ceeb4958244a0d82a9d50f358f0c6c0b8433004ea71f62810bd757e9cf11c896596896c31859e6af62716b218f61f72b6d4d3fe
|
7
|
+
data.tar.gz: 3f52eade080b1de1ecdc39624a87b6840239392ebcf30bada85e44706cbd7c819cecc566a1eb5b7e8fa9cff5c567ab5929c3ec2189c4fb300e94357cbbb4ca42
|
data/CHANGELOG.md
CHANGED
@@ -1,288 +1,61 @@
|
|
1
1
|
# Change log
|
2
2
|
|
3
|
-
##
|
3
|
+
## master
|
4
4
|
|
5
|
-
|
6
|
-
|
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).
|
24
|
-
|
25
|
-
## 0.6.5 (2020-04-01)
|
26
|
-
|
27
|
-
- Relax `anyway_config` dependency. ([@palkan][])
|
28
|
-
|
29
|
-
## 0.6.4 (2020-01-24)
|
30
|
-
|
31
|
-
- Fix Ruby 2.7 warnings. ([@palkan][])
|
32
|
-
|
33
|
-
– Add `REMOTE_ADDR` socket env variable using a synthetic header passed from a websocket
|
34
|
-
server. ([@sponomarev][])
|
35
|
-
|
36
|
-
Recreating a request object in your custom connection factory using `Rack::Request` or
|
37
|
-
`ActionDispatch::Request` (already implemented in [anycable-rails](https://github.com/anycable/anycable-rails))
|
38
|
-
gives you an access to `request.ip` with the properly set IP address.
|
39
|
-
|
40
|
-
- Align socket env to be more compatible with Rack Spec ([@sponomarev][])
|
41
|
-
|
42
|
-
Provide as much env details as possible to be able to reconstruct the full
|
43
|
-
request object in a custom connection factory.
|
44
|
-
|
45
|
-
## 0.6.3 (2019-03-26)
|
46
|
-
|
47
|
-
- Relax `redis` gem version requirement. ([@palkan][])
|
48
|
-
|
49
|
-
Use the same restriction as Action Cable does (`>= 3`).
|
50
|
-
|
51
|
-
## 0.6.2 (2019-03-15)
|
52
|
-
|
53
|
-
- Add GRPC service method name and message content to exception notifications ([@sponomarev][])
|
54
|
-
|
55
|
-
`Anycable.capture_exception` allows accessing GRPC service method name and message content
|
56
|
-
on which an exception was captured. It can be used for exceptions grouping in your tracker and
|
57
|
-
providing additional data to investigate a root of a problem.
|
58
|
-
|
59
|
-
Example:
|
60
|
-
|
61
|
-
```ruby
|
62
|
-
AnyCable.capture_exception do |ex, method, message|
|
63
|
-
Honeybadger.notify(ex, component: "any_cable", action: method, params: message)
|
64
|
-
end
|
65
|
-
```
|
66
|
-
|
67
|
-
Usage of a handler proc with just a single argument is preserved for the sake of compatibility.
|
68
|
-
|
69
|
-
- Add deprecation warning to default host usage ([@sponomarev][])
|
70
|
-
|
71
|
-
Exposing AnyCable publicly is considered to be harmful and planned to be changed
|
72
|
-
in future versions.
|
73
|
-
|
74
|
-
- Allow running the server as a detachable daemon ([@sponomarev][])
|
75
|
-
|
76
|
-
Server is fully managed by the binary itself.
|
77
|
-
|
78
|
-
```sh
|
79
|
-
# Start anycable daemon
|
80
|
-
$ bundle exec anycabled start
|
81
|
-
|
82
|
-
# Pass cli options to anycable through daemon. Separate daemon options and anycable options with `--`
|
83
|
-
$ bundle exec anycabled start -- --rpc-host 127.0.0.1:31337
|
84
|
-
|
85
|
-
# Stop anycable daemon
|
86
|
-
$ bundle exec anycabled stop
|
87
|
-
|
88
|
-
# See more anycable daemon options
|
89
|
-
$ bundle exec anycabled
|
90
|
-
```
|
91
|
-
|
92
|
-
## 0.6.1 (2019-01-05)
|
93
|
-
|
94
|
-
- [Fix #63](https://github.com/anycable/anycable-rails/issues/63) Load `anyway_config` after application boot to make sure that all frameworks dependent functionality is loaded. ([@palkan][])
|
95
|
-
|
96
|
-
## 0.6.0 (2018-11-15)
|
97
|
-
|
98
|
-
### Features
|
99
|
-
|
100
|
-
#### Broadcast adapters
|
101
|
-
|
102
|
-
AnyCable allows you to use custom broadcasting adapters (Redis is used by default):
|
103
|
-
|
104
|
-
```ruby
|
105
|
-
# Specify by name (tries to load `AnyCable::BroadcastAdapters::MyAdapter` from
|
106
|
-
# "anycable/broadcast_adapters/my_adapter")
|
107
|
-
AnyCable.broadcast_adapter = :my_adapter, {option: "value"}
|
108
|
-
# or provide an instance (should respond_to #broadcast)
|
109
|
-
AnyCable.broadcast_adapter = MyAdapter.new
|
110
|
-
```
|
111
|
-
|
112
|
-
**Breaking:** to use Redis adapter you must ensure that it is present in your Gemfile; AnyCable gem doesn't have `redis` as a dependency anymore.
|
113
|
-
|
114
|
-
#### CLI
|
115
|
-
|
116
|
-
AnyCable now ships with a CLI–`anycable`.
|
5
|
+
## 1.0.2
|
117
6
|
|
118
|
-
|
7
|
+
- Handle TLS Redis connections by using VERIFY_NONE mode. ([@palkan][])
|
119
8
|
|
120
|
-
|
121
|
-
# run anycable and load app from app.rb
|
122
|
-
bundle exec anycable -r app.rb
|
123
|
-
# or
|
124
|
-
bundle exec anycable --require app.rb
|
125
|
-
```
|
9
|
+
## 1.0.1 (2020-07-07)
|
126
10
|
|
127
|
-
|
11
|
+
- Support providing passwords for Redis Sentinels. ([@palkan][])
|
128
12
|
|
129
|
-
|
13
|
+
Use the following format: `ANYCABLE_REDIS_SENTINELS=:password1@my.redis.sentinel.first:26380,:password2@my.redis.sentinel.second:26380`.
|
130
14
|
|
131
|
-
|
15
|
+
## 1.0.0 (2020-07-01)
|
132
16
|
|
133
|
-
|
17
|
+
- Add `embedded` option to CLI runner. ([@palkan][])
|
134
18
|
|
135
|
-
|
136
|
-
bundle exec anycable --server-command "anycable-go -p 3334"
|
137
|
-
```
|
19
|
+
- Add `Env#istate` and `EnvResponse#istate` to store channel state. ([@palkan][])
|
138
20
|
|
139
|
-
|
21
|
+
That would allow to mimic instance variables usage in Action Cable channels.
|
140
22
|
|
141
|
-
-
|
142
|
-
- Expose gRPC server parameters via `rpc_*` config params:
|
23
|
+
- Add `CommandResponse#stopped_streams` to support unsubscribing from particular broadcastings. ([@palkan])
|
143
24
|
|
144
|
-
|
145
|
-
AnyCable.configure do |config|
|
146
|
-
config.rpc_pool_size = 120
|
147
|
-
config.rpc_max_waiting_requests = 10
|
148
|
-
# etc
|
149
|
-
end
|
150
|
-
```
|
25
|
+
`Socket#unsubscribe` is now implemented as well.
|
151
26
|
|
152
|
-
- `
|
153
|
-
- Make HTTP health check url configurable
|
154
|
-
- Add ability to pass Redis Sentinel config as array of string.
|
27
|
+
- Add `AnyCable.broadcast_adapter#broadcast_command` method. ([@palkan][])
|
155
28
|
|
156
|
-
|
29
|
+
It could be used to send commands to WS server (e.g., remote disconnect).
|
157
30
|
|
158
|
-
|
159
|
-
ANYCABLE_REDIS_SENTINELS=127.0.0.1:26380,127.0.0.1:26381 bundle exec anycable
|
160
|
-
```
|
31
|
+
- Add `:http` broadcasting adapter. ([@palkan][])
|
161
32
|
|
162
|
-
|
163
|
-
|
164
|
-
- Added middlewares support
|
165
|
-
|
166
|
-
See [docs](https://docs.anycable.io/#/./middlewares).
|
167
|
-
|
168
|
-
- Added gRPC health checker.
|
169
|
-
|
170
|
-
See [docs](https://docs.anycable.io/#/./health_checking).
|
171
|
-
|
172
|
-
- Added hook to run code only within RPC server context.
|
173
|
-
|
174
|
-
Use `AnyCable.configure_server { ... }` to run code only when RPC server is running.
|
175
|
-
|
176
|
-
### API changes
|
177
|
-
|
178
|
-
**NOTE**: the old API is still working but deprecated (you'll see a notice).
|
179
|
-
|
180
|
-
- Use `AnyCable` instead of `Anycable`
|
181
|
-
|
182
|
-
- New API for registering error handlers:
|
183
|
-
|
184
|
-
```ruby
|
185
|
-
AnyCable.capture_exception do |ex|
|
186
|
-
Honeybadger.notify(ex)
|
187
|
-
end
|
188
|
-
```
|
189
|
-
|
190
|
-
- `AnyCable::Server.start` is deprecated
|
191
|
-
|
192
|
-
## 0.5.2 (2018-09-06)
|
193
|
-
|
194
|
-
- [#48](https://github.com/anycable/anycable/pull/48) Add HTTP health server ([@DarthSim][])
|
195
|
-
|
196
|
-
## 0.5.1 (2018-06-13)
|
197
|
-
|
198
|
-
Minor fixes.
|
199
|
-
|
200
|
-
## 0.5.0 (2017-10-21)
|
201
|
-
|
202
|
-
- [#2](https://github.com/anycable/anycable/issues/2) Add support for [Redis Sentinel](https://redis.io/topics/sentinel). ([@accessd][])
|
203
|
-
|
204
|
-
- [#28](https://github.com/anycable/anycable/issues/28) Support arbitrary headers. ([@palkan][])
|
205
|
-
|
206
|
-
Previously we hardcoded only "Cookie" header. Now we add all passed headers by WebSocket server to request env.
|
207
|
-
|
208
|
-
- [#27](https://github.com/anycable/anycable/issues/27) Add `error_msg` to RPC responses. ([@palkan][])
|
209
|
-
|
210
|
-
Now RPC responses has 3 statuses:
|
211
|
-
|
212
|
-
1) `SUCCESS` – successful request, operation succeed
|
213
|
-
2) `FAILURE` – successful request, operation failed (e.g. authentication failed)
|
214
|
-
3) `ERROR` – request failed (exception raised).
|
215
|
-
|
216
|
-
We provide `error_msg` only when request status is `ERROR`.
|
217
|
-
|
218
|
-
- [#25](https://github.com/anycable/anycable/issues/25) Improve logging and exceptions handling. ([@palkan][])
|
219
|
-
|
220
|
-
Default logger logs to STDOUT with `info` level by default but can be configured to log to file with
|
221
|
-
any severity.
|
222
|
-
|
223
|
-
GRPC logging is turned off by default (can be turned on through `log_grpc` configuration parameter).
|
224
|
-
|
225
|
-
`ANYCABLE_DEBUG=1` acts as a shortcut to set `debug` level and turn on GRPC logging.
|
226
|
-
|
227
|
-
Now it's possible to add custom exception handlers (e.g. to notify external exception tracking services).
|
228
|
-
|
229
|
-
More on [Wiki](https://github.com/anycable/anycable/wiki/Logging-&-Exceptions-Handling).
|
230
|
-
|
231
|
-
## 0.4.6 (2017-05-20)
|
232
|
-
|
233
|
-
- Add `Anycable::Server#stop` method. ([@sadovnik][])
|
234
|
-
|
235
|
-
## 0.4.5 (2017-03-17)
|
236
|
-
|
237
|
-
- Fixed #11. ([@palkan][])
|
238
|
-
|
239
|
-
## 0.4.4 (2017-03-06)
|
240
|
-
|
241
|
-
- Handle `StandardError` gracefully in RPC calls. ([@palkan][])
|
242
|
-
|
243
|
-
## 0.4.3 (2017-02-18)
|
244
|
-
|
245
|
-
- Update `grpc` version dependency to support Ruby 2.4. ([@palkan][])
|
246
|
-
|
247
|
-
## 0.4.2 (2017-01-28)
|
248
|
-
|
249
|
-
- Change socket streaming API. ([@palkan][])
|
250
|
-
|
251
|
-
Add `Socket#subscribe`, `unsubscribe` and `unsubscribe_from_all` methods.
|
252
|
-
|
253
|
-
## 0.4.1 (2017-01-24)
|
254
|
-
|
255
|
-
- Introduce _fake_ socket instance to handle transmissions and streams. ([@palkan][])
|
33
|
+
- **RPC schema has changed**. ([@palkan][])
|
256
34
|
|
257
|
-
-
|
35
|
+
Using `anycable-go` v1.x is required.
|
258
36
|
|
259
|
-
|
37
|
+
- **Ruby 2.5+ is required**. ([@palkan][])
|
260
38
|
|
261
|
-
|
262
|
-
connection.handle_channel_command(identifier, command, data)
|
263
|
-
```
|
39
|
+
- Added RPC proto version check. ([@palkan][])
|
264
40
|
|
265
|
-
|
41
|
+
Server must sent `protov` metadata with the supported versions (comma-separated list). If there is no matching version an exception is raised.
|
266
42
|
|
267
|
-
|
43
|
+
Current RPC proto version is **v1**.
|
268
44
|
|
269
|
-
-
|
45
|
+
- Added `request` support to channels. ([@palkan][])
|
270
46
|
|
271
|
-
|
47
|
+
Now you can access `request` object in channels, too (e.g., to read headers/cookies/URL/etc).
|
272
48
|
|
273
|
-
-
|
49
|
+
- Change default server address from `[::]:50051` to `127.0.0.1:50051`. ([@palkan][])
|
274
50
|
|
275
|
-
|
51
|
+
See [#71](https://github.com/anycable/anycable/pull/71).
|
276
52
|
|
277
|
-
|
53
|
+
- Fix building Redis Sentinel config. ([@palkan][])
|
278
54
|
|
279
|
-
|
55
|
+
---
|
280
56
|
|
281
|
-
|
57
|
+
See [Changelog](https://github.com/anycable/anycable/blob/0-6-stable/CHANGELOG.md) for versions <1.0.0.
|
282
58
|
|
283
59
|
[@palkan]: https://github.com/palkan
|
284
|
-
[@sadovnik]: https://github.com/sadovnik
|
285
|
-
[@accessd]: https://github.com/accessd
|
286
|
-
[@DarthSim]: https://github.com/DarthSim
|
287
60
|
[@sponomarev]: https://github.com/sponomarev
|
288
61
|
[@bibendi]: https://github.com/bibendi
|
data/MIT-LICENSE
CHANGED
data/README.md
CHANGED
@@ -2,31 +2,35 @@
|
|
2
2
|
[](https://rubygems.org/gems/anycable)
|
3
3
|
[](https://github.com/anycable/anycable/actions)
|
4
4
|
[](https://gitter.im/anycable/Lobby)
|
5
|
-
[](https://docs.anycable.io)
|
5
|
+
[](https://docs.anycable.io/v1)
|
6
6
|
|
7
7
|
# AnyCable
|
8
8
|
|
9
9
|
<img align="right" height="150" width="129"
|
10
10
|
title="AnyCable logo" src="https://docs.anycable.io/assets/images/logo.svg">
|
11
11
|
|
12
|
-
AnyCable allows you to use any WebSocket server (written in any language) as a replacement for your Ruby server (such as Faye,
|
12
|
+
AnyCable allows you to use any WebSocket server (written in any language) as a replacement for your Ruby server (such as Faye, Action Cable, etc).
|
13
13
|
|
14
14
|
AnyCable uses the same protocol as ActionCable, so you can use its [JavaScript client](https://www.npmjs.com/package/actioncable) without any monkey-patching.
|
15
15
|
|
16
|
+
**Important** This is a readme for the upcoming v1.0 release. For v0.6.x see the readme from the [0-6-stable](https://github.com/anycable/anycable/tree/0-6-stable) branch.
|
17
|
+
|
16
18
|
<a href="https://evilmartians.com/">
|
17
19
|
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
18
20
|
|
19
21
|
## Requirements
|
20
22
|
|
21
23
|
- Ruby >= 2.5
|
22
|
-
- Redis (for broadcasting
|
24
|
+
- Redis (for broadcasting **in production**, [discuss other options](https://github.com/anycable/anycable/issues/2) with us!)
|
23
25
|
|
24
26
|
## Usage
|
25
27
|
|
26
|
-
Check out our 📑 [Documentation](https://docs.anycable.io).
|
28
|
+
Check out our 📑 [Documentation](https://docs.anycable.io/v1).
|
27
29
|
|
28
30
|
## Links
|
29
31
|
|
32
|
+
- [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)
|
33
|
+
|
30
34
|
- [AnyCable: Action Cable on steroids!](https://evilmartians.com/chronicles/anycable-actioncable-on-steroids)
|
31
35
|
|
32
36
|
- [Connecting LiteCable to Hanami](http://gabrielmalakias.com.br/ruby/hanami/iot/2017/05/26/websockets-connecting-litecable-to-hanami.html) by [@GabrielMalakias](https://github.com/GabrielMalakias)
|
@@ -45,12 +49,9 @@ Check out our 📑 [Documentation](https://docs.anycable.io).
|
|
45
49
|
|
46
50
|
- RailsClub Moscow 2016 [slides](https://speakerdeck.com/palkan/railsclub-moscow-2016-anycable) and [video](https://www.youtube.com/watch?v=-k7GQKuBevY&list=PLiWUIs1hSNeOXZhotgDX7Y7qBsr24cu7o&index=4) (RU)
|
47
51
|
|
48
|
-
##
|
49
|
-
|
50
|
-
- [AnyCable Go](https://github.com/anycable/anycable-go)
|
51
|
-
- [ErlyCable](https://github.com/anycable/erlycable)
|
52
|
+
## Building
|
52
53
|
|
53
|
-
|
54
|
+
### Generating gRPC files from `.proto`
|
54
55
|
|
55
56
|
- Install required GRPC gems:
|
56
57
|
|
data/lib/anycable.rb
CHANGED
@@ -31,7 +31,7 @@ module AnyCable
|
|
31
31
|
def logger
|
32
32
|
return @logger if instance_variable_defined?(:@logger)
|
33
33
|
|
34
|
-
log_output = AnyCable.config.log_file ||
|
34
|
+
log_output = AnyCable.config.log_file || $stdout
|
35
35
|
@logger = Logger.new(log_output).tap do |logger|
|
36
36
|
logger.level = AnyCable.config.log_level
|
37
37
|
end
|
@@ -67,7 +67,7 @@ module AnyCable
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def broadcast_adapter
|
70
|
-
self.broadcast_adapter =
|
70
|
+
self.broadcast_adapter = AnyCable.config.broadcast_adapter.to_sym unless instance_variable_defined?(:@broadcast_adapter)
|
71
71
|
@broadcast_adapter
|
72
72
|
end
|
73
73
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnyCable
|
4
|
+
module BroadcastAdapters
|
5
|
+
class Base
|
6
|
+
def raw_broadcast(_data)
|
7
|
+
raise NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
def broadcast(stream, payload)
|
11
|
+
raw_broadcast({stream: stream, data: payload}.to_json)
|
12
|
+
end
|
13
|
+
|
14
|
+
def broadcast_command(command, **payload)
|
15
|
+
raw_broadcast({command: command, payload: payload}.to_json)
|
16
|
+
end
|
17
|
+
|
18
|
+
def announce!
|
19
|
+
logger.info "Broadcasting via #{self.class.name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def logger
|
25
|
+
AnyCable.logger
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "uri"
|
5
|
+
require "net/http"
|
6
|
+
|
7
|
+
module AnyCable
|
8
|
+
module BroadcastAdapters
|
9
|
+
# HTTP adapter for broadcasting.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# AnyCable.broadast_adapter = :http
|
14
|
+
#
|
15
|
+
# It uses configuration from global AnyCable config
|
16
|
+
# by default.
|
17
|
+
#
|
18
|
+
# You can override these params:
|
19
|
+
#
|
20
|
+
# AnyCable.broadcast_adapter = :http, url: "http://ws.example.com/_any_cable_"
|
21
|
+
class Http < Base
|
22
|
+
# Taken from: https://github.com/influxdata/influxdb-ruby/blob/886058079c66d4fd019ad74ca11342fddb0b753d/lib/influxdb/errors.rb#L18
|
23
|
+
RECOVERABLE_EXCEPTIONS = [
|
24
|
+
Errno::ECONNABORTED,
|
25
|
+
Errno::ECONNREFUSED,
|
26
|
+
Errno::ECONNRESET,
|
27
|
+
Errno::EHOSTUNREACH,
|
28
|
+
Errno::EINVAL,
|
29
|
+
Errno::ENETUNREACH,
|
30
|
+
Net::HTTPBadResponse,
|
31
|
+
Net::HTTPHeaderSyntaxError,
|
32
|
+
Net::ProtocolError,
|
33
|
+
SocketError,
|
34
|
+
(OpenSSL::SSL::SSLError if defined?(OpenSSL))
|
35
|
+
].compact.freeze
|
36
|
+
|
37
|
+
OPEN_TIMEOUT = 5
|
38
|
+
READ_TIMEOUT = 10
|
39
|
+
|
40
|
+
MAX_ATTEMPTS = 3
|
41
|
+
DELAY = 2
|
42
|
+
|
43
|
+
attr_reader :url, :headers, :authorized
|
44
|
+
alias_method :authorized?, :authorized
|
45
|
+
|
46
|
+
def initialize(url: AnyCable.config.http_broadcast_url, secret: AnyCable.config.http_broadcast_secret)
|
47
|
+
@url = url
|
48
|
+
@headers = {}
|
49
|
+
if secret
|
50
|
+
headers["Authorization"] = "Bearer #{secret}"
|
51
|
+
@authorized = true
|
52
|
+
end
|
53
|
+
|
54
|
+
@uri = URI.parse(url)
|
55
|
+
@queue = Queue.new
|
56
|
+
end
|
57
|
+
|
58
|
+
def raw_broadcast(payload)
|
59
|
+
ensure_thread_is_alive
|
60
|
+
queue << payload
|
61
|
+
end
|
62
|
+
|
63
|
+
# Wait for background thread to process all the messages
|
64
|
+
# and stop it
|
65
|
+
def shutdown
|
66
|
+
queue << :stop
|
67
|
+
thread.join if thread&.alive?
|
68
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
69
|
+
logger.error "Broadcasting thread exited with exception: #{e.message}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def announce!
|
73
|
+
logger.info "Broadcasting HTTP url: #{url}#{authorized? ? " (with authorization)" : ""}"
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
attr_reader :uri, :queue, :thread
|
79
|
+
|
80
|
+
def ensure_thread_is_alive
|
81
|
+
return if thread&.alive?
|
82
|
+
|
83
|
+
@thread = Thread.new do
|
84
|
+
loop do
|
85
|
+
msg = queue.pop
|
86
|
+
break if msg == :stop
|
87
|
+
|
88
|
+
handle_response perform_request(msg)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def perform_request(payload)
|
94
|
+
build_http do |http|
|
95
|
+
req = Net::HTTP::Post.new(url, {"Content-Type" => "application/json"}.merge(headers))
|
96
|
+
req.body = payload
|
97
|
+
http.request(req)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def handle_response(response)
|
102
|
+
return unless response
|
103
|
+
return if Net::HTTPCreated === response
|
104
|
+
|
105
|
+
logger.error "Broadcast request responded with unexpected status: #{response.code}"
|
106
|
+
end
|
107
|
+
|
108
|
+
def build_http
|
109
|
+
retry_count = 0
|
110
|
+
|
111
|
+
begin
|
112
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
113
|
+
http.open_timeout = OPEN_TIMEOUT
|
114
|
+
http.read_timeout = READ_TIMEOUT
|
115
|
+
http.use_ssl = url.match?(/^https/)
|
116
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
117
|
+
yield http
|
118
|
+
rescue Timeout::Error, *RECOVERABLE_EXCEPTIONS => e
|
119
|
+
retry_count += 1
|
120
|
+
return logger.error("Broadcast request failed: #{e.message}") if MAX_ATTEMPTS < retry_count
|
121
|
+
|
122
|
+
sleep((DELAY**retry_count) * retry_count)
|
123
|
+
retry
|
124
|
+
ensure
|
125
|
+
http.finish if http.started?
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -19,7 +19,7 @@ module AnyCable
|
|
19
19
|
# You can override these params:
|
20
20
|
#
|
21
21
|
# AnyCable.broadcast_adapter = :redis, url: "redis://my_redis", channel: "_any_cable_"
|
22
|
-
class Redis
|
22
|
+
class Redis < Base
|
23
23
|
attr_reader :redis_conn, :channel
|
24
24
|
|
25
25
|
def initialize(
|
@@ -31,11 +31,12 @@ module AnyCable
|
|
31
31
|
@channel = channel
|
32
32
|
end
|
33
33
|
|
34
|
-
def
|
35
|
-
redis_conn.publish(
|
36
|
-
|
37
|
-
|
38
|
-
|
34
|
+
def raw_broadcast(payload)
|
35
|
+
redis_conn.publish(channel, payload)
|
36
|
+
end
|
37
|
+
|
38
|
+
def announce!
|
39
|
+
logger.info "Broadcasting Redis channel: #{channel}"
|
39
40
|
end
|
40
41
|
end
|
41
42
|
end
|
data/lib/anycable/cli.rb
CHANGED
@@ -20,17 +20,22 @@ module AnyCable
|
|
20
20
|
# Wait for external process termination (s)
|
21
21
|
WAIT_PROCESS = 2
|
22
22
|
|
23
|
-
attr_reader :server, :health_server
|
23
|
+
attr_reader :server, :health_server, :embedded
|
24
|
+
alias_method :embedded?, :embedded
|
25
|
+
|
26
|
+
def initialize(embedded: false)
|
27
|
+
@embedded = embedded
|
28
|
+
end
|
24
29
|
|
25
30
|
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
26
|
-
def run(args =
|
31
|
+
def run(args = [])
|
27
32
|
@at_stop = []
|
28
33
|
|
29
34
|
extra_options = parse_cli_options!(args)
|
30
35
|
|
31
36
|
# Boot app first, 'cause it might change
|
32
37
|
# configuration, loggin settings, etc.
|
33
|
-
boot_app!
|
38
|
+
boot_app! unless embedded?
|
34
39
|
|
35
40
|
parse_gem_options!(extra_options)
|
36
41
|
|
@@ -40,7 +45,7 @@ module AnyCable
|
|
40
45
|
|
41
46
|
print_versions!
|
42
47
|
|
43
|
-
logger.info "Serving #{defined?(::Rails) ? "Rails " : ""}application from #{boot_file}"
|
48
|
+
logger.info "Serving #{defined?(::Rails) ? "Rails " : ""}application from #{boot_file}" unless embedded?
|
44
49
|
|
45
50
|
verify_connection_factory!
|
46
51
|
|
@@ -66,6 +71,8 @@ module AnyCable
|
|
66
71
|
|
67
72
|
run_custom_server_command! unless server_command.nil?
|
68
73
|
|
74
|
+
return if embedded?
|
75
|
+
|
69
76
|
begin
|
70
77
|
wait_till_terminated
|
71
78
|
rescue Interrupt => e
|
@@ -81,7 +88,7 @@ module AnyCable
|
|
81
88
|
|
82
89
|
def shutdown
|
83
90
|
at_stop.each(&:call)
|
84
|
-
server
|
91
|
+
server&.stop
|
85
92
|
end
|
86
93
|
|
87
94
|
private
|
@@ -183,7 +190,7 @@ module AnyCable
|
|
183
190
|
end
|
184
191
|
|
185
192
|
def start_pubsub!
|
186
|
-
|
193
|
+
AnyCable.broadcast_adapter.announce!
|
187
194
|
end
|
188
195
|
|
189
196
|
# rubocop: disable Metrics/MethodLength, Metrics/AbcSize
|
@@ -313,9 +320,7 @@ module AnyCable
|
|
313
320
|
-r, --require=path Location of application file to require, default: "config/environment.rb"
|
314
321
|
--server-command=command Command to run WebSocket server
|
315
322
|
--rpc-host=host Local address to run gRPC server on, default: "[::]:50051"
|
316
|
-
--
|
317
|
-
--redis-channel=name Redis channel for broadcasting, default: "__anycable__"
|
318
|
-
--redis-sentinels=<...hosts> Redis Sentinel followers addresses (as a comma-separated list), default: nil
|
323
|
+
--broadcast-adapter=type Pub/sub adapter type for broadcasts, default: redis
|
319
324
|
--log-level=level Logging level, default: "info"
|
320
325
|
--log-file=path Path to log file, default: <none> (log to STDOUT)
|
321
326
|
--log-grpc Enable gRPC logging (disabled by default)
|
@@ -323,6 +328,15 @@ module AnyCable
|
|
323
328
|
-v, --version Print version and exit
|
324
329
|
-h, --help Show this help
|
325
330
|
|
331
|
+
REDIS PUB/SUB OPTIONS
|
332
|
+
--redis-url=url Redis URL for pub/sub, default: REDIS_URL or "redis://localhost:6379/5"
|
333
|
+
--redis-channel=name Redis channel for broadcasting, default: "__anycable__"
|
334
|
+
--redis-sentinels=<...hosts> Redis Sentinel followers addresses (as a comma-separated list), default: nil
|
335
|
+
|
336
|
+
HTTP PUB/SUB OPTIONS
|
337
|
+
--http-broadcast-url HTTP pub/sub endpoint URL, default: "http://localhost:8090/_broadcast"
|
338
|
+
--http-broadcast-secret HTTP pub/sub authorization secret, default: <none> (disabled)
|
339
|
+
|
326
340
|
HTTP HEALTH CHECKER OPTIONS
|
327
341
|
--http-health-port=port Port to run HTTP health server on, default: <none> (disabled)
|
328
342
|
--http-health-path=path Endpoint to server health cheks, default: "/health"
|
data/lib/anycable/config.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
require "anyway_config"
|
4
4
|
require "grpc"
|
5
5
|
|
6
|
+
require "uri"
|
7
|
+
|
6
8
|
module AnyCable
|
7
9
|
# AnyCable configuration.
|
8
10
|
class Config < Anyway::Config
|
@@ -19,11 +21,18 @@ module AnyCable
|
|
19
21
|
# See https://github.com/grpc/grpc/blob/f526602bff029b8db50a8d57134d72da33d8a752/include/grpc/impl/codegen/grpc_types.h#L292-L315
|
20
22
|
rpc_server_args: {},
|
21
23
|
|
24
|
+
## PubSub
|
25
|
+
broadcast_adapter: :redis,
|
26
|
+
|
22
27
|
### Redis options
|
23
28
|
redis_url: ENV.fetch("REDIS_URL", "redis://localhost:6379/5"),
|
24
29
|
redis_sentinels: nil,
|
25
30
|
redis_channel: "__anycable__",
|
26
31
|
|
32
|
+
### HTTP broadcasting options
|
33
|
+
http_broadcast_url: "http://localhost:8090/_broadcast",
|
34
|
+
http_broadcast_secret: nil,
|
35
|
+
|
27
36
|
### Logging options
|
28
37
|
log_file: nil,
|
29
38
|
log_level: :info,
|
@@ -38,7 +47,7 @@ module AnyCable
|
|
38
47
|
version_check_enabled: true
|
39
48
|
)
|
40
49
|
|
41
|
-
|
50
|
+
alias_method :version_check_enabled?, :version_check_enabled
|
42
51
|
|
43
52
|
ignore_options :rpc_server_args
|
44
53
|
flag_options :log_grpc, :debug
|
@@ -68,7 +77,7 @@ module AnyCable
|
|
68
77
|
@debug != false
|
69
78
|
end
|
70
79
|
|
71
|
-
|
80
|
+
alias_method :debug?, :debug
|
72
81
|
end
|
73
82
|
|
74
83
|
def http_health_port_provided?
|
@@ -89,14 +98,17 @@ module AnyCable
|
|
89
98
|
# Build Redis parameters
|
90
99
|
def to_redis_params
|
91
100
|
{url: redis_url}.tap do |params|
|
92
|
-
next if redis_sentinels.nil?
|
101
|
+
next if redis_sentinels.nil? || redis_sentinels.empty?
|
102
|
+
|
103
|
+
sentinels = Array(redis_sentinels)
|
93
104
|
|
94
|
-
|
95
|
-
redis_sentinels.is_a?(Array)
|
105
|
+
next if sentinels.empty?
|
96
106
|
|
97
|
-
|
107
|
+
params[:sentinels] = sentinels.map(&method(:parse_sentinel))
|
108
|
+
end.tap do |params|
|
109
|
+
next unless redis_url.match?(/rediss:\/\//)
|
98
110
|
|
99
|
-
params[:
|
111
|
+
params[:ssl_params] = {verify_mode: OpenSSL::SSL::VERIFY_NONE}
|
100
112
|
end
|
101
113
|
end
|
102
114
|
|
@@ -110,16 +122,14 @@ module AnyCable
|
|
110
122
|
|
111
123
|
private
|
112
124
|
|
113
|
-
SENTINEL_RXP = /^([\w\-_]*)\:(\d+)$/.freeze
|
114
|
-
|
115
125
|
def parse_sentinel(sentinel)
|
116
|
-
return sentinel if sentinel.is_a?(Hash)
|
117
|
-
|
118
|
-
matches = sentinel.match(SENTINEL_RXP)
|
126
|
+
return sentinel.transform_keys!(&:to_sym) if sentinel.is_a?(Hash)
|
119
127
|
|
120
|
-
|
128
|
+
uri = URI.parse("redis://#{sentinel}")
|
121
129
|
|
122
|
-
{
|
130
|
+
{host: uri.host, port: uri.port}.tap do |opts|
|
131
|
+
opts[:password] = uri.password if uri.password
|
132
|
+
end
|
123
133
|
end
|
124
134
|
end
|
125
135
|
end
|
@@ -20,7 +20,7 @@ module AnyCable
|
|
20
20
|
|
21
21
|
attr_reader :grpc_server, :port, :path, :server
|
22
22
|
|
23
|
-
def initialize(grpc_server, port:, path: "/health"
|
23
|
+
def initialize(grpc_server, port:, logger: nil, path: "/health")
|
24
24
|
@grpc_server = grpc_server
|
25
25
|
@port = port
|
26
26
|
@path = path
|
@@ -48,7 +48,9 @@ module AnyCable
|
|
48
48
|
|
49
49
|
private
|
50
50
|
|
51
|
-
|
51
|
+
def logger
|
52
|
+
@logger ||= AnyCable.logger
|
53
|
+
end
|
52
54
|
|
53
55
|
def build_server
|
54
56
|
require "webrick"
|
data/lib/anycable/middleware.rb
CHANGED
@@ -5,7 +5,7 @@ require "grpc"
|
|
5
5
|
module AnyCable
|
6
6
|
# Middleware is a wrapper over gRPC interceptors
|
7
7
|
# for request/response calls
|
8
|
-
class Middleware < GRPC::
|
8
|
+
class Middleware < GRPC::ServerInterceptor
|
9
9
|
def request_response(request: nil, call: nil, method: nil)
|
10
10
|
# Call middlewares only for AnyCable service
|
11
11
|
return yield unless method.receiver.is_a?(AnyCable::RPCHandler)
|
@@ -15,6 +15,11 @@ module AnyCable
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
def server_streamer(**kwargs)
|
19
|
+
p kwargs
|
20
|
+
yield
|
21
|
+
end
|
22
|
+
|
18
23
|
def call(*)
|
19
24
|
raise NotImplementedError
|
20
25
|
end
|
data/lib/anycable/rpc.rb
CHANGED
data/lib/anycable/rpc/rpc_pb.rb
CHANGED
@@ -11,9 +11,11 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
|
|
11
11
|
optional :url, :string, 1
|
12
12
|
map :headers, :string, :string, 2
|
13
13
|
map :cstate, :string, :string, 3
|
14
|
+
map :istate, :string, :string, 4
|
14
15
|
end
|
15
16
|
add_message "anycable.EnvResponse" do
|
16
17
|
map :cstate, :string, :string, 1
|
18
|
+
map :istate, :string, :string, 2
|
17
19
|
end
|
18
20
|
add_message "anycable.ConnectionRequest" do
|
19
21
|
optional :env, :message, 3, "anycable.Env"
|
@@ -40,6 +42,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
|
|
40
42
|
repeated :transmissions, :string, 5
|
41
43
|
optional :error_msg, :string, 6
|
42
44
|
optional :env, :message, 7, "anycable.EnvResponse"
|
45
|
+
repeated :stopped_streams, :string, 8
|
43
46
|
end
|
44
47
|
add_message "anycable.DisconnectRequest" do
|
45
48
|
optional :identifiers, :string, 1
|
data/lib/anycable/rpc_handler.rb
CHANGED
@@ -86,7 +86,8 @@ module AnyCable
|
|
86
86
|
status: result ? AnyCable::Status::SUCCESS : AnyCable::Status::FAILURE,
|
87
87
|
disconnect: socket.closed?,
|
88
88
|
stop_streams: socket.stop_streams?,
|
89
|
-
streams: socket.streams,
|
89
|
+
streams: socket.streams[:start],
|
90
|
+
stopped_streams: socket.streams[:stop],
|
90
91
|
transmissions: socket.transmissions,
|
91
92
|
env: build_env_response(socket)
|
92
93
|
)
|
@@ -115,7 +116,8 @@ module AnyCable
|
|
115
116
|
"REMOTE_ADDR" => request_env.headers.delete("REMOTE_ADDR"),
|
116
117
|
"rack.url_scheme" => uri.scheme&.sub(/^ws/, "http"),
|
117
118
|
# AnyCable specific fields
|
118
|
-
"anycable.raw_cstate" => request_env.cstate&.to_h
|
119
|
+
"anycable.raw_cstate" => request_env.cstate&.to_h,
|
120
|
+
"anycable.raw_istate" => request_env.istate&.to_h
|
119
121
|
}.delete_if { |_k, v| v.nil? })
|
120
122
|
|
121
123
|
env.merge!(build_headers(request_env.headers))
|
@@ -135,7 +137,7 @@ module AnyCable
|
|
135
137
|
"SERVER_PORT" => "80",
|
136
138
|
"rack.url_scheme" => "http",
|
137
139
|
"rack.input" => StringIO.new("", "r").tap { |io| io.set_encoding(Encoding::ASCII_8BIT) },
|
138
|
-
"rack.version" => Rack::VERSION,
|
140
|
+
"rack.version" => ::Rack::VERSION,
|
139
141
|
"rack.errors" => StringIO.new("").tap { |io| io.set_encoding(Encoding::ASCII_8BIT) },
|
140
142
|
"rack.multithread" => true,
|
141
143
|
"rack.multiprocess" => false,
|
@@ -158,7 +160,8 @@ module AnyCable
|
|
158
160
|
|
159
161
|
def build_env_response(socket)
|
160
162
|
AnyCable::EnvResponse.new(
|
161
|
-
cstate: socket.cstate.changed_fields
|
163
|
+
cstate: socket.cstate.changed_fields,
|
164
|
+
istate: socket.istate.changed_fields
|
162
165
|
)
|
163
166
|
end
|
164
167
|
|
data/lib/anycable/server.rb
CHANGED
@@ -23,7 +23,7 @@ module AnyCable
|
|
23
23
|
class Server
|
24
24
|
attr_reader :grpc_server, :host
|
25
25
|
|
26
|
-
def initialize(host:, logger:
|
26
|
+
def initialize(host:, logger: nil, **options)
|
27
27
|
@logger = logger
|
28
28
|
@host = host
|
29
29
|
@grpc_server = build_server(options)
|
@@ -70,7 +70,11 @@ module AnyCable
|
|
70
70
|
|
71
71
|
private
|
72
72
|
|
73
|
-
attr_reader :
|
73
|
+
attr_reader :start_thread
|
74
|
+
|
75
|
+
def logger
|
76
|
+
@logger ||= AnyCable.logger
|
77
|
+
end
|
74
78
|
|
75
79
|
def build_server(options)
|
76
80
|
GRPC::RpcServer.new(**options).tap do |server|
|
data/lib/anycable/socket.rb
CHANGED
@@ -17,6 +17,8 @@ module AnyCable
|
|
17
17
|
source&.[](key)
|
18
18
|
end
|
19
19
|
|
20
|
+
alias_method :[], :read
|
21
|
+
|
20
22
|
def write(key, val)
|
21
23
|
return if source&.[](key) == val
|
22
24
|
|
@@ -26,18 +28,21 @@ module AnyCable
|
|
26
28
|
source[key] = val
|
27
29
|
end
|
28
30
|
|
31
|
+
alias_method :[]=, :write
|
32
|
+
|
29
33
|
def changed_fields
|
30
34
|
return unless source && dirty_keys
|
31
35
|
source.slice(*dirty_keys)
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
35
|
-
attr_reader :transmissions, :env, :cstate
|
39
|
+
attr_reader :transmissions, :env, :cstate, :istate
|
36
40
|
|
37
41
|
def initialize(env: nil)
|
38
42
|
@transmissions = []
|
39
43
|
@env = env
|
40
44
|
@cstate = env["anycable.cstate"] = State.new(env["anycable.raw_cstate"])
|
45
|
+
@istate = env["anycable.istate"] = State.new(env["anycable.raw_istate"])
|
41
46
|
end
|
42
47
|
|
43
48
|
def transmit(websocket_message)
|
@@ -45,11 +50,11 @@ module AnyCable
|
|
45
50
|
end
|
46
51
|
|
47
52
|
def subscribe(_channel, broadcasting)
|
48
|
-
streams << broadcasting
|
53
|
+
streams[:start] << broadcasting
|
49
54
|
end
|
50
55
|
|
51
|
-
def unsubscribe(_channel,
|
52
|
-
|
56
|
+
def unsubscribe(_channel, broadcasting)
|
57
|
+
streams[:stop] << broadcasting
|
53
58
|
end
|
54
59
|
|
55
60
|
def unsubscribe_from_all(_channel)
|
@@ -57,7 +62,7 @@ module AnyCable
|
|
57
62
|
end
|
58
63
|
|
59
64
|
def streams
|
60
|
-
@streams ||= []
|
65
|
+
@streams ||= {start: [], stop: []}
|
61
66
|
end
|
62
67
|
|
63
68
|
def close
|
data/lib/anycable/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: anycable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
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: 2021-01-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: anyway_config
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '3.5'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: webmock
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.8'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '3.8'
|
111
125
|
description: AnyCable is a polyglot replacement for ActionCable-compatible servers
|
112
126
|
email:
|
113
127
|
- dementiev.vm@gmail.com
|
@@ -126,6 +140,8 @@ files:
|
|
126
140
|
- bin/setup
|
127
141
|
- lib/anycable.rb
|
128
142
|
- lib/anycable/broadcast_adapters.rb
|
143
|
+
- lib/anycable/broadcast_adapters/base.rb
|
144
|
+
- lib/anycable/broadcast_adapters/http.rb
|
129
145
|
- lib/anycable/broadcast_adapters/redis.rb
|
130
146
|
- lib/anycable/cli.rb
|
131
147
|
- lib/anycable/config.rb
|
@@ -165,9 +181,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
165
181
|
version: 2.5.0
|
166
182
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
167
183
|
requirements:
|
168
|
-
- - "
|
184
|
+
- - ">="
|
169
185
|
- !ruby/object:Gem::Version
|
170
|
-
version:
|
186
|
+
version: '0'
|
171
187
|
requirements: []
|
172
188
|
rubygems_version: 3.0.6
|
173
189
|
signing_key:
|