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.
@@ -0,0 +1,11 @@
1
+ module Fastr
2
+ module Async
3
+ def async_resp(&blk)
4
+ env['async.callback'].call(blk.call)
5
+ end
6
+
7
+ def render_async(resp=nil)
8
+ [-1, {}, []].freeze
9
+ end
10
+ end
11
+ end
@@ -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
- attr_accessor :env, :params, :app, :headers, :cookies
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
@@ -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
@@ -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
@@ -7,4 +7,12 @@ class String
7
7
 
8
8
  newStr.join('')
9
9
  end
10
+
11
+ def uncamelcase
12
+ self.gsub(/::/, '/').
13
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
14
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
15
+ tr("-", "_").
16
+ downcase
17
+ end
10
18
  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
- puts kls
7
- puts kls.instance_variable_get(:@filters).inspect
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
- if self.instance_variable_defined? :@filters
28
- f = self.instance_variable_get(:@filters)
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(:@filters, f)
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