merb 0.1.0 → 0.2.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 +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
|