anycable 0.2.0 → 0.3.0

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