anycable-rack-server 0.1.0 → 0.2.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +77 -22
- data/lib/anycable-rack-server.rb +19 -0
- data/lib/anycable/rack/broadcast_subscribers/base_subscriber.rb +41 -0
- data/lib/anycable/rack/broadcast_subscribers/http_subscriber.rb +44 -0
- data/lib/anycable/rack/broadcast_subscribers/redis_subscriber.rb +34 -19
- data/lib/anycable/rack/config.rb +22 -0
- data/lib/anycable/rack/connection.rb +28 -19
- data/lib/anycable/rack/hub.rb +32 -0
- data/lib/anycable/rack/middleware.rb +14 -14
- data/lib/anycable/rack/pinger.rb +1 -1
- data/lib/anycable/rack/railtie.rb +6 -30
- data/lib/anycable/rack/rpc/client.rb +32 -13
- data/lib/anycable/rack/rpc/rpc.proto +18 -4
- data/lib/anycable/rack/server.rb +59 -33
- data/lib/anycable/rack/socket.rb +14 -17
- data/lib/anycable/rack/version.rb +1 -1
- metadata +58 -28
- data/lib/anycable/rack/rpc_runner.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30ae82e08a26ece6943ba290783ed093045d5dbac93613741321aa9d62486838
|
4
|
+
data.tar.gz: a506eaaf81f5982b9a8774b6f6b0beae3e06e00ebd3e5bda1607c58375b15c19
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ba8c7529041061a05e46c1de89c7f44da1733843026145bd6b4a658221c2f68bd85f05f137c878658dea0ca0a7df74bfc3bbcb6d221b7a2cc013316737549a1
|
7
|
+
data.tar.gz: 64031fb35d63ac3d1fa64a490b7b821b9dd3b5070fc888dbb9e51ed1a3b9c4977b5d4ba5268c4f36dbe58d467187945207fc7e5e21ba4b28c2de8c39dd697308
|
data/LICENSE
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
MIT License
|
2
2
|
|
3
|
-
Copyright (c) 2019 Yulia Oletskaya
|
3
|
+
Copyright (c) 2019-2020 Yulia Oletskaya, Vladimir Dementyev
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
[![
|
1
|
+
[![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](https://cultofmartians.com/tasks/anycable-ruby-server.html)
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/anycable-rack-server.svg)](https://rubygems.org/gems/anycable-rack-server)
|
3
|
+
[![Build](https://github.com/anycable/anycable-rack-server/workflows/Build/badge.svg)](https://github.com/anycable/anycable-rack-server/actions)
|
2
4
|
|
3
5
|
# anycable-rack-server
|
4
6
|
|
@@ -8,9 +10,7 @@
|
|
8
10
|
|
9
11
|
```ruby
|
10
12
|
# Initialize server instance first.
|
11
|
-
|
12
|
-
# NOTE: you must run RPC server yourself and provide its host
|
13
|
-
ws_server = AnyCable::Rack::Server.new rpc_host: "localhost:50051"
|
13
|
+
ws_server = AnyCable::Rack::Server.new
|
14
14
|
|
15
15
|
app = Rack::Builder.new do
|
16
16
|
map "/cable" do
|
@@ -18,7 +18,7 @@ app = Rack::Builder.new do
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
# NOTE: don't forget to call `start!` method
|
21
|
+
# NOTE: don't forget to call `start!` method
|
22
22
|
ws_server.start!
|
23
23
|
|
24
24
|
run app
|
@@ -28,48 +28,103 @@ run app
|
|
28
28
|
|
29
29
|
Add `gem "anycable-rack-server"` to you `Gemfile` and make sure your Action Cable adapter is set to `:any_cable`. That's it! We automatically start AnyCable Rack server for your at `/cable` path.
|
30
30
|
|
31
|
-
##
|
31
|
+
## Configuration
|
32
|
+
|
33
|
+
AnyCable Rack Server uses [`anyway_config`](https://github.com/palkan/anyway_config) gem for configuration; thus it is possible to set configuration parameters through environment vars (prefixed with `ANYCABLE_`), `config/anycable.yml` file or `secrets.yml` when using Rails.
|
34
|
+
|
35
|
+
**NOTE:** AnyCable Rack Server uses the same config name (i.e., env prefix, YML file name, etc.) as AnyCable itself.
|
36
|
+
|
37
|
+
You can pass a config object as the option to `AnyCable::Rack::Server.new`:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
server = AnyCable::Server::Rack.new(config: AnyCable::Rack::Config.new(**params))
|
41
|
+
```
|
42
|
+
|
43
|
+
If no config is passed, a default, global, configuration would be used (`AnyCable::Rack.config`).
|
44
|
+
|
45
|
+
When using Rails, `config.anycable_rack` points to `AnyCable::Rack.config`.
|
46
|
+
|
47
|
+
### Headers
|
32
48
|
|
33
49
|
You can customize the headers being sent with each gRPC request.
|
34
50
|
|
35
51
|
Default headers: `'cookie', 'x-api-token'`.
|
36
52
|
|
37
|
-
Can be specified via
|
53
|
+
Can be specified via configuration:
|
38
54
|
|
39
55
|
```ruby
|
40
|
-
|
41
|
-
rpc_host: "localhost:50051",
|
42
|
-
headers: ["cookie", "x-my-header"]
|
43
|
-
)
|
56
|
+
AnyCable::Rack.config.headers = ["cookie", "x-my-header"]
|
44
57
|
```
|
45
58
|
|
46
|
-
|
59
|
+
Or in Rails:
|
47
60
|
|
48
61
|
```ruby
|
49
62
|
# <environment>.rb
|
50
63
|
config.any_cable_rack.headers = %w[cookie]
|
64
|
+
```
|
65
|
+
|
66
|
+
### Rails-specific options
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
# Mount WebSocket server at the specified path
|
51
70
|
config.any_cable_rack.mount_path = "/cable"
|
52
71
|
# NOTE: here we specify only the port (we assume that a server is running locally)
|
53
72
|
config.any_cable_rack.rpc_port = 50051
|
54
73
|
```
|
55
74
|
|
75
|
+
## Broadcast adapters
|
76
|
+
|
77
|
+
AnyCable Rack supports Redis (default) and HTTP broadcast adapters (see [the documentation](https://docs.anycable.io/v1/#/ruby/broadcast_adapters)).
|
78
|
+
|
79
|
+
Broadcast adapter is inherited from AnyCable configuration (so, you don't need to configure it twice).
|
80
|
+
|
81
|
+
### Using HTTP broadcast adapter
|
82
|
+
|
83
|
+
### With Rack
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
AnyCable::Rack.config.broadast_adapter = :http
|
87
|
+
|
88
|
+
ws_server = AnyCable::Rack::Server
|
89
|
+
|
90
|
+
app = Rack::Builder.new do
|
91
|
+
map "/cable" do
|
92
|
+
run ws_server
|
93
|
+
end
|
94
|
+
|
95
|
+
map "/_anycable_rack_broadcast" do
|
96
|
+
run ws_server.broadcast
|
97
|
+
end
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
### With Rails
|
102
|
+
|
103
|
+
By default, we mount broadcasts endpoint at `/_anycable_rack_broadcast`.
|
104
|
+
|
105
|
+
You can change this setting:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
config.any_cable_rack.http_broadcast_path = "/_my_broadcast"
|
109
|
+
```
|
110
|
+
|
111
|
+
**NOTE:** Don't forget to configure `http_broadcast_url` for AnyCable pointing to your web server and the specified broadcast path.
|
112
|
+
|
56
113
|
## Running RPC from the same process
|
57
114
|
|
58
115
|
The goal of the Rack server is to simplify the development/testing process. But we still have to run the RPC server.
|
59
116
|
|
60
|
-
This gem also provides a way to run RPC server
|
117
|
+
This gem also provides a way to run RPC server within the same process.
|
118
|
+
All you need to do is set `run_rpc = true` in the configuration:
|
61
119
|
|
62
120
|
```ruby
|
63
121
|
# in Rack app
|
122
|
+
AnyCable::Rack.config.run_rpc = true
|
64
123
|
|
65
|
-
|
66
|
-
|
67
|
-
rpc_host: "...", # optional host to run RPC server on (defaults to '[::]::50051')
|
68
|
-
command_args: [] # additional CLI arguments
|
69
|
-
)
|
124
|
+
# and only after that
|
125
|
+
ws_server.start!
|
70
126
|
|
71
|
-
# in Rails
|
72
|
-
# and we'll take care of it
|
127
|
+
# in Rails
|
73
128
|
config.any_cable_rack.run_rpc = true
|
74
129
|
```
|
75
130
|
|
@@ -79,8 +134,8 @@ Run units with `bundle exec rake`.
|
|
79
134
|
|
80
135
|
## Contributing
|
81
136
|
|
82
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/anycable/anycable-rack-server.
|
137
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/anycable/anycable-rack-server](https://github.com/anycable/anycable-rack-server).
|
83
138
|
|
84
139
|
## License
|
85
140
|
|
86
|
-
The gem is available as open source under the terms of the [MIT License](
|
141
|
+
The gem is available as open source under the terms of the [MIT License](./LICENSE).
|
data/lib/anycable-rack-server.rb
CHANGED
@@ -1,4 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "anycable/rack/config"
|
3
4
|
require "anycable/rack/server"
|
5
|
+
|
6
|
+
module AnyCable
|
7
|
+
module Rack
|
8
|
+
class << self
|
9
|
+
def config
|
10
|
+
@config ||= Config.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def rpc_server
|
14
|
+
return @rpc_server if instance_variable_defined?(:@rpc_server)
|
15
|
+
|
16
|
+
require "anycable/cli"
|
17
|
+
@rpc_server = AnyCable::CLI.new(embedded: true)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
4
23
|
require "anycable/rack/railtie" if defined?(Rails)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module AnyCable
|
6
|
+
module Rack
|
7
|
+
module BroadcastSubscribers
|
8
|
+
class BaseSubscriber
|
9
|
+
include Logging
|
10
|
+
|
11
|
+
attr_reader :hub, :coder
|
12
|
+
|
13
|
+
def initialize(hub:, coder:, **options)
|
14
|
+
@hub = hub
|
15
|
+
@coder = coder
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
# no-op
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop
|
23
|
+
# no-op
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def handle_message(msg)
|
29
|
+
log(:debug) { "Received pub/sub message: #{msg}" }
|
30
|
+
|
31
|
+
data = JSON.parse(msg)
|
32
|
+
if data["stream"]
|
33
|
+
hub.broadcast(data["stream"], data["data"], coder)
|
34
|
+
elsif data["command"] == "disconnect"
|
35
|
+
hub.disconnect(data["payload"]["identifier"], data["payload"]["reconnect"])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module AnyCable
|
6
|
+
module Rack
|
7
|
+
module BroadcastSubscribers
|
8
|
+
# HTTP Pub/Sub subscriber
|
9
|
+
class HTTPSubscriber < BaseSubscriber
|
10
|
+
attr_reader :token, :path
|
11
|
+
|
12
|
+
def initialize(**options)
|
13
|
+
super
|
14
|
+
@token = options[:token]
|
15
|
+
@path = options[:path]
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
log(:info) { "Accepting pub/sub request at #{path}" }
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
req = ::Rack::Request.new(env)
|
24
|
+
|
25
|
+
return invalid_request unless req.post?
|
26
|
+
|
27
|
+
if token && req.get_header("HTTP_AUTHORIZATION") != "Bearer #{token}"
|
28
|
+
return invalid_request(401)
|
29
|
+
end
|
30
|
+
|
31
|
+
handle_message req.body.read
|
32
|
+
|
33
|
+
[201, {"Content-Type" => "text/plain"}, ["OK"]]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def invalid_request(code = 422)
|
39
|
+
[code, {"Content-Type" => "text/plain"}, ["Invalid request"]]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
gem "redis", "~> 4"
|
4
|
+
|
3
5
|
require "redis"
|
4
6
|
require "json"
|
5
7
|
|
@@ -7,34 +9,47 @@ module AnyCable
|
|
7
9
|
module Rack
|
8
10
|
module BroadcastSubscribers
|
9
11
|
# Redis Pub/Sub subscriber
|
10
|
-
class RedisSubscriber
|
11
|
-
attr_reader :
|
12
|
+
class RedisSubscriber < BaseSubscriber
|
13
|
+
attr_reader :redis_conn, :thread, :channel
|
12
14
|
|
13
|
-
def initialize(hub:, coder:, **options)
|
14
|
-
|
15
|
-
@coder = coder
|
15
|
+
def initialize(hub:, coder:, channel:, **options)
|
16
|
+
super
|
16
17
|
@redis_conn = ::Redis.new(options)
|
17
|
-
@
|
18
|
+
@channel = channel
|
18
19
|
end
|
19
20
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
-
end
|
21
|
+
def start
|
22
|
+
subscribe(channel)
|
23
|
+
|
24
|
+
log(:info) { "Subscribed to #{channel}" }
|
26
25
|
end
|
27
26
|
|
28
|
-
def
|
29
|
-
|
30
|
-
@threads.delete(channel)
|
27
|
+
def stop
|
28
|
+
thread&.terminate
|
31
29
|
end
|
32
30
|
|
33
|
-
|
31
|
+
def subscribe(channel)
|
32
|
+
@thread ||= Thread.new do
|
33
|
+
Thread.current.abort_on_exception = true
|
34
|
+
|
35
|
+
redis_conn.without_reconnect do
|
36
|
+
redis_conn.subscribe(channel) do |on|
|
37
|
+
on.subscribe do |chan, count|
|
38
|
+
log(:debug) { "Redis subscriber connected to #{chan} (#{count})" }
|
39
|
+
end
|
40
|
+
|
41
|
+
on.unsubscribe do |chan, count|
|
42
|
+
log(:debug) { "Redis subscribed disconnected from #{chan} (#{count})" }
|
43
|
+
end
|
34
44
|
|
35
|
-
|
36
|
-
|
37
|
-
|
45
|
+
on.message do |_channel, msg|
|
46
|
+
handle_message(msg)
|
47
|
+
rescue
|
48
|
+
log(:error) { "Failed to broadcast message: #{msg}" }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
38
53
|
end
|
39
54
|
end
|
40
55
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "anyway_config"
|
4
|
+
|
5
|
+
module AnyCable
|
6
|
+
module Rack
|
7
|
+
class Config < Anyway::Config
|
8
|
+
DEFAULT_HEADERS = %w[cookie x-api-token].freeze
|
9
|
+
|
10
|
+
config_name :anycable
|
11
|
+
env_prefix "ANYCABLE"
|
12
|
+
|
13
|
+
attr_config mount_path: "/cable",
|
14
|
+
headers: DEFAULT_HEADERS,
|
15
|
+
rpc_addr: "localhost:50051",
|
16
|
+
rpc_client_pool_size: 5,
|
17
|
+
rpc_client_timeout: 5,
|
18
|
+
http_broadcast_path: "/_anycable_rack_broadcast",
|
19
|
+
run_rpc: false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -14,23 +14,24 @@ module AnyCable
|
|
14
14
|
include Logging
|
15
15
|
|
16
16
|
attr_reader :coder,
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
:headers,
|
18
|
+
:hub,
|
19
|
+
:socket,
|
20
|
+
:rpc_client,
|
21
|
+
:sid
|
22
22
|
|
23
|
-
def initialize(socket, hub:, coder:,
|
23
|
+
def initialize(socket, hub:, coder:, rpc_client:, headers:)
|
24
24
|
@socket = socket
|
25
25
|
@coder = coder
|
26
26
|
@headers = headers
|
27
27
|
@hub = hub
|
28
28
|
@sid = SecureRandom.hex(6)
|
29
29
|
|
30
|
-
@rpc_client =
|
30
|
+
@rpc_client = rpc_client
|
31
31
|
|
32
|
-
@_identifiers
|
32
|
+
@_identifiers = "{}"
|
33
33
|
@_subscriptions = Set.new
|
34
|
+
@_istate = {}
|
34
35
|
end
|
35
36
|
|
36
37
|
def handle_open
|
@@ -53,13 +54,13 @@ module AnyCable
|
|
53
54
|
log(:debug) { "Command: #{decoded}" }
|
54
55
|
|
55
56
|
case command
|
56
|
-
when "subscribe"
|
57
|
+
when "subscribe" then subscribe(channel_identifier)
|
57
58
|
when "unsubscribe" then unsubscribe(channel_identifier)
|
58
|
-
when "message"
|
59
|
+
when "message" then send_message(channel_identifier, decoded["data"])
|
59
60
|
else
|
60
61
|
log(:error, "Command not found #{command}")
|
61
62
|
end
|
62
|
-
rescue Exception => e
|
63
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
63
64
|
log(:error, "Failed to execute command #{command}: #{e.message}")
|
64
65
|
end
|
65
66
|
|
@@ -77,12 +78,8 @@ module AnyCable
|
|
77
78
|
socket.request
|
78
79
|
end
|
79
80
|
|
80
|
-
def request_path
|
81
|
-
request.fullpath
|
82
|
-
end
|
83
|
-
|
84
81
|
def rpc_connect
|
85
|
-
rpc_client.connect(headers: headers,
|
82
|
+
rpc_client.connect(headers: headers, url: request.url)
|
86
83
|
end
|
87
84
|
|
88
85
|
def rpc_disconnect
|
@@ -90,7 +87,8 @@ module AnyCable
|
|
90
87
|
identifiers: @_identifiers,
|
91
88
|
subscriptions: @_subscriptions.to_a,
|
92
89
|
headers: headers,
|
93
|
-
|
90
|
+
url: request.url,
|
91
|
+
state: @_cstate
|
94
92
|
)
|
95
93
|
end
|
96
94
|
|
@@ -99,7 +97,11 @@ module AnyCable
|
|
99
97
|
command: command,
|
100
98
|
identifier: identifier,
|
101
99
|
connection_identifiers: @_identifiers,
|
102
|
-
data: data
|
100
|
+
data: data,
|
101
|
+
headers: headers,
|
102
|
+
url: request.url,
|
103
|
+
connection_state: @_cstate,
|
104
|
+
state: @_istate[identifier]
|
103
105
|
)
|
104
106
|
end
|
105
107
|
|
@@ -133,13 +135,20 @@ module AnyCable
|
|
133
135
|
response.transmissions.each { |transmission| transmit(decode(transmission)) }
|
134
136
|
hub.remove_channel(socket, identifier) if response.stop_streams
|
135
137
|
response.streams.each { |stream| hub.add_subscriber(stream, socket, identifier) }
|
138
|
+
response.stopped_streams.each { |stream| hub.remove_subscriber(stream, socket, identifier) }
|
139
|
+
|
140
|
+
@_istate[identifier] ||= {}
|
141
|
+
@_istate[identifier].merge!(response.env.istate&.to_h || {})
|
142
|
+
|
136
143
|
close_connection if response.disconnect
|
137
144
|
end
|
138
145
|
|
139
146
|
def process_open(response)
|
147
|
+
response.transmissions&.each { |transmission| transmit(decode(transmission)) }
|
140
148
|
if response.status == :SUCCESS
|
141
149
|
@_identifiers = response.identifiers
|
142
|
-
response.
|
150
|
+
@_cstate = response.env.cstate&.to_h || {}
|
151
|
+
hub.add_socket(socket, @_identifiers)
|
143
152
|
log(:debug) { "Opened" }
|
144
153
|
else
|
145
154
|
log(:error, "RPC connection command failed: #{response.inspect}")
|
data/lib/anycable/rack/hub.rb
CHANGED
@@ -6,6 +6,8 @@ module AnyCable
|
|
6
6
|
module Rack
|
7
7
|
# From https://github.com/rails/rails/blob/v5.0.1/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb
|
8
8
|
class Hub
|
9
|
+
INTERNAL_STREAM = :__internal__
|
10
|
+
|
9
11
|
attr_reader :streams, :sockets
|
10
12
|
|
11
13
|
def initialize
|
@@ -16,6 +18,12 @@ module AnyCable
|
|
16
18
|
@sync = Mutex.new
|
17
19
|
end
|
18
20
|
|
21
|
+
def add_socket(socket, identifier)
|
22
|
+
@sync.synchronize do
|
23
|
+
@streams[INTERNAL_STREAM][identifier] << socket
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
19
27
|
def add_subscriber(stream, socket, channel)
|
20
28
|
@sync.synchronize do
|
21
29
|
@streams[stream][channel] << socket
|
@@ -69,6 +77,25 @@ module AnyCable
|
|
69
77
|
end
|
70
78
|
end
|
71
79
|
|
80
|
+
def broadcast_all(message)
|
81
|
+
sockets.each_key { |socket| socket.transmit(message) }
|
82
|
+
end
|
83
|
+
|
84
|
+
def disconnect(identifier, reconnect)
|
85
|
+
sockets = @sync.synchronize do
|
86
|
+
return unless @streams[INTERNAL_STREAM].key?(identifier)
|
87
|
+
|
88
|
+
@streams[INTERNAL_STREAM][identifier].to_a
|
89
|
+
end
|
90
|
+
|
91
|
+
msg = disconnect_message("remote", reconnect)
|
92
|
+
|
93
|
+
sockets.each do |socket|
|
94
|
+
socket.transmit(msg)
|
95
|
+
socket.close
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
72
99
|
def close_all
|
73
100
|
hub.sockets.dup.each do |socket|
|
74
101
|
hub.remove_socket(socket)
|
@@ -87,6 +114,11 @@ module AnyCable
|
|
87
114
|
def channel_message(channel_id, message, coder)
|
88
115
|
coder.encode(identifier: channel_id, message: message)
|
89
116
|
end
|
117
|
+
|
118
|
+
# FIXME: coder support?
|
119
|
+
def disconnect_message(reason, reconnect)
|
120
|
+
{type: :disconnect, reason: reason, reconnect: reconnect}.to_json
|
121
|
+
end
|
90
122
|
end
|
91
123
|
end
|
92
124
|
end
|
@@ -11,16 +11,16 @@ module AnyCable
|
|
11
11
|
class Middleware # :nodoc:
|
12
12
|
PROTOCOLS = ["actioncable-v1-json", "actioncable-unsupported"].freeze
|
13
13
|
attr_reader :pinger,
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
def initialize(pinger:, hub:, coder:,
|
20
|
-
@pinger
|
21
|
-
@hub
|
22
|
-
@coder
|
23
|
-
@
|
14
|
+
:hub,
|
15
|
+
:coder,
|
16
|
+
:rpc_client,
|
17
|
+
:header_names
|
18
|
+
|
19
|
+
def initialize(pinger:, hub:, coder:, rpc_client:, header_names:)
|
20
|
+
@pinger = pinger
|
21
|
+
@hub = hub
|
22
|
+
@coder = coder
|
23
|
+
@rpc_client = rpc_client
|
24
24
|
@header_names = header_names
|
25
25
|
end
|
26
26
|
|
@@ -59,7 +59,7 @@ module AnyCable
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def not_found
|
62
|
-
[404, {
|
62
|
+
[404, {"Content-Type" => "text/plain"}, ["Not Found"]]
|
63
63
|
end
|
64
64
|
|
65
65
|
def websocket?(env)
|
@@ -71,7 +71,7 @@ module AnyCable
|
|
71
71
|
socket,
|
72
72
|
hub: hub,
|
73
73
|
coder: coder,
|
74
|
-
|
74
|
+
rpc_client: rpc_client,
|
75
75
|
headers: fetch_headers(socket.request)
|
76
76
|
)
|
77
77
|
socket.onopen { connection.handle_open }
|
@@ -86,8 +86,8 @@ module AnyCable
|
|
86
86
|
|
87
87
|
def fetch_headers(request)
|
88
88
|
header_names.each_with_object({}) do |name, acc|
|
89
|
-
header_val = request.env["HTTP_#{name.tr(
|
90
|
-
acc[name]
|
89
|
+
header_val = request.env["HTTP_#{name.tr("-", "_").upcase}"]
|
90
|
+
acc[name] = header_val unless header_val.nil? || header_val.empty?
|
91
91
|
end
|
92
92
|
end
|
93
93
|
end
|
data/lib/anycable/rack/pinger.rb
CHANGED
@@ -3,48 +3,24 @@
|
|
3
3
|
module AnyCable
|
4
4
|
module Rack
|
5
5
|
class Railtie < ::Rails::Railtie # :nodoc: all
|
6
|
-
class Config < Anyway::Config
|
7
|
-
config_name :anycable_rack
|
8
|
-
env_prefix "ANYCABLE_RACK"
|
9
|
-
|
10
|
-
attr_config mount_path: "/cable",
|
11
|
-
headers: AnyCable::Rack::Server::DEFAULT_HEADERS,
|
12
|
-
rpc_port: 50_051,
|
13
|
-
rpc_host: "localhost",
|
14
|
-
run_rpc: false,
|
15
|
-
running_rpc: false
|
16
|
-
|
17
|
-
private :running_rpc=
|
18
|
-
end
|
19
|
-
|
20
6
|
config.before_configuration do
|
21
|
-
config.any_cable_rack =
|
7
|
+
config.any_cable_rack = AnyCable::Rack.config
|
22
8
|
end
|
23
9
|
|
24
10
|
initializer "anycable.rack.mount", after: "action_cable.routes" do
|
25
11
|
config.after_initialize do |app|
|
26
12
|
config = app.config.any_cable_rack
|
27
13
|
|
28
|
-
|
29
|
-
next unless ::ActionCable.server.config.cable&.fetch("adapter", nil) == "any_cable"
|
14
|
+
next unless config.mount_path
|
30
15
|
|
31
|
-
server = AnyCable::Rack::Server.new
|
32
|
-
headers: config.headers,
|
33
|
-
rpc_host: "#{config.rpc_host}:#{config.rpc_port}"
|
34
|
-
)
|
16
|
+
server = AnyCable::Rack::Server.new
|
35
17
|
|
36
18
|
app.routes.prepend do
|
37
19
|
mount server => config.mount_path
|
38
|
-
end
|
39
20
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
root_dir: ::Rails.root.to_s,
|
44
|
-
env: {
|
45
|
-
"ANYCABLE_RACK_RUNNING_RPC" => "true"
|
46
|
-
}
|
47
|
-
)
|
21
|
+
if AnyCable.config.broadcast_adapter.to_s == "http"
|
22
|
+
mount server.broadcast => config.http_broadcast_path
|
23
|
+
end
|
48
24
|
end
|
49
25
|
|
50
26
|
server.start!
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "connection_pool"
|
3
4
|
require "grpc"
|
4
5
|
|
5
6
|
module AnyCable
|
@@ -7,35 +8,53 @@ module AnyCable
|
|
7
8
|
module RPC
|
8
9
|
# AnyCable RPC client
|
9
10
|
class Client
|
10
|
-
attr_reader :
|
11
|
+
attr_reader :pool, :metadata
|
11
12
|
|
12
|
-
def initialize(host)
|
13
|
-
@
|
13
|
+
def initialize(host:, size:, timeout:)
|
14
|
+
@pool = ConnectionPool.new(size: size, timeout: timeout) do
|
15
|
+
AnyCable::RPC::Service.rpc_stub_class.new(host, :this_channel_is_insecure)
|
16
|
+
end
|
17
|
+
@metadata = {metadata: {"protov" => "v1"}}.freeze
|
14
18
|
end
|
15
19
|
|
16
|
-
def connect(headers:,
|
17
|
-
request = ConnectionRequest.new(headers: headers,
|
18
|
-
stub
|
20
|
+
def connect(headers:, url:)
|
21
|
+
request = ConnectionRequest.new(env: Env.new(headers: headers, url: url))
|
22
|
+
pool.with do |stub|
|
23
|
+
stub.connect(request, metadata)
|
24
|
+
end
|
19
25
|
end
|
20
26
|
|
21
|
-
def command(command:, identifier:, connection_identifiers:, data:)
|
27
|
+
def command(command:, identifier:, connection_identifiers:, data:, headers:, url:, connection_state: nil, state: nil)
|
22
28
|
message = CommandMessage.new(
|
23
29
|
command: command,
|
24
30
|
identifier: identifier,
|
25
31
|
connection_identifiers: connection_identifiers,
|
26
|
-
data: data
|
32
|
+
data: data,
|
33
|
+
env: Env.new(
|
34
|
+
headers: headers,
|
35
|
+
url: url,
|
36
|
+
cstate: connection_state,
|
37
|
+
istate: state
|
38
|
+
)
|
27
39
|
)
|
28
|
-
stub
|
40
|
+
pool.with do |stub|
|
41
|
+
stub.command(message, metadata)
|
42
|
+
end
|
29
43
|
end
|
30
44
|
|
31
|
-
def disconnect(identifiers:, subscriptions:, headers:,
|
45
|
+
def disconnect(identifiers:, subscriptions:, headers:, url:, state: nil)
|
32
46
|
request = DisconnectRequest.new(
|
33
47
|
identifiers: identifiers,
|
34
48
|
subscriptions: subscriptions,
|
35
|
-
|
36
|
-
|
49
|
+
env: Env.new(
|
50
|
+
headers: headers,
|
51
|
+
url: url,
|
52
|
+
cstate: state
|
53
|
+
)
|
37
54
|
)
|
38
|
-
stub
|
55
|
+
pool.with do |stub|
|
56
|
+
stub.disconnect(request, metadata)
|
57
|
+
end
|
39
58
|
end
|
40
59
|
end
|
41
60
|
end
|
@@ -14,9 +14,20 @@ enum Status {
|
|
14
14
|
FAILURE = 2;
|
15
15
|
}
|
16
16
|
|
17
|
-
message
|
18
|
-
string
|
17
|
+
message Env {
|
18
|
+
string url = 1;
|
19
19
|
map<string,string> headers = 2;
|
20
|
+
map<string,string> cstate = 3;
|
21
|
+
map<string,string> istate = 4;
|
22
|
+
}
|
23
|
+
|
24
|
+
message EnvResponse {
|
25
|
+
map<string,string> cstate = 1;
|
26
|
+
map<string,string> istate = 2;
|
27
|
+
}
|
28
|
+
|
29
|
+
message ConnectionRequest {
|
30
|
+
Env env = 3;
|
20
31
|
}
|
21
32
|
|
22
33
|
message ConnectionResponse {
|
@@ -24,6 +35,7 @@ message ConnectionResponse {
|
|
24
35
|
string identifiers = 2;
|
25
36
|
repeated string transmissions = 3;
|
26
37
|
string error_msg = 4;
|
38
|
+
EnvResponse env = 5;
|
27
39
|
}
|
28
40
|
|
29
41
|
message CommandMessage {
|
@@ -31,6 +43,7 @@ message CommandMessage {
|
|
31
43
|
string identifier = 2;
|
32
44
|
string connection_identifiers = 3;
|
33
45
|
string data = 4;
|
46
|
+
Env env = 5;
|
34
47
|
}
|
35
48
|
|
36
49
|
message CommandResponse {
|
@@ -40,13 +53,14 @@ message CommandResponse {
|
|
40
53
|
repeated string streams = 4;
|
41
54
|
repeated string transmissions = 5;
|
42
55
|
string error_msg = 6;
|
56
|
+
EnvResponse env = 7;
|
57
|
+
repeated string stopped_streams = 8;
|
43
58
|
}
|
44
59
|
|
45
60
|
message DisconnectRequest {
|
46
61
|
string identifiers = 1;
|
47
62
|
repeated string subscriptions = 2;
|
48
|
-
|
49
|
-
map<string,string> headers = 4;
|
63
|
+
Env env = 5;
|
50
64
|
}
|
51
65
|
|
52
66
|
message DisconnectResponse {
|
data/lib/anycable/rack/server.rb
CHANGED
@@ -7,8 +7,7 @@ require "anycable/rack/pinger"
|
|
7
7
|
require "anycable/rack/errors"
|
8
8
|
require "anycable/rack/middleware"
|
9
9
|
require "anycable/rack/logging"
|
10
|
-
require "anycable/rack/
|
11
|
-
require "anycable/rack/broadcast_subscribers/redis_subscriber"
|
10
|
+
require "anycable/rack/broadcast_subscribers/base_subscriber"
|
12
11
|
require "anycable/rack/coders/json"
|
13
12
|
|
14
13
|
module AnyCable # :nodoc: all
|
@@ -16,43 +15,39 @@ module AnyCable # :nodoc: all
|
|
16
15
|
class Server
|
17
16
|
include Logging
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
options = args.last.is_a?(Hash) ? args.last : {}
|
32
|
-
|
18
|
+
attr_reader :config,
|
19
|
+
:broadcast,
|
20
|
+
:coder,
|
21
|
+
:hub,
|
22
|
+
:middleware,
|
23
|
+
:pinger,
|
24
|
+
:rpc_client,
|
25
|
+
:headers,
|
26
|
+
:rpc_cli
|
27
|
+
|
28
|
+
def initialize(config: AnyCable::Rack.config)
|
29
|
+
@config = config
|
33
30
|
@hub = Hub.new
|
34
31
|
@pinger = Pinger.new
|
35
|
-
|
36
|
-
@
|
37
|
-
|
38
|
-
@
|
39
|
-
@
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
coder: coder,
|
44
|
-
**AnyCable.config.to_redis_params
|
32
|
+
# TODO: Support other coders
|
33
|
+
@coder = Coders::JSON
|
34
|
+
|
35
|
+
@broadcast = resolve_broadcast_adapter
|
36
|
+
@rpc_client = RPC::Client.new(
|
37
|
+
host: config.rpc_addr,
|
38
|
+
size: config.rpc_client_pool_size,
|
39
|
+
timeout: config.rpc_client_timeout
|
45
40
|
)
|
46
41
|
|
47
42
|
@middleware = Middleware.new(
|
48
|
-
header_names: headers,
|
43
|
+
header_names: config.headers,
|
49
44
|
pinger: pinger,
|
50
45
|
hub: hub,
|
51
|
-
|
46
|
+
rpc_client: rpc_client,
|
52
47
|
coder: coder
|
53
48
|
)
|
54
49
|
|
55
|
-
log(:info) { "
|
50
|
+
log(:info) { "Connecting to RPC server at #{config.rpc_addr}" }
|
56
51
|
end
|
57
52
|
# rubocop:enable
|
58
53
|
|
@@ -61,13 +56,19 @@ module AnyCable # :nodoc: all
|
|
61
56
|
|
62
57
|
pinger.run
|
63
58
|
|
64
|
-
broadcast.
|
59
|
+
broadcast.start
|
65
60
|
|
66
|
-
|
61
|
+
Rack.rpc_server.run if config.run_rpc
|
67
62
|
|
68
63
|
@_started = true
|
69
64
|
end
|
70
65
|
|
66
|
+
def shutdown
|
67
|
+
log(:info) { "Shutting down..." }
|
68
|
+
Rack.rpc_server&.shutdown
|
69
|
+
hub.broadcast_all(coder.encode(type: "disconnect", reason: "server_restart", reconnect: true))
|
70
|
+
end
|
71
|
+
|
71
72
|
def started?
|
72
73
|
@_started == true
|
73
74
|
end
|
@@ -76,7 +77,7 @@ module AnyCable # :nodoc: all
|
|
76
77
|
return unless started?
|
77
78
|
|
78
79
|
@_started = false
|
79
|
-
broadcast_subscriber.
|
80
|
+
broadcast_subscriber.stop
|
80
81
|
pinger.stop
|
81
82
|
hub.close_all
|
82
83
|
end
|
@@ -86,7 +87,32 @@ module AnyCable # :nodoc: all
|
|
86
87
|
end
|
87
88
|
|
88
89
|
def inspect
|
89
|
-
"#<AnyCable::Rack::Server(
|
90
|
+
"#<AnyCable::Rack::Server(rpc_addr: #{config.rpc_addr}, headers: [#{config.headers.join(", ")}])>"
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def resolve_broadcast_adapter
|
96
|
+
adapter = AnyCable.config.broadcast_adapter.to_s
|
97
|
+
require "anycable/rack/broadcast_subscribers/#{adapter}_subscriber"
|
98
|
+
|
99
|
+
if adapter.to_s == "redis"
|
100
|
+
BroadcastSubscribers::RedisSubscriber.new(
|
101
|
+
hub: hub,
|
102
|
+
coder: coder,
|
103
|
+
channel: AnyCable.config.redis_channel,
|
104
|
+
**AnyCable.config.to_redis_params
|
105
|
+
)
|
106
|
+
elsif adapter.to_s == "http"
|
107
|
+
BroadcastSubscribers::HTTPSubscriber.new(
|
108
|
+
hub: hub,
|
109
|
+
coder: coder,
|
110
|
+
token: AnyCable.config.http_broadcast_secret,
|
111
|
+
path: config.http_broadcast_path
|
112
|
+
)
|
113
|
+
else
|
114
|
+
raise ArgumentError, "Unsupported broadcast adatper: #{adapter}. AnyCable Rack server only supports: redis, http"
|
115
|
+
end
|
90
116
|
end
|
91
117
|
end
|
92
118
|
end
|
data/lib/anycable/rack/socket.rb
CHANGED
@@ -15,11 +15,11 @@ module AnyCable
|
|
15
15
|
@socket = socket
|
16
16
|
@version = version
|
17
17
|
|
18
|
-
@_open_handlers
|
18
|
+
@_open_handlers = []
|
19
19
|
@_message_handlers = []
|
20
|
-
@_close_handlers
|
21
|
-
@_error_handlers
|
22
|
-
@_active
|
20
|
+
@_close_handlers = []
|
21
|
+
@_error_handlers = []
|
22
|
+
@_active = true
|
23
23
|
end
|
24
24
|
|
25
25
|
def transmit(data, type: :text)
|
@@ -29,7 +29,7 @@ module AnyCable
|
|
29
29
|
type: type
|
30
30
|
)
|
31
31
|
socket.write(frame.to_s)
|
32
|
-
rescue
|
32
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
33
33
|
log(:error, "Socket send failed: #{e}")
|
34
34
|
close
|
35
35
|
end
|
@@ -62,13 +62,11 @@ module AnyCable
|
|
62
62
|
@_open_handlers.each(&:call)
|
63
63
|
each_frame do |data|
|
64
64
|
@_message_handlers.each do |handler|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
close
|
71
|
-
end
|
65
|
+
handler.call(data)
|
66
|
+
rescue => e # rubocop: disable Style/RescueStandardError
|
67
|
+
log(:error, "Socket receive failed: #{e}")
|
68
|
+
@_error_handlers.each { |eh| eh.call(e, data) }
|
69
|
+
close
|
72
70
|
end
|
73
71
|
end
|
74
72
|
ensure
|
@@ -104,7 +102,7 @@ module AnyCable
|
|
104
102
|
frame = WebSocket::Frame::Outgoing::Server.new(version: version, type: :close, code: 1000)
|
105
103
|
socket.write(frame.to_s) if frame.supported?
|
106
104
|
socket.close
|
107
|
-
rescue
|
105
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
108
106
|
# already closed
|
109
107
|
end
|
110
108
|
|
@@ -113,8 +111,7 @@ module AnyCable
|
|
113
111
|
Thread.current.abort_on_exception = true
|
114
112
|
loop do
|
115
113
|
sleep 5
|
116
|
-
|
117
|
-
transmit({ message: time, type: :ping }.to_json)
|
114
|
+
transmit nil, type: :ping
|
118
115
|
end
|
119
116
|
end
|
120
117
|
|
@@ -137,7 +134,7 @@ module AnyCable
|
|
137
134
|
|
138
135
|
framebuffer << data
|
139
136
|
|
140
|
-
while frame = framebuffer.next
|
137
|
+
while frame = framebuffer.next # rubocop:disable Lint/AssignmentInCondition
|
141
138
|
case frame.type
|
142
139
|
when :close
|
143
140
|
return
|
@@ -146,7 +143,7 @@ module AnyCable
|
|
146
143
|
end
|
147
144
|
end
|
148
145
|
end
|
149
|
-
rescue
|
146
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
150
147
|
log(:error, "Socket frame error: #{e}")
|
151
148
|
nil # client disconnected or timed out
|
152
149
|
end
|
metadata
CHANGED
@@ -1,71 +1,86 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: anycable-rack-server
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yulia Oletskaya
|
8
|
+
- Vladimir Dementyev
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2020-06-25 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: anyway_config
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 1.4.2
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 1.4.2
|
13
28
|
- !ruby/object:Gem::Dependency
|
14
29
|
name: anycable
|
15
30
|
requirement: !ruby/object:Gem::Requirement
|
16
31
|
requirements:
|
17
|
-
- - "
|
32
|
+
- - ">="
|
18
33
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0
|
34
|
+
version: '0'
|
20
35
|
type: :runtime
|
21
36
|
prerelease: false
|
22
37
|
version_requirements: !ruby/object:Gem::Requirement
|
23
38
|
requirements:
|
24
|
-
- - "
|
39
|
+
- - ">="
|
25
40
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0
|
41
|
+
version: '0'
|
27
42
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
43
|
+
name: connection_pool
|
29
44
|
requirement: !ruby/object:Gem::Requirement
|
30
45
|
requirements:
|
31
46
|
- - "~>"
|
32
47
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
48
|
+
version: '2.2'
|
34
49
|
type: :runtime
|
35
50
|
prerelease: false
|
36
51
|
version_requirements: !ruby/object:Gem::Requirement
|
37
52
|
requirements:
|
38
53
|
- - "~>"
|
39
54
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
55
|
+
version: '2.2'
|
41
56
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
57
|
+
name: websocket
|
43
58
|
requirement: !ruby/object:Gem::Requirement
|
44
59
|
requirements:
|
45
60
|
- - "~>"
|
46
61
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
62
|
+
version: '1.2'
|
48
63
|
type: :runtime
|
49
64
|
prerelease: false
|
50
65
|
version_requirements: !ruby/object:Gem::Requirement
|
51
66
|
requirements:
|
52
67
|
- - "~>"
|
53
68
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
69
|
+
version: '1.2'
|
55
70
|
- !ruby/object:Gem::Dependency
|
56
71
|
name: anyt
|
57
72
|
requirement: !ruby/object:Gem::Requirement
|
58
73
|
requirements:
|
59
|
-
- - "
|
74
|
+
- - ">="
|
60
75
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0
|
76
|
+
version: '0'
|
62
77
|
type: :development
|
63
78
|
prerelease: false
|
64
79
|
version_requirements: !ruby/object:Gem::Requirement
|
65
80
|
requirements:
|
66
|
-
- - "
|
81
|
+
- - ">="
|
67
82
|
- !ruby/object:Gem::Version
|
68
|
-
version: 0
|
83
|
+
version: '0'
|
69
84
|
- !ruby/object:Gem::Dependency
|
70
85
|
name: minitest
|
71
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -96,32 +111,46 @@ dependencies:
|
|
96
111
|
version: '0'
|
97
112
|
- !ruby/object:Gem::Dependency
|
98
113
|
name: rake
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '13.0'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '13.0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: redis
|
99
128
|
requirement: !ruby/object:Gem::Requirement
|
100
129
|
requirements:
|
101
130
|
- - "~>"
|
102
131
|
- !ruby/object:Gem::Version
|
103
|
-
version: '
|
132
|
+
version: '4'
|
104
133
|
type: :development
|
105
134
|
prerelease: false
|
106
135
|
version_requirements: !ruby/object:Gem::Requirement
|
107
136
|
requirements:
|
108
137
|
- - "~>"
|
109
138
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
139
|
+
version: '4'
|
111
140
|
- !ruby/object:Gem::Dependency
|
112
141
|
name: rubocop
|
113
142
|
requirement: !ruby/object:Gem::Requirement
|
114
143
|
requirements:
|
115
|
-
- - "
|
144
|
+
- - ">="
|
116
145
|
- !ruby/object:Gem::Version
|
117
|
-
version: 0.
|
146
|
+
version: '0.80'
|
118
147
|
type: :development
|
119
148
|
prerelease: false
|
120
149
|
version_requirements: !ruby/object:Gem::Requirement
|
121
150
|
requirements:
|
122
|
-
- - "
|
151
|
+
- - ">="
|
123
152
|
- !ruby/object:Gem::Version
|
124
|
-
version: 0.
|
153
|
+
version: '0.80'
|
125
154
|
description: AnyCable-compatible Ruby Rack middleware
|
126
155
|
email: yulia.oletskaya@gmail.com
|
127
156
|
executables: []
|
@@ -131,8 +160,11 @@ files:
|
|
131
160
|
- LICENSE
|
132
161
|
- README.md
|
133
162
|
- lib/anycable-rack-server.rb
|
163
|
+
- lib/anycable/rack/broadcast_subscribers/base_subscriber.rb
|
164
|
+
- lib/anycable/rack/broadcast_subscribers/http_subscriber.rb
|
134
165
|
- lib/anycable/rack/broadcast_subscribers/redis_subscriber.rb
|
135
166
|
- lib/anycable/rack/coders/json.rb
|
167
|
+
- lib/anycable/rack/config.rb
|
136
168
|
- lib/anycable/rack/connection.rb
|
137
169
|
- lib/anycable/rack/errors.rb
|
138
170
|
- lib/anycable/rack/hub.rb
|
@@ -142,7 +174,6 @@ files:
|
|
142
174
|
- lib/anycable/rack/railtie.rb
|
143
175
|
- lib/anycable/rack/rpc/client.rb
|
144
176
|
- lib/anycable/rack/rpc/rpc.proto
|
145
|
-
- lib/anycable/rack/rpc_runner.rb
|
146
177
|
- lib/anycable/rack/server.rb
|
147
178
|
- lib/anycable/rack/socket.rb
|
148
179
|
- lib/anycable/rack/version.rb
|
@@ -161,13 +192,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
161
192
|
version: '0'
|
162
193
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
194
|
requirements:
|
164
|
-
- - "
|
195
|
+
- - ">"
|
165
196
|
- !ruby/object:Gem::Version
|
166
|
-
version:
|
197
|
+
version: 1.3.1
|
167
198
|
requirements: []
|
168
|
-
|
169
|
-
rubygems_version: 2.7.6
|
199
|
+
rubygems_version: 3.0.6
|
170
200
|
signing_key:
|
171
201
|
specification_version: 4
|
172
|
-
summary:
|
202
|
+
summary: AnyCable Rack Server
|
173
203
|
test_files: []
|
@@ -1,60 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "anycable"
|
4
|
-
require "anycable/rack/logging"
|
5
|
-
|
6
|
-
$stdout.sync = true
|
7
|
-
|
8
|
-
module AnyCable
|
9
|
-
module Rack
|
10
|
-
# Runs AnyCable CLI in a separate process
|
11
|
-
module RPCRunner
|
12
|
-
class << self
|
13
|
-
include Logging
|
14
|
-
|
15
|
-
attr_accessor :running, :pid
|
16
|
-
|
17
|
-
def run(root_dir:, command_args: [], rpc_host: "[::]:50051", env: {})
|
18
|
-
return if @running
|
19
|
-
|
20
|
-
command_args << "--rpc-host=\"#{rpc_host}\""
|
21
|
-
|
22
|
-
command = "bundle exec anycable #{command_args.join(' ')}"
|
23
|
-
|
24
|
-
log(:info, "Running AnyCable (from #{root_dir}): #{command}")
|
25
|
-
|
26
|
-
out = AnyCable.config.debug? ? STDOUT : IO::NULL
|
27
|
-
|
28
|
-
@pid = Dir.chdir(root_dir) do
|
29
|
-
Process.spawn(
|
30
|
-
env,
|
31
|
-
command,
|
32
|
-
out: out,
|
33
|
-
err: out
|
34
|
-
)
|
35
|
-
end
|
36
|
-
|
37
|
-
log(:debug) { "AnyCable PID: #{pid}" }
|
38
|
-
|
39
|
-
@running = true
|
40
|
-
|
41
|
-
at_exit { stop }
|
42
|
-
end
|
43
|
-
|
44
|
-
def stop
|
45
|
-
return unless running
|
46
|
-
|
47
|
-
log(:debug) { "Terminate PID: #{pid}" }
|
48
|
-
|
49
|
-
Process.kill("SIGKILL", pid)
|
50
|
-
|
51
|
-
@running = false
|
52
|
-
end
|
53
|
-
|
54
|
-
def running?
|
55
|
-
running == true
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|