plezi 0.10.12 → 0.10.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
@@ -29,6 +29,8 @@ module Plezi
|
|
29
29
|
def self.included base
|
30
30
|
base.send :include, InstanceMethods
|
31
31
|
base.extend ClassMethods
|
32
|
+
base.superclass.instance_eval {extend SuperClassMethods}
|
33
|
+
base.send :include, Plezi::Base::WSObject
|
32
34
|
end
|
33
35
|
|
34
36
|
#the methods here will be injected to the Placebo controller as Instance methods.
|
@@ -45,55 +47,16 @@ module Plezi
|
|
45
47
|
return super() if defined? super
|
46
48
|
GR.warn "Placebo #{self.class.superclass.name} disconnected. Ignore if this message appears during shutdown."
|
47
49
|
end
|
48
|
-
|
49
|
-
|
50
|
-
def on_broadcast ws
|
51
|
-
data = ws.data
|
52
|
-
unless (data[:type] || data[:target]) && data[:method] && data[:data]
|
53
|
-
GReactor.warn "Broadcast message unknown... falling back on base broadcasting"
|
54
|
-
return super(data) if defined? super
|
55
|
-
return false
|
56
|
-
end
|
57
|
-
# return false if data[:type] && !self.is_a?(data[:type])
|
58
|
-
return false if data[:target] && data[:target] != ws.uuid
|
59
|
-
return false 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
|
-
io[:uuid] ||= SecureRandom.uuid
|
65
|
-
end
|
66
|
-
|
67
|
-
# Performs a websocket unicast to the specified target.
|
68
|
-
def unicast target_uuid, method_name, *args
|
69
|
-
GRHttp::Base::WSHandler.unicast target_uuid, data: args, target: target_uuid, method: method_name
|
70
|
-
__send_to_redis data: args, target: target_uuid, method: method_name
|
71
|
-
end
|
72
|
-
# broadcast to a specific controller
|
73
|
-
def broadcast controller_class, method_name, *args
|
74
|
-
GRHttp::Base::WSHandler.broadcast({data: args, type: controller_class, method: method_name}, self)
|
75
|
-
__send_to_redis data: args, type: controller_class, method: method_name
|
76
|
-
end
|
77
|
-
# multicast to all handlers.
|
78
|
-
def multicast method_name, *args
|
79
|
-
GRHttp::Base::WSHandler.broadcast({method: method_name, data: args, type: :all}, self)
|
80
|
-
__send_to_redis method: method_name, data: args, type: :all
|
81
|
-
end
|
82
|
-
protected
|
83
|
-
def __send_to_redis data
|
84
|
-
raise "Wrong method name for websocket broadcasting - expecting type Symbol" unless data[:method].is_a?(Symbol) || data[:method].is_a?(Symbol)
|
85
|
-
conn = Plezi.redis_connection
|
86
|
-
data[:server] = Plezi::Settings.uuid
|
87
|
-
return conn.publish( Plezi::Settings.redis_channel_name, data.to_yaml ) if conn
|
88
|
-
false
|
50
|
+
def placebo?
|
51
|
+
true
|
89
52
|
end
|
90
53
|
end
|
91
54
|
#the methods here will be injected to the Placebo controller as class methods.
|
92
55
|
module ClassMethods
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
56
|
+
end
|
57
|
+
module SuperClassMethods
|
58
|
+
def placebo?
|
59
|
+
true
|
97
60
|
end
|
98
61
|
end
|
99
62
|
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
module Plezi
|
2
|
+
|
3
|
+
# the methods defined in this module will be injected into the Controller class passed to
|
4
|
+
# Plezi (using the `route` or `shared_route` commands), and will be available
|
5
|
+
# for the controller to use within it's methods.
|
6
|
+
#
|
7
|
+
# for some reason, the documentation ignores the following additional attributes, which are listed here:
|
8
|
+
#
|
9
|
+
# request:: the HTTPRequest object containing all the data from the HTTP request. If a WebSocket connection was established, the `request` object will continue to contain the HTTP request establishing the connection (cookies, parameters sent and other information).
|
10
|
+
# params:: any parameters sent with the request (short-cut for `request.params`), will contain any GET or POST form data sent (including file upload and JSON format support).
|
11
|
+
# cookies:: a cookie-jar to get and set cookies (set: `cookie\[:name] = data` or get: `cookie\[:name]`). Cookies and some other data must be set BEFORE the response's headers are sent.
|
12
|
+
# flash:: a temporary cookie-jar, good for one request. this is a short-cut for the `response.flash` which handles this magical cookie style.
|
13
|
+
# response:: the HTTPResponse **OR** the WSResponse object that formats the response and sends it. use `response << data`. This object can be used to send partial data (such as headers, or partial html content) in blocking mode as well as sending data in the default non-blocking mode.
|
14
|
+
# host_params:: a copy of the parameters used to create the host and service which accepted the request and created this instance of the controller class.
|
15
|
+
#
|
16
|
+
module Base
|
17
|
+
|
18
|
+
# This module includes all the methods that will be injected into Websocket objects,
|
19
|
+
# specifically into Plezi Controllers and Placebo objects.
|
20
|
+
module WSObject
|
21
|
+
def self.included base
|
22
|
+
base.send :include, InstanceMethods
|
23
|
+
base.extend ClassMethods
|
24
|
+
base.superclass.instance_eval {extend SuperClassMethods}
|
25
|
+
end
|
26
|
+
|
27
|
+
module InstanceMethods
|
28
|
+
public
|
29
|
+
# handles broadcasts / unicasts
|
30
|
+
def on_broadcast ws
|
31
|
+
data = ws.data
|
32
|
+
unless (data[:type] || data[:target]) && data[:method] && data[:data]
|
33
|
+
GReactor.warn "Broadcast message unknown... falling back on base broadcasting"
|
34
|
+
return super(data) if defined? super
|
35
|
+
return false
|
36
|
+
end
|
37
|
+
return false if data[:type] && data[:type] != :all && !self.is_a?(data[:type])
|
38
|
+
return ((data[:type] == :all) ? false : (raise "Broadcasting recieved but no method can handle it - dump:\r\n #{data.to_s}") ) unless self.class.has_super_method?(data[:method])
|
39
|
+
self.method(data[:method]).call *data[:data]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Performs a websocket unicast to the specified target.
|
43
|
+
def unicast target_uuid, method_name, *args
|
44
|
+
self.class.unicast target_uuid, method_name, *args
|
45
|
+
end
|
46
|
+
|
47
|
+
# Use this to brodcast an event to all 'sibling' objects (websockets that have been created using the same Controller class).
|
48
|
+
#
|
49
|
+
# Accepts:
|
50
|
+
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
51
|
+
# 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.
|
52
|
+
#
|
53
|
+
# The method will be called asynchrnously for each sibling instance of this Controller class.
|
54
|
+
#
|
55
|
+
def broadcast method_name, *args
|
56
|
+
return false unless self.class.has_method? method_name
|
57
|
+
self.class._inner_broadcast({ method: method_name, data: args, type: self.class}, __get_io )
|
58
|
+
end
|
59
|
+
# Use this to multicast an event to ALL websocket connections on EVERY controller, including Placebo controllers.
|
60
|
+
#
|
61
|
+
# Accepts:
|
62
|
+
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
63
|
+
# 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.
|
64
|
+
#
|
65
|
+
# The method will be called asynchrnously for ALL websocket connections.
|
66
|
+
#
|
67
|
+
def multicast method_name, *args
|
68
|
+
self.class._inner_broadcast({ method: method_name, data: args, type: :all}, __get_io )
|
69
|
+
end
|
70
|
+
|
71
|
+
# Get's the websocket's unique identifier for unicast transmissions.
|
72
|
+
#
|
73
|
+
# This UUID is also used to make sure Radis broadcasts don't triger the
|
74
|
+
# boadcasting object's event.
|
75
|
+
def uuid
|
76
|
+
return @uuid if @uuid
|
77
|
+
if @response && @response.is_a?(GRHttp::WSEvent)
|
78
|
+
return (@uuid ||= @response.uuid + Plezi::Settings.uuid)
|
79
|
+
elsif @io
|
80
|
+
return (@uuid ||= (@io[:uuid] ||= SecureRandom.uuid) + Plezi::Settings.uuid)
|
81
|
+
end
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
alias :unicast_id :uuid
|
85
|
+
|
86
|
+
protected
|
87
|
+
def __get_io
|
88
|
+
@io ||= (@request ? @request.io : nil)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
module ClassMethods
|
92
|
+
|
93
|
+
def reset_routing_cache
|
94
|
+
@methods_list = nil
|
95
|
+
@exposed_methods_list = nil
|
96
|
+
@super_methods_list = nil
|
97
|
+
has_method? nil
|
98
|
+
has_exposed_method? nil
|
99
|
+
has_super_method? nil
|
100
|
+
end
|
101
|
+
def has_method? method_name
|
102
|
+
@methods_list ||= self.instance_methods.to_set
|
103
|
+
@methods_list.include? method_name
|
104
|
+
end
|
105
|
+
def has_super_method? method_name
|
106
|
+
@super_methods_list ||= self.superclass.instance_methods.to_set
|
107
|
+
@super_methods_list.include? method_name
|
108
|
+
end
|
109
|
+
def has_exposed_method? method_name
|
110
|
+
@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
|
111
|
+
@exposed_methods_list.include? method_name
|
112
|
+
end
|
113
|
+
protected
|
114
|
+
|
115
|
+
# a callback that resets the class router whenever a method (a potential route) is added
|
116
|
+
def method_added(id)
|
117
|
+
reset_routing_cache
|
118
|
+
end
|
119
|
+
# a callback that resets the class router whenever a method (a potential route) is removed
|
120
|
+
def method_removed(id)
|
121
|
+
reset_routing_cache
|
122
|
+
end
|
123
|
+
# a callback that resets the class router whenever a method (a potential route) is undefined (using #undef_method).
|
124
|
+
def method_undefined(id)
|
125
|
+
reset_routing_cache
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
module SuperClassMethods
|
131
|
+
public
|
132
|
+
|
133
|
+
# WebSockets: fires an event on all of this controller's active websocket connections.
|
134
|
+
#
|
135
|
+
# Class method.
|
136
|
+
#
|
137
|
+
# Use this to brodcast an event to all connections.
|
138
|
+
#
|
139
|
+
# accepts:
|
140
|
+
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
141
|
+
# *args:: any arguments that should be passed to the method (IF REDIS IS USED, LIMITATIONS APPLY).
|
142
|
+
#
|
143
|
+
# this method accepts and optional block (NON-REDIS ONLY) to be used as a callback for each sibling's event.
|
144
|
+
#
|
145
|
+
# the method will be called asynchrnously for each sibling instance of this Controller class.
|
146
|
+
def broadcast method_name, *args
|
147
|
+
return false unless has_method? method_name
|
148
|
+
_inner_broadcast method: method_name, data: args, type: self
|
149
|
+
end
|
150
|
+
|
151
|
+
# WebSockets: fires an event on a specific websocket connection using it's UUID.
|
152
|
+
#
|
153
|
+
# Use this to unidcast an event to specific websocket connection using it's UUID.
|
154
|
+
#
|
155
|
+
# accepts:
|
156
|
+
# target_uuid:: the target's unique UUID.
|
157
|
+
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
158
|
+
# *args:: any arguments that should be passed to the method (IF REDIS IS USED, LIMITATIONS APPLY).
|
159
|
+
def unicast target_uuid, method_name, *args
|
160
|
+
raise 'No target specified for unicasting!' unless target_uuid
|
161
|
+
@@uuid_cutoff ||= Plezi::Settings.uuid.length
|
162
|
+
_inner_broadcast method: method_name, data: args, target: target_uuid[0...@@uuid_cutoff], to_server: target_uuid[@@uuid_cutoff..-1]
|
163
|
+
end
|
164
|
+
|
165
|
+
# Use this to multicast an event to ALL websocket connections on EVERY controller, including Placebo controllers.
|
166
|
+
#
|
167
|
+
# Accepts:
|
168
|
+
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
169
|
+
# 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.
|
170
|
+
#
|
171
|
+
# The method will be called asynchrnously for ALL websocket connections.
|
172
|
+
#
|
173
|
+
def multicast method_name, *args
|
174
|
+
_inner_broadcast method: method_name, data: args, type: :all
|
175
|
+
end
|
176
|
+
|
177
|
+
# WebSockets
|
178
|
+
|
179
|
+
# sends the broadcast
|
180
|
+
def _inner_broadcast data, ignore_io = nil
|
181
|
+
if data[:target]
|
182
|
+
return ( (data[:to_server].nil? || data[:to_server] == Plezi::Settings.uuid) ? GRHttp::Base::WSHandler.unicast(data[:target], data) : false ) || __inner_redis_broadcast(data)
|
183
|
+
else
|
184
|
+
GRHttp::Base::WSHandler.broadcast data, ignore_io
|
185
|
+
__inner_redis_broadcast data
|
186
|
+
end
|
187
|
+
true
|
188
|
+
end
|
189
|
+
|
190
|
+
def __inner_redis_broadcast data
|
191
|
+
return unless conn = Plezi.redis_connection
|
192
|
+
data[:server] = Plezi::Settings.uuid
|
193
|
+
return conn.publish( ( data[:to_server] ? data[:to_server] : Plezi::Settings.redis_channel_name ), data.to_yaml ) if conn
|
194
|
+
false
|
195
|
+
end
|
196
|
+
|
197
|
+
def has_method? method_name
|
198
|
+
@methods_list ||= self.instance_methods.to_set
|
199
|
+
@methods_list.include? method_name
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -35,7 +35,7 @@ module Plezi
|
|
35
35
|
#
|
36
36
|
# returns true if data was sent.
|
37
37
|
def send_static_file request, response
|
38
|
-
root = request.io[:params][:
|
38
|
+
root = request.io[:params][:public]
|
39
39
|
return false unless root
|
40
40
|
file_requested = request[:path].to_s.split('/')
|
41
41
|
unless file_requested.include? '..'
|
data/lib/plezi/version.rb
CHANGED
data/plezi.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Plezi::VERSION
|
9
9
|
spec.authors = ["Boaz Segev"]
|
10
10
|
spec.email = ['boaz@2be.co.il']
|
11
|
-
spec.summary = %q{Plezi
|
12
|
-
spec.description = %q{Plezi
|
11
|
+
spec.summary = %q{Plezi - the easy way to add Websockets, RESTful routing and HTTP streaming serviced to Ruby Web-Apps.}
|
12
|
+
spec.description = %q{Plezi - the easy way to add Websockets, RESTful routing and HTTP streaming serviced to Ruby Web-Apps.}
|
13
13
|
spec.homepage = "http://boazsegev.github.io/plezi/"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "grhttp", "~> 0.0.
|
21
|
+
spec.add_dependency "grhttp", "~> 0.0.16"
|
22
22
|
spec.add_development_dependency "bundler", "~> 1.7"
|
23
23
|
spec.add_development_dependency "rake", "~> 10.0"
|
24
24
|
|
data/resources/404.erb
CHANGED
@@ -1,57 +1,114 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
2
|
<head>
|
3
3
|
<style>
|
4
|
+
/*
|
5
|
+
med-blue: #44518E
|
6
|
+
dark-gray: #424A70
|
7
|
+
blue: #1B2864
|
8
|
+
light-blue: #818ECE
|
9
|
+
light-gray: #99A3CE
|
10
|
+
*/
|
4
11
|
body, html
|
5
12
|
{
|
6
|
-
|
7
|
-
|
8
|
-
|
13
|
+
background-color: #99A3CE;
|
14
|
+
padding: 0; margin: 0;
|
15
|
+
width: 100%;
|
16
|
+
font-size: 1em;
|
9
17
|
}
|
18
|
+
|
10
19
|
h1
|
11
20
|
{
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
21
|
+
font-family: 'Shadows Into Light', cursive;
|
22
|
+
background-color: #1B2864;
|
23
|
+
color: #99A3CE;
|
24
|
+
text-align: center;
|
25
|
+
border-bottom: 4px solid #424A70;
|
26
|
+
margin: 0 0 1em 0;
|
27
|
+
padding: 0.4em 0;
|
28
|
+
width: 100%;
|
29
|
+
}
|
30
|
+
h2
|
31
|
+
{
|
32
|
+
background-color: #44518E;
|
33
|
+
color: #C6CCE7;
|
34
|
+
text-align: left;
|
35
|
+
margin: 0 0 1em 0;
|
36
|
+
padding: 0.5em 5%;
|
37
|
+
border-radius: 20px 20px 0 0;
|
38
|
+
font-family: 'Architects Daughter', cursive;
|
39
|
+
border-bottom: 3px solid #424A70;
|
40
|
+
font-size: 1.2em;
|
41
|
+
}
|
42
|
+
h3
|
43
|
+
{
|
44
|
+
font-family: 'Architects Daughter', cursive;
|
45
|
+
background-color: #44518E;
|
46
|
+
color: #C6CCE7;
|
47
|
+
text-align: left;
|
48
|
+
margin: 0 0 1em 0;
|
49
|
+
padding: 0.5em 5%;
|
50
|
+
border-radius: 1em 1em 0 0;
|
51
|
+
border-bottom: 2px solid #424A70;
|
52
|
+
font-size: 1.1em;
|
53
|
+
}
|
54
|
+
h2:before {
|
55
|
+
content: "|||";
|
56
|
+
color: #99A3CE;
|
57
|
+
padding-right: 0.3em;
|
58
|
+
margin-left: -0.5em;
|
59
|
+
}
|
60
|
+
h3:before {
|
61
|
+
content: "|||||";
|
62
|
+
color: #99A3CE;
|
63
|
+
padding-right: 0.3em;
|
64
|
+
margin-left: -1em;
|
65
|
+
}
|
66
|
+
h1 a, h2 a, h3 a
|
67
|
+
{
|
68
|
+
color: #EBCD86;
|
69
|
+
}
|
70
|
+
h1 a:hover, h2 a:hover, h3 a:hover
|
71
|
+
{
|
72
|
+
color: #EBD7A6;
|
19
73
|
}
|
20
74
|
p
|
21
75
|
{
|
22
|
-
|
23
|
-
|
24
|
-
|
76
|
+
font-size: 1em;
|
77
|
+
padding: 0 1em;
|
78
|
+
margin: 0.5em 0;
|
25
79
|
}
|
26
|
-
|
80
|
+
a
|
81
|
+
{
|
82
|
+
color: #D0AC54;
|
83
|
+
text-decoration: none;
|
84
|
+
}
|
85
|
+
a:hover
|
27
86
|
{
|
28
|
-
|
29
|
-
|
30
|
-
padding: 0 0 2% 0;
|
31
|
-
border-radius: 20px;
|
32
|
-
min-height: 50%;
|
33
|
-
color: #007;
|
87
|
+
color: #927121;
|
88
|
+
text-decoration: underline;
|
34
89
|
}
|
35
|
-
#wrapper
|
90
|
+
#wrapper
|
36
91
|
{
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
border-radius: 20px;
|
92
|
+
background-color: #fff;
|
93
|
+
margin: 1em 5%;
|
94
|
+
padding: 0 0 2%;
|
95
|
+
border-radius: 20px;
|
96
|
+
min-height: 50%;
|
97
|
+
color: #007;
|
44
98
|
}
|
99
|
+
|
45
100
|
#wrapper p{ padding: 0 2%;}
|
46
|
-
|
101
|
+
.bold { font-weight: bold; }
|
102
|
+
#wrapper ol li{ padding: 0 2%;}
|
103
|
+
pre
|
104
|
+
{
|
105
|
+
border-radius: 20px;
|
106
|
+
padding: 0.5em 0;
|
107
|
+
background-color: #444;
|
108
|
+
color: #ddd;
|
109
|
+
}
|
110
|
+
@media screen and (max-width: 680px)
|
47
111
|
{
|
48
|
-
color:#904;
|
49
|
-
font-size: 1.4em;
|
50
|
-
padding: 0.5em 0;
|
51
|
-
text-align: center;
|
52
|
-
background-color: #fee;
|
53
|
-
border-bottom: 1px solid #800;
|
54
|
-
margin: 0;
|
55
112
|
}
|
56
113
|
</style>
|
57
114
|
</head>
|