anycable 0.0.1 → 0.1.0
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/.rubocop.yml +1 -0
- data/Makefile +4 -0
- data/README.md +74 -2
- data/Rakefile +2 -0
- data/anycable.gemspec +3 -0
- data/bin/console +8 -0
- data/lib/anycable.rb +17 -0
- data/lib/anycable/actioncable/channel.rb +43 -0
- data/lib/anycable/actioncable/connection.rb +72 -0
- data/lib/anycable/actioncable/server.rb +14 -0
- data/lib/anycable/config.rb +14 -0
- data/lib/anycable/pubsub.rb +20 -0
- data/lib/anycable/rpc/rpc.rb +51 -0
- data/lib/anycable/rpc/rpc_services.rb +25 -0
- data/lib/anycable/rpc_handler.rb +147 -0
- data/lib/anycable/server.rb +28 -0
- data/lib/anycable/version.rb +1 -1
- data/lib/generators/anycable/USAGE +7 -0
- data/lib/generators/anycable/anycable_generator.rb +11 -0
- data/lib/generators/anycable/templates/script +8 -0
- data/lib/tasks/anycable_tasks.rake +16 -4
- data/protos/rpc.proto +52 -0
- metadata +58 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1728535555e8fb2b4117c3acc46a37c99f854b20
|
4
|
+
data.tar.gz: 50344774fb497a690c610a6e4d2758ab8237aa89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d1679838e7116f199f29e1ef51d156422da51daa52c6f6b61e42adf0d1d17e36c47f45614fa05f400757a3811d80e03ca26196cca1055c314a3f7b97b9dec523
|
7
|
+
data.tar.gz: 3cf4eb06da615053c914b86da466d77f3f4f9bc84265d3fcb4079ebd8e30ce451d9bbd2cf5fa62637f543bfc3fbe0f5ba21594e0870bef9e6c1fe6b53ad70c66
|
data/.rubocop.yml
CHANGED
data/Makefile
ADDED
data/README.md
CHANGED
@@ -2,15 +2,87 @@
|
|
2
2
|
|
3
3
|
# Anycable
|
4
4
|
|
5
|
+
AnyCable allows you to use any WebSocket server (written in any language) as a replacement for built-in Ruby ActionCable server.
|
6
|
+
|
7
|
+
With AnyCable you can use channels, client-side JS, broadcasting - (almost) all that you can do with ActionCable.
|
8
|
+
|
9
|
+
You can even use ActionCable in development and not be afraid of compatibility issues.
|
10
|
+
|
11
|
+
## Requirements
|
12
|
+
|
13
|
+
- Ruby ~> 2.3;
|
14
|
+
- Rails ~> 5.0;
|
15
|
+
- Redis
|
16
|
+
|
17
|
+
## How It Works?
|
18
|
+
|
5
19
|
TBD
|
6
20
|
|
7
|
-
##
|
21
|
+
## Compatible WebSocket servers
|
8
22
|
|
9
23
|
TBD
|
10
24
|
|
25
|
+
|
11
26
|
## Installation
|
12
27
|
|
13
|
-
|
28
|
+
Add Anycable to your application's Gemfile:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
gem 'anycable', group: :production
|
32
|
+
```
|
33
|
+
|
34
|
+
And then run:
|
35
|
+
|
36
|
+
```shell
|
37
|
+
rails generate anycable
|
38
|
+
```
|
39
|
+
|
40
|
+
to create executable.
|
41
|
+
|
42
|
+
You can use _built-in_ ActionCable for test and development.
|
43
|
+
|
44
|
+
## Configuration
|
45
|
+
|
46
|
+
Add `config/anycable.yml`if you want to override defaults (see below):
|
47
|
+
|
48
|
+
```yml
|
49
|
+
production:
|
50
|
+
# gRPC server host and port
|
51
|
+
rpc_host: "localhost:50051"
|
52
|
+
# Redis URL (for broadcasting)
|
53
|
+
redis_url: "redis://localhost:6379/2"
|
54
|
+
# Redis channel name
|
55
|
+
redis_channel: "anycable"
|
56
|
+
|
57
|
+
```
|
58
|
+
|
59
|
+
Anycable uses [anyway_config](https://github.com/palkan/anyway_config), thus it is also possible to set configuration variables through `secrets.yml` or environment vars.
|
60
|
+
|
61
|
+
## Usage
|
62
|
+
|
63
|
+
Run Anycable server:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
bundle exec anycable
|
67
|
+
```
|
68
|
+
|
69
|
+
## ActionCable Compatibility
|
70
|
+
|
71
|
+
|
72
|
+
Feature | Status
|
73
|
+
-------------------------|--------
|
74
|
+
Connection Identifiers | +
|
75
|
+
Connection Request (cookies, params) | +
|
76
|
+
Disconnect Handling | coming soon
|
77
|
+
Subscribe to channels | +
|
78
|
+
Parameterized subscriptions | coming soon
|
79
|
+
Unsubscribe from channels | +
|
80
|
+
[Subscription Instance Variables](http://edgeapi.rubyonrails.org/classes/ActionCable/Channel/Streams.html) | -
|
81
|
+
Performing Channel Actions | +
|
82
|
+
Streaming | +
|
83
|
+
[Custom stream callbacks](http://edgeapi.rubyonrails.org/classes/ActionCable/Channel/Streams.html) | -
|
84
|
+
Broadcasting | +
|
85
|
+
|
14
86
|
|
15
87
|
## Contributing
|
16
88
|
|
data/Rakefile
CHANGED
data/anycable.gemspec
CHANGED
@@ -18,6 +18,9 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
20
|
spec.add_dependency "rails", "~> 5"
|
21
|
+
spec.add_dependency "anyway_config", "~>0.4.0"
|
22
|
+
spec.add_dependency "grpc", "~> 1.0"
|
23
|
+
spec.add_dependency "redis", "~> 3.0"
|
21
24
|
|
22
25
|
spec.add_development_dependency "bundler", "~> 1"
|
23
26
|
spec.add_development_dependency "rake", "~> 10.0"
|
data/bin/console
ADDED
data/lib/anycable.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "anycable/version"
|
3
|
+
require "anycable/config"
|
4
|
+
require "anycable/actioncable/server"
|
3
5
|
|
4
6
|
# Anycable allows to use any websocket service (written in any language) as a replacement
|
5
7
|
# for ActionCable server.
|
@@ -9,4 +11,19 @@ require "anycable/version"
|
|
9
11
|
#
|
10
12
|
# Broadcasting messages to WS is done through Redis Pub/Sub.
|
11
13
|
module Anycable
|
14
|
+
def self.logger=(logger)
|
15
|
+
@logger = logger
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.logger
|
19
|
+
@logger ||= Anycable.config.debug ? Logger.new(STDOUT) : Logger.new('/dev/null')
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.config
|
23
|
+
@config ||= Config.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.configure
|
27
|
+
yield(config) if block_given?
|
28
|
+
end
|
12
29
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "action_cable"
|
3
|
+
|
4
|
+
module ActionCable
|
5
|
+
module Channel
|
6
|
+
class Base # :nodoc:
|
7
|
+
alias do_subscribe subscribe_to_channel
|
8
|
+
|
9
|
+
public :do_subscribe, :subscription_rejected?
|
10
|
+
|
11
|
+
def subscribe_to_channel
|
12
|
+
# noop
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :stop_streams
|
16
|
+
|
17
|
+
def stream_from(broadcasting, callback = nil, coder: nil)
|
18
|
+
raise ArgumentError('Unsupported') if callback.present? || coder.present? || block_given?
|
19
|
+
streams << broadcasting
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop_all_streams
|
23
|
+
@stop_streams = true
|
24
|
+
end
|
25
|
+
|
26
|
+
def streams
|
27
|
+
@streams ||= []
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop_streams?
|
31
|
+
stop_streams == true
|
32
|
+
end
|
33
|
+
|
34
|
+
def delegate_connection_identifiers
|
35
|
+
connection.identifiers.each do |identifier|
|
36
|
+
define_singleton_method(identifier) do
|
37
|
+
connection.fetch_identifier(identifier)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "action_cable"
|
3
|
+
|
4
|
+
module ActionCable
|
5
|
+
module Connection
|
6
|
+
class Base # :nodoc:
|
7
|
+
attr_reader :transmissions
|
8
|
+
|
9
|
+
def initialize(env: {}, identifiers_json: '{}')
|
10
|
+
@ids = ActiveSupport::JSON.decode(identifiers_json)
|
11
|
+
@cached_ids = {}
|
12
|
+
@env = env
|
13
|
+
@coder = ActiveSupport::JSON
|
14
|
+
@closed = false
|
15
|
+
@transmissions = []
|
16
|
+
@subscriptions = ActionCable::Connection::Subscriptions.new(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def handle_open
|
20
|
+
connect if respond_to?(:connect)
|
21
|
+
send_welcome_message
|
22
|
+
rescue ActionCable::Connection::Authorization::UnauthorizedError
|
23
|
+
close
|
24
|
+
end
|
25
|
+
|
26
|
+
def handle_close
|
27
|
+
# subscriptions.unsubscribe_from_all
|
28
|
+
disconnect if respond_to?(:disconnect)
|
29
|
+
end
|
30
|
+
|
31
|
+
def close
|
32
|
+
@closed = true
|
33
|
+
end
|
34
|
+
|
35
|
+
def closed?
|
36
|
+
@closed
|
37
|
+
end
|
38
|
+
|
39
|
+
def transmit(cable_message)
|
40
|
+
transmissions << encode(cable_message)
|
41
|
+
end
|
42
|
+
|
43
|
+
def dispose
|
44
|
+
@closed = false
|
45
|
+
transmissions.clear
|
46
|
+
end
|
47
|
+
|
48
|
+
# Generate identifiers info.
|
49
|
+
# Converts GlobalID compatible vars to corresponding global IDs params.
|
50
|
+
def identifiers_hash
|
51
|
+
identifiers.each_with_object({}) do |id, acc|
|
52
|
+
obj = instance_variable_get("@#{id}")
|
53
|
+
next unless obj
|
54
|
+
acc[id] = obj.try(:to_gid_param) || obj
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Fetch identifier and deserialize if neccessary
|
59
|
+
def fetch_identifier(name)
|
60
|
+
@cached_ids[name] ||= @cached_ids.fetch(name) do
|
61
|
+
val = @ids[name.to_s]
|
62
|
+
next val unless val.is_a?(String)
|
63
|
+
GlobalID::Locator.locate(val) || val
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def logger
|
68
|
+
::Rails.logger
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "anyway"
|
3
|
+
|
4
|
+
module Anycable
|
5
|
+
# Anycable configuration
|
6
|
+
class Config < Anyway::Config
|
7
|
+
config_name :anycable
|
8
|
+
|
9
|
+
attr_config rpc_host: "localhost:50051",
|
10
|
+
redis_url: "redis://localhost:6379/5",
|
11
|
+
redis_channel: "anycable",
|
12
|
+
debug: false
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "redis"
|
3
|
+
|
4
|
+
module Anycable
|
5
|
+
# PubSub for broadcasting
|
6
|
+
class PubSub
|
7
|
+
attr_reader :redis_conn
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@redis_conn = Redis.new(url: Anycable.config.redis_url)
|
11
|
+
end
|
12
|
+
|
13
|
+
def broadcast(channel, payload)
|
14
|
+
redis_conn.publish(
|
15
|
+
Anycable.config.redis_channel,
|
16
|
+
{ stream: channel, data: payload }.to_json
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# source: rpc.proto
|
3
|
+
|
4
|
+
require 'google/protobuf'
|
5
|
+
|
6
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
7
|
+
add_message "anycable.ConnectionRequest" do
|
8
|
+
optional :path, :string, 1
|
9
|
+
map :headers, :string, :string, 2
|
10
|
+
end
|
11
|
+
add_message "anycable.ConnectionResponse" do
|
12
|
+
optional :status, :enum, 1, "anycable.Status"
|
13
|
+
optional :identifiers, :string, 2
|
14
|
+
repeated :transmissions, :string, 3
|
15
|
+
end
|
16
|
+
add_message "anycable.CommandMessage" do
|
17
|
+
optional :command, :string, 1
|
18
|
+
optional :identifier, :string, 2
|
19
|
+
optional :connection_identifiers, :string, 3
|
20
|
+
optional :data, :string, 4
|
21
|
+
end
|
22
|
+
add_message "anycable.CommandResponse" do
|
23
|
+
optional :status, :enum, 1, "anycable.Status"
|
24
|
+
optional :disconnect, :bool, 2
|
25
|
+
optional :stop_streams, :bool, 3
|
26
|
+
optional :stream_from, :bool, 4
|
27
|
+
optional :stream_id, :string, 5
|
28
|
+
repeated :transmissions, :string, 6
|
29
|
+
end
|
30
|
+
add_message "anycable.DisconnectRequest" do
|
31
|
+
optional :identifiers, :string, 1
|
32
|
+
repeated :subscriptions, :string, 2
|
33
|
+
end
|
34
|
+
add_message "anycable.DisconnectResponse" do
|
35
|
+
optional :status, :enum, 1, "anycable.Status"
|
36
|
+
end
|
37
|
+
add_enum "anycable.Status" do
|
38
|
+
value :ERROR, 0
|
39
|
+
value :SUCCESS, 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module Anycable
|
44
|
+
ConnectionRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.ConnectionRequest").msgclass
|
45
|
+
ConnectionResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.ConnectionResponse").msgclass
|
46
|
+
CommandMessage = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.CommandMessage").msgclass
|
47
|
+
CommandResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.CommandResponse").msgclass
|
48
|
+
DisconnectRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.DisconnectRequest").msgclass
|
49
|
+
DisconnectResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.DisconnectResponse").msgclass
|
50
|
+
Status = Google::Protobuf::DescriptorPool.generated_pool.lookup("anycable.Status").enummodule
|
51
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# Source: rpc.proto for package 'anycable'
|
3
|
+
|
4
|
+
require 'grpc'
|
5
|
+
require_relative 'rpc'
|
6
|
+
|
7
|
+
module Anycable
|
8
|
+
module RPC
|
9
|
+
class Service
|
10
|
+
include GRPC::GenericService
|
11
|
+
|
12
|
+
self.marshal_class_method = :encode
|
13
|
+
self.unmarshal_class_method = :decode
|
14
|
+
self.service_name = 'anycable.RPC'
|
15
|
+
|
16
|
+
rpc :Connect, ConnectionRequest, ConnectionResponse
|
17
|
+
rpc :Subscribe, CommandMessage, CommandResponse
|
18
|
+
rpc :Unsubscribe, CommandMessage, CommandResponse
|
19
|
+
rpc :Perform, CommandMessage, CommandResponse
|
20
|
+
rpc :Disconnect, DisconnectRequest, DisconnectResponse
|
21
|
+
end
|
22
|
+
|
23
|
+
Stub = Service.rpc_stub_class
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'anycable/actioncable/connection'
|
3
|
+
require 'anycable/actioncable/channel'
|
4
|
+
require 'anycable/rpc/rpc'
|
5
|
+
require 'anycable/rpc/rpc_services'
|
6
|
+
|
7
|
+
# rubocop:disable Metrics/ClassLength
|
8
|
+
# rubocop:disable Metrics/AbcSize
|
9
|
+
# rubocop:disable Metrics/MethodLength
|
10
|
+
module Anycable
|
11
|
+
# RPC service handler
|
12
|
+
class RPCHandler < Anycable::RPC::Service
|
13
|
+
# Handle connection request from WebSocket server
|
14
|
+
def connect(request, _unused_call)
|
15
|
+
logger.debug("RPC Connect: #{request}")
|
16
|
+
|
17
|
+
connection = ApplicationCable::Connection.new(
|
18
|
+
env:
|
19
|
+
path_env(request.path).merge(
|
20
|
+
'HTTP_COOKIE' => request.headers['Cookie']
|
21
|
+
)
|
22
|
+
)
|
23
|
+
|
24
|
+
connection.handle_open
|
25
|
+
|
26
|
+
if connection.closed?
|
27
|
+
Anycable::ConnectionResponse.new(status: Anycable::Status::ERROR)
|
28
|
+
else
|
29
|
+
Anycable::ConnectionResponse.new(
|
30
|
+
status: Anycable::Status::SUCCESS,
|
31
|
+
identifiers: connection.identifiers_hash.to_json,
|
32
|
+
transmissions: connection.transmissions
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def disconnect(request, _unused_call)
|
38
|
+
logger.debug("RPC Disonnect: #{request}")
|
39
|
+
# TODO: implement disconnect logic
|
40
|
+
Anycable::DisconnectResponse.new(status: Anycable::Status::SUCCESS)
|
41
|
+
end
|
42
|
+
|
43
|
+
def subscribe(message, _unused_call)
|
44
|
+
logger.debug("RPC Subscribe: #{message}")
|
45
|
+
connection = ApplicationCable::Connection.new(
|
46
|
+
identifiers_json: message.connection_identifiers
|
47
|
+
)
|
48
|
+
|
49
|
+
channel = channel_for(connection, message)
|
50
|
+
|
51
|
+
if channel.present?
|
52
|
+
channel.do_subscribe
|
53
|
+
if channel.subscription_rejected?
|
54
|
+
Anycable::CommandResponse.new(
|
55
|
+
status: Anycable::Status::ERROR,
|
56
|
+
disconnect: connection.closed?,
|
57
|
+
transmissions: connection.transmissions
|
58
|
+
)
|
59
|
+
else
|
60
|
+
Anycable::CommandResponse.new(
|
61
|
+
status: Anycable::Status::SUCCESS,
|
62
|
+
disconnect: connection.closed?,
|
63
|
+
stop_streams: channel.stop_streams?,
|
64
|
+
stream_from: channel.streams.present?,
|
65
|
+
stream_id: channel.streams.first || '',
|
66
|
+
transmissions: connection.transmissions
|
67
|
+
)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
Anycable::CommandResponse.new(
|
71
|
+
status: Anycable::Status::ERROR
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def unsubscribe(message, _unused_call)
|
77
|
+
logger.debug("RPC Unsubscribe: #{message}")
|
78
|
+
Anycable::CommandResponse.new(
|
79
|
+
status: Anycable::Status::SUCCESS,
|
80
|
+
disconnect: false,
|
81
|
+
stop_streams: true,
|
82
|
+
stream_from: false
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def perform(message, _unused_call)
|
87
|
+
logger.debug("RPC Perform: #{message}")
|
88
|
+
connection = ApplicationCable::Connection.new(
|
89
|
+
identifiers_json: message.connection_identifiers
|
90
|
+
)
|
91
|
+
|
92
|
+
channel = channel_for(connection, message)
|
93
|
+
|
94
|
+
if channel.present?
|
95
|
+
channel.perform_action(ActiveSupport::JSON.decode(message.data))
|
96
|
+
Anycable::CommandResponse.new(
|
97
|
+
status: Anycable::Status::SUCCESS,
|
98
|
+
disconnect: connection.closed?,
|
99
|
+
stop_streams: channel.stop_streams?,
|
100
|
+
stream_from: channel.streams.present?,
|
101
|
+
stream_id: channel.streams.first || '',
|
102
|
+
transmissions: connection.transmissions
|
103
|
+
)
|
104
|
+
else
|
105
|
+
Anycable::CommandResponse.new(
|
106
|
+
status: Anycable::Status::ERROR
|
107
|
+
)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
# Build env from path
|
114
|
+
def path_env(path)
|
115
|
+
uri = URI.parse(path)
|
116
|
+
{
|
117
|
+
'QUERY_STRING' => uri.query,
|
118
|
+
'SCRIPT_NAME' => '',
|
119
|
+
'PATH_INFO' => uri.path,
|
120
|
+
'SERVER_PORT' => uri.port.to_s,
|
121
|
+
'HTTP_HOST' => uri.host,
|
122
|
+
# Hack to avoid Missing rack.input error
|
123
|
+
'rack.request.form_input' => '',
|
124
|
+
'rack.input' => '',
|
125
|
+
'rack.request.form_hash' => {}
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
def channel_for(connection, message)
|
130
|
+
id_key = message.identifier
|
131
|
+
id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access
|
132
|
+
|
133
|
+
subscription_klass = id_options[:channel].safe_constantize
|
134
|
+
|
135
|
+
if subscription_klass
|
136
|
+
subscription_klass.new(connection, id_key, id_options)
|
137
|
+
else
|
138
|
+
logger.error "Subscription class not found (#{message.inspect})"
|
139
|
+
nil
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def logger
|
144
|
+
Anycable.logger
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'grpc'
|
3
|
+
require 'anycable'
|
4
|
+
require 'anycable/rpc_handler'
|
5
|
+
|
6
|
+
# Set GRPC logger
|
7
|
+
module GRPC
|
8
|
+
def self.logger
|
9
|
+
Anycable.logger
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Anycable
|
14
|
+
# Wrapper over GRPC server
|
15
|
+
module Server
|
16
|
+
class << self
|
17
|
+
attr_accessor :grpc_server
|
18
|
+
|
19
|
+
def start
|
20
|
+
@grpc_server = GRPC::RpcServer.new
|
21
|
+
grpc_server.add_http2_port(Anycable.config.rpc_host, :this_port_is_insecure)
|
22
|
+
grpc_server.handle(Anycable::RPCHandler)
|
23
|
+
Anycable.logger.info "RPC server is listening on #{Anycable.config.rpc_host}"
|
24
|
+
grpc_server.run_till_terminated
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/anycable/version.rb
CHANGED
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "rails/generators/base"
|
3
|
+
|
4
|
+
class AnycableGenerator < Rails::Generators::Base # :nodoc:
|
5
|
+
source_root File.expand_path('../templates', __FILE__)
|
6
|
+
|
7
|
+
def create_executable_file
|
8
|
+
template "script", "bin/anycable"
|
9
|
+
chmod "bin/anycable", 0o755
|
10
|
+
end
|
11
|
+
end
|
@@ -1,5 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
namespace :anycable do
|
3
|
+
desc "Make test gRPC call"
|
4
|
+
task check: :environment do
|
5
|
+
require 'grpc'
|
6
|
+
require 'anycable/rpc/rpc_services'
|
7
|
+
|
8
|
+
Anycable.logger = Logger.new(STDOUT)
|
9
|
+
stub = Anycable::RPC::Stub.new(Anycable.config.rpc_host, :this_channel_is_insecure)
|
10
|
+
stub.connect(
|
11
|
+
Anycable::ConnectionRequest.new(
|
12
|
+
path: 'http://example.com',
|
13
|
+
headers: { 'Cookie' => 'test=1;' }
|
14
|
+
)
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
data/protos/rpc.proto
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
syntax = "proto3";
|
2
|
+
|
3
|
+
package anycable;
|
4
|
+
|
5
|
+
service RPC {
|
6
|
+
rpc Connect (ConnectionRequest) returns (ConnectionResponse) {}
|
7
|
+
rpc Subscribe (CommandMessage) returns (CommandResponse) {}
|
8
|
+
rpc Unsubscribe (CommandMessage) returns (CommandResponse) {}
|
9
|
+
rpc Perform (CommandMessage) returns (CommandResponse) {}
|
10
|
+
rpc Disconnect (DisconnectRequest) returns (DisconnectResponse) {}
|
11
|
+
}
|
12
|
+
|
13
|
+
enum Status {
|
14
|
+
ERROR = 0;
|
15
|
+
SUCCESS = 1;
|
16
|
+
}
|
17
|
+
|
18
|
+
message ConnectionRequest {
|
19
|
+
string path = 1;
|
20
|
+
map<string,string> headers = 2;
|
21
|
+
}
|
22
|
+
|
23
|
+
message ConnectionResponse {
|
24
|
+
Status status = 1;
|
25
|
+
string identifiers = 2;
|
26
|
+
repeated string transmissions = 3;
|
27
|
+
}
|
28
|
+
|
29
|
+
message CommandMessage {
|
30
|
+
string command = 1;
|
31
|
+
string identifier = 2;
|
32
|
+
string connection_identifiers = 3;
|
33
|
+
string data = 4;
|
34
|
+
}
|
35
|
+
|
36
|
+
message CommandResponse {
|
37
|
+
Status status = 1;
|
38
|
+
bool disconnect = 2;
|
39
|
+
bool stop_streams = 3;
|
40
|
+
bool stream_from = 4;
|
41
|
+
string stream_id = 5;
|
42
|
+
repeated string transmissions = 6;
|
43
|
+
}
|
44
|
+
|
45
|
+
message DisconnectRequest {
|
46
|
+
string identifiers = 1;
|
47
|
+
repeated string subscriptions = 2;
|
48
|
+
}
|
49
|
+
|
50
|
+
message DisconnectResponse {
|
51
|
+
Status status = 1;
|
52
|
+
}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: anycable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- palkan
|
@@ -24,6 +24,48 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: anyway_config
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.4.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.4.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: grpc
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: redis
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
27
69
|
- !ruby/object:Gem::Dependency
|
28
70
|
name: bundler
|
29
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,14 +150,29 @@ files:
|
|
108
150
|
- CHANGELOG.md
|
109
151
|
- Gemfile
|
110
152
|
- MIT-LICENSE
|
153
|
+
- Makefile
|
111
154
|
- README.md
|
112
155
|
- Rakefile
|
113
156
|
- anycable.gemspec
|
157
|
+
- bin/console
|
114
158
|
- bin/setup
|
115
159
|
- circle.yml
|
116
160
|
- lib/anycable.rb
|
161
|
+
- lib/anycable/actioncable/channel.rb
|
162
|
+
- lib/anycable/actioncable/connection.rb
|
163
|
+
- lib/anycable/actioncable/server.rb
|
164
|
+
- lib/anycable/config.rb
|
165
|
+
- lib/anycable/pubsub.rb
|
166
|
+
- lib/anycable/rpc/rpc.rb
|
167
|
+
- lib/anycable/rpc/rpc_services.rb
|
168
|
+
- lib/anycable/rpc_handler.rb
|
169
|
+
- lib/anycable/server.rb
|
117
170
|
- lib/anycable/version.rb
|
171
|
+
- lib/generators/anycable/USAGE
|
172
|
+
- lib/generators/anycable/anycable_generator.rb
|
173
|
+
- lib/generators/anycable/templates/script
|
118
174
|
- lib/tasks/anycable_tasks.rake
|
175
|
+
- protos/rpc.proto
|
119
176
|
homepage: http://github.com/anycable/anycable
|
120
177
|
licenses:
|
121
178
|
- MIT
|