fastr 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/lib/fastr/async.rb
ADDED
data/lib/fastr/controller.rb
CHANGED
@@ -1,35 +1,78 @@
|
|
1
1
|
module Fastr
|
2
|
+
# All controllers in a Fastr application should inherit from this class.
|
3
|
+
#
|
4
|
+
# == Sample Controller
|
5
|
+
#
|
6
|
+
# class HomeController < Fastr::Controller
|
7
|
+
# def index
|
8
|
+
# [200, {"Content-Type" => "text/plain"}, ["Hello, World!"]]
|
9
|
+
# end
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# == Response headers
|
13
|
+
#
|
14
|
+
# You can add response headers directly by accessing the instance attribute {#headers}.
|
15
|
+
#
|
16
|
+
# self.headers['My-Header'] = 'value'
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# == Cookies
|
20
|
+
#
|
21
|
+
# You can access cookies in the request by accessing the instance attribute {#cookies}.
|
22
|
+
#
|
23
|
+
# self.cookies['MYCOOKIE']
|
24
|
+
#
|
25
|
+
# == Params
|
26
|
+
#
|
27
|
+
# You can access the parameters in the request by accessing the instance attribute {#params}.
|
28
|
+
#
|
29
|
+
# self.params['paramSentInRequest']
|
30
|
+
#
|
31
|
+
# @author Chris Moos
|
2
32
|
class Controller
|
3
|
-
|
33
|
+
# The current Rack environment for the request.
|
34
|
+
#
|
35
|
+
# @return [Hash]
|
36
|
+
attr_accessor :env
|
37
|
+
|
38
|
+
# The params for this request.
|
39
|
+
#
|
40
|
+
# @return [Hash]
|
41
|
+
attr_accessor :params
|
42
|
+
|
43
|
+
# The application for the controller's current request.
|
44
|
+
#
|
45
|
+
# @return [Fastr::Application]
|
46
|
+
attr_accessor :app
|
47
|
+
|
48
|
+
# Headers to send in the response.
|
49
|
+
#
|
50
|
+
# @return [Hash]
|
51
|
+
attr_accessor :headers
|
52
|
+
|
53
|
+
# Cookies sent in the request
|
54
|
+
#
|
55
|
+
# @return [Hash]
|
56
|
+
attr_accessor :cookies
|
57
|
+
|
58
|
+
# GET parameters.
|
59
|
+
#
|
60
|
+
# @return [Hash]
|
61
|
+
attr_accessor :get_params
|
62
|
+
|
63
|
+
# POST parameters.
|
64
|
+
#
|
65
|
+
# @return [Hash]
|
66
|
+
attr_accessor :post_params
|
4
67
|
|
5
68
|
include Fastr::Template
|
6
69
|
include Fastr::Deferrable
|
70
|
+
include Fastr::Cookie
|
71
|
+
include Fastr::Filter
|
72
|
+
include Fastr::Async
|
7
73
|
|
8
74
|
def self.inherited(kls)
|
9
75
|
kls.instance_eval('include Fastr::Log')
|
10
76
|
end
|
11
|
-
|
12
|
-
def set_cookie(key, value, options={})
|
13
|
-
cookie = ["#{key}=#{value};"]
|
14
|
-
|
15
|
-
|
16
|
-
if options.has_key? :expires and options[:expires].kind_of? Time
|
17
|
-
options[:expires] = options[:expires].utc.strftime('%a, %d-%b-%Y %H:%M:%S GMT')
|
18
|
-
end
|
19
|
-
|
20
|
-
options.each do |k,v|
|
21
|
-
cookie << "#{k}=#{v.to_s};"
|
22
|
-
end
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
cookie_val = cookie.join(' ')
|
27
|
-
|
28
|
-
if self.headers['Set-Cookie'].nil?
|
29
|
-
self.headers['Set-Cookie'] = [cookie_val]
|
30
|
-
else
|
31
|
-
self.headers['Set-Cookie'] << cookie_val
|
32
|
-
end
|
33
|
-
end
|
34
77
|
end
|
35
78
|
end
|
data/lib/fastr/cookie.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Fastr
|
2
|
+
# This module adds helpers for handling cookies.
|
3
|
+
#
|
4
|
+
# == Setting a cookie
|
5
|
+
#
|
6
|
+
# set_cookie("mycookie", "value", {:expires => Time.now + 3600})
|
7
|
+
#
|
8
|
+
# @author Chris Moos
|
9
|
+
module Cookie
|
10
|
+
# Adds a <em>Set-Cookie</em> header in the response.
|
11
|
+
#
|
12
|
+
# @param key [String]
|
13
|
+
# @param value [String]
|
14
|
+
# @option options [Time] :expires The time when the cookie should expire.
|
15
|
+
def set_cookie(key, value, options={})
|
16
|
+
cookie = ["#{key}=#{value};"]
|
17
|
+
|
18
|
+
|
19
|
+
if options.has_key? :expires and options[:expires].kind_of? Time
|
20
|
+
options[:expires] = options[:expires].utc.strftime('%a, %d-%b-%Y %H:%M:%S GMT')
|
21
|
+
end
|
22
|
+
|
23
|
+
# Sort the cookies alphabetically.
|
24
|
+
options.sort { |a,b| a.to_s <=> b.to_s }.each do |k,v|
|
25
|
+
cookie << "#{k}=#{v.to_s};"
|
26
|
+
end
|
27
|
+
|
28
|
+
cookie_val = cookie.join(' ')
|
29
|
+
|
30
|
+
if self.headers['Set-Cookie'].nil?
|
31
|
+
self.headers['Set-Cookie'] = [cookie_val]
|
32
|
+
else
|
33
|
+
self.headers['Set-Cookie'] << cookie_val
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/fastr/deferrable.rb
CHANGED
@@ -2,37 +2,37 @@ module Fastr
|
|
2
2
|
module Deferrable
|
3
3
|
def defer_response(code, headers, &block)
|
4
4
|
response = DeferrableResponse.new
|
5
|
-
|
5
|
+
|
6
6
|
EM.next_tick do
|
7
7
|
env['async.callback'].call([code, headers, response])
|
8
8
|
block.call(response)
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
[-1, {}, []].freeze
|
12
12
|
end
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
class DeferrableResponse
|
16
16
|
include EventMachine::Deferrable
|
17
|
-
|
17
|
+
|
18
18
|
def send_data(data)
|
19
19
|
@callback.call(data)
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
def task(operation, callback)
|
23
23
|
EM.defer(operation, callback)
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
def closed(&cb)
|
27
27
|
self.errback(&cb)
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
def finish
|
31
31
|
self.succeed
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
def each(&cb)
|
35
35
|
@callback = cb
|
36
36
|
end
|
37
37
|
end
|
38
|
-
end
|
38
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module Fastr
|
2
|
+
module Dispatch
|
3
|
+
# The folder containing static content.
|
4
|
+
PUBLIC_FOLDER = "public"
|
5
|
+
|
6
|
+
# Convenience wrapper for do_dispatch
|
7
|
+
# This is the heart of the server, called indirectly by a Rack aware server.
|
8
|
+
#
|
9
|
+
# @param env [Hash]
|
10
|
+
# @return [Array]
|
11
|
+
def dispatch(env)
|
12
|
+
return [500, {'Content-Type' => 'text/plain'}, ["Server Not Ready"]] if @booting
|
13
|
+
|
14
|
+
begin
|
15
|
+
new_env = plugin_before_dispatch(env)
|
16
|
+
plugin_after_dispatch(new_env, do_dispatch(new_env))
|
17
|
+
rescue Exception => e
|
18
|
+
bt = e.backtrace.join("\n")
|
19
|
+
[500, {'Content-Type' => 'text/plain'}, ["Exception: #{e}\n\n#{bt}"]]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Route, instantiate controller, return response from controller's action.
|
24
|
+
def do_dispatch(env)
|
25
|
+
path = env['PATH_INFO']
|
26
|
+
|
27
|
+
# Try to serve a public file
|
28
|
+
ret = dispatch_public(env, path)
|
29
|
+
return ret if not ret.nil?
|
30
|
+
|
31
|
+
log.debug "Checking for routes that match: #{path}"
|
32
|
+
route = router.match(env)
|
33
|
+
|
34
|
+
if route.has_key? :ok
|
35
|
+
dispatch_controller(route, env)
|
36
|
+
else
|
37
|
+
[404, {"Content-Type" => "text/plain"}, ["404 Not Found: #{path}"]]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def dispatch_controller(route, env)
|
42
|
+
vars = route[:ok]
|
43
|
+
controller = vars[:controller]
|
44
|
+
action = vars[:action].to_sym
|
45
|
+
|
46
|
+
raise Fastr::Error.new("Controller and action not present in route") if controller.nil? or action.nil?
|
47
|
+
|
48
|
+
|
49
|
+
klass = "#{controller.camelcase}Controller"
|
50
|
+
|
51
|
+
log.info "Routing to controller: #{klass}, action: #{action}"
|
52
|
+
|
53
|
+
klass_inst = Module.const_get(klass)
|
54
|
+
obj = klass_inst.new
|
55
|
+
setup_controller(obj, env, vars)
|
56
|
+
|
57
|
+
# Run before filters
|
58
|
+
response = Fastr::Filter.run_before_filters(obj, klass_inst, action)
|
59
|
+
|
60
|
+
# No before filters halted, send to action
|
61
|
+
if response.nil?
|
62
|
+
response = obj.send(action)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Run after filters
|
66
|
+
response = Fastr::Filter.run_after_filters(obj, klass_inst, action, response)
|
67
|
+
|
68
|
+
code, hdrs, body = *response
|
69
|
+
|
70
|
+
# Merge headers with anything specified in the controller
|
71
|
+
hdrs.merge!(obj.headers)
|
72
|
+
|
73
|
+
[code, hdrs, body]
|
74
|
+
end
|
75
|
+
|
76
|
+
def dispatch_public(env, path)
|
77
|
+
path = "#{self.app_path}/#{PUBLIC_FOLDER}/#{path[1..(path.length - 1)]}"
|
78
|
+
if not File.directory? path and File.exists? path
|
79
|
+
f = File.open(path)
|
80
|
+
hdrs = {}
|
81
|
+
|
82
|
+
type = MIME::Types.type_for(File.basename(path))
|
83
|
+
|
84
|
+
if not type.nil?
|
85
|
+
hdrs["Content-Type"] = type[0].to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
return [200, hdrs, [f.read]]
|
89
|
+
else
|
90
|
+
return nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Sets up a controller for a request.
|
95
|
+
def setup_controller(controller, env, vars)
|
96
|
+
controller.env = env
|
97
|
+
controller.headers = {}
|
98
|
+
|
99
|
+
setup_controller_params(controller, env, vars)
|
100
|
+
|
101
|
+
controller.cookies = Fastr::HTTP.parse_cookies(env)
|
102
|
+
controller.app = self
|
103
|
+
end
|
104
|
+
|
105
|
+
# Populate the parameters based on the HTTP method.
|
106
|
+
def setup_controller_params(controller, env, vars)
|
107
|
+
if Fastr::HTTP.method?(env, :get)
|
108
|
+
controller.get_params = Fastr::HTTP.parse_query_string(env['QUERY_STRING'])
|
109
|
+
controller.params = controller.get_params.merge(vars)
|
110
|
+
elsif Fastr::HTTP.method?(env, :post)
|
111
|
+
controller.post_params = {}
|
112
|
+
controller.post_params = Fastr::HTTP.parse_query_string(env['rack.input'].read) if env['rack.input']
|
113
|
+
controller.params = controller.post_params.merge(vars)
|
114
|
+
else
|
115
|
+
controller.params = vars
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Runs before_dispatch in all plugins.
|
120
|
+
#
|
121
|
+
# @param env [Hash]
|
122
|
+
# @return [Hash]
|
123
|
+
def plugin_before_dispatch(env)
|
124
|
+
new_env = env
|
125
|
+
|
126
|
+
self.plugins.each do |plugin|
|
127
|
+
if plugin.respond_to? :before_dispatch
|
128
|
+
new_env = plugin.send(:before_dispatch, self, env)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
new_env
|
133
|
+
end
|
134
|
+
|
135
|
+
# Runs after_dispatch in all plugins.
|
136
|
+
#
|
137
|
+
# @param env [Hash]
|
138
|
+
# @return [Hash]
|
139
|
+
def plugin_after_dispatch(env, response)
|
140
|
+
new_response = response
|
141
|
+
|
142
|
+
self.plugins.each do |plugin|
|
143
|
+
if plugin.respond_to? :after_dispatch
|
144
|
+
new_response = plugin.send(:after_dispatch, self, env, response)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
new_response
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/lib/fastr/filter.rb
CHANGED
@@ -1,17 +1,123 @@
|
|
1
1
|
module Fastr
|
2
|
-
module Filter
|
2
|
+
module Filter
|
3
|
+
include Fastr::Log
|
4
|
+
|
3
5
|
def self.included(kls)
|
4
|
-
|
5
6
|
kls.extend(ClassMethods)
|
6
|
-
|
7
|
-
|
7
|
+
end
|
8
|
+
|
9
|
+
# Run through all the before filters and maybe return a response.
|
10
|
+
#
|
11
|
+
# @param controller [Fastr::Controller] The controller instance for the action
|
12
|
+
# @param klass [Class] The controller class for the action
|
13
|
+
# @param action [Symbol] The action that the filters should run for
|
14
|
+
# @param response [Array]
|
15
|
+
# @return [Hash] The new response
|
16
|
+
def self.run_before_filters(controller, klass, action)
|
17
|
+
new_response = nil
|
18
|
+
get_filters_for_action(klass, :before, action).each do |filter|
|
19
|
+
new_response = execute_filter(filter, controller)
|
20
|
+
end
|
21
|
+
new_response
|
22
|
+
end
|
23
|
+
|
24
|
+
# Run the response through all after filters and return the new response.
|
25
|
+
#
|
26
|
+
# @param controller [Fastr::Controller] The controller instance for the action
|
27
|
+
# @param klass [Class] The controller class for the action
|
28
|
+
# @param action [Symbol] The action that the filters should run for
|
29
|
+
# @param response [Array]
|
30
|
+
# @return [Array] The new response
|
31
|
+
def self.run_after_filters(controller, klass, action, response)
|
32
|
+
new_response = response
|
33
|
+
get_filters_for_action(klass, :after, action).each do |filter|
|
34
|
+
new_response = execute_filter(filter, controller, new_response)
|
35
|
+
end
|
36
|
+
new_response
|
37
|
+
end
|
38
|
+
|
39
|
+
# Executes a filter.
|
40
|
+
#
|
41
|
+
# @param filter [Hash]
|
42
|
+
# @param obj [Object]
|
43
|
+
# @param args
|
44
|
+
# @return [Object]
|
45
|
+
def self.execute_filter(filter, obj, *args)
|
46
|
+
if args.length == 0
|
47
|
+
state = nil
|
48
|
+
else
|
49
|
+
state = *args
|
50
|
+
end
|
51
|
+
|
52
|
+
filter[:methods].each do |method|
|
53
|
+
if args.length == 0
|
54
|
+
state = obj.send(method)
|
55
|
+
else
|
56
|
+
state = obj.send(method, state)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
return state
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get all the filters for an action.
|
64
|
+
#
|
65
|
+
# @param klass [Class] The class for the action
|
66
|
+
# @param type [Symbol] Type of filters
|
67
|
+
# @param action [Symbol] The action for the class
|
68
|
+
# @return [Array]
|
69
|
+
def self.get_filters_for_action(klass, type, action)
|
70
|
+
get_filters(klass, type).find_all { |f| run_filter?(f, action) }
|
71
|
+
end
|
72
|
+
|
73
|
+
# Gets the filters for a class.
|
74
|
+
#
|
75
|
+
# @param klass [Class] The class to get the filters from
|
76
|
+
# @param type [Symbol] The type of filters to get
|
77
|
+
# @return [Array]
|
78
|
+
def self.get_filters(klass, type)
|
79
|
+
filter_var = "@filters_#{type}".to_sym
|
80
|
+
if klass.instance_variable_defined? filter_var
|
81
|
+
return klass.instance_variable_get(filter_var)
|
82
|
+
else
|
83
|
+
return []
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
def self.run_filter?(filter, action)
|
90
|
+
return true if filter.has_key? :all and filter[:all] == true
|
91
|
+
return true if filter.has_key? :only and filter[:only].include? action
|
92
|
+
return true if filter.has_key? :except and not filter[:except].include? action
|
93
|
+
return false
|
94
|
+
end
|
95
|
+
|
96
|
+
# Halt the filter chain and return a response.
|
97
|
+
# @param response [Hash] Rack response
|
98
|
+
def filter_stop(response)
|
99
|
+
response
|
100
|
+
end
|
101
|
+
|
102
|
+
# Continue through the filter chain
|
103
|
+
def filter_continue
|
104
|
+
nil
|
8
105
|
end
|
9
106
|
|
10
107
|
module ClassMethods
|
11
108
|
def before_filter(*args)
|
109
|
+
add_filter(:before, *args)
|
110
|
+
end
|
111
|
+
|
112
|
+
def after_filter(*args)
|
113
|
+
add_filter(:after, *args)
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def add_filter(type, *args)
|
12
119
|
methods = []
|
13
120
|
options = {}
|
14
|
-
puts self
|
15
121
|
args.each do |arg|
|
16
122
|
if arg.kind_of? Symbol
|
17
123
|
methods << arg
|
@@ -19,16 +125,17 @@ module Fastr
|
|
19
125
|
options = arg
|
20
126
|
end
|
21
127
|
end
|
22
|
-
|
23
|
-
setup_filter(methods, options)
|
128
|
+
setup_filter(methods, options, type)
|
24
129
|
end
|
25
130
|
|
26
|
-
def setup_filter(methods, options)
|
27
|
-
|
28
|
-
|
131
|
+
def setup_filter(methods, options, type)
|
132
|
+
filter_var = "@filters_#{type}".to_sym
|
133
|
+
|
134
|
+
if self.instance_variable_defined? filter_var
|
135
|
+
f = self.instance_variable_get(filter_var)
|
29
136
|
else
|
30
137
|
f = []
|
31
|
-
self.instance_variable_set(
|
138
|
+
self.instance_variable_set(filter_var, f)
|
32
139
|
end
|
33
140
|
|
34
141
|
if options.has_key? :only and options[:only].kind_of? Array
|