anycable 0.2.0 → 0.3.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/.travis.yml +8 -1
- data/CHANGELOG.md +9 -0
- data/PITCHME.md +139 -0
- data/PITCHME.yaml +1 -0
- data/README.md +29 -5
- data/assets/Memory3.png +0 -0
- data/assets/Memory5.png +0 -0
- data/assets/RTT3.png +0 -0
- data/assets/RTT5.png +0 -0
- data/assets/Scheme1.png +0 -0
- data/assets/Scheme2.png +0 -0
- data/assets/cpu_chart.gif +0 -0
- data/assets/cpu_chart2.gif +0 -0
- data/assets/evlms.png +0 -0
- data/gemfiles/rails5.gemfile +6 -0
- data/gemfiles/railsmaster.gemfile +6 -0
- data/lib/anycable/actioncable/connection.rb +25 -2
- data/lib/anycable/refinements/subscriptions.rb +14 -0
- data/lib/anycable/rpc/rpc.rb +2 -0
- data/lib/anycable/rpc_handler.rb +41 -39
- data/lib/anycable/version.rb +1 -1
- data/lib/generators/anycable/templates/script +2 -2
- data/protos/rpc.proto +2 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 925d630d73669095c73fb2c1cfe55a9e87eb3f96
|
4
|
+
data.tar.gz: 0314e4c2a10909275c91e5cc813eaa9fb8b98999
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d31377f132589da64214429fdeac9e3332ec66294869631d5a3d2ab33ed58dd496dc094734c23c07457eea2884ce21b5cb33542866e455394e4df516c76f0f9
|
7
|
+
data.tar.gz: c13beb8e1f441b420b859523aa8545ee8997a63e4ec6b21095dc8d469cc8a4934e512c854e943d031d5d96b91b5c048c1833261c184f8745a3d46db63f43c420
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/PITCHME.md
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
#HSLIDE
|
2
|
+
|
3
|
+
## AnyCable
|
4
|
+
### A polyglot replacement for <span style="color:#e49436">ActionCable</span> server
|
5
|
+
|
6
|
+
#HSLIDE
|
7
|
+
|
8
|
+
## ActionCable
|
9
|
+
|
10
|
+
#### Easy to use <!-- .element: class="fragment" -->
|
11
|
+
|
12
|
+
#### Allows you to access business logic <!-- .element: class="fragment" -->
|
13
|
+
|
14
|
+
#### Has JS client that just works <!-- .element: class="fragment" -->
|
15
|
+
|
16
|
+
#HSLIDE
|
17
|
+
|
18
|
+
## ActionCable
|
19
|
+
|
20
|
+
### is good for [designing live features](http://weblog.rubyonrails.org/2016/6/30/Rails-5-0-final/)
|
21
|
+
|
22
|
+
|
23
|
+
#HSLIDE
|
24
|
+
|
25
|
+
## But...
|
26
|
+
### is it ready for <span style="color:#e49436">production</span>?
|
27
|
+
|
28
|
+
#HSLIDE
|
29
|
+
|
30
|
+
## Benchmarks
|
31
|
+
|
32
|
+
#### Unfortunately, <span style="color:#e49436">ActionCable</span> leaves much to be desired
|
33
|
+
|
34
|
+
<span style="font-size:0.6em; color:gray">Press Down key to see charts and gifs</span>
|
35
|
+
|
36
|
+
#VSLIDE
|
37
|
+
|
38
|
+
## Memory
|
39
|
+
|
40
|
+

|
41
|
+
|
42
|
+
#VSLIDE
|
43
|
+
|
44
|
+
## CPU
|
45
|
+
|
46
|
+

|
47
|
+
|
48
|
+
#VSLIDE
|
49
|
+
|
50
|
+
## Broadcast Round Trip Time
|
51
|
+
|
52
|
+

|
53
|
+
|
54
|
+
#HSLIDE
|
55
|
+
|
56
|
+
### Let's extract <span style="color:#e49436">WebSockets</span> somewhere else!
|
57
|
+
|
58
|
+
#HSLIDE
|
59
|
+
|
60
|
+
## AnyCable
|
61
|
+
|
62
|
+
#### Combines the good parts from <span style="color:#e49436">ActionCable</span> with the power of your favorite language for concurrent applications
|
63
|
+
|
64
|
+
<span style="font-size:0.6em; color:gray">How it works? See below</span>
|
65
|
+
|
66
|
+
#VSLIDE
|
67
|
+
|
68
|
+
## How AnyCable Works
|
69
|
+
|
70
|
+

|
71
|
+
|
72
|
+
#VSLIDE
|
73
|
+
|
74
|
+
## [gRPC](http://grpc.io)
|
75
|
+
|
76
|
+
### Makes AnyCable to be a <span style="color:#e49436">polyglot</span>
|
77
|
+
|
78
|
+
#VSLIDE
|
79
|
+
|
80
|
+
## AnyCable
|
81
|
+
|
82
|
+
#### [Compatible](https://github.com/anycable/anycable#actioncable-compatibility) with ActionCable (channels, javascript, broadcasting)
|
83
|
+
|
84
|
+
#### You can still use ActionCable for <span style="color:#e49436">development</span> and <span style="color:#e49436">testing</span>
|
85
|
+
|
86
|
+
#VSLIDE
|
87
|
+
|
88
|
+
## AnyCable Servers
|
89
|
+
|
90
|
+
- [anycable-go](https://github.com/anycable/anycable-go)
|
91
|
+
|
92
|
+
- [erlycable](https://github.com/anycable/erlycable)
|
93
|
+
|
94
|
+
#VSLIDE
|
95
|
+
|
96
|
+
## AnyCable
|
97
|
+
|
98
|
+
### [Demo Application](https://github.com/anycable/anycable_demo)
|
99
|
+
|
100
|
+
#HSLIDE
|
101
|
+
|
102
|
+
## Benchmarks Again
|
103
|
+
|
104
|
+
#### AnyCable shows much more better performance.
|
105
|
+
|
106
|
+
<span style="font-size:0.6em; color:gray">Press Down key to see charts and gifs</span>
|
107
|
+
|
108
|
+
#VSLIDE
|
109
|
+
|
110
|
+
## Memory
|
111
|
+
|
112
|
+

|
113
|
+
|
114
|
+
#VSLIDE
|
115
|
+
|
116
|
+
## CPU
|
117
|
+
|
118
|
+

|
119
|
+
|
120
|
+
#VSLIDE
|
121
|
+
|
122
|
+
## Broadcast Round Trip Time
|
123
|
+
|
124
|
+

|
125
|
+
|
126
|
+
|
127
|
+
#HSLIDE
|
128
|
+
|
129
|
+
## Let's Make ActionCable Not Suck!
|
130
|
+
|
131
|
+
[anycable.evilmartians.io](http://anycable.evilmartians.io/)
|
132
|
+
|
133
|
+
Vladimir Dementyev [@palkan_tula](http://twitter.com/palkan_tula)
|
134
|
+
|
135
|
+
[Evil Martians](http://evilmartians.com)
|
136
|
+
|
137
|
+
Twitter [@any_cable](http://twitter.com/any_cable)
|
138
|
+
|
139
|
+
GitHub [@anycable](http://github.com/anycable)
|
data/PITCHME.yaml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
theme : moon
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
[](https://rubygems.org/gems/anycable) [](https://travis-ci.org/anycable/anycable) [](https://circleci.com/gh/anycable/anycable/tree/master)
|
1
|
+
[](https://gitpitch.com/anycable/anycable/master?grs=github) [](https://rubygems.org/gems/anycable) [](https://travis-ci.org/anycable/anycable) [](https://circleci.com/gh/anycable/anycable/tree/master)
|
2
|
+
[](https://gitter.im/anycable/Lobby)
|
2
3
|
|
3
4
|
# Anycable
|
4
5
|
|
@@ -10,6 +11,9 @@ You can even use ActionCable in development and not be afraid of compatibility i
|
|
10
11
|
|
11
12
|
[Example Application](https://github.com/anycable/anycable_demo)
|
12
13
|
|
14
|
+
**NOTE**: MacOS users, please, beware of [Sierra-related bug](
|
15
|
+
https://github.com/grpc/grpc/issues/8403).
|
16
|
+
|
13
17
|
<a href="https://evilmartians.com/">
|
14
18
|
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
15
19
|
|
@@ -23,10 +27,17 @@ You can even use ActionCable in development and not be afraid of compatibility i
|
|
23
27
|
|
24
28
|
<img src="https://trello-attachments.s3.amazonaws.com/5781e0ed48e4679e302833d3/820x987/5b6a305417b04e20e75f49c5816e027c/Anycable_vs_ActionCable_copy.jpg" width="400" />
|
25
29
|
|
30
|
+
## Links
|
31
|
+
|
32
|
+
- [GitPitch Slides](https://gitpitch.com/anycable/anycable/master?grs=github)
|
33
|
+
|
34
|
+
- 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)
|
35
|
+
|
36
|
+
|
26
37
|
## Compatible WebSocket servers
|
27
38
|
|
28
39
|
- [Anycable Go](https://github.com/anycable/anycable-go)
|
29
|
-
-
|
40
|
+
- [ErlyCable](https://github.com/anycable/erlycable)
|
30
41
|
|
31
42
|
|
32
43
|
## Installation
|
@@ -66,22 +77,35 @@ Anycable uses [anyway_config](https://github.com/palkan/anyway_config), thus it
|
|
66
77
|
|
67
78
|
## Usage
|
68
79
|
|
69
|
-
Run Anycable server:
|
80
|
+
Run Anycable RPC server:
|
70
81
|
|
71
82
|
```ruby
|
72
83
|
./bin/anycable
|
73
84
|
```
|
74
85
|
|
86
|
+
and also run AnyCable-compatible WebSocket server, e.g. [anycable-go](https://github.com/anycable/anycable-go):
|
87
|
+
|
88
|
+
```sh
|
89
|
+
anycable-go -addr='localhost:3334'
|
90
|
+
```
|
91
|
+
|
92
|
+
Don't forget to set cable url in your `config/environments/production.rb`:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
config.action_cable.url = "ws://localhost:3334/cable"
|
96
|
+
```
|
97
|
+
|
75
98
|
## ActionCable Compatibility
|
76
99
|
|
100
|
+
This is the compatibility list for the AnyCable gem, not for AnyCable servers (which may not support some of the features yet).
|
77
101
|
|
78
102
|
Feature | Status
|
79
103
|
-------------------------|--------
|
80
104
|
Connection Identifiers | +
|
81
105
|
Connection Request (cookies, params) | +
|
82
|
-
Disconnect Handling |
|
106
|
+
Disconnect Handling | +
|
83
107
|
Subscribe to channels | +
|
84
|
-
Parameterized subscriptions |
|
108
|
+
Parameterized subscriptions | +
|
85
109
|
Unsubscribe from channels | +
|
86
110
|
[Subscription Instance Variables](http://edgeapi.rubyonrails.org/classes/ActionCable/Channel/Streams.html) | -
|
87
111
|
Performing Channel Actions | +
|
data/assets/Memory3.png
ADDED
Binary file
|
data/assets/Memory5.png
ADDED
Binary file
|
data/assets/RTT3.png
ADDED
Binary file
|
data/assets/RTT5.png
ADDED
Binary file
|
data/assets/Scheme1.png
ADDED
Binary file
|
data/assets/Scheme2.png
ADDED
Binary file
|
Binary file
|
Binary file
|
data/assets/evlms.png
ADDED
Binary file
|
@@ -1,19 +1,42 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "action_cable"
|
3
|
+
require "anycable/refinements/subscriptions"
|
3
4
|
|
4
5
|
module ActionCable
|
5
6
|
module Connection
|
6
7
|
class Base # :nodoc:
|
8
|
+
using Anycable::Refinements::Subscriptions
|
9
|
+
|
7
10
|
attr_reader :transmissions
|
8
11
|
|
9
|
-
|
12
|
+
class << self
|
13
|
+
def identified_by(*identifiers)
|
14
|
+
super
|
15
|
+
Array(identifiers).each do |identifier|
|
16
|
+
define_method(identifier) do
|
17
|
+
instance_variable_get(:"@#{identifier}") || fetch_identifier(identifier)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(env: {}, identifiers_json: '{}', subscriptions: [])
|
10
24
|
@ids = ActiveSupport::JSON.decode(identifiers_json)
|
25
|
+
|
11
26
|
@cached_ids = {}
|
12
27
|
@env = env
|
13
28
|
@coder = ActiveSupport::JSON
|
14
29
|
@closed = false
|
15
30
|
@transmissions = []
|
16
31
|
@subscriptions = ActionCable::Connection::Subscriptions.new(self)
|
32
|
+
|
33
|
+
# Initialize channels if any
|
34
|
+
subscriptions.each { |id| @subscriptions.fetch(id) }
|
35
|
+
end
|
36
|
+
|
37
|
+
# Create a channel instance from identifier for the connection
|
38
|
+
def channel_for(identifier)
|
39
|
+
subscriptions.fetch(identifier)
|
17
40
|
end
|
18
41
|
|
19
42
|
def handle_open
|
@@ -24,7 +47,7 @@ module ActionCable
|
|
24
47
|
end
|
25
48
|
|
26
49
|
def handle_close
|
27
|
-
|
50
|
+
subscriptions.unsubscribe_from_all
|
28
51
|
disconnect if respond_to?(:disconnect)
|
29
52
|
end
|
30
53
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Anycable
|
3
|
+
module Refinements
|
4
|
+
module Subscriptions # :nodoc:
|
5
|
+
refine ActionCable::Connection::Subscriptions do
|
6
|
+
# Find or add a subscription to the list
|
7
|
+
def fetch(identifier)
|
8
|
+
add("identifier" => identifier) unless subscriptions[identifier]
|
9
|
+
subscriptions[identifier]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/anycable/rpc/rpc.rb
CHANGED
@@ -29,6 +29,8 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
|
|
29
29
|
add_message "anycable.DisconnectRequest" do
|
30
30
|
optional :identifiers, :string, 1
|
31
31
|
repeated :subscriptions, :string, 2
|
32
|
+
optional :path, :string, 3
|
33
|
+
map :headers, :string, :string, 4
|
32
34
|
end
|
33
35
|
add_message "anycable.DisconnectResponse" do
|
34
36
|
optional :status, :enum, 1, "anycable.Status"
|
data/lib/anycable/rpc_handler.rb
CHANGED
@@ -36,19 +36,27 @@ module Anycable
|
|
36
36
|
|
37
37
|
def disconnect(request, _unused_call)
|
38
38
|
logger.debug("RPC Disonnect: #{request}")
|
39
|
-
# TODO: implement disconnect logic
|
40
|
-
Anycable::DisconnectResponse.new(status: Anycable::Status::SUCCESS)
|
41
|
-
end
|
42
39
|
|
43
|
-
def subscribe(message, _unused_call)
|
44
|
-
logger.debug("RPC Subscribe: #{message}")
|
45
40
|
connection = ApplicationCable::Connection.new(
|
46
|
-
|
41
|
+
env:
|
42
|
+
path_env(request.path).merge(
|
43
|
+
'HTTP_COOKIE' => request.headers['Cookie']
|
44
|
+
),
|
45
|
+
identifiers_json: request.identifiers,
|
46
|
+
subscriptions: request.subscriptions
|
47
47
|
)
|
48
48
|
|
49
|
-
|
49
|
+
if connection.handle_close
|
50
|
+
Anycable::DisconnectResponse.new(status: Anycable::Status::SUCCESS)
|
51
|
+
else
|
52
|
+
Anycable::ConnectionResponse.new(status: Anycable::Status::ERROR)
|
53
|
+
end
|
54
|
+
end
|
50
55
|
|
51
|
-
|
56
|
+
def subscribe(message, _unused_call)
|
57
|
+
logger.debug("RPC Subscribe: #{message}")
|
58
|
+
|
59
|
+
run_command(message) do |connection, channel|
|
52
60
|
channel.do_subscribe
|
53
61
|
if channel.subscription_rejected?
|
54
62
|
Anycable::CommandResponse.new(
|
@@ -65,31 +73,27 @@ module Anycable
|
|
65
73
|
transmissions: connection.transmissions
|
66
74
|
)
|
67
75
|
end
|
68
|
-
else
|
69
|
-
Anycable::CommandResponse.new(
|
70
|
-
status: Anycable::Status::ERROR
|
71
|
-
)
|
72
76
|
end
|
73
77
|
end
|
74
78
|
|
75
79
|
def unsubscribe(message, _unused_call)
|
76
80
|
logger.debug("RPC Unsubscribe: #{message}")
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
81
|
+
|
82
|
+
run_command(message) do |connection, channel|
|
83
|
+
connection.subscriptions.remove_subscription(channel)
|
84
|
+
|
85
|
+
Anycable::CommandResponse.new(
|
86
|
+
status: Anycable::Status::SUCCESS,
|
87
|
+
disconnect: false,
|
88
|
+
stop_streams: true
|
89
|
+
)
|
90
|
+
end
|
82
91
|
end
|
83
92
|
|
84
93
|
def perform(message, _unused_call)
|
85
94
|
logger.debug("RPC Perform: #{message}")
|
86
|
-
connection = ApplicationCable::Connection.new(
|
87
|
-
identifiers_json: message.connection_identifiers
|
88
|
-
)
|
89
95
|
|
90
|
-
|
91
|
-
|
92
|
-
if channel.present?
|
96
|
+
run_command(message) do |connection, channel|
|
93
97
|
channel.perform_action(ActiveSupport::JSON.decode(message.data))
|
94
98
|
Anycable::CommandResponse.new(
|
95
99
|
status: Anycable::Status::SUCCESS,
|
@@ -98,6 +102,20 @@ module Anycable
|
|
98
102
|
streams: channel.streams,
|
99
103
|
transmissions: connection.transmissions
|
100
104
|
)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def run_command(message)
|
111
|
+
connection = ApplicationCable::Connection.new(
|
112
|
+
identifiers_json: message.connection_identifiers
|
113
|
+
)
|
114
|
+
|
115
|
+
channel = connection.channel_for(message.identifier)
|
116
|
+
|
117
|
+
if channel.present?
|
118
|
+
yield connection, channel
|
101
119
|
else
|
102
120
|
Anycable::CommandResponse.new(
|
103
121
|
status: Anycable::Status::ERROR
|
@@ -105,8 +123,6 @@ module Anycable
|
|
105
123
|
end
|
106
124
|
end
|
107
125
|
|
108
|
-
private
|
109
|
-
|
110
126
|
# Build env from path
|
111
127
|
def path_env(path)
|
112
128
|
uri = URI.parse(path)
|
@@ -123,20 +139,6 @@ module Anycable
|
|
123
139
|
}
|
124
140
|
end
|
125
141
|
|
126
|
-
def channel_for(connection, message)
|
127
|
-
id_key = message.identifier
|
128
|
-
id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access
|
129
|
-
|
130
|
-
subscription_klass = id_options[:channel].safe_constantize
|
131
|
-
|
132
|
-
if subscription_klass
|
133
|
-
subscription_klass.new(connection, id_key, id_options)
|
134
|
-
else
|
135
|
-
logger.error "Subscription class not found (#{message.inspect})"
|
136
|
-
nil
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
142
|
def logger
|
141
143
|
Anycable.logger
|
142
144
|
end
|
data/lib/anycable/version.rb
CHANGED
data/protos/rpc.proto
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: anycable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- palkan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-12-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -151,18 +151,32 @@ files:
|
|
151
151
|
- Gemfile
|
152
152
|
- MIT-LICENSE
|
153
153
|
- Makefile
|
154
|
+
- PITCHME.md
|
155
|
+
- PITCHME.yaml
|
154
156
|
- README.md
|
155
157
|
- Rakefile
|
156
158
|
- anycable.gemspec
|
159
|
+
- assets/Memory3.png
|
160
|
+
- assets/Memory5.png
|
161
|
+
- assets/RTT3.png
|
162
|
+
- assets/RTT5.png
|
163
|
+
- assets/Scheme1.png
|
164
|
+
- assets/Scheme2.png
|
165
|
+
- assets/cpu_chart.gif
|
166
|
+
- assets/cpu_chart2.gif
|
167
|
+
- assets/evlms.png
|
157
168
|
- bin/console
|
158
169
|
- bin/setup
|
159
170
|
- circle.yml
|
171
|
+
- gemfiles/rails5.gemfile
|
172
|
+
- gemfiles/railsmaster.gemfile
|
160
173
|
- lib/anycable.rb
|
161
174
|
- lib/anycable/actioncable/channel.rb
|
162
175
|
- lib/anycable/actioncable/connection.rb
|
163
176
|
- lib/anycable/actioncable/server.rb
|
164
177
|
- lib/anycable/config.rb
|
165
178
|
- lib/anycable/pubsub.rb
|
179
|
+
- lib/anycable/refinements/subscriptions.rb
|
166
180
|
- lib/anycable/rpc/rpc.rb
|
167
181
|
- lib/anycable/rpc/rpc_services.rb
|
168
182
|
- lib/anycable/rpc_handler.rb
|