plezi 0.10.12 → 0.10.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # handles broadcasts / unicasts
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
- public
94
- def has_super_method? method_name
95
- @super_methods_list ||= self.superclass.instance_methods.to_set
96
- @super_methods_list.include? method_name
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][:root]
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? '..'
@@ -1,3 +1,3 @@
1
1
  module Plezi
2
- VERSION = "0.10.12"
2
+ VERSION = "0.10.13"
3
3
  end
@@ -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 is the native Ruby Framework for real time web-apps. An easy way to write Websockets, RESTful routing and HTTP streaming apps.}
12
- spec.description = %q{Plezi is the native Ruby Framework for real time web-apps. An easy way to write Websockets, RESTful routing and HTTP streaming apps.}
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.15"
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
 
@@ -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
- background-color: #eee;
7
- padding: 0; margin: 0;
8
- width: 100%;
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
- background-color: #ddd;
13
- color: #008;
14
- text-align: center;
15
- border-bottom: 1px solid #000;
16
- margin: 0;
17
- padding: 0.5em;
18
- width: auto;
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
- color:#004;
23
- font-size: 1.2em;
24
- padding: 0 1em;
76
+ font-size: 1em;
77
+ padding: 0 1em;
78
+ margin: 0.5em 0;
25
79
  }
26
- #wrapper
80
+ a
81
+ {
82
+ color: #D0AC54;
83
+ text-decoration: none;
84
+ }
85
+ a:hover
27
86
  {
28
- background-color: #fff;
29
- margin: 1em 5%;
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 h2
90
+ #wrapper
36
91
  {
37
- background-color: #ddd;
38
- color: #008;
39
- text-align: center;
40
- margin: 0 0 1em 0;
41
- padding: 0.5em 0;
42
- width: 100%;
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
- #wrapper #missing
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>