merb 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +71 -14
- data/Rakefile +20 -31
- data/examples/skeleton.tar +0 -0
- data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +1 -1
- data/lib/merb.rb +3 -2
- data/lib/merb/caching.rb +5 -0
- data/lib/merb/caching/action_cache.rb +56 -0
- data/lib/merb/caching/fragment_cache.rb +38 -0
- data/lib/merb/caching/store/file_cache.rb +82 -0
- data/lib/merb/caching/store/memory_cache.rb +67 -0
- data/lib/merb/core_ext.rb +1 -1
- data/lib/merb/core_ext/merb_hash.rb +35 -0
- data/lib/merb/core_ext/merb_object.rb +88 -2
- data/lib/merb/merb_controller.rb +71 -69
- data/lib/merb/merb_dispatcher.rb +72 -0
- data/lib/merb/merb_exceptions.rb +6 -1
- data/lib/merb/merb_handler.rb +19 -47
- data/lib/merb/merb_mailer.rb +1 -1
- data/lib/merb/merb_request.rb +11 -3
- data/lib/merb/merb_router.rb +113 -8
- data/lib/merb/merb_server.rb +71 -12
- data/lib/merb/merb_upload_handler.rb +8 -6
- data/lib/merb/merb_upload_progress.rb +1 -1
- data/lib/merb/merb_view_context.rb +13 -3
- data/lib/merb/mixins/basic_authentication_mixin.rb +1 -3
- data/lib/merb/mixins/controller_mixin.rb +149 -17
- data/lib/merb/mixins/form_control_mixin.rb +1 -0
- data/lib/merb/mixins/render_mixin.rb +148 -151
- data/lib/merb/mixins/responder_mixin.rb +133 -18
- data/lib/merb/mixins/view_context_mixin.rb +24 -0
- data/lib/merb/session/merb_ar_session.rb +4 -4
- data/lib/merb/session/merb_memory_session.rb +6 -5
- data/lib/merb/template.rb +10 -0
- data/lib/merb/template/erubis.rb +52 -0
- data/lib/merb/template/haml.rb +77 -0
- data/lib/merb/template/markaby.rb +48 -0
- data/lib/merb/template/xml_builder.rb +34 -0
- metadata +19 -17
- data/lib/merb/mixins/merb_status_codes.rb +0 -59
data/lib/merb/core_ext.rb
CHANGED
@@ -2,6 +2,41 @@ class Hash
|
|
2
2
|
def with_indifferent_access
|
3
3
|
MerbHash.new(self)
|
4
4
|
end
|
5
|
+
def to_params()
|
6
|
+
result = ''
|
7
|
+
stack = []
|
8
|
+
|
9
|
+
each do |key, value|
|
10
|
+
Hash === value ? stack << [key, value] : result << "#{key}=#{value}&"
|
11
|
+
end
|
12
|
+
|
13
|
+
stack.each do |parent, hash|
|
14
|
+
hash.each do |key, value|
|
15
|
+
if Hash === value
|
16
|
+
stack << ["#{parent}[#{key}]", value]
|
17
|
+
else
|
18
|
+
result << "#{parent}[#{key}]=#{value}&"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
result.chop
|
23
|
+
end
|
24
|
+
|
25
|
+
# lets through the keys in the argument
|
26
|
+
# >> {:one => 1, :two => 2, :three => 3}.pass(:one)
|
27
|
+
# => {:one=>1}
|
28
|
+
def pass(*keys)
|
29
|
+
self.reject { |k,v| ! keys.include?(k) }
|
30
|
+
end
|
31
|
+
alias only pass
|
32
|
+
|
33
|
+
# blocks the keys in the arguments
|
34
|
+
# >> {:one => 1, :two => 2, :three => 3}.block(:one)
|
35
|
+
# => {:two=>2, :three=>3}
|
36
|
+
def block(*keys)
|
37
|
+
self.reject { |k,v| keys.include?(k) }
|
38
|
+
end
|
39
|
+
alias except block
|
5
40
|
end
|
6
41
|
|
7
42
|
# like HashWithIndifferentAccess from ActiveSupport.
|
@@ -1,15 +1,72 @@
|
|
1
|
+
Traits = Hash.new{|h,k| h[k] = {}}
|
2
|
+
|
1
3
|
class Object
|
4
|
+
# traits thanks to Michael Fellinger m.fellinger@gmail.com
|
5
|
+
# Adds a method to Object to annotate your objects with certain traits.
|
6
|
+
# It's basically a simple Hash that takes the current object as key
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# class Foo
|
11
|
+
# trait :instance => false
|
12
|
+
#
|
13
|
+
# def initialize
|
14
|
+
# trait :instance => true
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# Foo.trait[:instance]
|
19
|
+
# # false
|
20
|
+
#
|
21
|
+
# foo = Foo.new
|
22
|
+
# foo.trait[:instance]
|
23
|
+
# # true
|
24
|
+
|
25
|
+
def trait hash = nil
|
26
|
+
if hash
|
27
|
+
Traits[self].merge! hash
|
28
|
+
else
|
29
|
+
Traits[self]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# builds a trait from all the ancestors, closer ancestors
|
34
|
+
# overwrite distant ancestors
|
35
|
+
#
|
36
|
+
# class Foo
|
37
|
+
# trait :one => :eins
|
38
|
+
# trait :first => :erstes
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# class Bar < Foo
|
42
|
+
# trait :two => :zwei
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# class Foobar < Bar
|
46
|
+
# trait :three => :drei
|
47
|
+
# trait :first => :overwritten
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# Foobar.ancestral_trait
|
51
|
+
# {:three=>:drei, :two=>:zwei, :one=>:eins, :first=>:overwritten}
|
52
|
+
|
53
|
+
def ancestral_trait
|
54
|
+
ancs = (ancestors rescue self.class.ancestors)
|
55
|
+
ancs.reverse.inject({}){|s,v| s.merge(v.trait)}.merge(trait)
|
56
|
+
end
|
57
|
+
|
2
58
|
def returning(value)
|
3
59
|
yield(value)
|
4
60
|
value
|
5
61
|
end
|
6
62
|
|
7
|
-
def
|
8
|
-
def meta_eval(&blk)
|
63
|
+
def meta_class() class << self; self end end
|
64
|
+
def meta_eval(&blk) meta_class.instance_eval( &blk ) end
|
9
65
|
def meta_def(name, &blk) meta_eval { define_method name, &blk } end
|
10
66
|
def class_def name, &blk
|
11
67
|
self.class.class_eval { define_method name, &blk }
|
12
68
|
end
|
69
|
+
|
13
70
|
def blank?
|
14
71
|
if respond_to? :empty? then empty?
|
15
72
|
elsif respond_to? :zero? then zero?
|
@@ -17,4 +74,33 @@ class Object
|
|
17
74
|
end
|
18
75
|
end
|
19
76
|
|
77
|
+
def with_options(options)
|
78
|
+
yield Merb::OptionMerger.new(self, options)
|
79
|
+
end
|
20
80
|
end
|
81
|
+
|
82
|
+
module Merb
|
83
|
+
class OptionMerger #:nodoc:
|
84
|
+
instance_methods.each do |method|
|
85
|
+
undef_method(method) if method !~ /^(__|instance_eval|class)/
|
86
|
+
end
|
87
|
+
|
88
|
+
def initialize(context, options)
|
89
|
+
@context, @options = context, options
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
def method_missing(method, *arguments, &block)
|
94
|
+
merge_argument_options! arguments
|
95
|
+
@context.send(method, *arguments, &block)
|
96
|
+
end
|
97
|
+
|
98
|
+
def merge_argument_options!(arguments)
|
99
|
+
arguments << if arguments.last.respond_to? :to_hash
|
100
|
+
@options.merge(arguments.pop)
|
101
|
+
else
|
102
|
+
@options.dup
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/lib/merb/merb_controller.rb
CHANGED
@@ -12,85 +12,55 @@ module Merb
|
|
12
12
|
# to your controller via params. It also parses the ?query=string and
|
13
13
|
# puts that into params as well.
|
14
14
|
class Controller
|
15
|
-
|
16
|
-
|
15
|
+
|
16
|
+
trait :layout => :application
|
17
|
+
trait :session_id_key => :_session_id
|
17
18
|
|
18
19
|
include Merb::ControllerMixin
|
19
20
|
include Merb::RenderMixin
|
20
21
|
include Merb::ResponderMixin
|
21
22
|
|
22
|
-
attr_accessor :status, :body
|
23
|
-
|
23
|
+
attr_accessor :status, :body, :request
|
24
|
+
|
24
25
|
MULTIPART_REGEXP = /\Amultipart\/form-data.*boundary=\"?([^\";,]+)/n.freeze
|
25
|
-
|
26
|
-
FIELD_ATTRIBUTE_REGEXP = /(?:\s(\w+)="([^"]+)")/.freeze
|
27
|
-
CONTENT_TYPE_REGEXP = /^Content-Type: (.+?)(\r$|\Z)/m.freeze
|
28
|
-
|
26
|
+
|
29
27
|
# parses the http request into params, headers and cookies
|
30
28
|
# that you can use in your controller classes. Also handles
|
31
29
|
# file uploads by writing a tempfile and passing a reference
|
32
30
|
# in params.
|
33
|
-
def initialize(
|
34
|
-
env =
|
35
|
-
@status, @method, @
|
36
|
-
{'Content-Type' =>'text/html'}
|
37
|
-
cookies = query_parse(env['HTTP_COOKIE'], ';,')
|
38
|
-
querystring = query_parse(env['QUERY_STRING'])
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
if MULTIPART_REGEXP =~ env['CONTENT_TYPE']
|
43
|
-
boundary_regexp = /(?:\r?\n|\A)#{Regexp::quote("--#$1")}(?:--)?\r$/
|
44
|
-
until @in.eof?
|
45
|
-
attrs=MerbHash[]
|
46
|
-
for line in @in
|
47
|
-
case line
|
48
|
-
when "\r\n" : break
|
49
|
-
when CONTENT_DISPOSITION_REGEXP
|
50
|
-
attrs.update ::MerbHash[*$'.scan(FIELD_ATTRIBUTE_REGEXP).flatten]
|
51
|
-
when CONTENT_TYPE_REGEXP
|
52
|
-
attrs[:type] = $1
|
53
|
-
end
|
54
|
-
end
|
55
|
-
name=attrs[:name]
|
56
|
-
io_buffer=if attrs[:filename]
|
57
|
-
io_buffer=attrs[:tempfile]=Tempfile.new(:Merb)
|
58
|
-
io_buffer.binmode
|
59
|
-
else
|
60
|
-
attrs=""
|
61
|
-
end
|
62
|
-
while chunk=@in.read(16384)
|
63
|
-
if chunk =~ boundary_regexp
|
64
|
-
io_buffer << $`.chomp
|
65
|
-
@in.seek(-$'.size, IO::SEEK_CUR)
|
66
|
-
break
|
67
|
-
end
|
68
|
-
io_buffer << chunk
|
69
|
-
end
|
70
|
-
if name =~ /(.*)?\[\]/
|
71
|
-
(querystring[$1] ||= []) << attrs
|
72
|
-
else
|
73
|
-
querystring[name]=attrs if name
|
74
|
-
end
|
75
|
-
attrs[:tempfile].rewind if attrs.is_a?MerbHash
|
76
|
-
end
|
31
|
+
def initialize(request, env, args, response)
|
32
|
+
@env = MerbHash[env.to_hash]
|
33
|
+
@status, @method, @response, @headers = 200, (env['REQUEST_METHOD']||'GET').downcase.to_sym, response,
|
34
|
+
{'Content-Type' =>'text/html'}
|
35
|
+
cookies = query_parse(@env['HTTP_COOKIE'], ';,')
|
36
|
+
querystring = query_parse(@env['QUERY_STRING'])
|
37
|
+
|
38
|
+
if MULTIPART_REGEXP =~ @env['CONTENT_TYPE'] && @method == :post
|
39
|
+
querystring.update(parse_multipart(request, $1))
|
77
40
|
elsif @method == :post
|
78
|
-
if ['application/json', 'text/x-json'].include?(env['CONTENT_TYPE'])
|
41
|
+
if ['application/json', 'text/x-json'].include?(@env['CONTENT_TYPE'])
|
79
42
|
MERB_LOGGER.info("JSON Request")
|
80
|
-
json = JSON.parse(
|
81
|
-
json =
|
82
|
-
querystring.
|
43
|
+
json = JSON.parse(request.read || "") || {}
|
44
|
+
json = MerbHash.new(json) if json.is_a? Hash
|
45
|
+
querystring.update(json)
|
83
46
|
else
|
84
|
-
querystring.
|
47
|
+
querystring.update(query_parse(request.read))
|
85
48
|
end
|
86
49
|
end
|
87
|
-
|
88
|
-
@cookies
|
89
|
-
@
|
50
|
+
|
51
|
+
@cookies, @params = cookies, querystring.update(args)
|
52
|
+
@cookies[ancestral_trait[:session_id_key]] = @params[ancestral_trait[:session_id_key]] if @params.key?(ancestral_trait[:session_id_key])
|
53
|
+
|
54
|
+
allow = [:post, :put, :delete]
|
55
|
+
allow << :get if MERB_ENV == 'development'
|
56
|
+
if @params.key?(:_method) && allow.include?(@method)
|
57
|
+
@method = @params.delete(:_method).downcase.intern
|
58
|
+
end
|
90
59
|
@request = Request.new(@env, @method)
|
91
|
-
|
60
|
+
|
61
|
+
MERB_LOGGER.info("Params: #{params.inspect}\nCookies: #{cookies.inspect}")
|
92
62
|
end
|
93
|
-
|
63
|
+
|
94
64
|
def dispatch(action=:to_s)
|
95
65
|
start = Time.now
|
96
66
|
setup_session if respond_to?:setup_session
|
@@ -150,8 +120,37 @@ module Merb
|
|
150
120
|
@session
|
151
121
|
end
|
152
122
|
|
123
|
+
# accessor for @response. Please use response and
|
124
|
+
# never @response directly.
|
125
|
+
def response
|
126
|
+
@response
|
127
|
+
end
|
128
|
+
|
129
|
+
trait :template_extensions => { }
|
130
|
+
trait :template_root => File.expand_path(MERB_ROOT / "dist/app/views")
|
131
|
+
trait :layout_root => File.expand_path(MERB_ROOT / "dist/app/views/layout")
|
132
|
+
# lookup the trait[:template_extensions] for the extname of the filename
|
133
|
+
# you pass. Answers with the engine that matches the extension, Template::Erubis
|
134
|
+
# is used if none matches.
|
135
|
+
def engine_for(file)
|
136
|
+
extension = File.extname(file)[1..-1]
|
137
|
+
ancestral_trait[:template_extensions][extension]
|
138
|
+
rescue
|
139
|
+
::Merb::Template::Erubis
|
140
|
+
end
|
141
|
+
|
142
|
+
# This method is called by templating-engines to register themselves with
|
143
|
+
# a list of extensions that will be looked up on #render of an action.
|
144
|
+
def self.register_engine(engine, *extensions)
|
145
|
+
[extensions].flatten.uniq.each do |ext|
|
146
|
+
trait[:template_extensions][ext] = engine
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
|
153
152
|
# shared_accessor sets up a class instance variable that can
|
154
|
-
# be unique for each class but also inherits the
|
153
|
+
# be unique for each class but also inherits the shared attrs
|
155
154
|
# from its superclasses. Since @@class variables are almost
|
156
155
|
# global vars within an inheritance tree, we use
|
157
156
|
# @class_instance_variables instead
|
@@ -260,14 +259,17 @@ module Merb
|
|
260
259
|
"You can specify either :only or :exclude but
|
261
260
|
not both at the same time for the same filter."
|
262
261
|
) if opts.has_key?(:only) && opts.has_key?(:exclude)
|
263
|
-
|
264
|
-
raise(ArgumentError,
|
265
|
-
'after filters can only be a Proc object'
|
266
|
-
) unless Proc === filter
|
267
|
-
|
262
|
+
|
268
263
|
opts = shuffle_filters!(opts)
|
269
264
|
|
270
|
-
|
265
|
+
case filter
|
266
|
+
when Symbol, Proc, String
|
267
|
+
(self.after_filters ||= []) << [filter, opts]
|
268
|
+
else
|
269
|
+
raise(ArgumentError,
|
270
|
+
'After filters need to be either a Symbol, String or a Proc'
|
271
|
+
)
|
272
|
+
end
|
271
273
|
end
|
272
274
|
|
273
275
|
def self.shuffle_filters!(opts={})
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Merb
|
2
|
+
|
3
|
+
class Dispatcher
|
4
|
+
class << self
|
5
|
+
|
6
|
+
attr_accessor :path_prefix
|
7
|
+
|
8
|
+
@@mutex = Mutex.new
|
9
|
+
@@use_mutex = ::Merb::Server.use_mutex
|
10
|
+
# This is where we grab the incoming request PATH_INFO
|
11
|
+
# and use that in the merb routematcher to determine
|
12
|
+
# which controller and method to run.
|
13
|
+
# returns a 2 element tuple of:
|
14
|
+
# [controller, action]
|
15
|
+
def handle(request, response)
|
16
|
+
request_uri = request.params[Mongrel::Const::REQUEST_URI]
|
17
|
+
request_uri.sub!(path_prefix, '') if path_prefix
|
18
|
+
route = route_path(request_uri)
|
19
|
+
|
20
|
+
allowed = route.delete(:allowed)
|
21
|
+
rest = route.delete(:rest)
|
22
|
+
|
23
|
+
controller = instantiate_controller(route[:controller], request.body, request.params, route, response)
|
24
|
+
|
25
|
+
if rest
|
26
|
+
method = controller.request.method
|
27
|
+
if allowed.keys.include?(method) && action = allowed[method]
|
28
|
+
controller.params[:action] = action
|
29
|
+
else
|
30
|
+
raise Merb::RestfulMethodNotAllowed.new(method, allowed)
|
31
|
+
end
|
32
|
+
else
|
33
|
+
action = route[:action]
|
34
|
+
end
|
35
|
+
if @@use_mutex
|
36
|
+
@@mutex.synchronize {
|
37
|
+
controller.dispatch(action)
|
38
|
+
}
|
39
|
+
else
|
40
|
+
controller.dispatch(action)
|
41
|
+
end
|
42
|
+
[controller, action]
|
43
|
+
end
|
44
|
+
|
45
|
+
def route_path(path)
|
46
|
+
path = path.sub(/\/+/, '/').sub(/\?.*$/, '')
|
47
|
+
path = path[0..-2] if (path[-1] == ?/) && path.size > 1
|
48
|
+
Merb::RouteMatcher.new.route_request(path)
|
49
|
+
end
|
50
|
+
|
51
|
+
# take a controller class name string and reload or require
|
52
|
+
# the right controller file then CamelCase it and turn it
|
53
|
+
# into a new object passing in the request and response.
|
54
|
+
# this is where your Merb::Controller is instantiated.
|
55
|
+
def instantiate_controller(controller_name, req, env, params, res)
|
56
|
+
if !File.exist?(DIST_ROOT+"/app/controllers/#{controller_name.snake_case}.rb")
|
57
|
+
raise "Bad controller! #{controller_name.snake_case}"
|
58
|
+
end unless $TESTING
|
59
|
+
begin
|
60
|
+
controller_name.import
|
61
|
+
return Object.const_get( controller_name.camel_case ).new(req, env, params, res)
|
62
|
+
rescue RuntimeError
|
63
|
+
warn "Error getting instance of '#{controller_name.camel_case}': #{$!}"
|
64
|
+
raise $!
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end # end class << self
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
data/lib/merb/merb_exceptions.rb
CHANGED
@@ -8,7 +8,12 @@ module Merb
|
|
8
8
|
class Noroutefound < MerbError; end
|
9
9
|
class MissingControllerFile < MerbError; end
|
10
10
|
class MerbControllerError < MerbError; end
|
11
|
-
|
11
|
+
class RestfulMethodNotAllowed < MerbError
|
12
|
+
def initialize(method, allowed)
|
13
|
+
super("RestfulMethodNotAllowed: #{method}\n"+ "Allowed: #{allowed.keys.join(' ')})")
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
12
17
|
# format exception message for browser display
|
13
18
|
def self.html_exception(e)
|
14
19
|
::Merb::Server.show_error ? ErrorResponse.new(e).out : "500 Internal Server Error!"
|
data/lib/merb/merb_handler.rb
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
class Mongrel::HttpResponse
|
2
|
+
NO_CLOSE_STATUS_FORMAT = "HTTP/1.1 %d %s\r\n".freeze
|
3
|
+
def send_status_no_connection_close(content_length=@body.length)
|
4
|
+
if not @status_sent
|
5
|
+
@header['Content-Length'] = content_length unless @status == 304
|
6
|
+
write(NO_CLOSE_STATUS_FORMAT % [@status, Mongrel::HTTP_STATUS_CODES[@status]])
|
7
|
+
@status_sent = true
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
|
1
14
|
class MerbHandler < Mongrel::HttpHandler
|
2
15
|
@@file_only_methods = ["GET","HEAD"]
|
3
16
|
|
@@ -6,7 +19,6 @@ class MerbHandler < Mongrel::HttpHandler
|
|
6
19
|
# by default.
|
7
20
|
def initialize(dir, opts = {})
|
8
21
|
@files = Mongrel::DirHandler.new(dir,false)
|
9
|
-
@guard = Mutex.new
|
10
22
|
end
|
11
23
|
|
12
24
|
# process incoming http requests and do a number of things
|
@@ -30,7 +42,7 @@ class MerbHandler < Mongrel::HttpHandler
|
|
30
42
|
return
|
31
43
|
end
|
32
44
|
|
33
|
-
MERB_LOGGER.info("\nRequest:
|
45
|
+
MERB_LOGGER.info("\nRequest: REQUEST_URI: #{request.params[Mongrel::Const::REQUEST_URI]} (#{Time.now.strftime("%Y-%m-%d %H:%M:%S")})")
|
34
46
|
|
35
47
|
# Rails style page caching. Check the public dir first for
|
36
48
|
# .html pages and serve directly. Otherwise fall back to Merb
|
@@ -50,23 +62,11 @@ class MerbHandler < Mongrel::HttpHandler
|
|
50
62
|
@files.process(request,response)
|
51
63
|
else
|
52
64
|
begin
|
53
|
-
#
|
54
|
-
# params and is outside of the synchronize call so that
|
55
|
-
# multiple file uploads can be done at once.
|
65
|
+
# dLet Merb:Dispatcher find the route and call the filter chain and action
|
56
66
|
controller = nil
|
57
|
-
controller, action = handle(request)
|
58
|
-
MERB_LOGGER.info("Routing to controller: #{controller.class} action: #{action}\nParsing HTTP Input took: #{Time.now - start} seconds")
|
67
|
+
controller, action = Merb::Dispatcher.handle(request, response)
|
59
68
|
|
60
|
-
#
|
61
|
-
# in your controller actions. AR performs much better in single
|
62
|
-
# threaded mode so we lock here for the shortest amount of time
|
63
|
-
# possible. Route recognition and mime parsing has already occured
|
64
|
-
# at this point because those processes are thread safe. This
|
65
|
-
# gives us the best trade off for multi threaded performance
|
66
|
-
# of thread safe, and a lock around calls to your controller actions.
|
67
|
-
@guard.synchronize {
|
68
|
-
controller.dispatch(action)
|
69
|
-
}
|
69
|
+
MERB_LOGGER.info("Routing to controller: #{controller.class} action: #{action}\nRoute Recognition & Parsing HTTP Input took: #{Time.now - start} seconds")
|
70
70
|
rescue Object => e
|
71
71
|
response.start(500) do |head,out|
|
72
72
|
head["Content-Type"] = "text/html"
|
@@ -113,6 +113,8 @@ class MerbHandler < Mongrel::HttpHandler
|
|
113
113
|
if controller.body.respond_to? :close
|
114
114
|
controller.body.close
|
115
115
|
end
|
116
|
+
elsif Proc === controller.body
|
117
|
+
controller.body.call
|
116
118
|
else
|
117
119
|
MERB_LOGGER.info("Response status: #{response.status}\nComplete Request took: #{Time.now - start} seconds\n\n")
|
118
120
|
# render response from successful controller
|
@@ -124,34 +126,4 @@ class MerbHandler < Mongrel::HttpHandler
|
|
124
126
|
end
|
125
127
|
end
|
126
128
|
|
127
|
-
# This is where we grab the incoming request PATH_INFO
|
128
|
-
# and use that in the merb routematcher to determine
|
129
|
-
# which controller and method to run.
|
130
|
-
# returns a 2 element tuple of:
|
131
|
-
# [controller, action]
|
132
|
-
def handle(request)
|
133
|
-
path = request.params[Mongrel::Const::PATH_INFO].sub(/\/+/, '/')
|
134
|
-
path = path[0..-2] if (path[-1] == ?/) && path.size > 1
|
135
|
-
route = Merb::RouteMatcher.new.route_request(path)
|
136
|
-
[ instantiate_controller(route[:controller], request.body, request.params, route),
|
137
|
-
route[:action] ]
|
138
|
-
end
|
139
|
-
|
140
|
-
# take a controller class name string and reload or require
|
141
|
-
# the right controller file then CamelCase it and turn it
|
142
|
-
# into a new object passing in the request and response.
|
143
|
-
# this is where your Merb::Controller is instantiated.
|
144
|
-
def instantiate_controller(controller_name, req, env, params)
|
145
|
-
if !File.exist?(DIST_ROOT+"/app/controllers/#{controller_name.snake_case}.rb")
|
146
|
-
raise Merb::MissingControllerFile
|
147
|
-
end
|
148
|
-
begin
|
149
|
-
controller_name.import
|
150
|
-
return Object.const_get( controller_name.camel_case ).new(req, env, params)
|
151
|
-
rescue RuntimeError
|
152
|
-
warn "Error getting instance of '#{controller_name.camel_case}': #{$!}"
|
153
|
-
raise $!
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
129
|
end
|