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.
- data/README +206 -197
- data/Rakefile +12 -21
- data/bin/merb +1 -1
- data/examples/skeleton/Rakefile +6 -20
- data/examples/skeleton/dist/app/mailers/layout/application.erb +1 -0
- data/examples/skeleton/dist/conf/database.yml +23 -0
- data/examples/skeleton/dist/conf/environments/development.rb +1 -0
- data/examples/skeleton/dist/conf/environments/production.rb +1 -0
- data/examples/skeleton/dist/conf/environments/test.rb +1 -0
- data/examples/skeleton/dist/conf/merb.yml +32 -28
- data/examples/skeleton/dist/conf/merb_init.rb +16 -13
- data/examples/skeleton/dist/conf/router.rb +9 -9
- data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +2 -2
- data/lib/merb.rb +23 -18
- data/lib/merb/caching/fragment_cache.rb +3 -7
- data/lib/merb/caching/store/memcache.rb +20 -0
- data/lib/merb/core_ext/merb_array.rb +0 -0
- data/lib/merb/core_ext/merb_class.rb +44 -4
- data/lib/merb/core_ext/merb_enumerable.rb +43 -1
- data/lib/merb/core_ext/merb_hash.rb +200 -122
- data/lib/merb/core_ext/merb_kernel.rb +2 -0
- data/lib/merb/core_ext/merb_module.rb +41 -0
- data/lib/merb/core_ext/merb_numeric.rb +57 -5
- data/lib/merb/core_ext/merb_object.rb +172 -6
- data/lib/merb/generators/merb_app/merb_app.rb +15 -9
- data/lib/merb/merb_abstract_controller.rb +193 -0
- data/lib/merb/merb_constants.rb +26 -1
- data/lib/merb/merb_controller.rb +143 -234
- data/lib/merb/merb_dispatcher.rb +28 -20
- data/lib/merb/merb_drb_server.rb +2 -3
- data/lib/merb/merb_exceptions.rb +194 -49
- data/lib/merb/merb_handler.rb +34 -26
- data/lib/merb/merb_mail_controller.rb +200 -0
- data/lib/merb/merb_mailer.rb +33 -13
- data/lib/merb/merb_part_controller.rb +42 -0
- data/lib/merb/merb_plugins.rb +293 -0
- data/lib/merb/merb_request.rb +6 -4
- data/lib/merb/merb_router.rb +99 -65
- data/lib/merb/merb_server.rb +65 -21
- data/lib/merb/merb_upload_handler.rb +2 -1
- data/lib/merb/merb_view_context.rb +36 -15
- data/lib/merb/mixins/basic_authentication_mixin.rb +5 -5
- data/lib/merb/mixins/controller_mixin.rb +67 -28
- data/lib/merb/mixins/erubis_capture_mixin.rb +1 -8
- data/lib/merb/mixins/form_control_mixin.rb +280 -42
- data/lib/merb/mixins/render_mixin.rb +127 -45
- data/lib/merb/mixins/responder_mixin.rb +5 -7
- data/lib/merb/mixins/view_context_mixin.rb +260 -94
- data/lib/merb/session.rb +23 -0
- data/lib/merb/session/merb_ar_session.rb +28 -16
- data/lib/merb/session/merb_mem_cache_session.rb +108 -0
- data/lib/merb/session/merb_memory_session.rb +65 -20
- data/lib/merb/template/erubis.rb +22 -13
- data/lib/merb/template/haml.rb +5 -16
- data/lib/merb/template/markaby.rb +5 -3
- data/lib/merb/template/xml_builder.rb +17 -5
- data/lib/merb/test/merb_fake_request.rb +63 -0
- data/lib/merb/test/merb_multipart.rb +58 -0
- data/lib/tasks/db.rake +2 -0
- data/lib/tasks/merb.rake +20 -8
- metadata +24 -25
- data/examples/skeleton.tar +0 -0
data/lib/merb/merb_dispatcher.rb
CHANGED
@@ -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
|
-
#
|
16
|
-
#
|
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
|
-
|
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
|
-
|
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::
|
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
|
-
#
|
56
|
-
#
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
65
|
-
Object.send(:remove_const,
|
66
|
-
load(
|
72
|
+
if MERB_ENV == 'development'
|
73
|
+
Object.send(:remove_const, cnt) rescue nil
|
74
|
+
load(path)
|
67
75
|
end
|
68
|
-
return Object.
|
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 $!
|
data/lib/merb/merb_drb_server.rb
CHANGED
data/lib/merb/merb_exceptions.rb
CHANGED
@@ -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
|
157
|
+
class HTTPMethodNotAllowed < MerbError
|
12
158
|
def initialize(method, allowed)
|
13
|
-
super("
|
159
|
+
super("HTTPMethodNotAllowed: #{method}\n"+ "Allowed: #{allowed.keys.join(' ')})")
|
14
160
|
end
|
15
161
|
|
16
162
|
end
|
17
|
-
|
163
|
+
|
164
|
+
# Format exception message for browser display.
|
18
165
|
def self.html_exception(e)
|
19
|
-
::Merb::Server.show_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 =
|
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
|
-
#
|
59
|
-
# traceback
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
<
|
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
|
-
|
138
|
-
|
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
|
-
<
|
141
|
-
<
|
142
|
-
|
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(
|
145
|
-
<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">
|
data/lib/merb/merb_handler.rb
CHANGED
@@ -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
|
-
#
|
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.
|
26
|
-
#
|
27
|
-
#
|
28
|
-
# 2. Serve static asset and html files directly from public/ if
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
# controller
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
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
|
-
#
|
49
|
-
#
|
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
|
-
|
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
|
-
#
|
82
|
-
#
|
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
|
-
|
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
|
-
#
|
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
|
-
|
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
|
|