fastr 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +182 -6
- data/bin/fastr +46 -10
- data/lib/fastr.rb +5 -0
- data/lib/fastr/application.rb +59 -152
- data/lib/fastr/async.rb +11 -0
- data/lib/fastr/controller.rb +67 -24
- data/lib/fastr/cookie.rb +37 -0
- data/lib/fastr/deferrable.rb +9 -9
- data/lib/fastr/dispatch.rb +151 -0
- data/lib/fastr/extensions/string.rb +8 -0
- data/lib/fastr/filter.rb +118 -11
- data/lib/fastr/http.rb +58 -0
- data/lib/fastr/plugin.rb +8 -8
- data/lib/fastr/router.rb +56 -7
- data/lib/fastr/settings.rb +9 -5
- data/lib/fastr/template.rb +83 -24
- data/lib/fastr/template/erubis.rb +23 -0
- data/lib/fastr/template/haml.rb +23 -0
- data/lib/fastr/test.rb +9 -0
- data/lib/fastr/test/application.rb +25 -0
- data/lib/fastr/test/controller.rb +78 -0
- data/lib/fastr/test/logger.rb +5 -0
- data/lib/fastr/test/tasks.rb +3 -0
- data/lib/fastr/test/tasks/test.rake +10 -0
- data/test/fastr_app/app/config/settings.rb +1 -0
- data/test/fastr_app/app/controllers/dispatch.rb +31 -0
- data/test/helper.rb +6 -4
- data/test/test_application.rb +13 -9
- data/test/test_cookie.rb +65 -0
- data/test/test_deferrable.rb +7 -7
- data/test/test_deferrable_response.rb +8 -8
- data/test/test_dispatch.rb +125 -0
- metadata +62 -11
- data/lib/fastr/session.rb +0 -5
- data/lib/fastr/session/cookie.rb +0 -0
data/README.rdoc
CHANGED
@@ -2,6 +2,16 @@
|
|
2
2
|
|
3
3
|
Micro web framework for Ruby. Should be used with an EventMachine rack server.
|
4
4
|
|
5
|
+
== Guide
|
6
|
+
|
7
|
+
The guide is a work in progress, check it out here: http://chrismoos.com/fastr
|
8
|
+
|
9
|
+
You can help with the guide by forking this repository: http://github.com/chrismoos/fastr_doc
|
10
|
+
|
11
|
+
== API Documentation
|
12
|
+
|
13
|
+
You can see the latest API documentation here: http://rdoc.info/projects/chrismoos/fastr
|
14
|
+
|
5
15
|
== Getting Started
|
6
16
|
|
7
17
|
$ sudo gem install fastr
|
@@ -24,16 +34,20 @@ The routes are configured in app/config/routes.rb
|
|
24
34
|
|
25
35
|
router.draw do |route|
|
26
36
|
route.for '/:controller/:action'
|
27
|
-
#route.for '/home/:action', :action => '[A-Za-z]+'
|
28
|
-
#route.for '/test', :to => 'home#index'
|
37
|
+
# route.for '/home/:action', :action => '[A-Za-z]+'
|
38
|
+
# route.for '/test', :to => 'home#index'
|
39
|
+
# route.for '/users/:id', :to => 'users#create', :methods => [:post]
|
29
40
|
end
|
30
41
|
|
42
|
+
By default a route will match against all HTTP methods (GET, POST, etc,.).
|
43
|
+
|
31
44
|
== Settings
|
32
45
|
|
33
46
|
Various settings can be configured in app/config/settings.rb
|
34
47
|
|
35
48
|
config.log_level = Logger::DEBUG
|
36
49
|
config.cache_templates = true
|
50
|
+
config.plugins << MyPluginModule
|
37
51
|
|
38
52
|
== Controller
|
39
53
|
|
@@ -68,15 +82,76 @@ The return for a controller is just a rack response, i.e [200, {"Content-Type" =
|
|
68
82
|
You can also use the following render methods:
|
69
83
|
|
70
84
|
render(:text, "My text")
|
71
|
-
|
72
|
-
With HAML, the template is rendered and any instance variables in your controller are available in the template.
|
73
85
|
|
74
|
-
|
75
|
-
|
86
|
+
Fastr currently has support for HAML and eRuby templates. Any instance variables in your controller are available in the template. The correct template engine will be chosen based on the file extension. By default no template engines will be loaded, you need to explicitly require the engine(s) you will be using in your init.rb file (below the require 'fastr' line):
|
87
|
+
|
88
|
+
require 'fastr'
|
89
|
+
require 'fastr/template/erubis'
|
90
|
+
require 'fastr/template/haml'
|
91
|
+
|
92
|
+
Some rendering examples:
|
93
|
+
|
94
|
+
render(:template, "users/index.haml") # this path is relative to your app/views/ folder
|
95
|
+
render(:template, "users/index.html.erb") # this path is relative to your app/views/ folder
|
96
|
+
|
97
|
+
You can also specify a hash of data that will be available in the @vars instance variable from your template:
|
98
|
+
|
99
|
+
render(:template, "users/index.html.erb", {:vars => {:greeting => "Aloha!"}})
|
100
|
+
|
101
|
+
This is particularly useful when you render a partial:
|
102
|
+
|
103
|
+
render(:partial, "users/_greeting.html.erb", {:vars => {:message => "Welcome!"}})
|
104
|
+
|
105
|
+
It's also possible to specify a response code and headers:
|
106
|
+
|
107
|
+
render(:template, "users/index.html.erb", {:vars => {:greeting => "Aloha!"}, :response_code => 200, :headers => "Content-Type" => "text/html"})
|
108
|
+
|
76
109
|
JSON:
|
77
110
|
|
78
111
|
render(:json, {:status => "ok", :message => "done"})
|
79
112
|
|
113
|
+
== Async Responses
|
114
|
+
|
115
|
+
You should never block EventMachine. If you're doing any kind of I/O in your controller action you need to render your response asynchronously:
|
116
|
+
|
117
|
+
class DemoController < Fastr::Controller
|
118
|
+
def fast_index
|
119
|
+
EM.add_timer(1) do
|
120
|
+
async_resp { render(:text, "fast_index\n") }
|
121
|
+
end
|
122
|
+
render_async
|
123
|
+
end
|
124
|
+
|
125
|
+
def slow_index
|
126
|
+
sleep(1)
|
127
|
+
render(:text, "slow_index\n")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
Here's the difference:
|
132
|
+
|
133
|
+
$ ab -n 10 -c 10 "http://127.0.0.1:4444/demo/fast_index"
|
134
|
+
Concurrency Level: 10
|
135
|
+
Time taken for tests: 1.010 seconds
|
136
|
+
Requests per second: 9.90 [#/sec] (mean)
|
137
|
+
|
138
|
+
$ ab -n 10 -c 10 "http://127.0.0.1:4444/demo/slow_index"
|
139
|
+
Concurrency Level: 10
|
140
|
+
Time taken for tests: 10.011 seconds
|
141
|
+
Requests per second: 1.00 [#/sec] (mean)
|
142
|
+
|
143
|
+
If all your actions in a controller are async you can use an after_filter to make things cleaner:
|
144
|
+
|
145
|
+
class DemoController < Fastr::Controller
|
146
|
+
after_filter :render_async
|
147
|
+
|
148
|
+
def index
|
149
|
+
EM.add_timer(1) do
|
150
|
+
async_resp { render(:text, "fast_index\n") }
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
80
155
|
== Deferred Responses
|
81
156
|
|
82
157
|
fastr also lets you return a deferred response. This is useful if you want to chunk the response back to the client, or have a long running operation that you want to perform without blocking EventMachine.
|
@@ -112,8 +187,79 @@ The following is an example of a controller action.
|
|
112
187
|
end
|
113
188
|
end
|
114
189
|
|
190
|
+
== AsyncRecord (experimental database support)
|
191
|
+
|
192
|
+
One of the greatest things about running on an event-based server is that you can get accelerated performance in database access.
|
193
|
+
|
194
|
+
Usually there is a lot of time spent blocking for a database query to return. In Fastr, using AsyncRecord, your queries don't block the request. You will receive a callback once the query has completed. This has major performance implications.
|
195
|
+
|
196
|
+
NOTE: Even though your connections are non-blocking to the database server, the database server is still blocking when accessing IO (disk/memory).
|
197
|
+
|
198
|
+
To use AsyncRecord, do the following:
|
199
|
+
|
200
|
+
Setup your init.rb file:
|
201
|
+
|
202
|
+
require 'async_record'
|
203
|
+
conn = AsyncRecord::Connection::MySQL.new(:host => "127.0.0.1", :port => 3306, :user => "root", :database => "database")
|
204
|
+
conn.connect
|
205
|
+
AsyncRecord::Base.set_connection(conn)
|
206
|
+
|
207
|
+
Define a model (app/models/user.rb):
|
208
|
+
|
209
|
+
class User < AsyncRecord::Base
|
210
|
+
set_table_name "users"
|
211
|
+
end
|
212
|
+
|
213
|
+
=== Controller
|
214
|
+
|
215
|
+
In your controller, try the following (remember to put the following in a deferred response):
|
216
|
+
|
217
|
+
=== Get all the rows in the table:
|
218
|
+
|
219
|
+
User.all(:limit => 256) do |users|
|
220
|
+
users.each do |user|
|
221
|
+
response.send_data("#{user.username}\n")
|
222
|
+
end
|
223
|
+
response.succeed
|
224
|
+
end
|
225
|
+
|
226
|
+
=== Find a row by ID
|
227
|
+
|
228
|
+
User.find(1) do |user|
|
229
|
+
if user.nil?
|
230
|
+
response.send_data("User not found")
|
231
|
+
else
|
232
|
+
response.send_data("User: #{user.username}\n")
|
233
|
+
end
|
234
|
+
response.succeed
|
235
|
+
end
|
236
|
+
|
237
|
+
=== Get the count of rows in the table
|
238
|
+
|
239
|
+
User.count do |count|
|
240
|
+
response.send_data("Count: #{count}")
|
241
|
+
response.succeed
|
242
|
+
end
|
243
|
+
|
244
|
+
=== Run a custom query
|
245
|
+
|
246
|
+
User.query("select username from users") do |results|
|
247
|
+
response.send_data("Results: #{results.inspect}")
|
248
|
+
response.succeed
|
249
|
+
end
|
250
|
+
|
251
|
+
WARNING: AsyncRecord is under heavy development, but its pretty cool :).
|
252
|
+
|
115
253
|
== Plugins
|
116
254
|
|
255
|
+
=== Loading manually
|
256
|
+
|
257
|
+
To explicitly load a plugin that doesn't exist in your custom/plugins directory, you can do the following in your *settings.rb* file:
|
258
|
+
|
259
|
+
config.plugins << MyPluginModule
|
260
|
+
|
261
|
+
=== Loading from custom directory
|
262
|
+
|
117
263
|
Fastr searches the custom/plugins directory in your application's root directory for loading plugins.
|
118
264
|
|
119
265
|
Example structure:
|
@@ -145,6 +291,36 @@ Here is an example plugin and what is currently supported:
|
|
145
291
|
end
|
146
292
|
end
|
147
293
|
|
294
|
+
== Filters
|
295
|
+
|
296
|
+
You can add before and after filters to your controller. The filters are executed before an action is called, and after.
|
297
|
+
|
298
|
+
=== Before Filters
|
299
|
+
|
300
|
+
before_filter :my_before_filter_noop, :my_before_filter_halt
|
301
|
+
|
302
|
+
def my_before_filter_noop
|
303
|
+
filter_continue # use this if you want the filter chain to continue
|
304
|
+
end
|
305
|
+
|
306
|
+
def my_before_filter_halt
|
307
|
+
[200, {}, ["STOP HERE AND RETURN"]] # Return a rack response if you want the chain to halt
|
308
|
+
end
|
309
|
+
|
310
|
+
=== After Filters
|
311
|
+
|
312
|
+
after_filter :my_after_filter
|
313
|
+
|
314
|
+
def my_after_filter(response)
|
315
|
+
# here you can modify the response
|
316
|
+
# response is just a rack response, i.e [200, {}, "Hello, Filter!"]
|
317
|
+
# This filter adds a custom header
|
318
|
+
code, headers, body = *response
|
319
|
+
headers['My-Custom-Header'] = 'vall'
|
320
|
+
|
321
|
+
[code, headers, body]
|
322
|
+
end
|
323
|
+
|
148
324
|
== Static Files
|
149
325
|
|
150
326
|
Anything stored in the public folder in your project's root directory will be served as a static file. This directory is checked before the routes. The mime type is set based on the file's extension.
|
data/bin/fastr
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'rubygems'
|
4
|
+
require 'fastr'
|
3
5
|
require 'fileutils'
|
4
6
|
|
5
7
|
module Fastr
|
@@ -11,7 +13,8 @@ module Fastr
|
|
11
13
|
|
12
14
|
# Directory Structre
|
13
15
|
|
14
|
-
dirs = ['app/config', 'app/controllers', 'app/views', 'app/models', 'lib', 'test', 'public', 'custom/plugins'
|
16
|
+
dirs = ['app/config', 'app/controllers', 'app/views', 'app/models', 'lib', 'test', 'public', 'custom/plugins',
|
17
|
+
'test/unit']
|
15
18
|
dirs.each do |dir|
|
16
19
|
FileUtils.mkdir_p("#{app_name}/#{dir}")
|
17
20
|
end
|
@@ -20,8 +23,9 @@ module Fastr
|
|
20
23
|
File.open("#{app_name}/app/config/routes.rb", "w") do |f|
|
21
24
|
f.write("router.draw do |route|\n")
|
22
25
|
f.write("\troute.for '/:controller/:action'\n")
|
23
|
-
f.write("\t#route.for '/home/:action', :action => '[A-Za-z]+'\n")
|
24
|
-
f.write("\t#route.for '/test', :to => 'home#index'\n")
|
26
|
+
f.write("\t# route.for '/home/:action', :action => '[A-Za-z]+'\n")
|
27
|
+
f.write("\t# route.for '/test', :to => 'home#index'\n")
|
28
|
+
f.write("\t# route.for '/users/:id', :to => 'users#create', :methods => [:post]\n")
|
25
29
|
f.write("end")
|
26
30
|
f.close
|
27
31
|
end
|
@@ -31,7 +35,7 @@ module Fastr
|
|
31
35
|
f.puts("require 'fastr'")
|
32
36
|
f.puts("EM.kqueue = true if EM.kqueue?")
|
33
37
|
f.puts("EM.epoll = true if EM.epoll?")
|
34
|
-
f.puts("fastrApp = Fastr::Application.new(File.expand_path(File.dirname(__FILE__)))")
|
38
|
+
f.puts("fastrApp = Fastr::Application.new(::File.expand_path(::File.dirname(__FILE__)))")
|
35
39
|
f.puts("app = lambda { |env|")
|
36
40
|
f.puts("\tfastrApp.dispatch(env)")
|
37
41
|
f.puts("}")
|
@@ -49,14 +53,33 @@ module Fastr
|
|
49
53
|
# Gemfile
|
50
54
|
File.open("#{app_name}/Gemfile", 'a') do |f|
|
51
55
|
f.puts "source :gemcutter"
|
52
|
-
|
53
|
-
f.puts "gem 'jeweler'"
|
54
|
-
f.puts "gem 'eventmachine'"
|
55
|
-
f.puts "gem 'haml'"
|
56
|
-
f.puts "\ngem 'fastr'"
|
56
|
+
f.puts "gem 'fastr'"
|
57
57
|
f.close
|
58
58
|
end
|
59
59
|
|
60
|
+
# init.rb
|
61
|
+
File.open("#{app_name}/app/config/init.rb", "w") do |f|
|
62
|
+
f.puts('# Enable AsyncRecord support (experimental)')
|
63
|
+
f.puts("# you must add \"require 'async_record'\" to your config.ru file ")
|
64
|
+
f.puts('#conn = AsyncRecord::Connection::MySQL.new(:host => "127.0.0.1", :port => 3306, :user => "root", :database => "database")')
|
65
|
+
f.puts('#conn.connect')
|
66
|
+
f.puts('#AsyncRecord::Base.set_connection(conn)')
|
67
|
+
end
|
68
|
+
|
69
|
+
# Rakefile
|
70
|
+
File.open("#{app_name}/Rakefile", "w") do |f|
|
71
|
+
f.puts("require 'fastr/test/tasks'")
|
72
|
+
end
|
73
|
+
|
74
|
+
# Test Helper
|
75
|
+
File.open("#{app_name}/test/test_helper.rb", "w") do |f|
|
76
|
+
f.puts("require 'rubygems'")
|
77
|
+
f.puts("require 'fastr/test'")
|
78
|
+
f.puts("require 'test/unit'")
|
79
|
+
f.puts("require 'fastr'")
|
80
|
+
f.puts("\nload(File.expand_path('../app/config/init.rb', File.expand_path(File.dirname(__FILE__))))")
|
81
|
+
end
|
82
|
+
|
60
83
|
puts "#{app_name} initialized!"
|
61
84
|
end
|
62
85
|
|
@@ -87,7 +110,20 @@ elsif command == 'generate' and ARGV.length > 2
|
|
87
110
|
|
88
111
|
puts "Creating controller: #{path}"
|
89
112
|
File.open(path, "w") do |f|
|
90
|
-
f.puts("class #{name.
|
113
|
+
f.puts("class #{name.camelcase}Controller < Fastr::Controller")
|
114
|
+
f.puts("end")
|
115
|
+
f.close
|
116
|
+
end
|
117
|
+
|
118
|
+
path = "test/unit/test_#{name}_controller.rb"
|
119
|
+
puts "Creating test class: #{path}"
|
120
|
+
File.open(path, "w") do |f|
|
121
|
+
f.puts("require 'test_helper'\n\n")
|
122
|
+
f.puts("class #{name.camelcase}ControllerTest < Test::Unit::TestCase")
|
123
|
+
f.puts("\tinclude Fastr::Test::Controller\n\n")
|
124
|
+
f.puts("\tdef test_something")
|
125
|
+
f.puts("\t\tassert(true)")
|
126
|
+
f.puts("\tend")
|
91
127
|
f.puts("end")
|
92
128
|
f.close
|
93
129
|
end
|
data/lib/fastr.rb
CHANGED
@@ -12,4 +12,9 @@ module Fastr
|
|
12
12
|
autoload :Deferrable, "#{ROOT}/fastr/deferrable"
|
13
13
|
autoload :Settings, "#{ROOT}/fastr/settings"
|
14
14
|
autoload :Plugin, "#{ROOT}/fastr/plugin"
|
15
|
+
autoload :Cookie, "#{ROOT}/fastr/cookie"
|
16
|
+
autoload :Filter, "#{ROOT}/fastr/filter"
|
17
|
+
autoload :Async, "#{ROOT}/fastr/async"
|
18
|
+
autoload :HTTP, "#{ROOT}/fastr/http"
|
19
|
+
autoload :Dispatch, "#{ROOT}/fastr/dispatch"
|
15
20
|
end
|
data/lib/fastr/application.rb
CHANGED
@@ -2,15 +2,34 @@ require 'logger'
|
|
2
2
|
require 'cgi'
|
3
3
|
require 'mime/types'
|
4
4
|
|
5
|
-
module Fastr
|
5
|
+
module Fastr
|
6
|
+
# This class represents a fastr application.
|
7
|
+
# @author Chris Moos
|
6
8
|
class Application
|
7
9
|
include Fastr::Log
|
8
|
-
|
10
|
+
include Fastr::Dispatch
|
11
|
+
|
12
|
+
# The file that contains application settings.
|
9
13
|
SETTINGS_FILE = "app/config/settings.rb"
|
14
|
+
|
15
|
+
# The file that is evaluated when fastr finishes booting.
|
10
16
|
INIT_FILE = "app/config/init.rb"
|
11
|
-
PUBLIC_FOLDER = "public"
|
12
17
|
|
13
|
-
|
18
|
+
# The router for this application.
|
19
|
+
# @return [Fastr::Router]
|
20
|
+
attr_accessor :router
|
21
|
+
|
22
|
+
# The full path the application's path.
|
23
|
+
# @return [String]
|
24
|
+
attr_accessor :app_path
|
25
|
+
|
26
|
+
# The settings for this application.
|
27
|
+
# @return [Fastr::Settings]
|
28
|
+
attr_accessor :settings
|
29
|
+
|
30
|
+
# The list of plugins enabled for this application.
|
31
|
+
# @return [Array]
|
32
|
+
attr_accessor :plugins
|
14
33
|
|
15
34
|
# These are resources we are watching to change.
|
16
35
|
# They will be reloaded upon change.
|
@@ -28,45 +47,7 @@ module Fastr
|
|
28
47
|
@booting = true
|
29
48
|
boot
|
30
49
|
end
|
31
|
-
|
32
|
-
# Convenience wrapper for do_dispatch
|
33
|
-
# This is the heart of the server, called indirectly by a Rack aware server.
|
34
|
-
def dispatch(env)
|
35
|
-
return [500, {}, "Server Not Ready"] if @booting
|
36
|
-
|
37
|
-
begin
|
38
|
-
new_env = plugin_before_dispatch(env)
|
39
|
-
plugin_after_dispatch(new_env, do_dispatch(new_env))
|
40
|
-
rescue Exception => e
|
41
|
-
bt = e.backtrace.join("\n")
|
42
|
-
[500, {}, "Exception: #{e}\n\n#{bt}"]
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def plugin_before_dispatch(env)
|
47
|
-
new_env = env
|
48
|
-
|
49
|
-
self.plugins.each do |plugin|
|
50
|
-
if plugin.respond_to? :before_dispatch
|
51
|
-
new_env = plugin.send(:before_dispatch, self, env)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
new_env
|
56
|
-
end
|
57
|
-
|
58
|
-
def plugin_after_dispatch(env, response)
|
59
|
-
new_response = response
|
60
|
-
|
61
|
-
self.plugins.each do |plugin|
|
62
|
-
if plugin.respond_to? :after_dispatch
|
63
|
-
new_response = plugin.send(:after_dispatch, self, env, response)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
new_response
|
68
|
-
end
|
69
|
-
|
50
|
+
|
70
51
|
def plugin_after_boot
|
71
52
|
self.plugins.each do |plugin|
|
72
53
|
if plugin.respond_to? :after_boot
|
@@ -74,97 +55,9 @@ module Fastr
|
|
74
55
|
end
|
75
56
|
end
|
76
57
|
end
|
77
|
-
|
78
|
-
# Route, instantiate controller, return response from controller's action.
|
79
|
-
def do_dispatch(env)
|
80
|
-
path = env['PATH_INFO']
|
81
|
-
|
82
|
-
# Try to serve a public file
|
83
|
-
ret = dispatch_public(env, path)
|
84
|
-
return ret if not ret.nil?
|
85
|
-
|
86
|
-
log.debug "Checking for routes that match: #{path}"
|
87
|
-
route = router.match(env)
|
88
58
|
|
89
|
-
if route.has_key? :ok
|
90
|
-
vars = route[:ok]
|
91
|
-
controller = vars[:controller]
|
92
|
-
action = vars[:action]
|
93
|
-
|
94
|
-
raise Fastr::Error.new("Controller and action not present in route") if controller.nil? or action.nil?
|
95
|
-
|
96
|
-
|
97
|
-
klass = "#{controller.capitalize}Controller"
|
98
|
-
|
99
|
-
log.info "Routing to controller: #{klass}, action: #{action}"
|
100
|
-
|
101
|
-
obj = Module.const_get(klass).new
|
102
|
-
setup_controller(obj, env)
|
103
|
-
|
104
|
-
code, hdrs, body = *obj.send(action)
|
105
|
-
|
106
|
-
# Merge headers with anything specified in the controller
|
107
|
-
hdrs.merge!(obj.headers)
|
108
|
-
|
109
|
-
[code, hdrs, body]
|
110
|
-
else
|
111
|
-
[404, {"Content-Type" => "text/plain"}, "404 Not Found: #{path}"]
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
59
|
private
|
116
|
-
|
117
|
-
def dispatch_public(env, path)
|
118
|
-
path = "#{self.app_path}/#{PUBLIC_FOLDER}/#{path[1..(path.length - 1)]}"
|
119
|
-
if not File.directory? path and File.exists? path
|
120
|
-
f = File.open(path)
|
121
|
-
hdrs = {}
|
122
|
-
|
123
|
-
type = MIME::Types.type_for(File.basename(path))
|
124
|
-
|
125
|
-
hdrs["Content-Type"] = type.to_s if not type.nil?
|
126
|
-
|
127
|
-
return [200, hdrs, f.read]
|
128
|
-
else
|
129
|
-
return nil
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
def setup_controller(controller, env)
|
134
|
-
controller.env = env
|
135
|
-
controller.params = {}
|
136
|
-
controller.headers = {}
|
137
60
|
|
138
|
-
CGI::parse(env['QUERY_STRING']).each do |k,v|
|
139
|
-
if v.length == 1
|
140
|
-
controller.params[k] = v[0]
|
141
|
-
else
|
142
|
-
controller.params[k] = v
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
controller.cookies = get_cookies(env)
|
147
|
-
|
148
|
-
|
149
|
-
controller.app = self
|
150
|
-
end
|
151
|
-
|
152
|
-
def get_cookies(env)
|
153
|
-
if env.has_key? "HTTP_COOKIE"
|
154
|
-
cookies = env['HTTP_COOKIE'].split(';')
|
155
|
-
c = {}
|
156
|
-
cookies.each do |cookie|
|
157
|
-
info = cookie.strip.split("=")
|
158
|
-
if info.length == 2
|
159
|
-
c[info[0].strip] = info[1].strip
|
160
|
-
end
|
161
|
-
end
|
162
|
-
c
|
163
|
-
else
|
164
|
-
{}
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
61
|
#
|
169
62
|
# This is used to initialize the application.
|
170
63
|
# It runs in a thread because startup depends on EventMachine running
|
@@ -172,22 +65,21 @@ module Fastr
|
|
172
65
|
def boot
|
173
66
|
Thread.new do
|
174
67
|
sleep 1 until EM.reactor_running?
|
175
|
-
|
68
|
+
|
176
69
|
begin
|
177
70
|
log.info "Loading application..."
|
178
|
-
|
71
|
+
app_init
|
179
72
|
load_settings
|
180
73
|
Fastr::Plugin.load(self)
|
181
74
|
load_app_classes
|
182
75
|
setup_router
|
183
76
|
setup_watcher
|
184
|
-
|
77
|
+
|
185
78
|
log.info "Application loaded successfully."
|
186
|
-
|
79
|
+
|
187
80
|
@booting = false
|
188
|
-
|
81
|
+
|
189
82
|
plugin_after_boot
|
190
|
-
app_init
|
191
83
|
rescue Exception => e
|
192
84
|
log.error "#{e}"
|
193
85
|
puts e.backtrace
|
@@ -196,62 +88,77 @@ module Fastr
|
|
196
88
|
end
|
197
89
|
end
|
198
90
|
end
|
199
|
-
|
91
|
+
|
200
92
|
# Initializes the router and loads the routes.
|
201
93
|
def setup_router
|
202
94
|
self.router = Fastr::Router.new(self)
|
203
95
|
self.router.load
|
204
96
|
end
|
205
|
-
|
97
|
+
|
206
98
|
# Loads all application classes. Called on startup.
|
207
99
|
def load_app_classes
|
208
100
|
@@load_paths.each do |name, path|
|
209
101
|
log.debug "Loading #{name} classes..."
|
210
|
-
|
102
|
+
|
211
103
|
Dir["#{self.app_path}/#{path}"].each do |f|
|
212
104
|
log.debug "Loading: #{f}"
|
213
105
|
load(f)
|
214
106
|
end
|
215
107
|
end
|
216
108
|
end
|
217
|
-
|
109
|
+
|
218
110
|
def app_init
|
219
111
|
return if not File.exists? INIT_FILE
|
220
|
-
|
112
|
+
|
221
113
|
init_file = File.open(INIT_FILE)
|
222
114
|
self.instance_eval(init_file.read)
|
223
115
|
end
|
224
|
-
|
116
|
+
|
225
117
|
def load_settings
|
226
|
-
|
227
|
-
|
228
|
-
|
118
|
+
settings_file = "#{self.app_path}/#{SETTINGS_FILE}"
|
119
|
+
return if not File.exists? settings_file
|
120
|
+
|
121
|
+
config_file = File.open(settings_file)
|
229
122
|
self.instance_eval(config_file.read)
|
230
123
|
end
|
231
|
-
|
124
|
+
|
232
125
|
# Watch for any file changes in the load paths.
|
233
126
|
def setup_watcher
|
234
127
|
this = self
|
235
|
-
Handler.send(:define_method, :app) do
|
128
|
+
Handler.send(:define_method, :app) do
|
236
129
|
this
|
237
130
|
end
|
238
|
-
|
131
|
+
|
239
132
|
@@load_paths.each do |name, path|
|
240
133
|
Dir["#{self.app_path}/#{path}"].each do |f|
|
241
134
|
EM.watch_file(f, Handler)
|
242
135
|
end
|
243
136
|
end
|
244
137
|
end
|
245
|
-
|
138
|
+
|
246
139
|
def config
|
247
140
|
return self.settings
|
248
141
|
end
|
249
|
-
|
142
|
+
|
250
143
|
module Handler
|
251
144
|
def file_modified
|
252
145
|
app.log.debug "Reloading file: #{path}"
|
146
|
+
reload(path)
|
147
|
+
end
|
148
|
+
|
149
|
+
def reload(path)
|
150
|
+
filename = File.basename(path)
|
151
|
+
|
152
|
+
# Is it a controller?
|
153
|
+
match = /^((\w+)_controller).rb$/.match(filename)
|
154
|
+
reload_controller(match[1]) if not match.nil?
|
155
|
+
|
253
156
|
load(path)
|
254
157
|
end
|
158
|
+
|
159
|
+
def reload_controller(name)
|
160
|
+
Object.send(:remove_const, name.camelcase.to_sym)
|
161
|
+
end
|
255
162
|
end
|
256
163
|
end
|
257
|
-
end
|
164
|
+
end
|