plezi 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/CHANGELOG.md +450 -0
  4. data/Gemfile +4 -0
  5. data/KNOWN_ISSUES.md +13 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +341 -0
  8. data/Rakefile +2 -0
  9. data/TODO.md +19 -0
  10. data/bin/plezi +301 -0
  11. data/lib/plezi.rb +125 -0
  12. data/lib/plezi/base/cache.rb +77 -0
  13. data/lib/plezi/base/connections.rb +33 -0
  14. data/lib/plezi/base/dsl.rb +177 -0
  15. data/lib/plezi/base/engine.rb +85 -0
  16. data/lib/plezi/base/events.rb +84 -0
  17. data/lib/plezi/base/io_reactor.rb +41 -0
  18. data/lib/plezi/base/logging.rb +62 -0
  19. data/lib/plezi/base/rack_app.rb +89 -0
  20. data/lib/plezi/base/services.rb +57 -0
  21. data/lib/plezi/base/timers.rb +71 -0
  22. data/lib/plezi/handlers/controller_magic.rb +383 -0
  23. data/lib/plezi/handlers/http_echo.rb +27 -0
  24. data/lib/plezi/handlers/http_host.rb +215 -0
  25. data/lib/plezi/handlers/http_router.rb +69 -0
  26. data/lib/plezi/handlers/magic_helpers.rb +43 -0
  27. data/lib/plezi/handlers/route.rb +272 -0
  28. data/lib/plezi/handlers/stubs.rb +143 -0
  29. data/lib/plezi/server/README.md +33 -0
  30. data/lib/plezi/server/helpers/http.rb +169 -0
  31. data/lib/plezi/server/helpers/mime_types.rb +999 -0
  32. data/lib/plezi/server/protocols/http_protocol.rb +318 -0
  33. data/lib/plezi/server/protocols/http_request.rb +133 -0
  34. data/lib/plezi/server/protocols/http_response.rb +294 -0
  35. data/lib/plezi/server/protocols/websocket.rb +208 -0
  36. data/lib/plezi/server/protocols/ws_response.rb +92 -0
  37. data/lib/plezi/server/services/basic_service.rb +224 -0
  38. data/lib/plezi/server/services/no_service.rb +196 -0
  39. data/lib/plezi/server/services/ssl_service.rb +193 -0
  40. data/lib/plezi/version.rb +3 -0
  41. data/plezi.gemspec +26 -0
  42. data/resources/404.erb +68 -0
  43. data/resources/404.haml +64 -0
  44. data/resources/404.html +67 -0
  45. data/resources/404.slim +63 -0
  46. data/resources/500.erb +68 -0
  47. data/resources/500.haml +63 -0
  48. data/resources/500.html +67 -0
  49. data/resources/500.slim +63 -0
  50. data/resources/Gemfile +85 -0
  51. data/resources/anorexic_gray.png +0 -0
  52. data/resources/anorexic_websockets.html +47 -0
  53. data/resources/code.rb +8 -0
  54. data/resources/config.ru +39 -0
  55. data/resources/controller.rb +139 -0
  56. data/resources/db_ac_config.rb +58 -0
  57. data/resources/db_dm_config.rb +51 -0
  58. data/resources/db_sequel_config.rb +42 -0
  59. data/resources/en.yml +204 -0
  60. data/resources/environment.rb +41 -0
  61. data/resources/haml_config.rb +6 -0
  62. data/resources/i18n_config.rb +14 -0
  63. data/resources/rakefile.rb +22 -0
  64. data/resources/redis_config.rb +35 -0
  65. data/resources/routes.rb +26 -0
  66. data/resources/welcome_page.html +72 -0
  67. data/websocket chatroom.md +639 -0
  68. metadata +141 -0
@@ -0,0 +1,27 @@
1
+ module Plezi
2
+ #####
3
+ # this is a Handler stub class for an HTTP echo server.
4
+ module HTTPEcho
5
+ module_function
6
+
7
+ # handles requests by printing out the parsed data. gets the `request` parameter from the HTTP protocol.
8
+ def on_request request
9
+ response = HTTPResponse.new request, 200, {"content-type" => "text/plain"}, ["parsed as:\r\n", request.to_s]
10
+ response.body.last << "\n\n params:"
11
+ request.params.each {|k,v| response.body.last << "\n#{k}: #{v}"}
12
+ response.send
13
+ response.finish
14
+ end
15
+
16
+ # does nothing - a simple stub as required from handlers
17
+ def add_route *args
18
+ self
19
+ end
20
+
21
+ # does nothing - a simple stub as required from handlers
22
+ def add_host *args
23
+ self
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,215 @@
1
+ module Plezi
2
+ #####
3
+ # this is a Handler stub class for an HTTP echo server.
4
+ class HTTPHost
5
+
6
+ # the parameters / settings for the Host.
7
+ attr_reader :params
8
+ # the routing array
9
+ attr_reader :routes
10
+
11
+ # initializes an HTTP host with the parameters for the specific host.
12
+ #
13
+ # parameters are the same (almost) as `add_service` and include `root` for file root, `assets`
14
+ # and other non service related options.
15
+ def initialize params = {}
16
+ @params = params
17
+ @routes = []
18
+ # params[:save_assets] = true unless params[:save_assets] == false
19
+ params[:index_file] ||= 'index.html'
20
+ params[:assets_public] ||= '/assets'
21
+ params[:assets_public].chomp! '/'
22
+
23
+ @sass_cache = Sass::CacheStores::Memory.new if defined?(::Sass)
24
+ # @sass_cache_lock = Mutex.new
25
+ end
26
+
27
+ # adds a route under the specific host
28
+ def add_route path, controller, &block
29
+ routes << Route.new(path, controller, params, &block)
30
+ end
31
+
32
+ # handles requests sent to the host. returns true if the host delt with the request.
33
+ #
34
+ # since hosts are required to handle the requests (send 404 errors if resources arrn't found),
35
+ # this method always returns true.
36
+ def on_request request
37
+ begin
38
+ # render any assets?
39
+ return true if render_assets request
40
+
41
+ # send static file, if exists and root is set.
42
+ return true if send_static_file request
43
+
44
+ # return if a route answered the request
45
+ routes.each {|r| return true if r.on_request(request) }
46
+
47
+ # send folder listing if root is set, directory listing is set and folder exists
48
+
49
+ #to-do
50
+
51
+ #return error code or 404 not found
52
+ send_by_code request, 404
53
+ rescue Exception => e
54
+ # return 500 internal server error.
55
+ Plezi.error e
56
+ send_by_code request, 500
57
+ end
58
+ true
59
+ end
60
+
61
+ # Dresses up as a Rack app (If you don't like WebSockets, it's a reasonable aaproach).
62
+ def call request
63
+ request = Rack::Request.new request if defined? Rack
64
+ ret = nil
65
+ begin
66
+ # render any assets?
67
+ ret = render_assets request
68
+ return ret if ret
69
+
70
+ # send static file, if exists and root is set.
71
+ ret = send_static_file request
72
+ return ret if ret
73
+
74
+ # return if a route answered the request
75
+ routes.each {|r| ret = r.call(request); return ret if ret }
76
+
77
+ # send folder listing if root is set, directory listing is set and folder exists
78
+
79
+ #to-do
80
+
81
+ #return error code or 404 not found
82
+ return send_by_code request, 404
83
+ rescue Exception => e
84
+ # return 500 internal server error.
85
+ Plezi.error e
86
+ return send_by_code request, 500
87
+ end
88
+ true
89
+ end
90
+
91
+ ################
92
+ ## basic responses
93
+ ## (error codes and static files)
94
+
95
+ # sends a response for an error code, rendering the relevent file (if exists).
96
+ def send_by_code request, code, headers = {}
97
+ begin
98
+ if params[:root]
99
+ if defined?(::Slim) && Plezi.file_exists?(File.join(params[:root], "#{code}.slim"))
100
+ Plezi.cache_data File.join(params[:root], "#{code}.slim"), Slim::Template.new( File.join( params[:root], "#{code}.slim" ) ) unless Plezi.cached? File.join(params[:root], "#{code}.slim")
101
+ return send_raw_data request, Plezi.get_cached( File.join(params[:root], "#{code}.slim") ).render( self ), 'text/html', code, headers
102
+ elsif defined?(::Haml) && Plezi.file_exists?(File.join(params[:root], "#{code}.haml"))
103
+ Plezi.cache_data File.join(params[:root], "#{code}.haml"), Haml::Engine.new( IO.read( File.join( params[:root], "#{code}.haml" ) ) ) unless Plezi.cached? File.join(params[:root], "#{code}.haml")
104
+ return send_raw_data request, Plezi.get_cached( File.join(params[:root], "#{code}.haml") ).render( self ), 'text/html', code, headers
105
+ elsif defined?(::ERB) && Plezi.file_exists?(File.join(params[:root], "#{code}.erb"))
106
+ return send_raw_data request, ERB.new( Plezi.load_file( File.join(params[:root], "#{code}.erb") ) ).result(binding), 'text/html', code, headers
107
+ elsif Plezi.file_exists?(File.join(params[:root], "#{code}.html"))
108
+ return send_file(request, File.join(params[:root], "#{code}.html"), code, headers)
109
+ end
110
+ end
111
+ return true if send_raw_data(request, HTTPResponse::STATUS_CODES[code], "text/plain", code, headers)
112
+ rescue Exception => e
113
+ Plezi.error e
114
+ end
115
+ false
116
+ end
117
+
118
+ # attempts to send a static file by the request path (using `send_file` and `send_raw_data`).
119
+ #
120
+ # returns true if data was sent.
121
+ def send_static_file request
122
+ return false unless params[:root]
123
+ file_requested = request[:path].to_s.split('/')
124
+ unless file_requested.include? '..'
125
+ file_requested.shift
126
+ file_requested = File.join(params[:root], *file_requested)
127
+ return true if send_file request, file_requested
128
+ return send_file request, File.join(file_requested, params[:index_file])
129
+ end
130
+ false
131
+ end
132
+
133
+ # sends a file/cacheed data if it exists. otherwise returns false.
134
+ def send_file request, filename, status_code = 200, headers = {}
135
+ if Plezi.file_exists?(filename) && !::File.directory?(filename)
136
+ return send_raw_data request, Plezi.load_file(filename), MimeTypeHelper::MIME_DICTIONARY[::File.extname(filename)], status_code, headers
137
+ end
138
+ return false
139
+ end
140
+ # sends raw data through the connection. always returns true (data send).
141
+ def send_raw_data request, data, mime, status_code = 200, headers = {}
142
+ response = HTTPResponse.new request, status_code, headers
143
+ response['cache-control'] = 'public, max-age=86400'
144
+ response << data
145
+ response['content-length'] = data.bytesize
146
+ response.finish
147
+ true
148
+ end
149
+
150
+ ###############
151
+ ## asset rendering and responses
152
+
153
+ # renders assets, if necessary, and places the rendered result in the cache and in the public folder.
154
+ def render_assets request
155
+ # contine only if assets are defined and called for
156
+ return false unless @params[:assets] && request.path.match(/^#{params[:assets_public]}\/.+/)
157
+ # review callback, if defined
158
+ return true if params[:assets_callback] && params[:assets_callback].call(request)
159
+
160
+ # get file requested
161
+ source_file = File.join(params[:assets], *(request.path.match(/^#{params[:assets_public]}\/(.+)/)[1].split('/')))
162
+
163
+ # stop if file name is reserved / has security issues
164
+ return false if source_file.match(/(scss|sass|coffee|\.\.\/)$/)
165
+
166
+ # set where to store the rendered asset
167
+ target_file = false
168
+ target_file = File.join( params[:root], params[:assets_public], *request.path.match(/^#{params[:assets_public]}\/(.*)/)[1].split('/') ) if params[:root]
169
+
170
+ # send the file if it exists (no render needed)
171
+ if File.exists?(source_file)
172
+ 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)
173
+ return (data ? send_raw_data(request, data, MimeTypeHelper::MIME_DICTIONARY[::File.extname(source_file)]) : false)
174
+ end
175
+
176
+ # render supported assets
177
+ case source_file
178
+ when /\.css$/
179
+ sass = source_file.gsub /css$/, 'sass'
180
+ sass.gsub! /sass$/, 'scss' unless Plezi.file_exists?(sass)
181
+ return false unless Plezi.file_exists?(sass)
182
+ # review mtime and render sass if necessary
183
+ if defined?(::Sass) && refresh_sass?(sass)
184
+ eng = Sass::Engine.for_file(sass, cache_store: @sass_cache)
185
+ Plezi.cache_data sass, eng.dependencies
186
+ css, map = eng.render_with_sourcemap(params[:assets_public])
187
+ Plezi.save_file target_file, css, params[:save_assets]
188
+ Plezi.save_file (target_file + ".map"), map, params[:save_assets]
189
+ end
190
+ # try to send the cached css file which started the request.
191
+ return send_file request, target_file
192
+ when /\.js$/
193
+ coffee = source_file.gsub /js$/i, 'coffee'
194
+ return false unless Plezi.file_exists?(coffee)
195
+ # review mtime and render coffee if necessary
196
+ if defined?(::CoffeeScript) && Plezi.cache_needs_update?(coffee)
197
+ # render coffee to cache
198
+ Plezi.cache_data coffee, nil
199
+ Plezi.save_file target_file, CoffeeScript.compile(IO.read coffee), params[:save_assets]
200
+ end
201
+ # try to send the cached js file which started the request.
202
+ return send_file request, target_file
203
+ end
204
+ false
205
+ end
206
+ def refresh_sass? sass
207
+ return false unless File.exists?(sass)
208
+ return true if Plezi.cache_needs_update?(sass)
209
+ mt = Plezi.file_mtime(sass)
210
+ 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])
211
+ false
212
+ end
213
+ end
214
+
215
+ end
@@ -0,0 +1,69 @@
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
+
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
32
+
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
38
+
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
54
+ 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."] ]
65
+ end
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,43 @@
1
+ module Plezi
2
+
3
+ # set magic cookies
4
+ #
5
+ # magic cookies keep track of both incoming and outgoing cookies, setting the response's cookies as well as the combined cookie respetory (held by the request object).
6
+ #
7
+ # use only the []= for magic cookies. merge and update might not set the response cookies.
8
+ class Cookies < ::Hash
9
+ # sets the Magic Cookie's controller object (which holds the response object and it's `set_cookie` method).
10
+ def set_controller controller
11
+ @controller = controller
12
+ end
13
+ # overrides the []= method to set the cookie for the response (by encoding it and preparing it to be sent), as well as to save the cookie in the combined cookie jar (unencoded and available).
14
+ def []= key, val
15
+ if key.is_a?(Symbol) && self.has_key?( key.to_s)
16
+ key = key.to_s
17
+ elsif key.is_a?(String) && self.has_key?( key.to_sym)
18
+ key = key.to_sym
19
+ end
20
+ @controller.response.set_cookie key, (val ? val.dup : nil) if @controller
21
+ super
22
+ end
23
+ end
24
+
25
+ # tweeks a hash object to read both :symbols and strings (similar to Rails but without).
26
+ def self.make_hash_accept_symbols hash
27
+ @magic_hash_proc ||= Proc.new do |hs,k|
28
+ if k.is_a?(Symbol) && hs.has_key?( k.to_s)
29
+ hs[k.to_s]
30
+ elsif k.is_a?(String) && hs.has_key?( k.to_sym)
31
+ hs[k.to_sym]
32
+ end
33
+ end
34
+ hash.default_proc = @magic_hash_proc
35
+ hash.values.each do |v|
36
+ if v.is_a?(Hash)
37
+ make_hash_accept_symbols v
38
+ end
39
+ end
40
+ end
41
+
42
+
43
+ end
@@ -0,0 +1,272 @@
1
+ module Plezi
2
+ #####
3
+ # this class holds the route and matching logic that will normally be used for HTTP handling
4
+ # it is used internally and documentation is present for development and edge users.
5
+ class Route
6
+ # the Regexp that will be used to match the request.
7
+ attr_reader :path
8
+ # the controller that answers the request on this path (if exists).
9
+ attr_reader :controller
10
+ # the proc that answers the request on this path (if exists).
11
+ attr_reader :proc
12
+ # the parameters for the router and service that were used to create the service, router and host.
13
+ attr_reader :params
14
+
15
+ # lets the route answer the request. returns false if no response has been sent.
16
+ def on_request request
17
+ fill_paramaters = match request.path
18
+ return false unless fill_paramaters
19
+ fill_paramaters.each {|k,v| HTTP.add_param_to_hash k, v, request.params }
20
+ response = HTTPResponse.new request
21
+ if controller
22
+ ret = controller.new(request, response, params)._route_path_to_methods_and_set_the_response_
23
+ response.try_finish if ret
24
+ return ret
25
+ elsif proc
26
+ ret = proc.call(request, response)
27
+ # response << ret if ret.is_a?(String)
28
+ response.try_finish if ret
29
+ return ret
30
+ elsif controller == false
31
+ request.path = path.match(request.path).to_a.last
32
+ end
33
+ return false
34
+ end
35
+
36
+ # handles Rack requests (dresses up as Rack).
37
+ def call request
38
+ fill_paramaters = match request.path_info
39
+ return false unless fill_paramaters
40
+ fill_paramaters.each {|k,v| HTTP.add_param_to_hash k, v, request.params }
41
+ response = HTTPResponse.new request
42
+ if controller
43
+ ret = controller.new(request, response, params)._route_path_to_methods_and_set_the_response_
44
+ return response if ret
45
+ elsif proc
46
+ ret = proc.call(request, response)
47
+ return response if ret
48
+ elsif controller == false
49
+ request.path_info = path.match(request.path_info).to_a.last
50
+ end
51
+ return false
52
+ end
53
+
54
+ # the initialize method accepts a Regexp or a String and creates the path object.
55
+ #
56
+ # Regexp paths will be left unchanged
57
+ #
58
+ # a string can be either a simple string `"/users"` or a string with paramaters:
59
+ # `"/static/:required/(:optional)/(:optional_with_format){[\d]*}/:optional_2"`
60
+ def initialize path, controller, params={}, &block
61
+ @path_sections , @params = false, params
62
+ initialize_path path
63
+ initialize_controller controller, block
64
+ end
65
+
66
+ # initializes the controller,
67
+ # by inheriting the class into an Plezi controller subclass (with the Plezi::ControllerMagic injected).
68
+ #
69
+ # Proc objects are currently passed through without any change - as Proc routes are assumed to handle themselves correctly.
70
+ def initialize_controller controller, block
71
+ @controller, @proc = controller, block
72
+ if controller.is_a?(Class)
73
+ # add controller magic
74
+ @controller = self.class.make_controller_magic controller
75
+ end
76
+ end
77
+
78
+ # initializes the path by converting the string into a Regexp
79
+ # and noting any parameters that might need to be extracted for RESTful routes.
80
+ def initialize_path path
81
+ @fill_paramaters = {}
82
+ if path.is_a? Regexp
83
+ @path = path
84
+ elsif path.is_a? String
85
+ # prep used prameters
86
+ param_num = 0
87
+ section_search = "([\\/][^\\/]*)"
88
+ optional_section_search = "([\\/][^\\/]*)?"
89
+ @path = '^'
90
+
91
+ # prep path string
92
+ # path = path.gsub(/(^\/)|(\/$)/, '')
93
+
94
+ # scan for the '/' divider
95
+ # (split path string only when the '/' is not inside a {} regexp)
96
+ # level = 0
97
+ # scn = StringScanner.new(path)
98
+ # while scn.matched != ''
99
+ # scn.scan_until /[^\\][\/\{\}]|$/
100
+ # case scn.matched
101
+ # when '{'
102
+ # level += 1
103
+ # when '}'
104
+ # level -= 1
105
+ # when '/'
106
+ # split_pos ||= []
107
+ # split_pos << scn.pos if level == 0
108
+ # end
109
+ # end
110
+
111
+ # prep path string and split it where the '/' charected is unescaped.
112
+ path = path.gsub(/(^\/)|(\/$)/, '').gsub(/([^\\])\//, '\1 - /').split ' - /'
113
+ @path_sections = path.length
114
+ path.each do |section|
115
+ if section == '*'
116
+ # create catch all
117
+ @path << "(.*)"
118
+ # finish
119
+ @path = /#{@path}$/
120
+ return
121
+
122
+ # check for routes formatted: /:paramater - required paramaters
123
+ elsif section.match /^\:([^\(\)\{\}\:]*)$/
124
+ #create a simple section catcher
125
+ @path << section_search
126
+ # add paramater recognition value
127
+ @fill_paramaters[param_num += 1] = section.match(/^\:([^\(\)\{\}\:]*)$/)[1]
128
+
129
+ # check for routes formatted: /:paramater{regexp} - required paramaters
130
+ elsif section.match /^\:([^\(\)\{\}\:\/]*)\{(.*)\}$/
131
+ #create a simple section catcher
132
+ @path << ( "(\/(" + section.match(/^\:([^\(\)\{\}\:\/]*)\{(.*)\}$/)[2] + "))" )
133
+ # add paramater recognition value
134
+ @fill_paramaters[param_num += 1] = section.match(/^\:([^\(\)\{\}\:\/]*)\{(.*)\}$/)[1]
135
+ param_num += 1 # we are using two spaces
136
+
137
+ # check for routes formatted: /(:paramater) - optional paramaters
138
+ elsif section.match /^\(\:([^\(\)\{\}\:]*)\)$/
139
+ #create a optional section catcher
140
+ @path << optional_section_search
141
+ # add paramater recognition value
142
+ @fill_paramaters[param_num += 1] = section.match(/^\(\:([^\(\)\{\}\:]*)\)$/)[1]
143
+
144
+ # check for routes formatted: /(:paramater){regexp} - optional paramaters
145
+ elsif section.match /^\(\:([^\(\)\{\}\:]*)\)\{(.*)\}$/
146
+ #create a optional section catcher
147
+ @path << ( "(\/(" + section.match(/^\(\:([^\(\)\{\}\:]*)\)\{(.*)\}$/)[2] + "))?" )
148
+ # add paramater recognition value
149
+ @fill_paramaters[param_num += 1] = section.match(/^\(\:([^\(\)\{\}\:]*)\)\{(.*)\}$/)[1]
150
+ param_num += 1 # we are using two spaces
151
+
152
+ else
153
+ @path << "\/"
154
+ @path << section
155
+ end
156
+ end
157
+ unless @fill_paramaters.values.include?("id")
158
+ @path << optional_section_search
159
+ @fill_paramaters[param_num += 1] = "id"
160
+ end
161
+ @path = /#{@path}$/
162
+ else
163
+ raise "Path cannot be initialized - path must be either a string or a regular experssion."
164
+ end
165
+ return
166
+ end
167
+
168
+ # this performs the match and assigns the paramaters, if required.
169
+ def match path
170
+ hash = {}
171
+ m = nil
172
+ # unless @fill_paramaters.values.include?("format")
173
+ # if (m = path.match /([^\.]*)\.([^\.\/]+)$/)
174
+ # HTTP.add_param_to_hash 'format', m[2], hash
175
+ # path = m[1]
176
+ # end
177
+ # end
178
+ m = @path.match path
179
+ return false unless m
180
+ @fill_paramaters.each { |k, v| hash[v] = m[k][1..-1] if m[k] && m[k] != '/' }
181
+ hash
182
+ end
183
+
184
+ ###########
185
+ ## class magic methods
186
+
187
+ protected
188
+
189
+ # injects some magic to the controller
190
+ #
191
+ # adds the `redirect_to` and `send_data` methods to the controller class, as well as the properties:
192
+ # env:: the env recieved by the Rack server.
193
+ # params:: the request's paramaters.
194
+ # cookies:: the request's cookies.
195
+ # flash:: an amazing Hash object that sets temporary cookies for one request only - greate for saving data between redirect calls.
196
+ #
197
+ def self.make_controller_magic(controller)
198
+ new_class_name = "Plezi__#{controller.name.gsub /[\:\-]/, '_'}"
199
+ return Module.const_get new_class_name if Module.const_defined? new_class_name
200
+ # controller.include Plezi::ControllerMagic
201
+ controller.instance_eval { include Plezi::ControllerMagic }
202
+ ret = Class.new(controller) do
203
+
204
+ def initialize request, response, host_params
205
+ @request, @params, @flash, @host_params = request, request.params, response.flash, host_params
206
+ @response = response
207
+ # @response["content-type"] ||= ::Plezi.default_content_type
208
+
209
+ @_accepts_broadcast = false
210
+
211
+ # create magical cookies
212
+ @cookies = request.cookies
213
+ @cookies.set_controller self
214
+
215
+ super()
216
+ end
217
+
218
+ def _route_path_to_methods_and_set_the_response_
219
+ #run :before filter
220
+ return false if available_routing_methods.include?(:before) && before == false
221
+ #check request is valid and call requested method
222
+ ret = requested_method
223
+ return false unless available_routing_methods.include?(ret)
224
+ return false unless (ret = self.method(ret).call)
225
+ #run :after filter
226
+ return false if available_routing_methods.include?(:after) && after == false
227
+ # review returned type for adding String to response
228
+ if ret.is_a?(String)
229
+ response << ret
230
+ response['content-length'] = ret.bytesize if response.body.empty? && !response.headers_sent?
231
+ end
232
+ return true
233
+ end
234
+ # WebSockets.
235
+ #
236
+ # this method handles the protocol and handler transition between the HTTP connection
237
+ # (with a protocol instance of HTTPProtocol and a handler instance of HTTPRouter)
238
+ # and the WebSockets connection
239
+ # (with a protocol instance of WSProtocol and an instance of the Controller class set as a handler)
240
+ def pre_connect
241
+ # make sure this is a websocket controller
242
+ return false unless self.class.public_instance_methods.include?(:on_message)
243
+ # call the controller's original method, if exists, and check connection.
244
+ return false if (defined?(super) && !super)
245
+ # finish if the response was sent
246
+ return true if response.headers_sent?
247
+ # complete handshake
248
+ return false unless WSProtocol.new( request.service, request.service.parameters).http_handshake request, response, self
249
+ # set up controller as WebSocket handler
250
+ @response = WSResponse.new request
251
+ # create the redis connection (in case this in the first instance of this class)
252
+ self.class.redis_connection
253
+ # set broadcasts and return true
254
+ @_accepts_broadcast = true
255
+ end
256
+
257
+
258
+ # WebSockets.
259
+ #
260
+ # stops broadcasts from being called on closed sockets that havn't been collected by the garbage collector.
261
+ def on_disconnect
262
+ @_accepts_broadcast = false
263
+ super if defined? super
264
+ end
265
+ end
266
+ Object.const_set(new_class_name, ret)
267
+ ret
268
+ end
269
+
270
+ end
271
+
272
+ end