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.
@@ -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
- module Base
9
- module Router
10
- @routes = []
11
- @app = nil
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
- module_function
13
+ module_function
14
14
 
15
- def new(app)
16
- if app && app != call_method
17
- puts 'Plezi as Middleware'
18
- @app = app
19
- end
20
- Plezi.app
21
- end
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
- def call(env)
24
- request = Rack::Request.new(env)
25
- response = Rack::Response.new
26
- ret = nil
27
- @routes.each { |route| ret = route.call(request, response); break if ret }
28
- unless ret
29
- return @app.call(env) if @app
30
- ret = ::Plezi::Base::Err404Ctrl.new._pl_respond(request, response, request.params)
31
- end
32
- response.write(ret) if ret.is_a?(String)
33
- return response.finish
34
- rescue => e
35
- puts e.message, e.backtrace
36
- response = Rack::Response.new
37
- response.write ::Plezi::Base::Err500Ctrl.new._pl_respond(request, response, request.params)
38
- return response.finish
39
- end
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
- # returns the `call` method. Used repeatedly in middleware mode and only once in application mode.
42
- def call_method
43
- @call_method ||= Plezi::Base::Router.method(:call)
44
- end
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
- def route(path, controller)
47
- path = path.chomp('/'.freeze) unless path == '/'.freeze
48
- case controller
49
- when :client
50
- controller = ::Plezi::Base::Router::ADClient
51
- when :assets
52
- controller = ::Plezi::Base::Assets
53
- path << '/*'.freeze unless path[-1] == '*'.freeze
54
- when Regexp
55
- path << '/*'.freeze unless path[-1] == '*'.freeze
56
- return @routes << RouteRewrite.new(path, controller)
57
- end
58
- @routes << Route.new(path, controller)
59
- end
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
- def list
62
- @routes
63
- end
67
+ def list
68
+ @routes
69
+ end
64
70
 
65
- def url_for(controller, method_sym, params = {})
66
- # GET,PUT,POST,DELETE
67
- r = nil
68
- url = '/'.dup
69
- @routes.each do |tmp|
70
- case tmp.controller
71
- when Class
72
- next if tmp.controller != controller
73
- r = tmp
74
- break
75
- when Regexp
76
- nm = nil
77
- nm = tmp.param_names[0] if params[tmp.param_names[0]]
78
- nm ||= tmp.param_names[0].to_sym
79
- url << "#{params.delete nm}/" if params[nm] && params[nm].to_s =~ tmp.controller
80
- else
81
- next
82
- end
83
- end
84
- return nil if r.nil?
85
- case method_sym.to_sym
86
- when :new
87
- params.delete :id
88
- params.delete :_method
89
- params.delete '_method'.freeze
90
- params['id'.freeze] = :new
91
- when :create
92
- params['id'.freeze] = :new
93
- params.delete :id
94
- params['_method'.freeze] = :post
95
- params.delete :_method
96
- when :update
97
- params.delete :_method
98
- params['_method'.freeze] = :put
99
- when :delete
100
- params.delete :_method
101
- params['_method'.freeze] = :delete
102
- when :index
103
- params.delete 'id'.freeze
104
- params.delete '_method'.freeze
105
- params.delete :id
106
- params.delete :_method
107
- when :show
108
- 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?
109
- params.delete '_method'.freeze
110
- params.delete :_method
111
- else
112
- params.delete :id
113
- params['id'.freeze] = method_sym
114
- end
115
- names = r.param_names
116
- url.chomp! '/'.freeze
117
- url << r.prefix
118
- url.clear if url == '/'.freeze
119
- while names.any? && params[name[0]]
120
- url << "/#{Rack::Utils.escape params[names.shift]}"
121
- end
122
- url << '/'.freeze if url.empty?
123
- (url << '?') << Rack::Utils.build_nested_query(params) if params.any?
124
- url
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
- end
127
- end
134
+ end
128
135
  end
@@ -1,3 +1,3 @@
1
1
  module Plezi
2
- VERSION = '0.14.4'.freeze
2
+ VERSION = '0.14.5'.freeze
3
3
  end
@@ -1,97 +1,135 @@
1
- require 'set'
2
- require 'securerandom'
3
- require 'yaml'
4
1
  module Plezi
5
- module Base
6
- module MessageDispatch
7
- class << self
8
- # Allows pub/sub drivers to attach to the message dispatch using `MessageDispatch.drivers << driver`
9
- attr_reader :drivers
10
- end
11
- @drivers = [].to_set
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
- module_function
12
+ module_function
14
13
 
15
- @ppid = ::Process.pid
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
- def pid
18
- if(@ppid != ::Process.pid)
19
- @pid = nil
20
- @ppid = ::Process.pid
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
- def _init
26
- @drivers.each(&:connect)
27
- end
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
- def push(message)
30
- # message[:type] = message[:type].name if message[:type].is_a?(Class)
31
- message[:origin] = pid
32
- hst = message.delete(:host) || Plezi.app_name
33
- yml = message.to_yaml
34
- @drivers.each { |d| d.push(hst, yml) }
35
- end
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
- def <<(msg)
38
- @safe_types ||= [Symbol, Date, Time, Encoding, Struct, Regexp, Range, Set].freeze
39
- msg = YAML.safe_load(msg, @safe_types)
40
- return if msg[:origin] == pid
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
- rescue => e
52
- puts '*** The following could be a security breach attempt:', e.message, e.backtrace
53
- nil
54
- end
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
- def unicast(_sender, target, meth, args)
57
- return false if target.nil?
58
- if (tuuid = target2uuid)
59
- Iodine::Websocket.defer(tuuid) { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[meth], *args)) if ws._pl_ws_map[meth] }
60
- return true
61
- end
62
- push target: target, args: args, host: target2pid(target)
63
- end
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
- def broadcast(sender, meth, args)
66
- if sender.is_a?(Class)
67
- Iodine::Websocket.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[meth], *args)) if ws.is_a?(sender) && ws._pl_ws_map[meth] }
68
- push type: sender.name, args: args, event: meth
69
- else
70
- sender.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[meth], *args)) if ws.is_a?(sender.class) && ws._pl_ws_map[meth] }
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
- def multicast(sender, meth, args)
76
- if sender.is_a?(Class)
77
- Iodine::Websocket.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[meth], *args)) if ws._pl_ws_map[meth] }
78
- push type: :all, args: args, event: meth
79
- else
80
- sender.each { |ws| ws._pl_ad_review(ws.__send__(ws._pl_ws_map[meth], *args)) if ws._pl_ws_map[meth] }
81
- push type: :all, args: args, event: meth
82
- end
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
- def target2uuid(target)
86
- return nil unless target.start_with? pid
87
- target[@prefix_len..-1].to_i
88
- end
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
- def target2pid(target)
91
- target ? target[0..(@prefix_len - 1)] : Plezi.app_name
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
- end
94
- end
132
+ end
95
133
  end
96
134
  # connect default drivers
97
135
  require 'plezi/websockets/redis'