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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eafc0ee0da24cde74dd95034c6e2021f30baefd6
4
- data.tar.gz: a72505c9ce7e11651bade19e3bd078c10faec624
3
+ metadata.gz: 925d630d73669095c73fb2c1cfe55a9e87eb3f96
4
+ data.tar.gz: 0314e4c2a10909275c91e5cc813eaa9fb8b98999
5
5
  SHA512:
6
- metadata.gz: e9ecf0ee0cd599092cf3ea4b1ff46688c3e4ed6551f03f3ec6e3eb37e79e6652f4df027c5e1a41aefb94c498584b2a4553fb5c4d3995b596b0c9c550f8718c39
7
- data.tar.gz: d729762ead17bfff76c8d9b287d8b2b06d63d7efe8383b22c0ec439b4b079379ad9713b53b161b6344adcb05341a3c0ed5f8bb79df2565c286596514f79dc311
6
+ metadata.gz: 0d31377f132589da64214429fdeac9e3332ec66294869631d5a3d2ab33ed58dd496dc094734c23c07457eea2884ce21b5cb33542866e455394e4df516c76f0f9
7
+ data.tar.gz: c13beb8e1f441b420b859523aa8545ee8997a63e4ec6b21095dc8d469cc8a4934e512c854e943d031d5d96b91b5c048c1833261c184f8745a3d46db63f43c420
@@ -1,4 +1,11 @@
1
1
  language: ruby
2
2
  cache: bundler
3
3
 
4
- rvm: 2.3.1
4
+ rvm: 2.3.3
5
+
6
+ matrix:
7
+ include:
8
+ - gemfile: gemfiles/rails5.gemfile
9
+ - gemfile: gemfiles/railsmaster.gemfile
10
+ allow_failures:
11
+ - gemfile: gemfiles/railsmaster.gemfile
@@ -0,0 +1,9 @@
1
+ # Change log
2
+
3
+ ## 0.3.0 (2016-12-28)
4
+
5
+ - Handle `Disconnect` requests. ([@palkan][])
6
+
7
+ Implement `Disconnect` handler, which invokes `Connection#disconnect` (along with `Channel#unsubscribed` for each subscription).
8
+
9
+ [@palkan]: https://github.com/palkan
@@ -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
+ ![memory](assets/Memory3.png)
41
+
42
+ #VSLIDE
43
+
44
+ ## CPU
45
+
46
+ ![cpu](assets/cpu_chart.gif)
47
+
48
+ #VSLIDE
49
+
50
+ ## Broadcast Round Trip Time
51
+
52
+ ![rtt](assets/RTT3.png)
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
+ ![diagram](assets/Scheme2.png)
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
+ ![memory](assets/Memory5.png)
113
+
114
+ #VSLIDE
115
+
116
+ ## CPU
117
+
118
+ ![cpu](assets/cpu_chart2.gif)
119
+
120
+ #VSLIDE
121
+
122
+ ## Broadcast Round Trip Time
123
+
124
+ ![rtt](assets/RTT5.png)
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)
@@ -0,0 +1 @@
1
+ theme : moon
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
- [![Gem Version](https://badge.fury.io/rb/anycable.svg)](https://rubygems.org/gems/anycable) [![Build Status](https://travis-ci.org/anycable/anycable.svg?branch=master)](https://travis-ci.org/anycable/anycable) [![Circle CI](https://circleci.com/gh/anycable/anycable/tree/master.svg?style=svg)](https://circleci.com/gh/anycable/anycable/tree/master)
1
+ [![GitPitch](https://gitpitch.com/assets/badge.svg)](https://gitpitch.com/anycable/anycable/master?grs=github) [![Gem Version](https://badge.fury.io/rb/anycable.svg)](https://rubygems.org/gems/anycable) [![Build Status](https://travis-ci.org/anycable/anycable.svg?branch=master)](https://travis-ci.org/anycable/anycable) [![Circle CI](https://circleci.com/gh/anycable/anycable/tree/master.svg?style=svg)](https://circleci.com/gh/anycable/anycable/tree/master)
2
+ [![Gitter](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](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
- - coming soon ...
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 | coming soon
106
+ Disconnect Handling | +
83
107
  Subscribe to channels | +
84
- Parameterized subscriptions | coming soon
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 | +
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rails', '~> 5.0.0'
4
+ gem 'rspec-rails', '~> 3.5.0'
5
+
6
+ gemspec path: '..'
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rails', github: 'rails/rails'
4
+ gem 'rspec-rails', '~> 3.5.0'
5
+
6
+ gemspec path: '..'
@@ -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
- def initialize(env: {}, identifiers_json: '{}')
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
- # subscriptions.unsubscribe_from_all
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
@@ -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"
@@ -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
- identifiers_json: message.connection_identifiers
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
- channel = channel_for(connection, message)
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
- if channel.present?
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
- Anycable::CommandResponse.new(
78
- status: Anycable::Status::SUCCESS,
79
- disconnect: false,
80
- stop_streams: true
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
- channel = channel_for(connection, message)
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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Anycable
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
  require ::File.expand_path('../../config/environment', __FILE__)
4
- Rails.application.eager_load!
5
-
6
4
  require 'anycable/server'
7
5
 
6
+ Rails.application.eager_load!
7
+
8
8
  Anycable::Server.start
@@ -44,6 +44,8 @@ message CommandResponse {
44
44
  message DisconnectRequest {
45
45
  string identifiers = 1;
46
46
  repeated string subscriptions = 2;
47
+ string path = 3;
48
+ map<string,string> headers = 4;
47
49
  }
48
50
 
49
51
  message DisconnectResponse {
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.2.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-10-11 00:00:00.000000000 Z
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