plezi 0.14.4 → 0.14.5
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|