plezi 0.14.4 → 0.14.5
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 +12 -0
- data/bin/ws_shootout +30 -0
- data/exe/plezi +108 -108
- data/lib/plezi.rb +18 -26
- data/lib/plezi/activation.rb +16 -16
- data/lib/plezi/api.rb +51 -50
- data/lib/plezi/controller/controller.rb +249 -229
- data/lib/plezi/controller/controller_class.rb +189 -174
- data/lib/plezi/controller/cookies.rb +49 -47
- data/lib/plezi/helpers.rb +35 -35
- data/lib/plezi/render/erb.rb +25 -26
- data/lib/plezi/render/has_cache.rb +31 -31
- data/lib/plezi/render/markdown.rb +53 -53
- data/lib/plezi/render/render.rb +36 -38
- data/lib/plezi/render/sass.rb +43 -44
- data/lib/plezi/render/slim.rb +25 -25
- data/lib/plezi/router/adclient.rb +14 -15
- data/lib/plezi/router/assets.rb +59 -61
- data/lib/plezi/router/errors.rb +22 -22
- data/lib/plezi/router/route.rb +98 -100
- data/lib/plezi/router/router.rb +120 -113
- data/lib/plezi/version.rb +1 -1
- data/lib/plezi/websockets/message_dispatch.rb +118 -80
- data/lib/plezi/websockets/redis.rb +42 -43
- data/plezi.gemspec +3 -3
- data/resources/client.js +229 -204
- data/resources/ctrlr.rb +26 -26
- data/resources/mini_app.rb +1 -1
- data/resources/simple-client.js +50 -43
- metadata +9 -8
data/lib/plezi/router/router.rb
CHANGED
@@ -2,127 +2,134 @@ require 'plezi/router/route'
|
|
2
2
|
require 'plezi/router/errors'
|
3
3
|
require 'plezi/router/assets'
|
4
4
|
require 'plezi/router/adclient'
|
5
|
-
require 'rack'
|
6
5
|
|
7
6
|
module Plezi
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
module Base
|
8
|
+
# this module is incharge of routing requests to the correct Controller.
|
9
|
+
module Router
|
10
|
+
@routes = []
|
11
|
+
@app = nil
|
12
12
|
|
13
|
-
|
13
|
+
module_function
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
15
|
+
# Creates a new router
|
16
|
+
def new(app)
|
17
|
+
if app && app != call_method
|
18
|
+
puts 'Plezi as Middleware'
|
19
|
+
@app = app
|
20
|
+
end
|
21
|
+
Plezi.app
|
22
|
+
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
24
|
+
# called when an HTTP request had arrived
|
25
|
+
def call(env)
|
26
|
+
request = Rack::Request.new(env)
|
27
|
+
response = Rack::Response.new
|
28
|
+
ret = nil
|
29
|
+
@routes.each { |route| ret = route.call(request, response); break if ret }
|
30
|
+
unless ret
|
31
|
+
return @app.call(env) if @app
|
32
|
+
ret = ::Plezi::Base::Err404Ctrl.new._pl_respond(request, response, request.params)
|
33
|
+
end
|
34
|
+
response.write(ret) if ret.is_a?(String)
|
35
|
+
return response.finish
|
36
|
+
rescue => e
|
37
|
+
puts e.message, e.backtrace
|
38
|
+
response = Rack::Response.new
|
39
|
+
response.write ::Plezi::Base::Err500Ctrl.new._pl_respond(request, response, request.params)
|
40
|
+
return response.finish
|
41
|
+
end
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
# returns the `call` method. Used repeatedly in middleware mode and only once in application mode.
|
44
|
+
def call_method
|
45
|
+
@call_method ||= Plezi::Base::Router.method(:call)
|
46
|
+
end
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
48
|
+
# Creates a new route.
|
49
|
+
#
|
50
|
+
# `path`:: should be a string describing the route. Named parameters are allowed.
|
51
|
+
# `controller`:: should be a Class object that will receive all the class properties of a Plezi Controller, or one of the allowed keywords.
|
52
|
+
def route(path, controller)
|
53
|
+
path = path.chomp('/'.freeze) unless path == '/'.freeze
|
54
|
+
case controller
|
55
|
+
when :client
|
56
|
+
controller = ::Plezi::Base::Router::ADClient
|
57
|
+
when :assets
|
58
|
+
controller = ::Plezi::Base::Assets
|
59
|
+
path << '/*'.freeze unless path[-1] == '*'.freeze
|
60
|
+
when Regexp
|
61
|
+
path << '/*'.freeze unless path[-1] == '*'.freeze
|
62
|
+
return @routes << RouteRewrite.new(path, controller)
|
63
|
+
end
|
64
|
+
@routes << Route.new(path, controller)
|
65
|
+
end
|
60
66
|
|
61
|
-
|
62
|
-
|
63
|
-
|
67
|
+
def list
|
68
|
+
@routes
|
69
|
+
end
|
64
70
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
71
|
+
# Returns the URL for requested controller method and paramerets.
|
72
|
+
def url_for(controller, method_sym, params = {})
|
73
|
+
# GET,PUT,POST,DELETE
|
74
|
+
r = nil
|
75
|
+
url = '/'.dup
|
76
|
+
@routes.each do |tmp|
|
77
|
+
case tmp.controller
|
78
|
+
when Class
|
79
|
+
next if tmp.controller != controller
|
80
|
+
r = tmp
|
81
|
+
break
|
82
|
+
when Regexp
|
83
|
+
nm = nil
|
84
|
+
nm = tmp.param_names[0] if params[tmp.param_names[0]]
|
85
|
+
nm ||= tmp.param_names[0].to_sym
|
86
|
+
url << "#{params.delete nm}/" if params[nm] && params[nm].to_s =~ tmp.controller
|
87
|
+
else
|
88
|
+
next
|
89
|
+
end
|
90
|
+
end
|
91
|
+
return nil if r.nil?
|
92
|
+
case method_sym.to_sym
|
93
|
+
when :new
|
94
|
+
params.delete :id
|
95
|
+
params.delete :_method
|
96
|
+
params.delete '_method'.freeze
|
97
|
+
params['id'.freeze] = :new
|
98
|
+
when :create
|
99
|
+
params['id'.freeze] = :new
|
100
|
+
params.delete :id
|
101
|
+
params['_method'.freeze] = :post
|
102
|
+
params.delete :_method
|
103
|
+
when :update
|
104
|
+
params.delete :_method
|
105
|
+
params['_method'.freeze] = :put
|
106
|
+
when :delete
|
107
|
+
params.delete :_method
|
108
|
+
params['_method'.freeze] = :delete
|
109
|
+
when :index
|
110
|
+
params.delete 'id'.freeze
|
111
|
+
params.delete '_method'.freeze
|
112
|
+
params.delete :id
|
113
|
+
params.delete :_method
|
114
|
+
when :show
|
115
|
+
raise "The URL for ':show' MUST contain a valid 'id' parameter for the object's index to display." unless params['id'.freeze].nil? && params[:id].nil?
|
116
|
+
params.delete '_method'.freeze
|
117
|
+
params.delete :_method
|
118
|
+
else
|
119
|
+
params.delete :id
|
120
|
+
params['id'.freeze] = method_sym
|
121
|
+
end
|
122
|
+
names = r.param_names
|
123
|
+
url.chomp! '/'.freeze
|
124
|
+
url << r.prefix
|
125
|
+
url.clear if url == '/'.freeze
|
126
|
+
while names.any? && params[name[0]]
|
127
|
+
url << "/#{Rack::Utils.escape params[names.shift]}"
|
128
|
+
end
|
129
|
+
url << '/'.freeze if url.empty?
|
130
|
+
(url << '?') << Rack::Utils.build_nested_query(params) if params.any?
|
131
|
+
url
|
132
|
+
end
|
125
133
|
end
|
126
|
-
|
127
|
-
end
|
134
|
+
end
|
128
135
|
end
|
data/lib/plezi/version.rb
CHANGED
@@ -1,97 +1,135 @@
|
|
1
|
-
require 'set'
|
2
|
-
require 'securerandom'
|
3
|
-
require 'yaml'
|
4
1
|
module Plezi
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
12
11
|
|
13
|
-
|
12
|
+
module_function
|
14
13
|
|
15
|
-
|
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
|
16
29
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
22
|
-
@pid ||= SecureRandom.urlsafe_base64.tap { |str| @prefix_len = str.length }
|
23
|
-
end
|
30
|
+
# initializes the drivers when possible.
|
31
|
+
def _init
|
32
|
+
@drivers.each(&:connect)
|
33
|
+
end
|
24
34
|
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
28
43
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
36
76
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
msg[:type] ||= msg['type'.freeze]
|
42
|
-
msg[:type] = Object.const_get msg[:type] if msg[:type] && msg[:type] != :all
|
43
|
-
if msg[:target] ||= msg['target'.freeze]
|
44
|
-
Iodine::Websocket.defer(target2uuid(msg[:target])) { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[msg[:event]], *(msg[:args] ||= msg['args'.freeze] || []))) if ws._pl_ws_map[msg[:event] ||= msg['event'.freeze]] }
|
45
|
-
elsif (msg[:type]) == :all
|
46
|
-
Iodine::Websocket.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[msg[:event]], *(msg[:args] ||= msg['args'.freeze] || []))) if ws._pl_ws_map[msg[:event] ||= msg['event'.freeze]] }
|
47
|
-
else
|
48
|
-
Iodine::Websocket.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[msg[:event]], *(msg[:args] ||= msg['args'.freeze] || []))) if ws.is_a?(msg[:type]) && msg[:type]._pl_ws_map[msg[:event] ||= msg['event'.freeze]] }
|
49
|
-
end
|
77
|
+
rescue => e
|
78
|
+
puts '*** The following could be a security breach attempt:', e.message, e.backtrace
|
79
|
+
nil
|
80
|
+
end
|
50
81
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
55
91
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
64
104
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
push type: sender.class.name, args: args, event: meth
|
72
|
-
end
|
73
|
-
end
|
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
|
74
111
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
end
|
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
|
84
120
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
89
126
|
|
90
|
-
|
91
|
-
|
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
|
92
131
|
end
|
93
|
-
|
94
|
-
end
|
132
|
+
end
|
95
133
|
end
|
96
134
|
# connect default drivers
|
97
135
|
require 'plezi/websockets/redis'
|