plezi 0.14.9 → 0.15.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: 75f5b97d8891d3fcdd74ee25d556883dbe50532a
4
- data.tar.gz: dc0d53e3107a0333a78c70d34900b72b6427615f
3
+ metadata.gz: 45a3eb75b619cb1724e18f7ae3124558769d09d1
4
+ data.tar.gz: a201af9772fccbe0f07462c42500861fee8fcdb8
5
5
  SHA512:
6
- metadata.gz: bc4f19777b080c9496d167b59aafd8e069e6efe8357980d8965382f7e2dba11760ef9a23d5a94fd8001929bff06297644b2d12e9291c4d740d0d805a542ac3ef
7
- data.tar.gz: 1ec40cf39efcc59d72c053e11ecc89cdcf782cf6fdd0ade51f80f0f470a5f05d03ada4da7c6bce2e2c25b1d3b606299a0224162f2fbc9b2e707b8fca81700ce0
6
+ metadata.gz: 74e90ba29166b17ce68bcfdae5d00ace0c8030e674afa2a60a88d15dd29b4ff304b685529ea59a4aee7fde1f35656fc4b2d52c0de24c785db56eddab632483d4
7
+ data.tar.gz: b515a650921a1fd5f0d592c563340405b28c3925ce84824f5266c785060fd605c900498f31f1f3ad7c325770092f762baa9afd894cd6cf2a616d25697cc5d561
@@ -2,7 +2,15 @@
2
2
 
3
3
  ***
4
4
 
5
- Change log v.0.14.8 (next)
5
+ Change log v.0.15.0
6
+
7
+ **Deprecation**: no more `broadcast`, `unicast`, `multicast` or `write2everyone`... Plezi fully embraced the Pub/Sub design and the [Iodine Extensions to the Rack Websocket Specification Proposal](https://github.com/boazsegev/iodine/blob/master/SPEC-Websocket-Draft.md).
8
+
9
+ **Feature**: Super powerful Pub/Sub support comes directly from the server layer (iodine), allowing for process cluster Pub/Sub without the need for any Pub/Sub service. Iodine also brings a native Redis connector to easily scale Pub/Sub to multiple machines using a Redis servers.
10
+
11
+ ***
12
+
13
+ Change log v.0.14.9 - EOL (last 0.14.x release)
6
14
 
7
15
  **Fix**: Asset Sass rendering will now only save the output to a file in production mode, allowing development mode to hot-load the assets and re-render them when changes occur.
8
16
 
data/README.md CHANGED
@@ -8,7 +8,32 @@ Are microservices on your mind? Do you dream of a an SPA that's easy to scale? D
8
8
 
9
9
  Welcome to your new home with [plezi.io](http://www.plezi.io), the Ruby real-time framework that assumes the business logic is *seperate* from the web service logic.
10
10
 
11
- **NOTICE**: Plezi 0.14.x (this branch) is NOT an update, it's a total rewrite. Features were _removed_ as well as altered. For example, Plezi is now a Rack framework, with the limitations of CGI design and the advantages of using existing middleware. API changes abound.
11
+ ## Short and Sweet
12
+
13
+ What if your next Pub/Sub application could be as easy as:
14
+
15
+ ```ruby
16
+ require 'plezi'
17
+ class MyChatroom
18
+ # HTTP
19
+ def index
20
+ render :index
21
+ end
22
+ def on_open
23
+ subscribe channel: :chat
24
+ @name = ::ERB::Util.h(params[:nickname] || "anonymous")
25
+ publish channel: :chat, message: "#{@name} joined the chat."
26
+ end
27
+ def on_message data
28
+ publish channel: :chat, message: "#{@name}: #{::ERB::Util.h data}"
29
+ end
30
+ def on_shutdown
31
+ write "Server is going away. Come back again some other time, #{@name}."
32
+ end
33
+ end
34
+
35
+ Plezi.route '/(:nickname)', MyChatroom
36
+ ```
12
37
 
13
38
  ## What does Plezi have to offer?
14
39
 
@@ -28,6 +53,8 @@ Plezi will provide the following features over plain Rack:
28
53
 
29
54
  * Auto-Dispatch (optional) to automatically map JSON websocket "events" to Controller functions (handlers).
30
55
 
56
+ * Native Pub/Sub provided by [Iodine](https://github.com/boazsegev/iodine).
57
+
31
58
  * Automatic (optional) scaling using Redis.
32
59
 
33
60
  * An extensible template rendering abstraction engine, supports Slim, Markdown (using RedCarpet) and ERB out of the box.
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # # Uncomment and set Redis URL for automatic scalling across proccesses and machines:
4
- # require 'redis'
5
- # ENV['PL_REDIS_URL'] ||= "redis://localhost:7777/0"
6
- # ENV['PL_REDIS_URL'] ||= "redis://:password@my.host:6389/0"
3
+ # Example benchmark scripts:
4
+ #
5
+ # websocket-bench broadcast ws://127.0.0.1:3000/ --concurrent 10 --sample-size 100 --server-type binary --step-size 1000 --limit-percentile 95 --limit-rtt 250ms --initial-clients 1000
6
+ #
7
+ # websocket-bench broadcast ws://127.0.0.1:3000/ --concurrent 10 --sample-size 100 --step-size 1000 --limit-percentile 95 --limit-rtt 250ms --initial-clients 1000 --server-type json
8
+ #
7
9
 
8
10
  Dir.chdir(File.expand_path(File.join('..', '..', 'lib'), __FILE__))
9
11
  require 'bundler/setup'
@@ -14,25 +16,39 @@ class ShootoutApp
14
16
  def index
15
17
  "This application should be used with the websocket-shootout benchmark utility."
16
18
  end
19
+ def on_open
20
+ subscribe channel: "shootout"
21
+ end
17
22
  # we won't be using AutoDispatch, but directly using the `on_message` callback.
18
23
  def on_message data
24
+ if data[0] == 'b' # binary
25
+ publish(channel: "shootout", message: data)
26
+ data[0] = 'r'
27
+ write data
28
+ return
29
+ end
19
30
  cmd, payload = JSON(data).values_at('type', 'payload')
20
31
  if cmd == 'echo'
21
32
  write({type: 'echo', payload: payload}.to_json)
22
33
  else
23
34
  # data = {type: 'broadcast', payload: payload}.to_json
24
35
  # broadcast :push2client, data
25
- ShootoutApp.write2everyone({type: 'broadcast', payload: payload}.to_json)
36
+ publish(channel: "shootout", message: ({type: 'broadcast', payload: payload}.to_json))
26
37
  write({type: "broadcastResult", payload: payload}.to_json)
27
38
  end
28
39
  rescue
29
40
  puts "Incoming message format error - not JSON?"
30
41
  end
31
42
 
32
- def push2client data
33
- write data
34
- end
35
-
36
43
  end
37
44
 
38
45
  Plezi.route '*', ShootoutApp
46
+
47
+ #
48
+ # def cycle
49
+ # puts `websocket-bench broadcast ws://127.0.0.1:3000/ --concurrent 10 --sample-size 100 --server-type binary --step-size 1000 --limit-percentile 95 --limit-rtt 250ms --initial-clients 1000`
50
+ # sleep(2)
51
+ # puts `wrk -c4000 -d15 -t12 http://localhost:3000/`
52
+ # true
53
+ # end
54
+ # sleep(10) while cycle
@@ -0,0 +1,38 @@
1
+ # finish with `exit` if running within `irb`
2
+ require 'plezi'
3
+ class ChatServer
4
+ def index
5
+ "Use Websockets to connect."
6
+ end
7
+ def on_open
8
+ return close unless params['id']
9
+ @name = params['id']
10
+ subscribe channel: "chat"
11
+ publish channel: "chat", message: "#{@name} joind the chat."
12
+ write "Welcome, #{@name}!"
13
+ # if we have Redis
14
+ if(Iodine.default_pubsub.is_a? Iodine::PubSub::RedisEngine)
15
+ # We'll add the name to the list of people in the chat.
16
+ # Blocks are used as event callbacks and are executed asynchronously.
17
+ Iodine.default_pubsub.send("SADD", "chat_members", @name) do
18
+ # after the name was added, we'll get all the current people in the chat
19
+ Iodine.default_pubsub.send("SMEMBERS", "chat_members") do |members|
20
+ # By now, we're outside the Websocket connection's lock.
21
+ # To safely access the connection, we'll use `defer`
22
+ defer { write "Currently in the chatroom: #{members.join ', '}" }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ def on_close
28
+ publish channel: "chat", message: "#{@name} joind the chat."
29
+ # if we have Redis
30
+ Iodine.default_pubsub.send("SREM", "chat_members", @name) if(Iodine.default_pubsub.is_a? Iodine::PubSub::RedisEngine)
31
+ end
32
+ def on_message data
33
+ publish channel: "chat", message: "#{@name}: #{data}"
34
+ end
35
+ end
36
+ path_to_client = File.expand_path( File.dirname(__FILE__) )
37
+ Plezi.templates = path_to_client
38
+ Plezi.route '/', ChatServer
data/exe/plezi CHANGED
@@ -104,7 +104,7 @@ module Builder
104
104
  puts ''
105
105
  puts "please change directory into the app directory: cd #{app_name}"
106
106
  puts ''
107
- puts "run the #{app_name} app using: ./#{app_name} or using the iodine / rackup commands."
107
+ puts "run the #{app_name} app using: ./#{app_name} or using the iodine or rackup commands."
108
108
  puts ''
109
109
  end
110
110
  end
@@ -1,5 +1,4 @@
1
- require 'plezi/websockets/message_dispatch' unless defined?(::Plezi::Base::MessageDispatch)
2
-
1
+ require 'uri' unless defined?(::URI)
3
2
  module Plezi
4
3
  protected
5
4
 
@@ -14,15 +13,13 @@ module Plezi
14
13
  def self.plezi_initialize
15
14
  if @plezi_initialize.nil?
16
15
  @plezi_initialize = true
17
- self.hash_proc_4symstr # crerate the Proc object used for request params
16
+ self.hash_proc_4symstr # creates the Proc object used for request params
18
17
  @plezi_autostart = true if @plezi_autostart.nil?
19
- if ENV['PL_REDIS_URL'.freeze] && !defined?(::Redis)
20
- puts "WARNNING: auto-scaling with redis is set using ENV['PL_REDIS_URL'.freeze]\r\n but the Redis gem isn't included! - SCALING IS IGNORED!"
21
- ::Iodine.processes ||= 1
22
- elsif !ENV['PL_REDIS_URL'.freeze]
23
- ::Iodine.processes ||= 1
18
+ Iodine.patch_rack
19
+ if((ENV['PL_REDIS_URL'.freeze] ||= ENV["REDIS_URL"]))
20
+ uri = URI(ENV['PL_REDIS_URL'.freeze])
21
+ Iodine.default_pubsub = Iodine::PubSub::RedisEngine.new(uri.host, uri.port, 0, uri.password)
24
22
  end
25
- ::Iodine.processes ||= 4
26
23
  at_exit do
27
24
  next if @plezi_autostart == false
28
25
  ::Iodine::Rack.app = ::Plezi.app
@@ -32,7 +29,3 @@ module Plezi
32
29
  true
33
30
  end
34
31
  end
35
-
36
- ::Iodine.threads ||= 16
37
- # ::Iodine.processes ||= (ENV['PL_REDIS_URL'.freeze] ? 4 : 1)
38
- ::Iodine.run { ::Plezi::Base::MessageDispatch._init }
@@ -1,7 +1,7 @@
1
1
  require 'plezi/render/render'
2
+ require 'plezi/controller/identification'
2
3
  require 'plezi/controller/cookies'
3
4
  require 'plezi/controller/controller_class'
4
- require 'plezi/websockets/message_dispatch'
5
5
 
6
6
  module Plezi
7
7
  # This module contains the functionality provided to any Controller class.
@@ -119,13 +119,13 @@ module Plezi
119
119
  ::Plezi::Base::Router.url_for self.class, func, params
120
120
  end
121
121
 
122
- # A connection's Plezi ID uniquely identifies the connection across application instances, allowing it to receive and send messages using {#unicast}.
122
+ # A connection's Plezi ID uniquely identifies the connection across application instances.
123
123
  def id
124
- @_pl_id ||= (conn_id && "#{::Plezi::Base::MessageDispatch.pid}-#{conn_id.to_s(16)}")
124
+ @_pl_id ||= (conn_id && "#{::Plezi::Base::Identification.pid}-#{conn_id.to_s(16)}")
125
125
  end
126
126
 
127
127
  # @private
128
- # This is the process specific Websocket's UUID. This function is here to protect you from yourself. Don't call it.
128
+ # This is the process specific Websocket's ID. This function is here to protect you from yourself. Don't call it.
129
129
  def conn_id
130
130
  defined?(super) && super
131
131
  end
@@ -143,71 +143,19 @@ module Plezi
143
143
  #
144
144
  # This allows a module "library" to be used similar to the way "rooms" are used in node.js, so that a number of different Controllers can listen to shared events.
145
145
  #
146
- # By dynamically extending a Controller instance using a module, Websocket broadcasts will be allowed to invoke the module's functions.
146
+ # By dynamically extending a Controller instance using a module, Auto Dispatch events can be routed to the newly available methods.
147
147
  #
148
148
  # Notice: It is impossible to `unextend` an extended module at this time.
149
149
  def extend(mod)
150
150
  raise TypeError, '`mod` should be a module' unless mod.class == Module
151
- raise "#{self} already extended by #{mod.name}" if is_a?(mod)
152
- mod.extend ::Plezi::Controller::ClassMethods
153
- super(mod)
151
+ unless is_a?(mod)
152
+ mod.extend ::Plezi::Controller::ClassMethods
153
+ super(mod)
154
+ end
154
155
  _pl_ws_map.update mod._pl_ws_map
155
156
  _pl_ad_map.update mod._pl_ad_map
156
157
  end
157
158
 
158
- # Invokes a method on the `target` websocket connection. When using Iodine, the method is invoked asynchronously.
159
- #
160
- # def perform_poke(target)
161
- # unicast target, :poke, self.id
162
- # end
163
- # def poke(from)
164
- # unicast from, :poke_back, self.id
165
- # end
166
- # def poke_back(from)
167
- # puts "#{from} is available"
168
- # end
169
- #
170
- # Methods invoked using {unicast}, {broadcast} or {multicast} will quietly fail if the connection was lost, the requested method is undefined or the 'target' was invalid.
171
- def unicast(target, event_method, *args)
172
- ::Plezi::Base::MessageDispatch.unicast(id ? self : self.class, target, event_method, args)
173
- end
174
-
175
- # Invokes a method on every websocket connection (except `self`) that belongs to this Controller / Type. When using Iodine, the method is invoked asynchronously.
176
- #
177
- # self.broadcast :my_method, "argument 1", "argument 2", 3
178
- #
179
- # Methods invoked using {unicast}, {broadcast} or {multicast} will quietly fail if the connection was lost, the requested method is undefined or the 'target' was invalid.
180
- def broadcast(event_method, *args)
181
- ::Plezi::Base::MessageDispatch.broadcast(id ? self : self.class, event_method, args)
182
- end
183
-
184
- # Invokes a method on every websocket connection in the application (except `self`).
185
- #
186
- # self.multicast :my_method, "argument 1", "argument 2", 3
187
- #
188
- # Methods invoked using {unicast}, {broadcast} or {multicast} will quietly fail if the connection was lost, the requested method is undefined or the 'target' was invalid.
189
- def multicast(event_method, *args)
190
- ::Plezi::Base::MessageDispatch.multicast(id ? self : self.class, event_method, args)
191
- end
192
-
193
- # Writes a message to every client websocket connection, for all controllers(!), EXCEPT self. Accepts an optional filter method using a location reference for a *static* (Class/Module/global) method. The filter method will be passerd the websocket object and it should return `true` / `false`.
194
- #
195
- # self.write2everyone {event: "global", message: "This will be sent to everyone"}.to_json
196
- # # or, we can define a filter method somewhere in our code
197
- # module Filter
198
- # def self.should_send? ws
199
- # true
200
- # end
201
- # end
202
- # # and we can use this filter method.
203
- # data = {event: "global", message: "This will be sent to everyone"}.to_json
204
- # self.write2everyone data, ::Filter, :should_send?
205
- #
206
- # It's important that the filter method is defined statically in our code and isn't dynamically allocated. Otherwise, scaling the application would be impossible.
207
- def write2everyone(data, filter_owner = nil, filter_name = nil)
208
- ::Plezi::Base::MessageDispatch.write2everyone(id ? self : self.class, data, filter_owner, filter_name)
209
- end
210
-
211
159
  # @private
212
160
  # This function is used internally by Plezi, do not call.
213
161
  def _pl_ws_map
@@ -226,6 +174,7 @@ module Plezi
226
174
  json = nil
227
175
  begin
228
176
  json = JSON.parse(data, symbolize_names: true)
177
+ # json.default_proc = Plezi.hash_proc_4symstr
229
178
  rescue
230
179
  puts 'AutoDispatch Warnnig: Received non-JSON message. Closing Connection.'
231
180
  close
@@ -7,55 +7,16 @@ module Plezi
7
7
  base._pl_init_class_data
8
8
  end
9
9
 
10
+ # Publishes a message to a channel.
11
+ def publish(args)
12
+ Iodine.publish args
13
+ end
14
+
10
15
  # Returns a relative URL for the controller, placing the requested parameters in the URL (inline, where possible and as query data when not possible).
11
16
  def url_for(func, params = {})
12
17
  ::Plezi::Base::Router.url_for self, func, params
13
18
  end
14
19
 
15
- # Invokes a method on the `target` websocket connection. When using Iodine, the method is invoked asynchronously.
16
- #
17
- # self.unicast target, :my_method, "argument 1"
18
- #
19
- # Methods invoked using {unicast}, {broadcast} or {multicast} will quitely fail if the connection was lost, the requested method is undefined or the 'target' was invalid.
20
- def unicast(target, event_method, *args)
21
- ::Plezi::Base::MessageDispatch.unicast(self, target, event_method, args)
22
- end
23
-
24
- # Invokes a method on every websocket connection that belongs to this Controller / Type. When using Iodine, the method is invoked asynchronously.
25
- #
26
- # self.broadcast :my_method, "argument 1", "argument 2", 3
27
- #
28
- # Methods invoked using {unicast}, {broadcast} or {multicast} will quitely fail if the connection was lost, the requested method is undefined or the 'target' was invalid.
29
- def broadcast(event_method, *args)
30
- ::Plezi::Base::MessageDispatch.broadcast(self, event_method, args)
31
- end
32
-
33
- # Invokes a method on every websocket connection in the application.
34
- #
35
- # self.multicast :my_method, "argument 1", "argument 2", 3
36
- #
37
- # Methods invoked using {unicast}, {broadcast} or {multicast} will quitely fail if the connection was lost, the requested method is undefined or the 'target' was invalid.
38
- def multicast(event_method, *args)
39
- ::Plezi::Base::MessageDispatch.multicast(self, event_method, args)
40
- end
41
- # Writes a message to every client websocket connection in all controllers(!). Accepts an optional filter method using a location reference for a *static* (Class/Module/global) method. The filter method will be passerd the websocket object and it should return `true` / `false`.
42
- #
43
- # self.write2everyone {event: "global", message: "This will be sent to everyone"}.to_json
44
- # # or, we can define a filter method somewhere in our code
45
- # module Filter
46
- # def self.should_send? ws
47
- # true
48
- # end
49
- # end
50
- # # and we can use this filter method.
51
- # data = {event: "global", message: "This will be sent to everyone"}.to_json
52
- # self.write2everyone data, ::Filter, :should_send?
53
- #
54
- # It's important that the filter method is defined statically in our code and isn't dynamically allocated. Otherwise, scaling the application would be impossible.
55
- def write2everyone(data, filter_owner = nil, filter_name = nil)
56
- ::Plezi::Base::MessageDispatch.write2everyone(self, data, filter_owner, filter_name)
57
- end
58
-
59
20
  # @private
60
21
  # This is used internally by Plezi, do not use.
61
22
  RESERVED_METHODS = [:delete, :create, :update, :new, :show, :pre_connect, :on_open, :on_close, :on_shutdown, :on_message].freeze
@@ -0,0 +1,29 @@
1
+ module Plezi
2
+ module Base
3
+ module Identification
4
+
5
+ module_function
6
+
7
+ @ppid = ::Process.pid
8
+ # returns a Plezi flavored pid UUID, used to set the pub/sub channel when scaling
9
+ def pid
10
+ process_pid = ::Process.pid
11
+ if @ppid != process_pid
12
+ @pid = nil
13
+ @ppid = process_pid
14
+ end
15
+ @pid ||= SecureRandom.urlsafe_base64.tap { |str| @prefix_len = str.length }
16
+ end
17
+ # Converts a target Global UUID to a localized UUID
18
+ def target2uuid(target)
19
+ return nil unless target.start_with? pid
20
+ target[@prefix_len..-1].to_i
21
+ end
22
+
23
+ # Extracts the machine part from a target's Global UUID
24
+ def target2pid(target)
25
+ target ? target[0..(@prefix_len - 1)] : Plezi.app_name
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,3 +1,3 @@
1
1
  module Plezi
2
- VERSION = '0.14.9'.freeze
2
+ VERSION = '0.15.0'.freeze
3
3
  end
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ['lib']
29
29
 
30
- spec.add_dependency 'iodine', '~> 0.3', '< 0.4'
30
+ spec.add_dependency 'iodine', '~> 0.4'
31
31
  spec.add_dependency 'rack', '>= 2.0.0'
32
32
  spec.add_dependency 'bundler', '~> 1.14'
33
33
  # spec.add_dependency 'redcarpet', '> 3.3.0'
@@ -1,34 +1,25 @@
1
1
  # Replace this sample with real code.
2
2
  class ExampleCtrl
3
- # HTTP
4
- def index
5
- # any String returned will be appended to the response. We return a String.
6
- render 'welcome'
7
- end
3
+ # HTTP
4
+ def index
5
+ # any String returned will be appended to the response. We return a String.
6
+ render 'welcome'
7
+ end
8
8
 
9
- # Websockets
10
- def on_message(data)
11
- data = ERB::Util.html_escape data
12
- print data
13
- broadcast :print, data
14
- end
9
+ # Websockets
10
+ def on_open
11
+ subscribe channel: "chat"
12
+ write 'Welcome to appname!'
13
+ @handle = params['id'.freeze] || 'Somebody'
14
+ publish channel: "chat", message: "#{ERB::Util.html_escape @handle} joind us :-)"
15
+ end
16
+ def on_message(data)
17
+ data = ERB::Util.html_escape data
18
+ publish channel: "chat", message: data
19
+ end
15
20
 
16
- def on_open
17
- print 'Welcome to appname!'
18
- @handle = params['id'.freeze] || 'Somebody'
19
- broadcast :print, "#{ERB::Util.html_escape @handle} joind us :-)"
20
- end
21
+ def on_close
22
+ publish channel: "chat", message: "#{@handle} left us :-("
23
+ end
21
24
 
22
- def on_close
23
- broadcast :print, "#{@handle} left us :-("
24
- end
25
-
26
- protected
27
-
28
- # write is inherites when a Websocket connection is opened.
29
- #
30
- # Inherited functions aren't exposed (for our security), so we need to wrap it.
31
- def print(data)
32
- write data
33
- end
34
25
  end
@@ -4,4 +4,4 @@
4
4
  # set the working directory
5
5
  cd "$(dirname "$0")"
6
6
  # load the website-app
7
- bundle exec iodine -t 16 -w 1 -www ./public
7
+ bundle exec iodine -t 16 -w 4 -www ./public
@@ -187,7 +187,7 @@ var websocket_fail_count = 0
187
187
  function init_websocket()
188
188
  {
189
189
  if(websocket && websocket.readyState == 1) return true; // console.log('no need to renew socket connection');
190
- websocket = new WebSocket(ws_uri);
190
+ websocket = new WebSocket(ws_uri + document.getElementById("input").value);
191
191
  websocket.onopen = function(e) {
192
192
  //restart fail count
193
193
  websocket_fail = 0
@@ -266,7 +266,7 @@ function send_text()
266
266
  </ol>
267
267
  <p class="bold">Good Luck!</p>
268
268
  <h3> Did you try our websocket broadcast? </h3>
269
- <p>Your appname app can send <span class="bold">WebSocket</span> broadcasts (and unicasts). Why not try them out? You can even try them across multiple windows.</p>
269
+ <p>Your appname app can send <span class="bold">WebSocket</span> messages and subscribe / publish to channels of information. This chat works across browser windows.</p>
270
270
  <form onsubmit='send_text(); return false;' id='monitor'>
271
271
  <p><input type='text' id='input' placeholder='Your message' /> <input type='submit' value='Broadcast'/> </p>
272
272
  <ul id='output'>
@@ -1,3 +1,4 @@
1
- # Edit these parameters if you wish to leverage multi-process concurrency. i.e.:
2
- # bundle exec iodine -p $PORT -t 16 -w 4 -www ./public -warmup # => 4 processes + fix lazy loading
3
- web: bundle exec iodine -p $PORT -t 16 -w 1 -www ./public
1
+ # # Edit these parameters if you wish to manually manage concurrency. i.e.:
2
+ # web: bundle exec iodine -p $PORT -t 16 -w 4 -www ./public -warmup
3
+ #
4
+ web: bundle exec iodine -p $PORT -www ./public
metadata CHANGED
@@ -1,23 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plezi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.9
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boaz Segev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-06-18 00:00:00.000000000 Z
11
+ date: 2017-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: iodine
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '0.3'
20
- - - "<"
21
18
  - !ruby/object:Gem::Version
22
19
  version: '0.4'
23
20
  type: :runtime
@@ -25,9 +22,6 @@ dependencies:
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
24
  - - "~>"
28
- - !ruby/object:Gem::Version
29
- version: '0.3'
30
- - - "<"
31
25
  - !ruby/object:Gem::Version
32
26
  version: '0.4'
33
27
  - !ruby/object:Gem::Dependency
@@ -106,6 +100,7 @@ files:
106
100
  - bin/hello_world
107
101
  - bin/setup
108
102
  - bin/ws_shootout
103
+ - examples/chat.rb
109
104
  - exe/plezi
110
105
  - lib/plezi.rb
111
106
  - lib/plezi/activation.rb
@@ -113,6 +108,7 @@ files:
113
108
  - lib/plezi/controller/controller.rb
114
109
  - lib/plezi/controller/controller_class.rb
115
110
  - lib/plezi/controller/cookies.rb
111
+ - lib/plezi/controller/identification.rb
116
112
  - lib/plezi/helpers.rb
117
113
  - lib/plezi/rake.rb
118
114
  - lib/plezi/render/erb.rb
@@ -127,8 +123,6 @@ files:
127
123
  - lib/plezi/router/route.rb
128
124
  - lib/plezi/router/router.rb
129
125
  - lib/plezi/version.rb
130
- - lib/plezi/websockets/message_dispatch.rb
131
- - lib/plezi/websockets/redis.rb
132
126
  - plezi.gemspec
133
127
  - resources/404.erb
134
128
  - resources/500.erb
@@ -1,135 +0,0 @@
1
- module Plezi
2
- module Base
3
- # Websocket Message Dispatching Service, including the autoscaling driver control (at the moment Redis is the only builtin driver).
4
- module MessageDispatch
5
- # add class attribute accessors.
6
- class << self
7
- # Allows pub/sub drivers to attach to the message dispatch using `MessageDispatch.drivers << driver`
8
- attr_reader :drivers
9
- end
10
- @drivers = [].to_set
11
-
12
- module_function
13
-
14
- # The YAML safe types used by Plezi
15
- SAFE_TYPES = [Symbol, Date, Time, Encoding, Struct, Regexp, Range, Set].freeze
16
- # a single use empty array (prevents the use of temporary objects where possible)
17
- EMPTY_ARGS = [].freeze
18
- # keeps track of the current process ID
19
- @ppid = ::Process.pid
20
- # returns a Plezi flavored pid UUID, used to set the pub/sub channel when scaling
21
- def pid
22
- process_pid = ::Process.pid
23
- if @ppid != process_pid
24
- @pid = nil
25
- @ppid = process_pid
26
- end
27
- @pid ||= SecureRandom.urlsafe_base64.tap { |str| @prefix_len = str.length }
28
- end
29
-
30
- # initializes the drivers when possible.
31
- def _init
32
- @drivers.each(&:connect)
33
- end
34
-
35
- # Pushes a message to the Pub/Sub drivers
36
- def push(message)
37
- # message[:type] = message[:type].name if message[:type].is_a?(Class)
38
- message[:origin] = pid
39
- hst = message.delete(:host) || Plezi.app_name
40
- yml = message.to_yaml
41
- @drivers.each { |drv| drv.push(hst, yml) }
42
- end
43
-
44
- # Parses a text message received through a Pub/Sub service.
45
- def <<(msg)
46
- msg = YAML.safe_load(msg, SAFE_TYPES)
47
- return if msg[:origin] == pid
48
- target_type = msg[:type] || :all
49
- event = msg[:event]
50
- if (target = msg[:target])
51
- Iodine::Websocket.defer(target2uuid(target)) { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[event], *(msg[:args]))) if ws._pl_ws_map[event] }
52
- return
53
- end
54
- if target_type == :all
55
- Iodine::Websocket.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[event], *(msg[:args]))) if ws._pl_ws_map[event] }
56
- return
57
- end
58
- if event == :write2everyone
59
- return unless msg[:data]
60
- mth = msg[:method]
61
- if(mth)
62
- target_type = Object.const_get target_type
63
- mth = target_type.method(mth)
64
- return unless mth
65
- Iodine::Websocket.each_write msg[:data], &mth
66
- else
67
- Iodine::Websocket.each_write msg[:data]
68
- end
69
- return
70
- end
71
- target_type = Object.const_get target_type
72
- if target_type._pl_ws_map[event]
73
- Iodine::Websocket.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[event], *(msg[:args]))) if ws.is_a?(target_type) }
74
- return
75
- end
76
-
77
- rescue => e
78
- puts '*** The following could be a security breach attempt:', e.message, e.backtrace
79
- nil
80
- end
81
-
82
- # Sends a message to a specific target, if it's on this machine, otherwise forwards the message to the Pub/Sub.
83
- def unicast(_sender, target, meth, args)
84
- return false if target.nil?
85
- if (tuuid = target2uuid(target))
86
- Iodine::Websocket.defer(tuuid) { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[meth], *args)) if ws._pl_ws_map[meth] }
87
- return true
88
- end
89
- push target: target, args: args, host: target2pid(target)
90
- end
91
-
92
- # Sends a message to a all targets of a speific **type**, as well as pushing the message to the Pub/Sub drivers.
93
- def broadcast(sender, meth, args)
94
- target_type = nil
95
- if sender.is_a?(Class)
96
- target_type = sender
97
- sender = Iodine::Websocket
98
- else
99
- target_type = sender.class
100
- end
101
- sender.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[meth], *args)) if ws.is_a?(target_type) && ws._pl_ws_map[meth] }
102
- push type: target_type.name, args: args, event: meth
103
- end
104
-
105
- # Sends a message to a all existing websocket connections, as well as pushing the message to the Pub/Sub drivers.
106
- def multicast(sender, meth, args)
107
- sender = Iodine::Websocket if sender.is_a?(Class)
108
- sender.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[meth], *args)) if ws._pl_ws_map[meth] }
109
- push type: :all, args: args, event: meth
110
- end
111
-
112
- # Writes directly to all clients of all controllers.
113
- def write2everyone(sender, data, filter_owner = nil, filter_name = nil)
114
- sender = Iodine::Websocket if sender.is_a?(Class)
115
- mth = nil
116
- raise TypeError, "Plezi\#write_each filter error - method doesn't exist? #{filter_owner}.#{filter_name}" if(filter_owner && !(mth = filter_owner.method(filter_name)))
117
- mth ? sender.each_write(data, &mth) : sender.each_write(data)
118
- push event: :write2everyone, data: data, type: (filter_owner || Iodine::Websocket).name, method: (filter_owner && filter_name)
119
- end
120
-
121
- # Converts a target Global UUID to a localized UUID
122
- def target2uuid(target)
123
- return nil unless target.start_with? pid
124
- target[@prefix_len..-1].to_i
125
- end
126
-
127
- # Extracts the machine part from a target's Global UUID
128
- def target2pid(target)
129
- target ? target[0..(@prefix_len - 1)] : Plezi.app_name
130
- end
131
- end
132
- end
133
- end
134
- # connect default drivers
135
- require 'plezi/websockets/redis'
@@ -1,54 +0,0 @@
1
- module Plezi
2
- module Base
3
- module MessageDispatch
4
- module RedisDriver
5
- @redis_locker ||= Mutex.new
6
- @redis = @redis_sub_thread = nil
7
-
8
- module_function
9
-
10
- def connect
11
- return false unless ENV['PL_REDIS_URL'] && defined?(::Redis)
12
- return @redis if (@redis_sub_thread && @redis_sub_thread.alive?) && @redis
13
- @redis_locker.synchronize do
14
- return @redis if (@redis_sub_thread && @redis_sub_thread.alive?) && @redis # repeat the test inside syncing, things change.
15
- @redis.quit if @redis
16
- @redis = ::Redis.new(url: ENV['PL_REDIS_URL'])
17
- raise "Redis connction failed for: #{ENV['PL_REDIS_URL']}" unless @redis
18
- @redis_sub_thread = Thread.new do
19
- begin
20
- ::Redis.new(url: ENV['PL_REDIS_URL']).subscribe(::Plezi.app_name, ::Plezi::Base::MessageDispatch.pid) do |on|
21
- on.message do |_channel, msg|
22
- ::Plezi::Base::MessageDispatch << msg
23
- end
24
- end
25
- rescue => e
26
- puts e.message, e.backtrace
27
- retry
28
- end
29
- end
30
- @redis
31
- end
32
- end
33
-
34
- # Get the current redis connection.
35
- def redis
36
- @redis || connect
37
- end
38
-
39
- def push(channel, message)
40
- return unless connect
41
- return if away?(channel)
42
- redis.publish(channel, message)
43
- end
44
-
45
- def away?(server)
46
- return true unless connect
47
- @redis.pubsub('CHANNELS', server).empty?
48
- end
49
- end
50
- end
51
- end
52
- end
53
-
54
- ::Plezi::Base::MessageDispatch.drivers << ::Plezi::Base::MessageDispatch::RedisDriver