plezi 0.9.2 → 0.10.1

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/README.md +44 -31
  4. data/bin/plezi +3 -3
  5. data/lib/plezi.rb +21 -43
  6. data/lib/plezi/common/defer.rb +21 -0
  7. data/lib/plezi/common/dsl.rb +115 -91
  8. data/lib/plezi/common/redis.rb +44 -0
  9. data/lib/plezi/common/settings.rb +58 -0
  10. data/lib/plezi/handlers/controller_core.rb +132 -0
  11. data/lib/plezi/handlers/controller_magic.rb +85 -259
  12. data/lib/plezi/handlers/http_router.rb +139 -60
  13. data/lib/plezi/handlers/route.rb +9 -178
  14. data/lib/plezi/handlers/stubs.rb +2 -2
  15. data/lib/plezi/helpers/http_sender.rb +72 -0
  16. data/lib/plezi/helpers/magic_helpers.rb +12 -0
  17. data/lib/plezi/{server → helpers}/mime_types.rb +0 -0
  18. data/lib/plezi/version.rb +1 -1
  19. data/plezi.gemspec +3 -11
  20. data/resources/Gemfile +20 -21
  21. data/resources/controller.rb +2 -2
  22. data/resources/oauth_config.rb +1 -1
  23. data/resources/redis_config.rb +2 -0
  24. data/test/plezi_tests.rb +39 -46
  25. metadata +24 -33
  26. data/lib/plezi/common/logging.rb +0 -60
  27. data/lib/plezi/eventmachine/connection.rb +0 -190
  28. data/lib/plezi/eventmachine/em.rb +0 -98
  29. data/lib/plezi/eventmachine/io.rb +0 -272
  30. data/lib/plezi/eventmachine/protocol.rb +0 -54
  31. data/lib/plezi/eventmachine/queue.rb +0 -51
  32. data/lib/plezi/eventmachine/ssl_connection.rb +0 -144
  33. data/lib/plezi/eventmachine/timers.rb +0 -117
  34. data/lib/plezi/eventmachine/workers.rb +0 -33
  35. data/lib/plezi/handlers/http_echo.rb +0 -27
  36. data/lib/plezi/handlers/http_host.rb +0 -214
  37. data/lib/plezi/handlers/magic_helpers.rb +0 -32
  38. data/lib/plezi/server/http.rb +0 -129
  39. data/lib/plezi/server/http_protocol.rb +0 -319
  40. data/lib/plezi/server/http_request.rb +0 -146
  41. data/lib/plezi/server/http_response.rb +0 -319
  42. data/lib/plezi/server/websocket.rb +0 -251
  43. data/lib/plezi/server/websocket_client.rb +0 -178
  44. data/lib/plezi/server/ws_response.rb +0 -161
@@ -1,69 +1,148 @@
1
1
  module Plezi
2
- #####
3
- # this is a Handler stub class for an HTTP echo server.
4
- class HTTPRouter
5
-
6
- # the hosts dictionary router.hosts['www.foo.com'] == HTTPHost
7
- attr_reader :hosts
8
- # the current active host object
9
- attr_reader :active_host
10
-
11
- # initializes an HTTP router (the normal Handler for HTTP requests)
12
- #
13
- # the router holds the different hosts and sends them messages/requests.
14
- def initialize
15
- @hosts = {}
16
- @active_host = nil
17
- end
18
2
 
19
- # adds a host to the router (or activates an existing host to add new routes). accepts a host name and any parameters not related to the service (see `Plezi.add_service`)
20
- def add_host host_name, params
21
- host_name = (host_name ? host_name.to_s.downcase : :default)
22
- @hosts[host_name] ||= HTTPHost.new params
23
- add_alias host_name, *params[:alias] if params[:alias]
24
- @active_host = @hosts[host_name]
25
- end
26
- # adds an alias to an existing host name (normally through the :alias parameter in the `add_host` method).
27
- def add_alias host_name, *aliases
28
- return false unless @hosts[host_name]
29
- aliases.each {|a| @hosts[a.to_s.downcase] = @hosts[host_name]}
30
- true
31
- end
3
+ module Base
32
4
 
33
- # adds a route to the active host. The active host is the last host referenced by the `add_host`.
34
- def add_route path, controller, &block
35
- raise 'No Host defined.' unless @active_host
36
- @active_host.add_route path, controller, &block
37
- end
5
+ #####
6
+ # handles the HTTP Routing
7
+ class HTTPRouter
38
8
 
39
- # adds a route to all existing hosts.
40
- def add_shared_route path, controller, &block
41
- raise 'No Host defined.' if @hosts.empty?
42
- @hosts.each {|n, h| h.add_route path, controller, &block }
43
- end
44
-
45
- # handles requests send by the HTTP Protocol (HTTPRequest objects)
46
- def on_request request
47
- request.service.timeout = 300
48
- if request[:host_name] && hosts[request[:host_name].to_s.downcase]
49
- hosts[request[:host_name].downcase].on_request request
50
- elsif hosts[:default]
51
- hosts[:default].on_request request
52
- else
53
- HTTPResponse.new( request, 404, {'content-type' => 'text/plain', 'content-length' => '15'}, ['host not found.']).finish
9
+ class Host
10
+ attr_reader :params
11
+ attr_reader :routes
12
+ def initialize params
13
+ @params = params
14
+ @routes = []
15
+ end
54
16
  end
55
- request.service.timeout = 5
56
- end
57
- # handles requests send by Rack - dresses up as Middleware, for Rack (if you're don't like WebSockets, go ahead...)
58
- def call env
59
- if env['HOST'] && hosts[env['HOST'].downcase]
60
- hosts[env['HOST'].downcase].call env
61
- elsif hosts[:default]
62
- hosts[:default].call env
63
- else
64
- [404, {'content-type' => 'text/plain', 'content-length' => '15'}, ['host not found.'] ]
17
+
18
+ # initializes an HTTP router (the normal Handler for HTTP requests)
19
+ #
20
+ # the router holds the different hosts and sends them messages/requests.
21
+ def initialize
22
+ @hosts = {}
23
+ @active_host = nil
24
+ @sass_cache = Sass::CacheStores::Memory.new if defined?(::Sass)
25
+ end
26
+
27
+ # adds a host to the router (or activates an existing host to add new routes). accepts a host name and any parameters not related to the service (see `Plezi.add_service`)
28
+ def add_host host_name, params = {}
29
+ host_name = (host_name ? host_name.to_s.downcase : :default)
30
+ @active_host = get_host(host_name) || ( @hosts[host_name] = Host.new(params) )
31
+ add_alias host_name, *params[:alias] if params[:alias]
32
+ @active_host
33
+ end
34
+ # adds an alias to an existing host name (normally through the :alias parameter in the `add_host` method).
35
+ def add_alias host_name, *aliases
36
+ host = get_host host_name
37
+ return false unless host
38
+ aliases.each {|a| @hosts[a.to_s.downcase] = host}
39
+ true
40
+ end
41
+
42
+ # adds a route to the active host. The active host is the last host referenced by the `add_host`.
43
+ def add_route path, controller, &block
44
+ raise 'No Host defined.' unless @active_host
45
+ @active_host.routes << Route.new(path, controller, &block)
46
+ end
47
+
48
+ # adds a route to all existing hosts.
49
+ def add_shared_route path, controller, &block
50
+ raise 'No Host defined.' if @hosts.empty?
51
+ @hosts.each {|n, h| h.routes << Route.new(path, controller, &block) }
52
+ end
53
+
54
+ # handles requests send by the HTTP Protocol (HTTPRequest objects)
55
+ def call request, response
56
+ begin
57
+ host = get_host(request[:host_name].to_s.downcase) || @hosts[:default]
58
+ return false unless host
59
+ request.io[:params] = host.params
60
+ # render any assets?
61
+ return true if render_assets request, response, host.params
62
+ # send static file, if exists and root is set.
63
+ return true if Base::HTTPSender.send_static_file request, response
64
+ # return if a route answered the request
65
+ host.routes.each {|r| a = r.on_request(request, response); return a if a}
66
+ #return error code or 404 not found
67
+ Base::HTTPSender.send_by_code request, response, 404
68
+ rescue => e
69
+ # return 500 internal server error.
70
+ GReactor.error e
71
+ Base::HTTPSender.send_by_code request, response, 500
72
+ end
73
+ end
74
+
75
+ protected
76
+
77
+ def get_host host_name
78
+ @hosts.each {|k, v| return v if k === host_name}
79
+ nil
65
80
  end
81
+
82
+ ###############
83
+ ## asset rendering and responses
84
+
85
+ # renders assets, if necessary, and places the rendered result in the cache and in the public folder.
86
+ def render_assets request, response, params
87
+ # contine only if assets are defined and called for
88
+ return false unless params[:assets] && request.path.match(/^#{params[:assets_public]}\/.+/)
89
+ # review callback, if defined
90
+ return true if params[:assets_callback] && params[:assets_callback].call(request, response)
91
+
92
+ # get file requested
93
+ source_file = File.join(params[:assets], *(request.path.match(/^#{params[:assets_public]}\/(.+)/)[1].split('/')))
94
+
95
+ # stop if file name is reserved / has security issues
96
+ return false if source_file.match(/(scss|sass|coffee|\.\.\/)$/)
97
+
98
+ # set where to store the rendered asset
99
+ target_file = false
100
+ target_file = File.join( params[:root], params[:assets_public], *request.path.match(/^#{params[:assets_public]}\/(.*)/)[1].split('/') ) if params[:root]
101
+
102
+ # send the file if it exists (no render needed)
103
+ if File.exists?(source_file)
104
+ data = Plezi.cache_needs_update?(source_file) ? Plezi.save_file(target_file, Plezi.reload_file(source_file), params[:save_assets]) : Plezi.load_file(source_file)
105
+ return (data ? Base::HTTPSender.send_raw_data(request, response, data, MimeTypeHelper::MIME_DICTIONARY[::File.extname(source_file)]) : false)
106
+ end
107
+
108
+ # render supported assets
109
+ case source_file
110
+ when /\.css$/
111
+ sass = source_file.gsub /css$/, 'sass'
112
+ sass.gsub! /sass$/, 'scss' unless Plezi.file_exists?(sass)
113
+ return false unless Plezi.file_exists?(sass)
114
+ # review mtime and render sass if necessary
115
+ if defined?(::Sass) && refresh_sass?(sass)
116
+ eng = Sass::Engine.for_file(sass, cache_store: @sass_cache)
117
+ Plezi.cache_data sass, eng.dependencies
118
+ css, map = eng.render_with_sourcemap(params[:assets_public])
119
+ Plezi.save_file target_file, css, params[:save_assets]
120
+ Plezi.save_file (target_file + ".map"), map, params[:save_assets]
121
+ end
122
+ # try to send the cached css file which started the request.
123
+ return Base::HTTPSender.send_file request, response, target_file
124
+ when /\.js$/
125
+ coffee = source_file.gsub /js$/i, 'coffee'
126
+ return false unless Plezi.file_exists?(coffee)
127
+ # review mtime and render coffee if necessary
128
+ if defined?(::CoffeeScript) && Plezi.cache_needs_update?(coffee)
129
+ # render coffee to cache
130
+ Plezi.cache_data coffee, nil
131
+ Plezi.save_file target_file, CoffeeScript.compile(IO.read coffee), params[:save_assets]
132
+ end
133
+ # try to send the cached js file which started the request.
134
+ return Base::HTTPSender.send_file request, response, target_file
135
+ end
136
+ false
137
+ end
138
+ def refresh_sass? sass
139
+ return false unless File.exists?(sass)
140
+ return true if Plezi.cache_needs_update?(sass)
141
+ mt = Plezi.file_mtime(sass)
142
+ Plezi.get_cached(sass).each {|e| return true if File.exists?(e.options[:filename]) && (File.mtime(e.options[:filename]) > mt)} # fn = File.join( e.options[:load_paths][0].root, e.options[:filename])
143
+ false
144
+ end
145
+
66
146
  end
67
147
  end
68
-
69
148
  end
@@ -13,18 +13,16 @@ module Plezi
13
13
  attr_reader :params
14
14
 
15
15
  # lets the route answer the request. returns false if no response has been sent.
16
- def on_request request
16
+ def on_request request, response
17
17
  fill_parameters = match request.path
18
18
  return false unless fill_parameters
19
19
  old_params = request.params.dup
20
- fill_parameters.each {|k,v| HTTP.add_param_to_hash k, v, request.params }
20
+ fill_parameters.each {|k,v| GRHttp::HTTP.add_param_to_hash k, v, request.params }
21
21
  ret = false
22
- response = HTTPResponse.new request
23
22
  if controller
24
- ret = controller.new(request, response, params)._route_path_to_methods_and_set_the_response_
23
+ ret = controller.new(request, response)._route_path_to_methods_and_set_the_response_
25
24
  elsif proc
26
25
  ret = proc.call(request, response)
27
- # response << ret if ret.is_a?(String)
28
26
  elsif controller == false
29
27
  request.path = path.match(request.path).to_a.last.to_s
30
28
  return false
@@ -33,28 +31,9 @@ module Plezi
33
31
  request.params.replace old_params unless fill_parameters.empty?
34
32
  return false
35
33
  end
36
- response.try_finish
37
34
  return ret
38
35
  end
39
36
 
40
- # handles Rack requests (dresses up as Rack).
41
- def call request
42
- fill_parameters = match request.path_info
43
- return false unless fill_parameters
44
- fill_parameters.each {|k,v| HTTP.add_param_to_hash k, v, request.params }
45
- response = HTTPResponse.new request
46
- if controller
47
- ret = controller.new(request, response, params)._route_path_to_methods_and_set_the_response_
48
- return response if ret
49
- elsif proc
50
- ret = proc.call(request, response)
51
- return response if ret
52
- elsif controller == false
53
- request.path_info = path.match(request.path_info).to_a.last
54
- end
55
- return false
56
- end
57
-
58
37
  # the initialize method accepts a Regexp or a String and creates the path object.
59
38
  #
60
39
  # Regexp paths will be left unchanged
@@ -122,7 +101,7 @@ module Plezi
122
101
  # convert dest.id and dest[:id] to their actual :id value.
123
102
  dest = {id: (dest.id rescue false) || (raise TypeError, "Expecting a Symbol, Hash, String, Numeric or an object that answers to obj[:id] or obj.id") }
124
103
  end
125
- dest.default_proc = Plezi::Helpers::HASH_SYM_PROC
104
+ dest.default_proc = Plezi::Base::Helpers::HASH_SYM_PROC
126
105
 
127
106
  url = '/'
128
107
 
@@ -133,7 +112,7 @@ module Plezi
133
112
  param_name = param_name[1].to_sym if param_name
134
113
 
135
114
  if param_name && dest[param_name]
136
- url << HTTP.encode(dest.delete(param_name).to_s, :url)
115
+ url << GRHttp::HTTP.encode(dest.delete(param_name).to_s, :url)
137
116
  url << '/'
138
117
  elsif !param_name
139
118
  url << sec
@@ -146,7 +125,7 @@ module Plezi
146
125
  end
147
126
  unless dest.empty?
148
127
  add = '?'
149
- dest.each {|k, v| url << "#{add}#{HTTP.encode(k.to_s, :url)}=#{HTTP.encode(v.to_s, :url)}"; add = '&'}
128
+ dest.each {|k, v| url << "#{add}#{GRHttp::HTTP.encode(k.to_s, :url)}=#{GRHttp::HTTP.encode(v.to_s, :url)}"; add = '&'}
150
129
  end
151
130
  url
152
131
 
@@ -250,7 +229,7 @@ module Plezi
250
229
  # m = nil
251
230
  # unless @fill_parameters.values.include?("format")
252
231
  # if (m = path.match /([^\.]*)\.([^\.\/]+)$/)
253
- # HTTP.add_param_to_hash 'format', m[2], hash
232
+ # GRHttp::HTTP.add_param_to_hash 'format', m[2], hash
254
233
  # path = m[1]
255
234
  # end
256
235
  # end
@@ -277,157 +256,9 @@ module Plezi
277
256
  new_class_name = "Plezi__#{controller.name.gsub /[\:\-\#\<\>\{\}\(\)\s]/, '_'}"
278
257
  return Module.const_get new_class_name if Module.const_defined? new_class_name
279
258
  # controller.include Plezi::ControllerMagic
280
- controller.instance_exec(container) {|r| include Plezi::ControllerMagic; set_pl_route r;}
259
+ controller.instance_exec(container) {|r| include Plezi::ControllerMagic; }
281
260
  ret = Class.new(controller) do
282
-
283
- def name
284
- new_class_name
285
- end
286
-
287
- def initialize request, response, host_params
288
- @request, @params, @flash, @host_params = request, request.params, response.flash, host_params
289
- @response = response
290
- # @response["content-type"] ||= ::Plezi.default_content_type
291
-
292
- @_accepts_broadcast = false
293
-
294
- # create magical cookies
295
- @cookies = request.cookies
296
- @cookies.set_controller self
297
-
298
- super()
299
- end
300
-
301
- # WebSockets.
302
- #
303
- # this method handles the protocol and handler transition between the HTTP connection
304
- # (with a protocol instance of HTTPProtocol and a handler instance of HTTPRouter)
305
- # and the WebSockets connection
306
- # (with a protocol instance of WSProtocol and an instance of the Controller class set as a handler)
307
- def pre_connect
308
- # make sure this is a websocket controller
309
- return false unless self.class.public_instance_methods.include?(:on_message)
310
- # call the controller's original method, if exists, and check connection.
311
- return false if (defined?(super) && !super)
312
- # finish if the response was sent
313
- return true if response.headers_sent?
314
- # complete handshake
315
- return false unless WSProtocol.new( request.service, request.service.params).http_handshake request, response, self
316
- # set up controller as WebSocket handler
317
- @response = WSResponse.new request
318
- # create the redis connection (in case this in the first instance of this class)
319
- self.class.redis_connection
320
- # set broadcasts and return true
321
- @_accepts_broadcast = true
322
- end
323
-
324
-
325
- # WebSockets.
326
- #
327
- # stops broadcasts from being called on closed sockets that havn't been collected by the garbage collector.
328
- def on_disconnect
329
- @_accepts_broadcast = false
330
- super if defined? super
331
- end
332
-
333
- # Inner Routing
334
- #
335
- #
336
- def _route_path_to_methods_and_set_the_response_
337
- #run :before filter
338
- return false if self.class.available_routing_methods.include?(:before) && self.before == false
339
- #check request is valid and call requested method
340
- ret = requested_method
341
- return false unless self.class.available_routing_methods.include?(ret)
342
- return false unless (ret = self.method(ret).call)
343
- #run :after filter
344
- return false if self.class.available_routing_methods.include?(:after) && self.after == false
345
- # review returned type for adding String to response
346
- if ret.is_a?(String)
347
- response << ret
348
- response['content-length'] = ret.bytesize if response.body.empty? && !response.headers_sent?
349
- end
350
- return true
351
- end
352
- # a callback that resets the class router whenever a method (a potential route) is added
353
- def self.method_added(id)
354
- reset_routing_cache
355
- end
356
- # a callback that resets the class router whenever a method (a potential route) is removed
357
- def self.method_removed(id)
358
- reset_routing_cache
359
- end
360
- # a callback that resets the class router whenever a method (a potential route) is undefined (using #undef_method).
361
- def self.method_undefined(id)
362
- reset_routing_cache
363
- end
364
-
365
- # # lists the available methods that will be exposed to HTTP requests
366
- # def self.available_public_methods
367
- # # set class global to improve performance while checking for supported methods
368
- # @@___available_public_methods___ ||= available_routing_methods - [:before, :after, :save, :show, :update, :delete, :initialize, :on_message, :pre_connect, :on_connect, :on_disconnect]
369
- # end
370
-
371
- # # lists the available methods that will be exposed to the HTTP router
372
- # def self.available_routing_methods
373
- # # set class global to improve performance while checking for supported methods
374
- # @@___available_routing_methods___ ||= (((public_instance_methods - Object.public_instance_methods) - Plezi::ControllerMagic::InstanceMethods.instance_methods).delete_if {|m| m.to_s[0] == '_'})
375
- # end
376
-
377
- # # resets this controller's router, to allow for dynamic changes
378
- # def self.reset_routing_cache
379
- # @@___available_routing_methods___ = @@___available_public_methods___ = nil
380
- # available_routing_methods
381
- # available_public_methods
382
- # end
383
-
384
-
385
- # # reviews the Redis connection, sets it up if it's missing and returns the Redis connection.
386
- # #
387
- # # todo: review thread status? (incase an exception killed it)
388
- # def self.redis_connection
389
- # return false unless defined?(Redis) && ENV['PL_REDIS_URL']
390
- # return @@redis if defined?(@@redis_sub_thread) && @@redis
391
- # @@redis_uri ||= URI.parse(ENV['PL_REDIS_URL'])
392
- # @@redis ||= Redis.new(host: @@redis_uri.host, port: @@redis_uri.port, password: @@redis_uri.password)
393
- # @@redis_sub_thread = Thread.new do
394
- # begin
395
- # Redis.new(host: @@redis_uri.host, port: @@redis_uri.port, password: @@redis_uri.password).subscribe(redis_channel_name) do |on|
396
- # on.message do |channel, msg|
397
- # args = JSON.parse(msg)
398
- # params = args.shift
399
- # __inner_process_broadcast params['_pl_ignore_object'], params['_pl_method_broadcasted'].to_sym, args
400
- # end
401
- # end
402
- # rescue Exception => e
403
- # Plezi.error e
404
- # retry
405
- # end
406
- # end
407
- # raise "Redis connction failed for: #{ENV['PL_REDIS_URL']}" unless @@redis
408
- # @@redis
409
- # end
410
-
411
- # # returns a Redis channel name for this controller.
412
- # def self.redis_channel_name
413
- # self.name.to_s
414
- # end
415
-
416
- # # broadcasts messages (methods) for this process
417
- # def self.__inner_process_broadcast ignore, method_name, args, &block
418
- # ObjectSpace.each_object(self) { |controller| Plezi.callback controller, method_name, *args, &block if controller.accepts_broadcast? && (!ignore || controller.uuid != ignore) }
419
- # end
420
-
421
- # # broadcasts messages (methods) between all processes (using Redis).
422
- # def self.__inner_redis_broadcast ignore, method_name, args, &block
423
- # return false unless redis_connection
424
- # raise "Radis broadcasts cannot accept blocks (no inter-process callbacks of memory sharing)!" if block
425
- # # raise "Radis broadcasts accept only one paramater, which is an optional Hash (no inter-process memory sharing)" if args.length > 1 || (args[0] && !args[0].is_a?(Hash))
426
- # args.unshift ({_pl_method_broadcasted: method_name, _pl_ignore_object: ignore})
427
- # redis_connection.publish(redis_channel_name, args.to_json )
428
- # true
429
- # end
430
-
261
+ include Plezi::Base::ControllerCore
431
262
  end
432
263
  Object.const_set(new_class_name, ret)
433
264
  Module.const_get(new_class_name).reset_routing_cache