plezi 0.14.4 → 0.14.5

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