plezi 0.10.12 → 0.10.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +15 -19
- data/lib/plezi.rb +1 -0
- data/lib/plezi/common/api.rb +9 -5
- data/lib/plezi/common/redis.rb +1 -1
- data/lib/plezi/common/settings.rb +1 -1
- data/lib/plezi/handlers/controller_core.rb +2 -38
- data/lib/plezi/handlers/controller_magic.rb +1 -119
- data/lib/plezi/handlers/placebo old.rb +161 -0
- data/lib/plezi/handlers/placebo.rb +8 -45
- data/lib/plezi/handlers/ws_object.rb +204 -0
- data/lib/plezi/helpers/http_sender.rb +1 -1
- data/lib/plezi/version.rb +1 -1
- data/plezi.gemspec +3 -3
- data/resources/404.erb +93 -36
- data/resources/404.haml +91 -34
- data/resources/404.html +83 -26
- data/resources/404.slim +83 -26
- data/resources/500.erb +109 -52
- data/resources/500.haml +84 -27
- data/resources/500.html +109 -51
- data/resources/500.slim +83 -26
- data/resources/controller.rb +2 -1
- data/resources/mini_app.rb +8 -5
- data/resources/welcome_page.html +4 -4
- data/test/plezi_tests.rb +82 -38
- metadata +10 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f37f01622fbcda6cf86ca9575aafb2145b5479f
|
4
|
+
data.tar.gz: cbdf67890ad9995b11d058cbe821c62fad863995
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f940d9e1731e9708228c8519a3be967762c427db4d3644aac6ceb439db8a9e44ac1de7bdfd4d3e998039f70452e1e2ffe02dc4ab444c4445793a017c1c02b432
|
7
|
+
data.tar.gz: 9a336108299a74946dd97b6718f2440f80df3d5ec6dc33a925c35e5f82eb0d15c21571bbdd6a79c7c1b3cbfafd7ee79a89b3b449773e467018667a21948be1c0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,23 @@
|
|
1
1
|
#Change Log
|
2
2
|
|
3
|
+
Change log v.0.10.13
|
4
|
+
|
5
|
+
**Fix**: The Placebo API was tested and an issue with the new Placebo class broadcast method was fixed.
|
6
|
+
|
7
|
+
**Update**: Websocket code refactoring unified Placebo and Controller's API and bahavior.
|
8
|
+
|
9
|
+
**Update**: Unicasting performance using Redis was improved by creating a different Redis channel for each process, so that the unicast is only sent to the process containing the receiver.
|
10
|
+
|
11
|
+
this should mean that apps using unicasting can scale freely while apps using broadcasting need to address boradcasting considirations (broadcasting causes ALL the websocket connections - in ALL processes - to answer a broadcast, which raises scaling considirations).
|
12
|
+
|
13
|
+
***
|
14
|
+
|
15
|
+
Change log v.0.10.12
|
16
|
+
|
17
|
+
**Placebo API**: The Placebo API was reviews and revamped slightly, to allow you a better experience and better error feedback.
|
18
|
+
|
19
|
+
**Template**: The template's error pages were restyled with Plezi's new color scheme.
|
20
|
+
|
3
21
|
***
|
4
22
|
|
5
23
|
Change log v.0.10.12
|
data/README.md
CHANGED
@@ -4,11 +4,15 @@
|
|
4
4
|
|
5
5
|
Plezi is an easy to use Ruby Websocket Framework, with full RESTful routing support and HTTP streaming support. It's name comes from the word "fun", or "pleasure", since Plezi is a pleasure to work with.
|
6
6
|
|
7
|
-
|
7
|
+
With Plezi, you can easily:
|
8
8
|
|
9
|
-
|
9
|
+
1. Add Websocket services and RESTful HTTP Streaming to your existing Web-App, (Rails/Sinatra or any other Rack based Ruby app).
|
10
10
|
|
11
|
-
|
11
|
+
2. Create an easily scalable backend for your SPA.
|
12
|
+
|
13
|
+
3. Create a full fledged Ruby web application, taking full advantage of RESTful routing, HTTP streaming and scalable Websocket features.
|
14
|
+
|
15
|
+
Plezi leverages [GRHttp server](https://github.com/boazsegev/GRHttp)'s new architecture. GRHttp is a pure Ruby HTTP and Websocket Generic Server built using [GReactor](https://github.com/boazsegev/GReactor) - a multi-threaded pure ruby alternative to EventMachine with basic process forking support (enjoy forking, if your code is scaling ready).
|
12
16
|
|
13
17
|
## Installation
|
14
18
|
|
@@ -43,21 +47,11 @@ the default first port for the app is 3000. you can set the first port to listen
|
|
43
47
|
|
44
48
|
you now have a smart framework app that will happily assimilate any gem you feed it. it responds extra well to Haml, Sass and Coffee-Script, which you can enable in it's Gemfile.
|
45
49
|
|
46
|
-
## Barebones Web Service
|
47
|
-
|
48
|
-
This example is basic, useless, but required for every doc out there...
|
49
|
-
|
50
|
-
"Hello World!" in 3 lines - try it in irb (exit irb to start server):
|
51
|
-
|
52
|
-
require 'plezi'
|
53
|
-
listen
|
54
|
-
route(/.?/) { |req, res| res << "Hello World!" }
|
55
|
-
|
56
|
-
After you exit irb, the Plezi server will start up. Go to http://localhost:3000/ and see it run :)
|
57
|
-
|
58
50
|
## Plezi Controller classes
|
59
51
|
|
60
|
-
One of the best things about the Plezi is it's ability to take in any class as a controller class and route to the classes methods with special support for RESTful methods (`index`, `show`, `new`, `save`, `update`, `delete`, `before` and `after`) and for WebSockets (`pre_connect`, `on_open`, `on_message(data)`, `on_close`, `broadcast`, `unicast`, `on_broadcast(data)`)
|
52
|
+
One of the best things about the Plezi is it's ability to take in any class as a controller class and route to the classes methods with special support for RESTful methods (`index`, `show`, `new`, `save`, `update`, `delete`, `before` and `after`) and for WebSockets (`pre_connect`, `on_open`, `on_message(data)`, `on_close`, `broadcast`, `unicast`, `multicast`, `on_broadcast(data)`).
|
53
|
+
|
54
|
+
Here is a Hello World using a Controller class (run in `irb`):
|
61
55
|
|
62
56
|
require 'plezi'
|
63
57
|
|
@@ -70,11 +64,13 @@ One of the best things about the Plezi is it's ability to take in any class as a
|
|
70
64
|
listen
|
71
65
|
route '*' , Controller
|
72
66
|
|
73
|
-
|
67
|
+
exit # Plezi will autostart once you exit irb.
|
68
|
+
|
69
|
+
Except while using WebSockets, returning a String will automatically add the string to the response before sending the response - which makes for cleaner code. It's also possible to use the `response` object to set the response or stream HTTP (return true instead of a stream when you're done).
|
74
70
|
|
75
|
-
|
71
|
+
It's also possible to define a number of controllers for a similar route. The controllers will answer in the order in which the routes are defined (this allows to group code by logic instead of url).
|
76
72
|
|
77
|
-
\* please read the demo code for Plezi::StubRESTCtrl and Plezi::StubWSCtrl to learn more. Also, read more about the [GRHttp
|
73
|
+
\* please read the demo code for Plezi::StubRESTCtrl and Plezi::StubWSCtrl to learn more. Also, read more about the [GRHttp Websocket and HTTP server](https://github.com/boazsegev/GRHttp) at the core of Plezi to get more information about the amazing [HTTPRequest](http://www.rubydoc.info/github/boazsegev/GRHttp/master/GRHttp/HTTPRequest) and [HTTPResponse](http://www.rubydoc.info/github/boazsegev/GRHttp/master/GRHttp/HTTPResponse) objects.
|
78
74
|
|
79
75
|
## Native Websocket and Redis support
|
80
76
|
|
data/lib/plezi.rb
CHANGED
@@ -45,6 +45,7 @@ require 'plezi/helpers/mime_types.rb'
|
|
45
45
|
### HTTP and WebSocket Handlers
|
46
46
|
require 'plezi/handlers/http_router.rb'
|
47
47
|
require 'plezi/handlers/route.rb'
|
48
|
+
require 'plezi/handlers/ws_object.rb'
|
48
49
|
require 'plezi/handlers/controller_magic.rb'
|
49
50
|
require 'plezi/handlers/controller_core.rb'
|
50
51
|
require 'plezi/handlers/placebo.rb'
|
data/lib/plezi/common/api.rb
CHANGED
@@ -10,9 +10,9 @@ module Plezi
|
|
10
10
|
# port:: port number. defaults to 3000 or the port specified when the script was called.
|
11
11
|
# host:: the host name. defaults to any host not explicitly defined (a catch-all). NOTICE: in order to allow for hostname aliases, this is host emulation and the listening socket will bind to all the addresses available. To limit the actual binding use the `:bind` parameter as set by the GReactor's API - in which case host aliases might not work.
|
12
12
|
# alias:: a String or an Array of Strings which represent alternative host names (i.e. `alias: ["admin.google.com", "admin.gmail.com"]`).
|
13
|
-
#
|
13
|
+
# public:: the public root folder. if this is defined, static files will be served from this folder and all it's sub-folders. Plezi does NOT support file indexing.
|
14
14
|
# assets:: the assets root folder. defaults to nil (no assets support). if the path is defined, assets will be served from `/assets/...` (or the public_asset path defined) before any static files. assets will not be served if the file in the /public/assets folder if up to date (a rendering attempt will be made for systems that allow file writing).
|
15
|
-
# assets_public:: the assets public uri location (uri format, NOT a file path). defaults to `/assets`.
|
15
|
+
# assets_public:: the assets public uri location (uri format, NOT a file path). defaults to `/assets`. `save_assets` will set if assets should be saved to the assets public folder as static files (defaults to false).
|
16
16
|
# assets_callback:: a method that accepts two parameters: (request, response) and renders any custom assets. the method should return `false` unless it had set the response.
|
17
17
|
# save_assets:: saves the rendered assets to the filesystem, under the public folder. defaults to false.
|
18
18
|
# templates:: the templates root folder. defaults to nil (no template support). templates can be rendered by a Controller class, using the `render` method.
|
@@ -20,14 +20,16 @@ module Plezi
|
|
20
20
|
# ssl_key:: the public key for the SSL service.
|
21
21
|
# ssl_cert:: the certificate for the SSL service.
|
22
22
|
#
|
23
|
-
#
|
23
|
+
# Assets:
|
24
24
|
#
|
25
|
-
#
|
25
|
+
# Assets support will render `.sass`, `.scss` and `.coffee` and save them as local files (`.css`, `.css`, and `.js` respectively)
|
26
26
|
# before sending them as static files.
|
27
27
|
#
|
28
|
+
# Should you need to render a different type of asset, you can define an assets_callback (or submit a pull request with a patch).
|
29
|
+
#
|
28
30
|
# templates:
|
29
31
|
#
|
30
|
-
# ERB, Slim and Haml are natively supported.
|
32
|
+
# Plezi's controller.render ERB, Slim and Haml are natively supported.
|
31
33
|
#
|
32
34
|
# @returns [Plezi::Router]
|
33
35
|
#
|
@@ -36,6 +38,8 @@ module Plezi
|
|
36
38
|
parameters[:index_file] ||= 'index.html'
|
37
39
|
parameters[:assets_public] ||= '/assets'
|
38
40
|
parameters[:assets_public].chomp! '/'
|
41
|
+
parameters[:public] ||= parameters[:root] # backwards compatability
|
42
|
+
puts "Warning: 'root' option is being depracated. use 'public' instead." if parameters[:root]
|
39
43
|
|
40
44
|
# check if the port is used twice.
|
41
45
|
@routers_locker.synchronize do
|
data/lib/plezi/common/redis.rb
CHANGED
@@ -21,7 +21,7 @@ module Plezi
|
|
21
21
|
raise "Redis connction failed for: #{ENV['PL_REDIS_URL']}" unless @redis
|
22
22
|
@redis_sub_thread = Thread.new do
|
23
23
|
begin
|
24
|
-
Redis.new(host: @redis_uri.host, port: @redis_uri.port, password: @redis_uri.password).subscribe(Plezi::Settings.redis_channel_name) do |on|
|
24
|
+
Redis.new(host: @redis_uri.host, port: @redis_uri.port, password: @redis_uri.password).subscribe(Plezi::Settings.redis_channel_name, Plezi::Settings.uuid) do |on|
|
25
25
|
on.message do |channel, msg|
|
26
26
|
begin
|
27
27
|
data = YAML.load(msg)
|
@@ -4,6 +4,7 @@ module Plezi
|
|
4
4
|
# the methods defined in this module will be injected into the Controller's Core class (inherited from the controller).
|
5
5
|
module ControllerCore
|
6
6
|
def self.included base
|
7
|
+
base.send :include, Plezi::Base::WSObject
|
7
8
|
base.send :include, InstanceMethods
|
8
9
|
base.extend ClassMethods
|
9
10
|
end
|
@@ -65,7 +66,7 @@ module Plezi
|
|
65
66
|
return false
|
66
67
|
end
|
67
68
|
return false if data[:type] && data[:type] != :all && !self.is_a?(data[:type])
|
68
|
-
return false if data[:target] && data[:target] != ws.uuid
|
69
|
+
# return false if data[:target] && data[:target] != ws.uuid + Plezi::Settings.uuid # already reviewed by the GRHttp
|
69
70
|
return false unless self.class.has_method?(data[:method])
|
70
71
|
self.method(data[:method]).call *data[:data]
|
71
72
|
end
|
@@ -90,43 +91,6 @@ module Plezi
|
|
90
91
|
end
|
91
92
|
|
92
93
|
module ClassMethods
|
93
|
-
public
|
94
|
-
|
95
|
-
def reset_routing_cache
|
96
|
-
@methods_list = nil
|
97
|
-
@exposed_methods_list = nil
|
98
|
-
@super_methods_list = nil
|
99
|
-
has_method? nil
|
100
|
-
has_exposed_method? nil
|
101
|
-
has_super_method? nil
|
102
|
-
end
|
103
|
-
def has_method? method_name
|
104
|
-
@methods_list ||= self.instance_methods.to_set
|
105
|
-
@methods_list.include? method_name
|
106
|
-
end
|
107
|
-
def has_exposed_method? method_name
|
108
|
-
@exposed_methods_list ||= ( (self.public_instance_methods - Class.new.instance_methods - Plezi::ControllerMagic::InstanceMethods.instance_methods - [:before, :after, :save, :show, :update, :delete, :initialize, :on_message, :on_broadcast, :pre_connect, :on_open, :on_close]).delete_if {|m| m.to_s[0] == '_'} ).to_set
|
109
|
-
@exposed_methods_list.include? method_name
|
110
|
-
end
|
111
|
-
def has_super_method? method_name
|
112
|
-
@super_methods_list ||= self.superclass.instance_methods.to_set
|
113
|
-
@super_methods_list.include? method_name
|
114
|
-
end
|
115
|
-
|
116
|
-
protected
|
117
|
-
|
118
|
-
# a callback that resets the class router whenever a method (a potential route) is added
|
119
|
-
def method_added(id)
|
120
|
-
reset_routing_cache
|
121
|
-
end
|
122
|
-
# a callback that resets the class router whenever a method (a potential route) is removed
|
123
|
-
def method_removed(id)
|
124
|
-
reset_routing_cache
|
125
|
-
end
|
126
|
-
# a callback that resets the class router whenever a method (a potential route) is undefined (using #undef_method).
|
127
|
-
def method_undefined(id)
|
128
|
-
reset_routing_cache
|
129
|
-
end
|
130
94
|
end
|
131
95
|
end
|
132
96
|
end
|
@@ -204,106 +204,11 @@ module Plezi
|
|
204
204
|
end
|
205
205
|
false
|
206
206
|
end
|
207
|
-
|
208
|
-
## WebSockets Magic
|
209
|
-
|
210
|
-
# Get's the websocket's unique identifier for unicast transmissions.
|
211
|
-
#
|
212
|
-
# This UUID is also used to make sure Radis broadcasts don't triger the
|
213
|
-
# boadcasting object's event.
|
214
|
-
def uuid
|
215
|
-
return @response.uuid if @response.is_a?(GRHttp::WSEvent)
|
216
|
-
nil
|
217
|
-
end
|
218
|
-
alias :unicast_id :uuid
|
219
|
-
|
220
|
-
# WebSockets.
|
221
|
-
#
|
222
|
-
# Use this to brodcast an event to all 'sibling' websockets (websockets that have been created using the same Controller class).
|
223
|
-
#
|
224
|
-
# Accepts:
|
225
|
-
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
226
|
-
# args*:: The method's argumenst - It MUST be possible to stringify the arguments into a YAML string, or broadcasting and unicasting will fail when scaling beyond one process / one machine.
|
227
|
-
#
|
228
|
-
# The method will be called asynchrnously for each sibling instance of this Controller class.
|
229
|
-
#
|
230
|
-
def broadcast method_name, *args
|
231
|
-
return false unless self.class.has_method? method_name
|
232
|
-
self.class._inner_broadcast({ method: method_name, data: args, type: self.class}, @response.io)
|
233
|
-
end
|
234
|
-
|
235
|
-
# {include:ControllerMagic::ClassMethods#unicast}
|
236
|
-
def unicast target_uuid, method_name, *args
|
237
|
-
self.class._inner_broadcast method: method_name, data: args, target: target_uuid
|
238
|
-
end
|
239
|
-
|
240
|
-
# Use this to multicast an event to ALL websocket connections on EVERY controller, including Placebo controllers.
|
241
|
-
#
|
242
|
-
# Accepts:
|
243
|
-
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
244
|
-
# args*:: The method's argumenst - It MUST be possible to stringify the arguments into a YAML string, or broadcasting and unicasting will fail when scaling beyond one process / one machine.
|
245
|
-
#
|
246
|
-
# The method will be called asynchrnously for ALL websocket connections.
|
247
|
-
#
|
248
|
-
def multicast method_name, *args
|
249
|
-
self.class._inner_broadcast({ method: method_name, data: args, type: :all}, @response.io)
|
250
|
-
end
|
251
|
-
|
252
|
-
|
253
|
-
# # will (probably NOT), in the future, require authentication or, alternatively, return an Array [user_name, password]
|
254
|
-
# #
|
255
|
-
# #
|
256
|
-
# def request_http_auth realm = false, auth = 'Digest'
|
257
|
-
# return request.service.handler.hosts[request[:host] || :default].send_by_code request, 401, "WWW-Authenticate" => "#{auth}#{realm ? "realm=\"#{realm}\"" : ''}" unless request['authorization']
|
258
|
-
# request['authorization']
|
259
|
-
# end
|
260
207
|
end
|
261
208
|
|
262
209
|
module ClassMethods
|
263
210
|
public
|
264
211
|
|
265
|
-
# WebSockets: fires an event on all of this controller's active websocket connections.
|
266
|
-
#
|
267
|
-
# Class method.
|
268
|
-
#
|
269
|
-
# Use this to brodcast an event to all connections.
|
270
|
-
#
|
271
|
-
# accepts:
|
272
|
-
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
273
|
-
# *args:: any arguments that should be passed to the method (IF REDIS IS USED, LIMITATIONS APPLY).
|
274
|
-
#
|
275
|
-
# this method accepts and optional block (NON-REDIS ONLY) to be used as a callback for each sibling's event.
|
276
|
-
#
|
277
|
-
# the method will be called asynchrnously for each sibling instance of this Controller class.
|
278
|
-
def broadcast method_name, *args
|
279
|
-
return false unless has_method? method_name
|
280
|
-
_inner_broadcast method: method_name, data: args, type: self
|
281
|
-
end
|
282
|
-
|
283
|
-
# WebSockets: fires an event on a specific websocket connection using it's UUID.
|
284
|
-
#
|
285
|
-
# Use this to unidcast an event to specific websocket connection using it's UUID.
|
286
|
-
#
|
287
|
-
# accepts:
|
288
|
-
# target_uuid:: the target's unique UUID.
|
289
|
-
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
290
|
-
# *args:: any arguments that should be passed to the method (IF REDIS IS USED, LIMITATIONS APPLY).
|
291
|
-
def unicast target_uuid, method_name, *args
|
292
|
-
_inner_broadcast method: method_name, data: args, target: target_uuid
|
293
|
-
end
|
294
|
-
|
295
|
-
# Use this to multicast an event to ALL websocket connections on EVERY controller, including Placebo controllers.
|
296
|
-
#
|
297
|
-
# Accepts:
|
298
|
-
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
299
|
-
# args*:: The method's argumenst - It MUST be possible to stringify the arguments into a YAML string, or broadcasting and unicasting will fail when scaling beyond one process / one machine.
|
300
|
-
#
|
301
|
-
# The method will be called asynchrnously for ALL websocket connections.
|
302
|
-
#
|
303
|
-
def multicast method_name, *args
|
304
|
-
_inner_broadcast({ method: method_name, data: args, type: :all}, @response.io)
|
305
|
-
end
|
306
|
-
|
307
212
|
# This class method behaves the same way as the instance method #url_for. See the instance method's documentation for more details.
|
308
213
|
def url_for dest
|
309
214
|
get_pl_route.url_for dest
|
@@ -311,7 +216,7 @@ module Plezi
|
|
311
216
|
|
312
217
|
# resets the routing cache
|
313
218
|
def reset_routing_cache
|
314
|
-
@inheritance.each {|sub| sub.reset_routing_cache}
|
219
|
+
@inheritance.each {|sub| sub.reset_routing_cache} if @inheritance
|
315
220
|
end
|
316
221
|
|
317
222
|
protected
|
@@ -346,29 +251,6 @@ module Plezi
|
|
346
251
|
def inherited sub
|
347
252
|
(@inheritance ||= [].to_set) << sub
|
348
253
|
end
|
349
|
-
|
350
|
-
public
|
351
|
-
|
352
|
-
|
353
|
-
# WebSockets
|
354
|
-
|
355
|
-
# sends the broadcast
|
356
|
-
def _inner_broadcast data, ignore_io = nil
|
357
|
-
if data[:target]
|
358
|
-
__inner_redis_broadcast data unless GRHttp::Base::WSHandler.unicast data[:target], data
|
359
|
-
else
|
360
|
-
GRHttp::Base::WSHandler.broadcast data, ignore_io
|
361
|
-
__inner_redis_broadcast data
|
362
|
-
end
|
363
|
-
true
|
364
|
-
end
|
365
|
-
|
366
|
-
def __inner_redis_broadcast data
|
367
|
-
conn = Plezi.redis_connection
|
368
|
-
data[:server] = Plezi::Settings.uuid
|
369
|
-
return conn.publish( Plezi::Settings.redis_channel_name, data.to_yaml ) if conn
|
370
|
-
false
|
371
|
-
end
|
372
254
|
end
|
373
255
|
|
374
256
|
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
module Plezi
|
2
|
+
|
3
|
+
# This API wil allows you to listen to Websocket Broadcasts sent to any object and to accept unicasts
|
4
|
+
# even when NOT connected to a websocket.
|
5
|
+
#
|
6
|
+
# Simpley create a class to handle any events and call `Plezi::Placebo.new ClassName` :
|
7
|
+
#
|
8
|
+
# class MyListener
|
9
|
+
# def _my_method_name *args
|
10
|
+
# #logic
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# Plezi::Placebo.new MyListener
|
15
|
+
#
|
16
|
+
# A new instance will be created and that instance will answer any broadcasts, for ALL possible
|
17
|
+
# Plezi controllers, as long as it had a method defined that is capable to handle the broadcast.
|
18
|
+
#
|
19
|
+
# The new instance will also accept unicasts sent to it's unique UUID.
|
20
|
+
#
|
21
|
+
# Returns an instance that is a member of the class passed, after that class was inherited by Plezi and
|
22
|
+
# more methods were injected into it's subclass.
|
23
|
+
module Placebo
|
24
|
+
|
25
|
+
# the base module exposes some of the core functionality, but shouldn't be relied upon as far as it's API goes.
|
26
|
+
module Base
|
27
|
+
#the methods here will be injected to the Placebo controller.
|
28
|
+
module Core
|
29
|
+
def self.included base
|
30
|
+
base.send :include, InstanceMethods
|
31
|
+
base.extend ClassMethods
|
32
|
+
base.superclass.instance_eval {extend SuperClassMethods}
|
33
|
+
end
|
34
|
+
|
35
|
+
#the methods here will be injected to the Placebo controller as Instance methods.
|
36
|
+
module InstanceMethods
|
37
|
+
public
|
38
|
+
attr_accessor :io
|
39
|
+
def initialize io
|
40
|
+
@io = io
|
41
|
+
@io[:websocket_handler] = self
|
42
|
+
super()
|
43
|
+
end
|
44
|
+
# notice of disconnect
|
45
|
+
def on_close
|
46
|
+
return super() if defined? super
|
47
|
+
GR.warn "Placebo #{self.class.superclass.name} disconnected. Ignore if this message appears during shutdown."
|
48
|
+
end
|
49
|
+
|
50
|
+
# handles broadcasts / unicasts
|
51
|
+
def on_broadcast ws
|
52
|
+
data = ws.data
|
53
|
+
unless (data[:type] || data[:target]) && data[:method] && data[:data]
|
54
|
+
GReactor.warn "Broadcast message unknown... falling back on base broadcasting"
|
55
|
+
return super(data) if defined? super
|
56
|
+
return false
|
57
|
+
end
|
58
|
+
return false if data[:type] && data[:type] != :all && !self.is_a?(data[:type])
|
59
|
+
return ((data[:type] == :all) ? false : (raise "Placebo Broadcasting recieved but no method can handle it - dump:\r\n #{data.to_s}") ) unless self.class.has_super_method?(data[:method])
|
60
|
+
self.method(data[:method]).call *data[:data]
|
61
|
+
end
|
62
|
+
# Returns the websocket connection's UUID, used for unicasting.
|
63
|
+
def uuid
|
64
|
+
return (@uuid ||= @response.uuid + Plezi::Settings.uuid) if @response.is_a?(GRHttp::WSEvent)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Performs a websocket unicast to the specified target.
|
68
|
+
def unicast target_uuid, method_name, *args
|
69
|
+
self.class.unicast target_uuid, method_name, *args
|
70
|
+
end
|
71
|
+
# broadcast to a specific controller
|
72
|
+
def broadcast controller_class, method_name, *args
|
73
|
+
GRHttp::Base::WSHandler.broadcast({data: args, type: controller_class, method: method_name}, self)
|
74
|
+
self.class.__send_to_redis data: args, type: controller_class, method: method_name
|
75
|
+
end
|
76
|
+
# multicast to all handlers.
|
77
|
+
def multicast method_name, *args
|
78
|
+
self.class.multicast method_name, *args
|
79
|
+
end
|
80
|
+
end
|
81
|
+
#the methods here will be injected to the Placebo controller as class methods.
|
82
|
+
module ClassMethods
|
83
|
+
public
|
84
|
+
def has_super_method? method_name
|
85
|
+
@super_methods_list ||= self.superclass.instance_methods.to_set
|
86
|
+
@super_methods_list.include? method_name
|
87
|
+
end
|
88
|
+
end
|
89
|
+
module SuperClassMethods
|
90
|
+
# multicast to all handlers.
|
91
|
+
def multicast method_name, *args
|
92
|
+
GRHttp::Base::WSHandler.broadcast({method: method_name, data: args, type: :all}, self)
|
93
|
+
__send_to_redis method: method_name, data: args, type: :all
|
94
|
+
end
|
95
|
+
# Broadcast to all instances (usually one instance) of THIS placebo controller.
|
96
|
+
#
|
97
|
+
# This should be used by the real websocket connections to forward messages to the placebo controller classes.
|
98
|
+
def broadcast method_name, *args
|
99
|
+
@methods_list ||= self.public_instance_methods.to_set
|
100
|
+
raise "No mothod defined to accept this broadcast." unless @methods_list.include? method_name
|
101
|
+
GRHttp::Base::WSHandler.broadcast data: args, type: self, method: method_name
|
102
|
+
__send_to_redis data: args, type: self, method: method_name
|
103
|
+
end
|
104
|
+
# Performs a websocket unicast to the specified target.
|
105
|
+
def unicast target_uuid, method_name, *args
|
106
|
+
GRHttp::Base::WSHandler.unicast target_uuid, data: args, target: target_uuid, method: method_name
|
107
|
+
__send_to_redis data: args, target: target_uuid, method: method_name
|
108
|
+
end
|
109
|
+
protected
|
110
|
+
def __send_to_redis data
|
111
|
+
raise "Wrong method name for websocket broadcasting - expecting type Symbol" unless data[:method].is_a?(Symbol) || data[:method].is_a?(Symbol)
|
112
|
+
conn = Plezi.redis_connection
|
113
|
+
data[:server] = Plezi::Settings.uuid
|
114
|
+
return conn.publish( Plezi::Settings.redis_channel_name, data.to_yaml ) if conn
|
115
|
+
false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
class PlaceboIO < GReactor::BasicIO
|
120
|
+
def clear?
|
121
|
+
io.closed?
|
122
|
+
end
|
123
|
+
def call
|
124
|
+
self.read
|
125
|
+
GR.warn "Placebo IO recieved IO signal - this is unexpected..."
|
126
|
+
end
|
127
|
+
def on_disconnect
|
128
|
+
@params[:out].close rescue nil
|
129
|
+
@cache[:websocket_handler].on_close if @cache[:websocket_handler]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
module_function
|
134
|
+
def new placebo_class
|
135
|
+
new_class_name = "PlaceboPlezi__#{placebo_class.name.gsub /[\:\-\#\<\>\{\}\(\)\s]/, '_'}"
|
136
|
+
new_class = nil
|
137
|
+
new_class = Module.const_get new_class_name if Module.const_defined? new_class_name
|
138
|
+
unless new_class
|
139
|
+
new_class = Class.new(placebo_class) do
|
140
|
+
include Placebo::Base::Core
|
141
|
+
end
|
142
|
+
Object.const_set(new_class_name, new_class)
|
143
|
+
end
|
144
|
+
i, o = IO.pipe
|
145
|
+
io = Placebo::Base::PlaceboIO.new i, out: o
|
146
|
+
io = GReactor.add_raw_io i, io
|
147
|
+
new_class.new(io)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
# class A
|
154
|
+
# def _hi
|
155
|
+
# 'hi'
|
156
|
+
# end
|
157
|
+
# end
|
158
|
+
# Plezi::Placebo.new A
|
159
|
+
# a = nil
|
160
|
+
# GReactor.each {|h| a= h}
|
161
|
+
# a[:websocket_handler].on_broadcast GRHttp::WSEvent.new(nil, type: true, data: [], method: :_hi)
|