anycable 1.0.0.preview1 → 1.0.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 +27 -253
- data/MIT-LICENSE +1 -1
- data/README.md +14 -11
- data/lib/anycable.rb +1 -1
- 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 +41 -21
- 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 +18 -8
- data/lib/anycable/rspec/rpc_stub_context.rb +1 -1
- 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 +13 -39
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 90f8b0fc45d4659234b7defd755b818c19dde55779a64cbdec794fc68441bf45
|
|
4
|
+
data.tar.gz: 2184541660e683c2206593636434b4a24578376936eb0bb63d4600602829c736
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bddcd49f2e31f8a1c0fcf202e44779abe0fcf4092bc3358f3b4efe7b86e4db1454e083e2c352e651ff38fafcf0d4f3b1907a019fba5e262aaeba0b7fd6a09abb
|
|
7
|
+
data.tar.gz: fa7586b9a059dfb9584894d1f5540fd33dff26411f8199a0f837cf26d0d0c9c2af150602ebbadc775af3f2b9393ed5c2de0f7435a2d403f11081166ddcae88df
|
data/CHANGELOG.md
CHANGED
|
@@ -1,283 +1,57 @@
|
|
|
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.4 (2020-01-24)
|
|
26
|
-
|
|
27
|
-
- Fix Ruby 2.7 warnings. ([@palkan])
|
|
28
|
-
|
|
29
|
-
– Add `REMOTE_ADDR` socket env variable using a synthetic header passed from a websocket
|
|
30
|
-
server. ([@sponomarev][])
|
|
31
|
-
|
|
32
|
-
Recreating a request object in your custom connection factory using `Rack::Request` or
|
|
33
|
-
`ActionDispatch::Request` (already implemented in [anycable-rails](https://github.com/anycable/anycable-rails))
|
|
34
|
-
gives you an access to `request.ip` with the properly set IP address.
|
|
35
|
-
|
|
36
|
-
- Align socket env to be more compatibile with Rack Spec ([@sponomarev][])
|
|
37
|
-
|
|
38
|
-
Provide as much env details as possible to be able to reconstruct the full
|
|
39
|
-
request object in a custom connection factory.
|
|
40
|
-
|
|
41
|
-
## 0.6.3 (2019-03-26)
|
|
42
|
-
|
|
43
|
-
- Relax `redis` gem version requirement. ([@palkan][])
|
|
44
|
-
|
|
45
|
-
Use the same restriction as Action Cable does (`>= 3`).
|
|
46
|
-
|
|
47
|
-
## 0.6.2 (2019-03-15)
|
|
48
|
-
|
|
49
|
-
- Add GRPC service method name and message content to exception notifications ([@sponomarev][])
|
|
50
|
-
|
|
51
|
-
`Anycable.capture_exception` allows accessing GRPC service method name and message content
|
|
52
|
-
on which an exception was captured. It can be used for exceptions grouping in your tracker and
|
|
53
|
-
providing additional data to investigate a root of a problem.
|
|
54
|
-
|
|
55
|
-
Example:
|
|
56
|
-
|
|
57
|
-
```ruby
|
|
58
|
-
AnyCable.capture_exception do |ex, method, message|
|
|
59
|
-
Honeybadger.notify(ex, component: "any_cable", action: method, params: message)
|
|
60
|
-
end
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Usage of a handler proc with just a single argument is preserved for the sake of compatibility.
|
|
64
|
-
|
|
65
|
-
- Add deprecation warning to default host usage ([@sponomarev][])
|
|
66
|
-
|
|
67
|
-
Exposing AnyCable publicly is considered to be harmful and planned to be changed
|
|
68
|
-
in future versions.
|
|
69
|
-
|
|
70
|
-
- Allow running the server as a detachable daemon ([@sponomarev][])
|
|
71
|
-
|
|
72
|
-
Server is fully managed by the binary itself.
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
# Start anycable daemon
|
|
76
|
-
$ bundle exec anycabled start
|
|
77
|
-
|
|
78
|
-
# Pass cli options to anycable through daemon. Separate daemon options and anycable options with `--`
|
|
79
|
-
$ bundle exec anycabled start -- --rpc-host 127.0.0.1:31337
|
|
80
|
-
|
|
81
|
-
# Stop anycable daemon
|
|
82
|
-
$ bundle exec anycabled stop
|
|
83
|
-
|
|
84
|
-
# See more anycable daemon options
|
|
85
|
-
$ bundle exec anycabled
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
## 0.6.1 (2019-01-05)
|
|
89
|
-
|
|
90
|
-
- [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][])
|
|
91
|
-
|
|
92
|
-
## 0.6.0 (2018-11-15)
|
|
93
|
-
|
|
94
|
-
### Features
|
|
95
|
-
|
|
96
|
-
#### Broadcast adapters
|
|
97
|
-
|
|
98
|
-
AnyCable allows you to use custom broadcasting adapters (Redis is used by default):
|
|
99
|
-
|
|
100
|
-
```ruby
|
|
101
|
-
# Specify by name (tries to load `AnyCable::BroadcastAdapters::MyAdapter` from
|
|
102
|
-
# "anycable/broadcast_adapters/my_adapter")
|
|
103
|
-
AnyCable.broadcast_adapter = :my_adapter, {option: "value"}
|
|
104
|
-
# or provide an instance (should respond_to #broadcast)
|
|
105
|
-
AnyCable.broadcast_adapter = MyAdapter.new
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
**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.
|
|
109
|
-
|
|
110
|
-
#### CLI
|
|
111
|
-
|
|
112
|
-
AnyCable now ships with a CLI–`anycable`.
|
|
113
|
-
|
|
114
|
-
Use it to run a gRPC server:
|
|
115
|
-
|
|
116
|
-
```sh
|
|
117
|
-
# run anycable and load app from app.rb
|
|
118
|
-
bundle exec anycable -r app.rb
|
|
119
|
-
# or
|
|
120
|
-
bundle exec anycable --require app.rb
|
|
121
|
-
```
|
|
5
|
+
## 1.0.1 (2020-07-07)
|
|
122
6
|
|
|
123
|
-
|
|
7
|
+
- Support providing passwords for Redis Sentinels. ([@palkan][])
|
|
124
8
|
|
|
125
|
-
|
|
9
|
+
Use the following format: `ANYCABLE_REDIS_SENTINELS=:password1@my.redis.sentinel.first:26380,:password2@my.redis.sentinel.second:26380`.
|
|
126
10
|
|
|
127
|
-
|
|
11
|
+
## 1.0.0 (2020-07-01)
|
|
128
12
|
|
|
129
|
-
|
|
13
|
+
- Add `embedded` option to CLI runner. ([@palkan][])
|
|
130
14
|
|
|
131
|
-
|
|
132
|
-
$ bundle exec anycable --server-command "anycable-go -p 3334"
|
|
133
|
-
```
|
|
15
|
+
- Add `Env#istate` and `EnvResponse#istate` to store channel state. ([@palkan][])
|
|
134
16
|
|
|
135
|
-
|
|
17
|
+
That would allow to mimic instance variables usage in Action Cable channels.
|
|
136
18
|
|
|
137
|
-
-
|
|
138
|
-
- Expose gRPC server parameters via `rpc_*` config params:
|
|
19
|
+
- Add `CommandResponse#stopped_streams` to support unsubscribing from particular broadcastings. ([@palkan])
|
|
139
20
|
|
|
140
|
-
|
|
141
|
-
AnyCable.configure do |config|
|
|
142
|
-
config.rpc_pool_size = 120
|
|
143
|
-
config.rpc_max_waiting_requests = 10
|
|
144
|
-
# etc
|
|
145
|
-
end
|
|
146
|
-
```
|
|
147
|
-
- `REDIS_URL` env is used by default if present (and no `ANYCABLE_REDIS_URL` specified)
|
|
148
|
-
- Make HTTP health check url configurable
|
|
149
|
-
- Add ability to pass Redis Sentinel config as array of string.
|
|
21
|
+
`Socket#unsubscribe` is now implemented as well.
|
|
150
22
|
|
|
151
|
-
|
|
23
|
+
- Add `AnyCable.broadcast_adapter#broadcast_command` method. ([@palkan][])
|
|
152
24
|
|
|
153
|
-
|
|
154
|
-
ANYCABLE_REDIS_SENTINELS=127.0.0.1:26380,127.0.0.1:26381 bundle exec anycable
|
|
155
|
-
```
|
|
25
|
+
It could be used to send commands to WS server (e.g., remote disconnect).
|
|
156
26
|
|
|
157
|
-
|
|
27
|
+
- Add `:http` broadcasting adapter. ([@palkan][])
|
|
158
28
|
|
|
159
|
-
-
|
|
160
|
-
|
|
161
|
-
See [docs](https://docs.anycable.io/#/./middlewares).
|
|
162
|
-
|
|
163
|
-
- Added gRPC health checker.
|
|
164
|
-
|
|
165
|
-
See [docs](https://docs.anycable.io/#/./health_checking).
|
|
166
|
-
|
|
167
|
-
- Added hook to run code only within RPC server context.
|
|
168
|
-
|
|
169
|
-
Use `AnyCable.configure_server { ... }` to run code only when RPC server is running.
|
|
170
|
-
|
|
171
|
-
### API changes
|
|
172
|
-
|
|
173
|
-
**NOTE**: the old API is still working but deprecated (you'll see a notice).
|
|
174
|
-
|
|
175
|
-
- Use `AnyCable` instead of `Anycable`
|
|
176
|
-
|
|
177
|
-
- New API for registering error handlers:
|
|
178
|
-
|
|
179
|
-
```ruby
|
|
180
|
-
AnyCable.capture_exception do |ex|
|
|
181
|
-
Honeybadger.notify(ex)
|
|
182
|
-
end
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
- `AnyCable::Server.start` is deprecated
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
## 0.5.2 (2018-09-06)
|
|
189
|
-
|
|
190
|
-
- [#48](https://github.com/anycable/anycable/pull/48) Add HTTP health server ([@DarthSim][])
|
|
191
|
-
|
|
192
|
-
## 0.5.1 (2018-06-13)
|
|
193
|
-
|
|
194
|
-
Minor fixes.
|
|
195
|
-
|
|
196
|
-
## 0.5.0 (2017-10-21)
|
|
197
|
-
|
|
198
|
-
- [#2](https://github.com/anycable/anycable/issues/2) Add support for [Redis Sentinel](https://redis.io/topics/sentinel). ([@accessd][])
|
|
199
|
-
|
|
200
|
-
- [#28](https://github.com/anycable/anycable/issues/28) Support arbitrary headers. ([@palkan][])
|
|
201
|
-
|
|
202
|
-
Previously we hardcoded only "Cookie" header. Now we add all passed headers by WebSocket server to request env.
|
|
203
|
-
|
|
204
|
-
- [#27](https://github.com/anycable/anycable/issues/27) Add `error_msg` to RPC responses. ([@palkan][])
|
|
205
|
-
|
|
206
|
-
Now RPC responses has 3 statuses:
|
|
207
|
-
|
|
208
|
-
- `SUCCESS` – successful request, operation succeed
|
|
209
|
-
- `FAILURE` – successful request, operation failed (e.g. authentication failed)
|
|
210
|
-
- `ERROR` – request failed (exception raised).
|
|
211
|
-
|
|
212
|
-
We provide `error_msg` only when request status is `ERROR`.
|
|
213
|
-
|
|
214
|
-
- [#25](https://github.com/anycable/anycable/issues/25) Improve logging and exceptions handling. ([@palkan][])
|
|
215
|
-
|
|
216
|
-
Default logger logs to STDOUT with `info` level by default but can be configured to log to file with
|
|
217
|
-
any severity.
|
|
218
|
-
|
|
219
|
-
GRPC logging is turned off by default (can be turned on through `log_grpc` configuration parameter).
|
|
220
|
-
|
|
221
|
-
`ANYCABLE_DEBUG=1` acts as a shortcut to set `debug` level and turn on GRPC logging.
|
|
222
|
-
|
|
223
|
-
Now it's possible to add custom exception handlers (e.g. to notify external exception tracking services).
|
|
224
|
-
|
|
225
|
-
More on [Wiki](https://github.com/anycable/anycable/wiki/Logging-&-Exceptions-Handling).
|
|
226
|
-
|
|
227
|
-
## 0.4.6 (2017-05-20)
|
|
228
|
-
|
|
229
|
-
- Add `Anycable::Server#stop` method. ([@sadovnik][])
|
|
230
|
-
|
|
231
|
-
## 0.4.5 (2017-03-17)
|
|
232
|
-
|
|
233
|
-
- Fixed #11. ([@palkan][])
|
|
234
|
-
|
|
235
|
-
## 0.4.4 (2017-03-06)
|
|
236
|
-
|
|
237
|
-
- Handle `StandardError` gracefully in RPC calls. ([@palkan][])
|
|
238
|
-
|
|
239
|
-
## 0.4.3 (2017-02-18)
|
|
240
|
-
|
|
241
|
-
- Update `grpc` version dependency to support Ruby 2.4. ([@palkan][])
|
|
242
|
-
|
|
243
|
-
## 0.4.2 (2017-01-28)
|
|
244
|
-
|
|
245
|
-
- Change socket streaming API. ([@palkan][])
|
|
246
|
-
|
|
247
|
-
Add `Socket#subscribe`, `unsubscribe` and `unsubscribe_from_all` methods.
|
|
248
|
-
|
|
249
|
-
## 0.4.1 (2017-01-24)
|
|
250
|
-
|
|
251
|
-
- Introduce _fake_ socket instance to handle transmissions and streams. ([@palkan][])
|
|
29
|
+
- **RPC schema has changed**. ([@palkan][])
|
|
252
30
|
|
|
253
|
-
-
|
|
31
|
+
Using `anycable-go` v1.x is required.
|
|
254
32
|
|
|
255
|
-
|
|
33
|
+
- **Ruby 2.5+ is required**. ([@palkan][])
|
|
256
34
|
|
|
257
|
-
|
|
258
|
-
connection.handle_channel_command(identifier, command, data)
|
|
259
|
-
```
|
|
35
|
+
- Added RPC proto version check. ([@palkan][])
|
|
260
36
|
|
|
261
|
-
|
|
37
|
+
Server must sent `protov` metadata with the supported versions (comma-separated list). If there is no matching version an exception is raised.
|
|
262
38
|
|
|
263
|
-
|
|
39
|
+
Current RPC proto version is **v1**.
|
|
264
40
|
|
|
265
|
-
-
|
|
41
|
+
- Added `request` support to channels. ([@palkan][])
|
|
266
42
|
|
|
267
|
-
|
|
43
|
+
Now you can access `request` object in channels, too (e.g., to read headers/cookies/URL/etc).
|
|
268
44
|
|
|
269
|
-
-
|
|
45
|
+
- Change default server address from `[::]:50051` to `127.0.0.1:50051`. ([@palkan][])
|
|
270
46
|
|
|
271
|
-
|
|
47
|
+
See [#71](https://github.com/anycable/anycable/pull/71).
|
|
272
48
|
|
|
273
|
-
|
|
49
|
+
- Fix building Redis Sentinel config. ([@palkan][])
|
|
274
50
|
|
|
275
|
-
|
|
51
|
+
---
|
|
276
52
|
|
|
277
|
-
|
|
53
|
+
See [Changelog](https://github.com/anycable/anycable/blob/0-6-stable/CHANGELOG.md) for versions <1.0.0.
|
|
278
54
|
|
|
279
55
|
[@palkan]: https://github.com/palkan
|
|
280
|
-
[@sadovnik]: https://github.com/sadovnik
|
|
281
|
-
[@accessd]: https://github.com/accessd
|
|
282
|
-
[@DarthSim]: https://github.com/DarthSim
|
|
283
56
|
[@sponomarev]: https://github.com/sponomarev
|
|
57
|
+
[@bibendi]: https://github.com/bibendi
|
data/MIT-LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -1,30 +1,36 @@
|
|
|
1
|
-
[](https://gitpitch.com/anycable/anycable/master?grs=github)
|
|
1
|
+
[](https://gitpitch.com/anycable/anycable/master?grs=github)
|
|
2
|
+
[](https://rubygems.org/gems/anycable)
|
|
3
|
+
[](https://github.com/anycable/anycable/actions)
|
|
2
4
|
[](https://gitter.im/anycable/Lobby)
|
|
3
|
-
[](https://docs.anycable.io)
|
|
5
|
+
[](https://docs.anycable.io/v1)
|
|
4
6
|
|
|
5
7
|
# AnyCable
|
|
6
8
|
|
|
7
9
|
<img align="right" height="150" width="129"
|
|
8
10
|
title="AnyCable logo" src="https://docs.anycable.io/assets/images/logo.svg">
|
|
9
11
|
|
|
10
|
-
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).
|
|
11
13
|
|
|
12
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.
|
|
13
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
|
+
|
|
14
18
|
<a href="https://evilmartians.com/">
|
|
15
19
|
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
|
16
20
|
|
|
17
21
|
## Requirements
|
|
18
22
|
|
|
19
23
|
- Ruby >= 2.5
|
|
20
|
-
- Redis (for broadcasting
|
|
24
|
+
- Redis (for broadcasting **in production**, [discuss other options](https://github.com/anycable/anycable/issues/2) with us!)
|
|
21
25
|
|
|
22
26
|
## Usage
|
|
23
27
|
|
|
24
|
-
Check out our 📑 [Documentation](https://docs.anycable.io).
|
|
28
|
+
Check out our 📑 [Documentation](https://docs.anycable.io/v1).
|
|
25
29
|
|
|
26
30
|
## Links
|
|
27
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
|
+
|
|
28
34
|
- [AnyCable: Action Cable on steroids!](https://evilmartians.com/chronicles/anycable-actioncable-on-steroids)
|
|
29
35
|
|
|
30
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)
|
|
@@ -43,12 +49,9 @@ Check out our 📑 [Documentation](https://docs.anycable.io).
|
|
|
43
49
|
|
|
44
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)
|
|
45
51
|
|
|
46
|
-
##
|
|
47
|
-
|
|
48
|
-
- [AnyCable Go](https://github.com/anycable/anycable-go)
|
|
49
|
-
- [ErlyCable](https://github.com/anycable/erlycable)
|
|
52
|
+
## Building
|
|
50
53
|
|
|
51
|
-
|
|
54
|
+
### Generating gRPC files from `.proto`
|
|
52
55
|
|
|
53
56
|
- Install required GRPC gems:
|
|
54
57
|
|
|
@@ -75,4 +78,4 @@ The gem is available as open source under the terms of the [MIT License](http://
|
|
|
75
78
|
|
|
76
79
|
## Security Contact
|
|
77
80
|
|
|
78
|
-
To report a security vulnerability, please
|
|
81
|
+
To report a security vulnerability, please contact us at `anycable@evilmartians.com`. We will coordinate the fix and disclosure.
|
data/lib/anycable.rb
CHANGED
|
@@ -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 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 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,
|
|
@@ -43,19 +52,33 @@ module AnyCable
|
|
|
43
52
|
ignore_options :rpc_server_args
|
|
44
53
|
flag_options :log_grpc, :debug
|
|
45
54
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
# Support both anyway_config 1.4 and 2.0.
|
|
56
|
+
# DEPRECATE: Drop <2.0 support in 1.1
|
|
57
|
+
if respond_to?(:on_load)
|
|
58
|
+
on_load { self.debug = debug != false }
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
def log_level
|
|
61
|
+
debug? ? :debug : super
|
|
62
|
+
end
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
def log_grpc
|
|
65
|
+
debug? || super
|
|
66
|
+
end
|
|
67
|
+
else
|
|
68
|
+
def log_level
|
|
69
|
+
debug ? :debug : @log_level
|
|
70
|
+
end
|
|
57
71
|
|
|
58
|
-
|
|
72
|
+
def log_grpc
|
|
73
|
+
debug || @log_grpc
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def debug
|
|
77
|
+
@debug != false
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
alias debug? debug
|
|
81
|
+
end
|
|
59
82
|
|
|
60
83
|
def http_health_port_provided?
|
|
61
84
|
!http_health_port.nil? && http_health_port != ""
|
|
@@ -77,12 +100,11 @@ module AnyCable
|
|
|
77
100
|
{url: redis_url}.tap do |params|
|
|
78
101
|
next if redis_sentinels.nil?
|
|
79
102
|
|
|
80
|
-
|
|
81
|
-
redis_sentinels.is_a?(Array)
|
|
103
|
+
sentinels = Array(redis_sentinels)
|
|
82
104
|
|
|
83
|
-
next if
|
|
105
|
+
next if sentinels.empty?
|
|
84
106
|
|
|
85
|
-
params[:sentinels] =
|
|
107
|
+
params[:sentinels] = sentinels.map(&method(:parse_sentinel))
|
|
86
108
|
end
|
|
87
109
|
end
|
|
88
110
|
|
|
@@ -96,16 +118,14 @@ module AnyCable
|
|
|
96
118
|
|
|
97
119
|
private
|
|
98
120
|
|
|
99
|
-
SENTINEL_RXP = /^([\w\-_]*)\:(\d+)$/.freeze
|
|
100
|
-
|
|
101
121
|
def parse_sentinel(sentinel)
|
|
102
|
-
return sentinel if sentinel.is_a?(Hash)
|
|
122
|
+
return sentinel.transform_keys!(&:to_sym) if sentinel.is_a?(Hash)
|
|
103
123
|
|
|
104
|
-
|
|
124
|
+
uri = URI.parse("redis://#{sentinel}")
|
|
105
125
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
126
|
+
{host: uri.host, port: uri.port}.tap do |opts|
|
|
127
|
+
opts[:password] = uri.password if uri.password
|
|
128
|
+
end
|
|
109
129
|
end
|
|
110
130
|
end
|
|
111
131
|
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
|
)
|
|
@@ -106,17 +107,18 @@ module AnyCable
|
|
|
106
107
|
uri = URI.parse(request_env.url)
|
|
107
108
|
|
|
108
109
|
env = base_rack_env
|
|
109
|
-
env.merge!(
|
|
110
|
+
env.merge!({
|
|
110
111
|
"PATH_INFO" => uri.path,
|
|
111
112
|
"QUERY_STRING" => uri.query,
|
|
112
113
|
"SERVER_NAME" => uri.host,
|
|
113
|
-
"SERVER_PORT" => uri.port
|
|
114
|
+
"SERVER_PORT" => uri.port,
|
|
114
115
|
"HTTP_HOST" => uri.host,
|
|
115
116
|
"REMOTE_ADDR" => request_env.headers.delete("REMOTE_ADDR"),
|
|
116
|
-
"rack.url_scheme" => uri.scheme,
|
|
117
|
+
"rack.url_scheme" => uri.scheme&.sub(/^ws/, "http"),
|
|
117
118
|
# AnyCable specific fields
|
|
118
|
-
"anycable.raw_cstate" => request_env.cstate&.to_h
|
|
119
|
-
|
|
119
|
+
"anycable.raw_cstate" => request_env.cstate&.to_h,
|
|
120
|
+
"anycable.raw_istate" => request_env.istate&.to_h
|
|
121
|
+
}.delete_if { |_k, v| v.nil? })
|
|
120
122
|
|
|
121
123
|
env.merge!(build_headers(request_env.headers))
|
|
122
124
|
end
|
|
@@ -125,6 +127,7 @@ module AnyCable
|
|
|
125
127
|
# Minimum required variables according to Rack Spec
|
|
126
128
|
# (not all of them though, just those enough for Action Cable to work)
|
|
127
129
|
# See https://rubydoc.info/github/rack/rack/master/file/SPEC
|
|
130
|
+
# and https://github.com/rack/rack/blob/master/lib/rack/lint.rb
|
|
128
131
|
{
|
|
129
132
|
"REQUEST_METHOD" => "GET",
|
|
130
133
|
"SCRIPT_NAME" => "",
|
|
@@ -133,7 +136,13 @@ module AnyCable
|
|
|
133
136
|
"SERVER_NAME" => "",
|
|
134
137
|
"SERVER_PORT" => "80",
|
|
135
138
|
"rack.url_scheme" => "http",
|
|
136
|
-
"rack.input" => ""
|
|
139
|
+
"rack.input" => StringIO.new("", "r").tap { |io| io.set_encoding(Encoding::ASCII_8BIT) },
|
|
140
|
+
"rack.version" => ::Rack::VERSION,
|
|
141
|
+
"rack.errors" => StringIO.new("").tap { |io| io.set_encoding(Encoding::ASCII_8BIT) },
|
|
142
|
+
"rack.multithread" => true,
|
|
143
|
+
"rack.multiprocess" => false,
|
|
144
|
+
"rack.run_once" => false,
|
|
145
|
+
"rack.hijack?" => false
|
|
137
146
|
}
|
|
138
147
|
end
|
|
139
148
|
|
|
@@ -151,7 +160,8 @@ module AnyCable
|
|
|
151
160
|
|
|
152
161
|
def build_env_response(socket)
|
|
153
162
|
AnyCable::EnvResponse.new(
|
|
154
|
-
cstate: socket.cstate.changed_fields
|
|
163
|
+
cstate: socket.cstate.changed_fields,
|
|
164
|
+
istate: socket.istate.changed_fields
|
|
155
165
|
)
|
|
156
166
|
end
|
|
157
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 [] 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 []= 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,27 +1,27 @@
|
|
|
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.1
|
|
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-07-07 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
|
|
@@ -72,14 +72,14 @@ dependencies:
|
|
|
72
72
|
requirements:
|
|
73
73
|
- - ">="
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
75
|
+
version: '13.0'
|
|
76
76
|
type: :development
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
80
|
- - ">="
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
82
|
+
version: '13.0'
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
84
|
name: rack
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -109,47 +109,19 @@ dependencies:
|
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
110
|
version: '3.5'
|
|
111
111
|
- !ruby/object:Gem::Dependency
|
|
112
|
-
name:
|
|
112
|
+
name: webmock
|
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
|
114
114
|
requirements:
|
|
115
115
|
- - "~>"
|
|
116
116
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: '
|
|
117
|
+
version: '3.8'
|
|
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: '
|
|
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.3.8
|
|
132
|
-
type: :development
|
|
133
|
-
prerelease: false
|
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
-
requirements:
|
|
136
|
-
- - ">="
|
|
137
|
-
- !ruby/object:Gem::Version
|
|
138
|
-
version: 0.3.8
|
|
139
|
-
- !ruby/object:Gem::Dependency
|
|
140
|
-
name: standard
|
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
|
142
|
-
requirements:
|
|
143
|
-
- - "~>"
|
|
144
|
-
- !ruby/object:Gem::Version
|
|
145
|
-
version: 0.1.7
|
|
146
|
-
type: :development
|
|
147
|
-
prerelease: false
|
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
-
requirements:
|
|
150
|
-
- - "~>"
|
|
151
|
-
- !ruby/object:Gem::Version
|
|
152
|
-
version: 0.1.7
|
|
124
|
+
version: '3.8'
|
|
153
125
|
description: AnyCable is a polyglot replacement for ActionCable-compatible servers
|
|
154
126
|
email:
|
|
155
127
|
- dementiev.vm@gmail.com
|
|
@@ -168,6 +140,8 @@ files:
|
|
|
168
140
|
- bin/setup
|
|
169
141
|
- lib/anycable.rb
|
|
170
142
|
- lib/anycable/broadcast_adapters.rb
|
|
143
|
+
- lib/anycable/broadcast_adapters/base.rb
|
|
144
|
+
- lib/anycable/broadcast_adapters/http.rb
|
|
171
145
|
- lib/anycable/broadcast_adapters/redis.rb
|
|
172
146
|
- lib/anycable/cli.rb
|
|
173
147
|
- lib/anycable/config.rb
|
|
@@ -207,9 +181,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
207
181
|
version: 2.5.0
|
|
208
182
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
209
183
|
requirements:
|
|
210
|
-
- - "
|
|
184
|
+
- - ">="
|
|
211
185
|
- !ruby/object:Gem::Version
|
|
212
|
-
version:
|
|
186
|
+
version: '0'
|
|
213
187
|
requirements: []
|
|
214
188
|
rubygems_version: 3.0.6
|
|
215
189
|
signing_key:
|