plezi 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/CHANGELOG.md +450 -0
- data/Gemfile +4 -0
- data/KNOWN_ISSUES.md +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +341 -0
- data/Rakefile +2 -0
- data/TODO.md +19 -0
- data/bin/plezi +301 -0
- data/lib/plezi.rb +125 -0
- data/lib/plezi/base/cache.rb +77 -0
- data/lib/plezi/base/connections.rb +33 -0
- data/lib/plezi/base/dsl.rb +177 -0
- data/lib/plezi/base/engine.rb +85 -0
- data/lib/plezi/base/events.rb +84 -0
- data/lib/plezi/base/io_reactor.rb +41 -0
- data/lib/plezi/base/logging.rb +62 -0
- data/lib/plezi/base/rack_app.rb +89 -0
- data/lib/plezi/base/services.rb +57 -0
- data/lib/plezi/base/timers.rb +71 -0
- data/lib/plezi/handlers/controller_magic.rb +383 -0
- data/lib/plezi/handlers/http_echo.rb +27 -0
- data/lib/plezi/handlers/http_host.rb +215 -0
- data/lib/plezi/handlers/http_router.rb +69 -0
- data/lib/plezi/handlers/magic_helpers.rb +43 -0
- data/lib/plezi/handlers/route.rb +272 -0
- data/lib/plezi/handlers/stubs.rb +143 -0
- data/lib/plezi/server/README.md +33 -0
- data/lib/plezi/server/helpers/http.rb +169 -0
- data/lib/plezi/server/helpers/mime_types.rb +999 -0
- data/lib/plezi/server/protocols/http_protocol.rb +318 -0
- data/lib/plezi/server/protocols/http_request.rb +133 -0
- data/lib/plezi/server/protocols/http_response.rb +294 -0
- data/lib/plezi/server/protocols/websocket.rb +208 -0
- data/lib/plezi/server/protocols/ws_response.rb +92 -0
- data/lib/plezi/server/services/basic_service.rb +224 -0
- data/lib/plezi/server/services/no_service.rb +196 -0
- data/lib/plezi/server/services/ssl_service.rb +193 -0
- data/lib/plezi/version.rb +3 -0
- data/plezi.gemspec +26 -0
- data/resources/404.erb +68 -0
- data/resources/404.haml +64 -0
- data/resources/404.html +67 -0
- data/resources/404.slim +63 -0
- data/resources/500.erb +68 -0
- data/resources/500.haml +63 -0
- data/resources/500.html +67 -0
- data/resources/500.slim +63 -0
- data/resources/Gemfile +85 -0
- data/resources/anorexic_gray.png +0 -0
- data/resources/anorexic_websockets.html +47 -0
- data/resources/code.rb +8 -0
- data/resources/config.ru +39 -0
- data/resources/controller.rb +139 -0
- data/resources/db_ac_config.rb +58 -0
- data/resources/db_dm_config.rb +51 -0
- data/resources/db_sequel_config.rb +42 -0
- data/resources/en.yml +204 -0
- data/resources/environment.rb +41 -0
- data/resources/haml_config.rb +6 -0
- data/resources/i18n_config.rb +14 -0
- data/resources/rakefile.rb +22 -0
- data/resources/redis_config.rb +35 -0
- data/resources/routes.rb +26 -0
- data/resources/welcome_page.html +72 -0
- data/websocket chatroom.md +639 -0
- 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
|