fastr 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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