anycable 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|