merb 0.3.4 → 0.3.7

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