merb 0.3.4 → 0.3.7

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.
Files changed (62) hide show
  1. data/README +206 -197
  2. data/Rakefile +12 -21
  3. data/bin/merb +1 -1
  4. data/examples/skeleton/Rakefile +6 -20
  5. data/examples/skeleton/dist/app/mailers/layout/application.erb +1 -0
  6. data/examples/skeleton/dist/conf/database.yml +23 -0
  7. data/examples/skeleton/dist/conf/environments/development.rb +1 -0
  8. data/examples/skeleton/dist/conf/environments/production.rb +1 -0
  9. data/examples/skeleton/dist/conf/environments/test.rb +1 -0
  10. data/examples/skeleton/dist/conf/merb.yml +32 -28
  11. data/examples/skeleton/dist/conf/merb_init.rb +16 -13
  12. data/examples/skeleton/dist/conf/router.rb +9 -9
  13. data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +2 -2
  14. data/lib/merb.rb +23 -18
  15. data/lib/merb/caching/fragment_cache.rb +3 -7
  16. data/lib/merb/caching/store/memcache.rb +20 -0
  17. data/lib/merb/core_ext/merb_array.rb +0 -0
  18. data/lib/merb/core_ext/merb_class.rb +44 -4
  19. data/lib/merb/core_ext/merb_enumerable.rb +43 -1
  20. data/lib/merb/core_ext/merb_hash.rb +200 -122
  21. data/lib/merb/core_ext/merb_kernel.rb +2 -0
  22. data/lib/merb/core_ext/merb_module.rb +41 -0
  23. data/lib/merb/core_ext/merb_numeric.rb +57 -5
  24. data/lib/merb/core_ext/merb_object.rb +172 -6
  25. data/lib/merb/generators/merb_app/merb_app.rb +15 -9
  26. data/lib/merb/merb_abstract_controller.rb +193 -0
  27. data/lib/merb/merb_constants.rb +26 -1
  28. data/lib/merb/merb_controller.rb +143 -234
  29. data/lib/merb/merb_dispatcher.rb +28 -20
  30. data/lib/merb/merb_drb_server.rb +2 -3
  31. data/lib/merb/merb_exceptions.rb +194 -49
  32. data/lib/merb/merb_handler.rb +34 -26
  33. data/lib/merb/merb_mail_controller.rb +200 -0
  34. data/lib/merb/merb_mailer.rb +33 -13
  35. data/lib/merb/merb_part_controller.rb +42 -0
  36. data/lib/merb/merb_plugins.rb +293 -0
  37. data/lib/merb/merb_request.rb +6 -4
  38. data/lib/merb/merb_router.rb +99 -65
  39. data/lib/merb/merb_server.rb +65 -21
  40. data/lib/merb/merb_upload_handler.rb +2 -1
  41. data/lib/merb/merb_view_context.rb +36 -15
  42. data/lib/merb/mixins/basic_authentication_mixin.rb +5 -5
  43. data/lib/merb/mixins/controller_mixin.rb +67 -28
  44. data/lib/merb/mixins/erubis_capture_mixin.rb +1 -8
  45. data/lib/merb/mixins/form_control_mixin.rb +280 -42
  46. data/lib/merb/mixins/render_mixin.rb +127 -45
  47. data/lib/merb/mixins/responder_mixin.rb +5 -7
  48. data/lib/merb/mixins/view_context_mixin.rb +260 -94
  49. data/lib/merb/session.rb +23 -0
  50. data/lib/merb/session/merb_ar_session.rb +28 -16
  51. data/lib/merb/session/merb_mem_cache_session.rb +108 -0
  52. data/lib/merb/session/merb_memory_session.rb +65 -20
  53. data/lib/merb/template/erubis.rb +22 -13
  54. data/lib/merb/template/haml.rb +5 -16
  55. data/lib/merb/template/markaby.rb +5 -3
  56. data/lib/merb/template/xml_builder.rb +17 -5
  57. data/lib/merb/test/merb_fake_request.rb +63 -0
  58. data/lib/merb/test/merb_multipart.rb +58 -0
  59. data/lib/tasks/db.rake +2 -0
  60. data/lib/tasks/merb.rake +20 -8
  61. metadata +24 -25
  62. data/examples/skeleton.tar +0 -0
@@ -11,31 +11,36 @@ module Merb
11
11
 
12
12
  @@mutex = Mutex.new
13
13
  @@use_mutex = ::Merb::Server.use_mutex
14
- # This is where we grab the incoming request REQUEST_URI
15
- # and use that in the merb routematcher to determine
16
- # which controller and method to run.
17
- # returns a 2 element tuple of:
18
- # [controller, action]
14
+ # This is where we grab the incoming request REQUEST_URI and use that in
15
+ # the merb RouteMatcher to determine which controller and method to run.
16
+ # Returns a 2 element tuple of: [controller, action]
19
17
  def handle(request, response)
20
- request_uri = request.params[Mongrel::Const::REQUEST_URI]
18
+ start = Time.now
19
+
20
+ request_uri = request.params[Merb::Const::REQUEST_URI]
21
21
  request_uri.sub!(path_prefix, '') if path_prefix
22
22
  route = route_path(request_uri)
23
23
 
24
24
  allowed = route.delete(:allowed)
25
25
  rest = route.delete(:rest)
26
-
27
- controller = instantiate_controller(route[:controller], request.body, request.params, route, response)
26
+ namespace = route.delete(:namespace)
27
+
28
+ cont = namespace ? "#{namespace}/#{route[:controller]}" : route[:controller]
29
+
30
+ klass = resolve_controller(cont)
31
+ controller = klass.build(request.body, request.params, route, response)
28
32
 
29
33
  if rest
30
34
  method = controller.request.method
31
35
  if allowed.keys.include?(method) && action = allowed[method]
32
36
  controller.params[:action] = action
33
37
  else
34
- raise Merb::RestfulMethodNotAllowed.new(method, allowed)
38
+ raise Merb::HTTPMethodNotAllowed.new(method, allowed)
35
39
  end
36
40
  else
37
41
  action = route[:action]
38
42
  end
43
+ controller._benchmarks[:setup_time] = Time.now - start
39
44
  if @@use_mutex
40
45
  @@mutex.synchronize {
41
46
  controller.dispatch(action)
@@ -52,20 +57,23 @@ module Merb
52
57
  Merb::Router.match(path)
53
58
  end
54
59
 
55
- # take a controller class name string and reload or require
56
- # the right controller file then CamelCase it and turn it
57
- # into a new object passing in the request and response.
58
- # this is where your Merb::Controller is instantiated.
59
- def instantiate_controller(controller_name, req, env, params, res)
60
- if !File.exist?(DIST_ROOT+"/app/controllers/#{controller_name.snake_case}.rb")
61
- raise "Bad controller! #{controller_name.snake_case}"
60
+ # Take a controller class name string and reload or require the right
61
+ # controller file then CamelCase it and return the class object.
62
+ def resolve_controller(controller_name)
63
+ segments = controller_name.split('/').map{|s| s.snake_case}
64
+ path = "#{DIST_ROOT}/app/controllers/#{controller_name}.rb"
65
+ cnt = segments.map{|s| s.camel_case }.join('::')
66
+
67
+ if !File.exist?(path)
68
+ raise "Bad controller! #{cnt}"
62
69
  end unless $TESTING
70
+
63
71
  begin
64
- unless MERB_ENV == 'production'
65
- Object.send(:remove_const, controller_name.camel_case.intern) rescue nil
66
- load(controller_name.snake_case + '.rb')
72
+ if MERB_ENV == 'development'
73
+ Object.send(:remove_const, cnt) rescue nil
74
+ load(path)
67
75
  end
68
- return Object.const_get( controller_name.camel_case ).new(req, env, params, res)
76
+ return Object.full_const_get(cnt)
69
77
  rescue RuntimeError
70
78
  warn "Error getting instance of '#{controller_name.camel_case}': #{$!}"
71
79
  raise $!
@@ -14,7 +14,6 @@ module Merb
14
14
 
15
15
  end
16
16
 
17
- end
17
+ end
18
18
 
19
-
20
- end
19
+ end
@@ -4,19 +4,170 @@ rescue LoadError => ex
4
4
  end
5
5
 
6
6
  module Merb
7
+
8
+ module ControllerExceptions
9
+ # ControllerExceptions are a way of simplifying controller code by placing
10
+ # exceptional logic elsewhere. ControllerExceptions are like
11
+ # mini-controllers which have one action. The exception action is called
12
+ # to render a web page when the exception is raised. Additionally all
13
+ # ControllerExceptions have an HTTP status code associated with them
14
+ # which is given to the browser when it's action is rendered.
15
+ #
16
+ # ControllerExceptions::Base is an abstract base class, it cannot be
17
+ # raised. Derived from Base are many exceptions, one for each HTTP status
18
+ # code. EG Unauthorized (401), PaymentRequired (402), and Forbidden (403).
19
+ #
20
+ # These exceptions can be raised by your controller without any further
21
+ # work. For example
22
+ #
23
+ # def show
24
+ # product = Product.find(params[:id])
25
+ # raise NotFound if product.nil?
26
+ # [...]
27
+ # end
28
+ #
29
+ # By default the NotFound exception will look in for a template at
30
+ # app/views/exceptions/not_found.{rhtml, haml, whatevs}
31
+ # If the template is not found then a simple message will be sent.
32
+ #
33
+ # To extend the use of the ControllerExceptions one may derive a new
34
+ # exception from one of the already defined ones. Then one must add a
35
+ # special method called error_response which is like a controller action
36
+ # preparing the context for rendering a template at
37
+ # app/views/exceptions/my_exception_snake_cased.whatevs
38
+ #
39
+ # As an example we will create an exception called AdminAccessRequired.
40
+ # First we decide on what HTTP status code such an error might have.
41
+ # 401, Unauthorized seems to fit.
42
+ #
43
+ # class AdminAccessRequired < Merb::ControllerExceptions::Unauthorized
44
+ # def error_response
45
+ # @tried_to_access request.uri
46
+ # render :layout => 'error_page'
47
+ # end
48
+ # end
49
+ #
50
+ # At app/views/exceptions/admin_access_required.rhtml we write
51
+ #
52
+ # <h1>You're not an administrator!</h1>
53
+ # <p>You tried to access <%= @tried_to_access %> but that URL is
54
+ # restricted to administrators.</p>
55
+ #
56
+ # The layout 'error_page' works like any other controller, it would be
57
+ # looked for at app/views/layouts
58
+ #
59
+ # TODO: if you need to pass data to your exception use a constructor.
60
+ class Base < StandardError
61
+ include Merb::RenderMixin
62
+ include Merb::ControllerMixin
63
+ include Merb::ResponderMixin
64
+
65
+ def _template_root
66
+ @controller._template_root
67
+ end
68
+
69
+ # The filename of the exception template. usually something like
70
+ # views/exceptions/not_found.rhtml
71
+ def template_path
72
+ _template_root / 'exceptions' / self.class.name.snake_case.split('::').last
73
+ end
74
+
75
+ def set_controller(controller)
76
+ @controller = controller
77
+ end
78
+
79
+ def request; @controller.request; end
80
+ def params; @controller.params; end
81
+ def cookies; @controller.cookies; end
82
+ def headers; @controller.headers; end
83
+ def session; @controller.session; end
84
+ def response; @controller.response; end
85
+
86
+ def status
87
+ self.class.const_get(:STATUS)
88
+ end
89
+
90
+ def call_action
91
+ # TODO: this method is meaningless at the moment but it could call
92
+ # filters
93
+ # before filters?
94
+ error_response
95
+ # after filters?
96
+ end
97
+
98
+ def error_response
99
+ if File.exists?(template_path)
100
+ render
101
+ else
102
+ "Error #{status} #{self.class.name.split('::').last}!"
103
+ end
104
+ end
105
+ end
106
+ module HTTPErrors
107
+ # created with:
108
+ # ruby -r rubygems -e "require 'mongrel'; puts Mongrel::HTTP_STATUS_CODES.keys.sort.map {|code| %Q{class #{Mongrel::HTTP_STATUS_CODES[code].gsub /[^\w]/,''} < Merb::ControllerExceptions::Base; STATUS = #{code}; end} }.join(%Q{\n})"
109
+ # we could do some magic here, but i think it's more instructive to just
110
+ # have these values copied
111
+ class Continue < Merb::ControllerExceptions::Base; STATUS = 100; end
112
+ class SwitchingProtocols < Merb::ControllerExceptions::Base; STATUS = 101; end
113
+ class OK < Merb::ControllerExceptions::Base; STATUS = 200; end
114
+ class Created < Merb::ControllerExceptions::Base; STATUS = 201; end
115
+ class Accepted < Merb::ControllerExceptions::Base; STATUS = 202; end
116
+ class NonAuthoritativeInformation < Merb::ControllerExceptions::Base; STATUS = 203; end
117
+ class NoContent < Merb::ControllerExceptions::Base; STATUS = 204; end
118
+ class ResetContent < Merb::ControllerExceptions::Base; STATUS = 205; end
119
+ class PartialContent < Merb::ControllerExceptions::Base; STATUS = 206; end
120
+ class MultipleChoices < Merb::ControllerExceptions::Base; STATUS = 300; end
121
+ class MovedPermanently < Merb::ControllerExceptions::Base; STATUS = 301; end
122
+ class MovedTemporarily < Merb::ControllerExceptions::Base; STATUS = 302; end
123
+ class SeeOther < Merb::ControllerExceptions::Base; STATUS = 303; end
124
+ class NotModified < Merb::ControllerExceptions::Base; STATUS = 304; end
125
+ class UseProxy < Merb::ControllerExceptions::Base; STATUS = 305; end
126
+ class BadRequest < Merb::ControllerExceptions::Base; STATUS = 400; end
127
+ class Unauthorized < Merb::ControllerExceptions::Base; STATUS = 401; end
128
+ class PaymentRequired < Merb::ControllerExceptions::Base; STATUS = 402; end
129
+ class Forbidden < Merb::ControllerExceptions::Base; STATUS = 403; end
130
+ class NotFound < Merb::ControllerExceptions::Base; STATUS = 404; end
131
+ class MethodNotAllowed < Merb::ControllerExceptions::Base; STATUS = 405; end
132
+ class NotAcceptable < Merb::ControllerExceptions::Base; STATUS = 406; end
133
+ class ProxyAuthenticationRequired < Merb::ControllerExceptions::Base; STATUS = 407; end
134
+ class RequestTimeout < Merb::ControllerExceptions::Base; STATUS = 408; end
135
+ class Conflict < Merb::ControllerExceptions::Base; STATUS = 409; end
136
+ class Gone < Merb::ControllerExceptions::Base; STATUS = 410; end
137
+ class LengthRequired < Merb::ControllerExceptions::Base; STATUS = 411; end
138
+ class PreconditionFailed < Merb::ControllerExceptions::Base; STATUS = 412; end
139
+ class RequestEntityTooLarge < Merb::ControllerExceptions::Base; STATUS = 413; end
140
+ class RequestURITooLarge < Merb::ControllerExceptions::Base; STATUS = 414; end
141
+ class UnsupportedMediaType < Merb::ControllerExceptions::Base; STATUS = 415; end
142
+ class InternalServerError < Merb::ControllerExceptions::Base; STATUS = 500; end
143
+ class NotImplemented < Merb::ControllerExceptions::Base; STATUS = 501; end
144
+ class BadGateway < Merb::ControllerExceptions::Base; STATUS = 502; end
145
+ class ServiceUnavailable < Merb::ControllerExceptions::Base; STATUS = 503; end
146
+ class GatewayTimeout < Merb::ControllerExceptions::Base; STATUS = 504; end
147
+ class HTTPVersionNotSupported < Merb::ControllerExceptions::Base; STATUS = 505; end
148
+ end
149
+
150
+ include HTTPErrors
151
+ end
152
+
7
153
  class MerbError < StandardError; end
8
154
  class Noroutefound < MerbError; end
9
155
  class MissingControllerFile < MerbError; end
10
156
  class MerbControllerError < MerbError; end
11
- class RestfulMethodNotAllowed < MerbError
157
+ class HTTPMethodNotAllowed < MerbError
12
158
  def initialize(method, allowed)
13
- super("RestfulMethodNotAllowed: #{method}\n"+ "Allowed: #{allowed.keys.join(' ')})")
159
+ super("HTTPMethodNotAllowed: #{method}\n"+ "Allowed: #{allowed.keys.join(' ')})")
14
160
  end
15
161
 
16
162
  end
17
- # format exception message for browser display
163
+
164
+ # Format exception message for browser display.
18
165
  def self.html_exception(e)
19
- ::Merb::Server.show_error ? ErrorResponse.new(e).out : "500 Internal Server Error!"
166
+ if ::Merb::Server.show_error
167
+ ErrorResponse.new(e).out
168
+ else
169
+ IO.read(DIST_ROOT / 'public/500.html') rescue "500 Internal Server Error!"
170
+ end
20
171
  end
21
172
 
22
173
  def self.exception(e)
@@ -34,7 +185,7 @@ module Merb
34
185
  backtrace = @error.backtrace
35
186
 
36
187
  colors = []
37
- min = 104
188
+ min = 200
38
189
  max = 255
39
190
  step = -((max - min) / backtrace.size).abs
40
191
  max.step(min, step) do |color|
@@ -55,8 +206,8 @@ module Merb
55
206
  error_page(colors, error, *backtrace)
56
207
  end
57
208
 
58
- # this method offers highlighting for the sourcecode-chunks from the
59
- # traceback, just gem install coderay
209
+ # This method offers highlighting for the sourcecode-chunks from the
210
+ # traceback. Just 'gem install coderay'.
60
211
  def error_page(colors, title, *backtrace)
61
212
  @backtrace = backtrace
62
213
  @colors = colors
@@ -71,39 +222,26 @@ module Merb
71
222
  <title><%= @title %></title>
72
223
  <style type="text/css">
73
224
  <!--
74
- h1.main {
75
- text-align: center;
76
- }
77
- table.main {
78
- background: #000;
79
- }
80
- table.main tr.head {
81
- background: #fee;
82
- width: 100%;
83
- }
84
- table.main tr.source_container {
85
- display: none;
86
- }
87
- tr.source_container div {
88
- width: 100%;
89
- overflow: auto;
90
- }
91
- tr.source_container div table {
92
- background: #ddd;
93
- width: 100%;
94
- }
95
- tr.source_container div table tr {
96
- text-align:center;
97
- }
98
- tr.source_container div table tr.source {
99
- text-align:left;
100
- }
101
- tr a {
102
- color: black;
103
- }
104
- div.source {
105
- background: #fff;
106
- }
225
+ table {border:0px}
226
+ tr {line-height:1.3}
227
+ td {font-size:.8em; padding:4px}
228
+ h1 {font-size:2em}
229
+ h1, td {font-family:'courier new', 'courier', monospace}
230
+ td.line {color:#000; text-align:center}
231
+ td.method {font-weight:bold; text-align:right}
232
+ td.file a {color:#000}
233
+ td.file a:hover {color:#F00}
234
+ tr.clickable:hover {background:#FFA}
235
+ .clickable {cursor: help }
236
+ #title {float: left;}
237
+ .source
238
+ {
239
+ border:1px #000 solid;
240
+ margin:2px;
241
+ padding:8px;
242
+ background:#F0F0F0;
243
+ }
244
+
107
245
  -->
108
246
  <% if @coderay %>
109
247
  <%= CodeRay::Encoders[:html]::CSS.new.stylesheet %>
@@ -132,17 +270,24 @@ module Merb
132
270
  </script>
133
271
  </head>
134
272
  <body>
135
- <span style="float:left;">
136
- <img src="data:image/jpg;base64,/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAJQAA/+4ADkFk\nb2JlAGTAAAAAAf/bAIQADQkJCQoJDQoKDRMMCwwTFhENDREWGhUVFhUVGhkU\nFhUVFhQZGR0fIB8dGScnKionJzk4ODg5QEBAQEBAQEBAQAEODAwOEA4RDw8R\nFA4RDhQVERISERUgFRUXFRUgKB0ZGRkZHSgjJiAgICYjLCwoKCwsNzc1NzdA\nQEBAQEBAQEBA/8AAEQgAJgAtAwEiAAIRAQMRAf/EAT8AAAEFAQEBAQEBAAAA\nAAAAAAMAAQIEBQYHCAkKCwEAAQUBAQEBAQEAAAAAAAAAAQACAwQFBgcICQoL\nEAABBAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGhsUIjJBVS\nwWIzNHKC0UMHJZJT8OHxY3M1FqKygyZEk1RkRcKjdDYX0lXiZfKzhMPTdePz\nRieUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm9jdHV2d3h5ent8fX5/cRAAIC\nAQIEBAMEBQYHBwYFNQEAAhEDITESBEFRYXEiEwUygZEUobFCI8FS0fAzJGLh\ncoKSQ1MVY3M08SUGFqKygwcmNcLSRJNUoxdkRVU2dGXi8rOEw9N14/NGlKSF\ntJXE1OT0pbXF1eX1VmZ2hpamtsbW5vYnN0dXZ3eHl6e3x//aAAwDAQACEQMR\nAD8A9OVDK6xRQYa02axuEBvnB7x8Fm/W36x19HqoxmAvyswkMraHFwY2N7va\nCe/5Vy/Seu29RvdXaI2CW6EBw4OhGnZAk9Einrbuvn03Oaw1lvmCCP8ANXPW\n9Zz7shlrM9zKBLnN3Fmg1gnj8An61m14eCbAN9j4ZVWJJc5x2gQPivO3ZnUM\nXLtfY8by73sDmuHw9pPCZRPVNgdH13F+teJU51WbZpX9O4xpPEho4juug9Wr\n0vW3t9Lbv9SRt2xO7dxEL5+blW5I2W2FrdsSJJgdj4r0P1W/+Nf6Un1p9PZJ\nnf8AaPU2+P0fwThdVaLF3TlfXZvUsr63ZIpZY4Y9VDKTWwuIY5vqEe3uXFx+\nSn0XBupy9t+loqDXy1zTJLST7tefJbVDLsjLycwO9Sy/09ANQafUJP8A4IrG\nQGufXltEAjZYPA900kkeCRoXA+sHSeoTXn41xJxnB9bY3e4HRcTbVsfB2h7j\ntDWkkAT7jrPjC9FzCMfpJZU5wDGgN7wNI4HZcJdiNrvP2t+62SNg9vedxnRO\nj2WyQfZy2oOA5kbfGedF0jb8w/Uq2ra/25NcGD9F1dk6/BqrYNODY5rrve4+\n2rGpBcR3Mr0j9iVf83vscD1J3bZMep/NelM+Hsn5oqa3RG0uuf6b9j9dsCRI\n+lx4haGT9g1FgZ6nc0n/AKrSEkko/Kg7vO9XtsxzV+y6a8xjtxu+hWWwPY0f\npBMnv2XN52ZmbyLunYocCZNl1bpdENmS0wOdUkkPsT9rL6rfbf8AnJiPzvT+\nzfpPVrZ6ezb6Ttv0e26OTzC9U3uk+z88ACRu+hz8YSSR6q6P/9k=\n"/>
137
- </span>
138
- <h1><%= @title %></h1>
273
+ <div>
274
+ <h1><img src="data:image/jpg;base64,/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAJQAA/+4ADkFk\nb2JlAGTAAAAAAf/bAIQADQkJCQoJDQoKDRMMCwwTFhENDREWGhUVFhUVGhkU\nFhUVFhQZGR0fIB8dGScnKionJzk4ODg5QEBAQEBAQEBAQAEODAwOEA4RDw8R\nFA4RDhQVERISERUgFRUXFRUgKB0ZGRkZHSgjJiAgICYjLCwoKCwsNzc1NzdA\nQEBAQEBAQEBA/8AAEQgAJgAtAwEiAAIRAQMRAf/EAT8AAAEFAQEBAQEBAAAA\nAAAAAAMAAQIEBQYHCAkKCwEAAQUBAQEBAQEAAAAAAAAAAQACAwQFBgcICQoL\nEAABBAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGhsUIjJBVS\nwWIzNHKC0UMHJZJT8OHxY3M1FqKygyZEk1RkRcKjdDYX0lXiZfKzhMPTdePz\nRieUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm9jdHV2d3h5ent8fX5/cRAAIC\nAQIEBAMEBQYHBwYFNQEAAhEDITESBEFRYXEiEwUygZEUobFCI8FS0fAzJGLh\ncoKSQ1MVY3M08SUGFqKygwcmNcLSRJNUoxdkRVU2dGXi8rOEw9N14/NGlKSF\ntJXE1OT0pbXF1eX1VmZ2hpamtsbW5vYnN0dXZ3eHl6e3x//aAAwDAQACEQMR\nAD8A9OVDK6xRQYa02axuEBvnB7x8Fm/W36x19HqoxmAvyswkMraHFwY2N7va\nCe/5Vy/Seu29RvdXaI2CW6EBw4OhGnZAk9Einrbuvn03Oaw1lvmCCP8ANXPW\n9Zz7shlrM9zKBLnN3Fmg1gnj8An61m14eCbAN9j4ZVWJJc5x2gQPivO3ZnUM\nXLtfY8by73sDmuHw9pPCZRPVNgdH13F+teJU51WbZpX9O4xpPEho4juug9Wr\n0vW3t9Lbv9SRt2xO7dxEL5+blW5I2W2FrdsSJJgdj4r0P1W/+Nf6Un1p9PZJ\nnf8AaPU2+P0fwThdVaLF3TlfXZvUsr63ZIpZY4Y9VDKTWwuIY5vqEe3uXFx+\nSn0XBupy9t+loqDXy1zTJLST7tefJbVDLsjLycwO9Sy/09ANQafUJP8A4IrG\nQGufXltEAjZYPA900kkeCRoXA+sHSeoTXn41xJxnB9bY3e4HRcTbVsfB2h7j\ntDWkkAT7jrPjC9FzCMfpJZU5wDGgN7wNI4HZcJdiNrvP2t+62SNg9vedxnRO\nj2WyQfZy2oOA5kbfGedF0jb8w/Uq2ra/25NcGD9F1dk6/BqrYNODY5rrve4+\n2rGpBcR3Mr0j9iVf83vscD1J3bZMep/NelM+Hsn5oqa3RG0uuf6b9j9dsCRI\n+lx4haGT9g1FgZ6nc0n/AKrSEkko/Kg7vO9XtsxzV+y6a8xjtxu+hWWwPY0f\npBMnv2XN52ZmbyLunYocCZNl1bpdENmS0wOdUkkPsT9rL6rfbf8AnJiPzvT+\nzfpPVrZ6ezb6Ttv0e26OTzC9U3uk+z88ACRu+hz8YSSR6q6P/9k=\n"/>
275
+ <%= @title %></h1>
276
+ </div>
277
+
139
278
  <table class="main">
140
- <tr class="head">
141
- <td>File</td><td>Line</td><td>Method</td>
142
- </tr>
279
+ <thead>
280
+ <tr>
281
+ <td class="method">Method</td>
282
+ <td class="line">Line</td>
283
+ <td class="file">File</td>
284
+ </tr>
285
+ </thead>
143
286
  <% @backtrace.each do |lines, hash, file, lineno, meth| %>
144
- <tr id="line_<%= hash %>" style="background:rgb(104,104,<%= @colors.shift %>);">
145
- <td><a href='#' onclick="toggle('source_<%= hash %>'); return false"><%= file %></a></td><td><%= lineno %></td><td><%= meth %></td>
287
+ <tr id="line_<%= hash %>" onclick="toggle('source_<%= hash %>'); return false"; class="clickable" style="background:rgb(250,250,<%= @colors.shift %>);">
288
+ <td class="method"><%= meth %></td>
289
+ <td class="line"><%= lineno %></td>
290
+ <td class="file"><%= file %></td>
146
291
  </tr>
147
292
  <tr id="source_<%= hash %>" <%= @coderay? " class='CodeRay' " : ''%> style="display:none;">
148
293
  <td colspan="3">
@@ -9,12 +9,10 @@ class Mongrel::HttpResponse
9
9
  end
10
10
  end
11
11
 
12
-
13
-
14
12
  class MerbHandler < Mongrel::HttpHandler
15
13
  @@file_only_methods = ["GET","HEAD"]
16
14
 
17
- # take the name of a directory and use that as the doc root or public
15
+ # Take the name of a directory and use that as the doc root or public
18
16
  # directory of your site. This is set to the root of your merb app + '/public'
19
17
  # by default.
20
18
  def initialize(dir, opts = {})
@@ -22,21 +20,21 @@ class MerbHandler < Mongrel::HttpHandler
22
20
  end
23
21
 
24
22
  # process incoming http requests and do a number of things
25
- # 1. check for rails style cached pages. add .html to the
26
- # url and see if there is a static file in public that matches.
27
- # serve that file directly without invoking Merb and be done with it.
28
- # 2. Serve static asset and html files directly from public/ if
29
- # they exist.
30
- # 3. If none of the above apply, we take apart the request url
31
- # and feed it into Merb::RouteMatcher to let it decide which
32
- # controller and method will serve the request.
33
- # 4. after the controller has done its thing, we check for the
34
- # X-SENDFILE header. if you set this header to the path to a file
35
- # in your controller then mongrel will serve the file directly
36
- # and your controller can go on processing other requests.
23
+ # 1. Check for rails style cached pages. add .html to the url and see if
24
+ # there is a static file in public that matches. serve that file directly
25
+ # without invoking Merb and be done with it.
26
+ # 2. Serve static asset and html files directly from public/ if they exist.
27
+ # 3. If none of the above apply, we take apart the request url and feed it
28
+ # into Merb::RouteMatcher to let it decide which controller and method will
29
+ # serve the request.
30
+ # 4. After the controller has done its thing, we check for the X-SENDFILE
31
+ # header. if you set this header to the path to a file in your controller
32
+ # then mongrel will serve the file directly and your controller can go on
33
+ # processing other requests.
37
34
  def process(request, response)
38
35
 
39
36
  start = Time.now
37
+ benchmarks = {}
40
38
 
41
39
  if response.socket.closed?
42
40
  return
@@ -44,9 +42,9 @@ class MerbHandler < Mongrel::HttpHandler
44
42
 
45
43
  MERB_LOGGER.info("\nRequest: REQUEST_URI: #{request.params[Mongrel::Const::REQUEST_URI]} (#{Time.now.strftime("%Y-%m-%d %H:%M:%S")})")
46
44
 
47
- # Rails style page caching. Check the public dir first for
48
- # .html pages and serve directly. Otherwise fall back to Merb
49
- # routing and request dispatching.
45
+ # Rails style page caching. Check the public dir first for .html pages and
46
+ # serve directly. Otherwise fall back to Merb routing and request
47
+ # dispatching.
50
48
  path_info = request.params[Mongrel::Const::PATH_INFO]
51
49
  page_cached = path_info + ".html"
52
50
  get_or_head = @@file_only_methods.include? request.params[Mongrel::Const::REQUEST_METHOD]
@@ -65,8 +63,12 @@ class MerbHandler < Mongrel::HttpHandler
65
63
  # Let Merb:Dispatcher find the route and call the filter chain and action
66
64
  controller = nil
67
65
  controller, action = Merb::Dispatcher.handle(request, response)
68
-
69
- MERB_LOGGER.info("Routing to controller: #{controller.class} action: #{action}\nRoute Recognition & Parsing HTTP Input took: #{Time.now - start} seconds")
66
+
67
+ benchmarks.merge!(controller._benchmarks)
68
+ benchmarks[:controller] = controller.class.to_s
69
+ benchmarks[:action] = action
70
+
71
+ MERB_LOGGER.info("Routing to controller: #{controller.class} action: #{action}\nRoute Recognition & Parsing HTTP Input took: #{benchmarks[:setup_time]} seconds")
70
72
  rescue Object => e
71
73
  response.start(500) do |head,out|
72
74
  head["Content-Type"] = "text/html"
@@ -78,8 +80,8 @@ class MerbHandler < Mongrel::HttpHandler
78
80
 
79
81
  sendfile, clength = nil
80
82
  response.status = controller.status
81
- # check for the X-SENDFILE header from your Merb::Controller
82
- # and serve the file directly instead of buffering.
83
+ # Check for the X-SENDFILE header from your Merb::Controller and serve
84
+ # the file directly instead of buffering.
83
85
  controller.headers.each do |k, v|
84
86
  if k =~ /^X-SENDFILE$/i
85
87
  sendfile = v
@@ -93,14 +95,15 @@ class MerbHandler < Mongrel::HttpHandler
93
95
  end
94
96
 
95
97
  if sendfile
96
- MERB_LOGGER.info("X-SENDFILE: #{sendfile}\nComplete Request took: #{Time.now - start} seconds")
98
+ benchmarks[:sendfile_time] = Time.now - start
99
+ MERB_LOGGER.info("X-SENDFILE: #{sendfile}\nComplete Request took: #{benchmarks[:sendfile_time]} seconds")
97
100
  file_status = File.stat(sendfile)
98
101
  response.status = 200
99
102
  # Set the last modified times as well and etag for all files
100
103
  response.header[Mongrel::Const::LAST_MODIFIED] = file_status.mtime.httpdate
101
104
  # Calculated the same as apache, not sure how well the works on win32
102
105
  response.header[Mongrel::Const::ETAG] = Mongrel::Const::ETAG_FORMAT % [file_status.mtime.to_i, file_status.size, file_status.ino]
103
- # send a status with out content length
106
+ # Send a status with out content length
104
107
  response.send_status(file_status.size)
105
108
  response.send_header
106
109
  response.send_file(sendfile)
@@ -116,13 +119,18 @@ class MerbHandler < Mongrel::HttpHandler
116
119
  elsif Proc === controller.body
117
120
  controller.body.call
118
121
  else
119
- MERB_LOGGER.info("Response status: #{response.status}\nComplete Request took: #{Time.now - start} seconds\n\n")
120
- # render response from successful controller
122
+ # Render response from successful controller
121
123
  body = (controller.body.to_s rescue '')
122
124
  response.send_status(body.length)
123
125
  response.send_header
124
126
  response.write(body)
125
127
  end
128
+
129
+ total_request_time = Time.now - start
130
+ benchmarks[:total_request_time] = total_request_time
131
+
132
+ MERB_LOGGER.info("Request Times: #{benchmarks.inspect}")
133
+ MERB_LOGGER.info("Response status: #{response.status}\nComplete Request took: #{total_request_time} seconds, #{1.0/total_request_time} Requests/Second\n\n")
126
134
  end
127
135
  end
128
136