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
@@ -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>
|