plezi 0.9.2 → 0.10.1

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