merb 0.3.7 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +25 -26
- data/Rakefile +48 -36
- data/app_generators/merb/USAGE +5 -0
- data/app_generators/merb/merb_generator.rb +107 -0
- data/app_generators/merb/templates/Rakefile +99 -0
- data/{examples/skeleton/dist → app_generators/merb/templates}/app/controllers/application.rb +1 -1
- data/app_generators/merb/templates/app/controllers/exceptions.rb +13 -0
- data/{examples/skeleton/dist → app_generators/merb/templates}/app/helpers/global_helper.rb +0 -0
- data/{examples/skeleton/dist/app/mailers → app_generators/merb/templates/app/mailers/views}/layout/application.erb +0 -0
- data/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb +207 -0
- data/app_generators/merb/templates/app/views/exceptions/not_acceptable.html.erb +38 -0
- data/app_generators/merb/templates/app/views/exceptions/not_found.html.erb +40 -0
- data/app_generators/merb/templates/app/views/layout/application.html.erb +11 -0
- data/app_generators/merb/templates/config/boot.rb +11 -0
- data/app_generators/merb/templates/config/dependencies.rb +41 -0
- data/{examples/skeleton/dist/conf → app_generators/merb/templates/config}/environments/development.rb +0 -0
- data/{examples/skeleton/dist/conf → app_generators/merb/templates/config}/environments/production.rb +0 -0
- data/{examples/skeleton/dist/conf → app_generators/merb/templates/config}/environments/test.rb +0 -0
- data/app_generators/merb/templates/config/merb.yml +64 -0
- data/app_generators/merb/templates/config/merb_init.rb +16 -0
- data/app_generators/merb/templates/config/plugins.yml +1 -0
- data/app_generators/merb/templates/config/router.rb +32 -0
- data/{lib/merb/core_ext/merb_array.rb → app_generators/merb/templates/config/upload.conf} +0 -0
- data/app_generators/merb/templates/public/images/merb.jpg +0 -0
- data/app_generators/merb/templates/public/merb.fcgi +6 -0
- data/app_generators/merb/templates/public/stylesheets/master.css +119 -0
- data/app_generators/merb/templates/script/destroy +28 -0
- data/app_generators/merb/templates/script/generate +28 -0
- data/{examples/skeleton → app_generators/merb/templates}/script/stop_merb +0 -0
- data/app_generators/merb/templates/script/win_script.cmd +1 -0
- data/app_generators/merb/templates/spec/spec.opts +6 -0
- data/app_generators/merb/templates/spec/spec_helper.rb +10 -0
- data/app_generators/merb/templates/test/test_helper.rb +13 -0
- data/app_generators/merb_plugin/USAGE +5 -0
- data/app_generators/merb_plugin/merb_plugin_generator.rb +64 -0
- data/app_generators/merb_plugin/templates/LICENSE +20 -0
- data/app_generators/merb_plugin/templates/README +4 -0
- data/app_generators/merb_plugin/templates/Rakefile +35 -0
- data/app_generators/merb_plugin/templates/TODO +5 -0
- data/app_generators/merb_plugin/templates/merbtasks.rb +6 -0
- data/app_generators/merb_plugin/templates/sampleplugin.rb +10 -0
- data/app_generators/merb_plugin/templates/sampleplugin_spec.rb +7 -0
- data/app_generators/merb_plugin/templates/spec_helper.rb +2 -0
- data/bin/merb +1 -1
- data/lib/autotest/discover.rb +3 -0
- data/lib/autotest/merb_rspec.rb +79 -0
- data/lib/merb.rb +72 -93
- data/lib/merb/{merb_abstract_controller.rb → abstract_controller.rb} +28 -5
- data/lib/merb/caching/action_cache.rb +65 -29
- data/lib/merb/caching/fragment_cache.rb +9 -4
- data/lib/merb/caching/store/file_cache.rb +22 -14
- data/lib/merb/caching/store/memory_cache.rb +26 -8
- data/lib/merb/{merb_constants.rb → constants.rb} +9 -7
- data/lib/merb/controller.rb +178 -0
- data/lib/merb/core_ext.rb +13 -11
- data/lib/merb/core_ext/array.rb +0 -0
- data/lib/merb/core_ext/{merb_class.rb → class.rb} +0 -0
- data/lib/merb/core_ext/{merb_enumerable.rb → enumerable.rb} +0 -0
- data/lib/merb/core_ext/get_args.rb +52 -0
- data/lib/merb/core_ext/{merb_hash.rb → hash.rb} +40 -11
- data/lib/merb/core_ext/{merb_inflections.rb → inflections.rb} +0 -0
- data/lib/merb/core_ext/{merb_inflector.rb → inflector.rb} +1 -1
- data/lib/merb/core_ext/{merb_kernel.rb → kernel.rb} +56 -3
- data/lib/merb/core_ext/mash.rb +88 -0
- data/lib/merb/core_ext/{merb_module.rb → module.rb} +0 -0
- data/lib/merb/core_ext/{merb_numeric.rb → numeric.rb} +0 -0
- data/lib/merb/core_ext/{merb_object.rb → object.rb} +10 -47
- data/lib/merb/core_ext/string.rb +56 -0
- data/lib/merb/core_ext/{merb_symbol.rb → symbol.rb} +0 -0
- data/lib/merb/dispatcher.rb +109 -0
- data/lib/merb/{merb_drb_server.rb → drb_server.rb} +0 -0
- data/lib/merb/erubis_ext.rb +10 -0
- data/lib/merb/exceptions.rb +173 -0
- data/lib/merb/generators/merb_app/merb_app.rb +5 -25
- data/lib/merb/generators/merb_generator_helpers.rb +317 -0
- data/lib/merb/generators/merb_plugin.rb +19 -0
- data/lib/merb/logger.rb +65 -0
- data/lib/merb/{merb_mail_controller.rb → mail_controller.rb} +102 -49
- data/lib/merb/{merb_mailer.rb → mailer.rb} +31 -27
- data/lib/merb/mixins/{basic_authentication_mixin.rb → basic_authentication.rb} +3 -3
- data/lib/merb/mixins/{controller_mixin.rb → controller.rb} +131 -112
- data/lib/merb/mixins/{erubis_capture_mixin.rb → erubis_capture.rb} +12 -21
- data/lib/merb/mixins/{form_control_mixin.rb → form_control.rb} +6 -12
- data/lib/merb/mixins/render.rb +401 -0
- data/lib/merb/mixins/responder.rb +378 -0
- data/lib/merb/mixins/{view_context_mixin.rb → view_context.rb} +65 -10
- data/lib/merb/mixins/web_controller.rb +29 -0
- data/lib/merb/{merb_handler.rb → mongrel_handler.rb} +59 -38
- data/lib/merb/part_controller.rb +19 -0
- data/lib/merb/plugins.rb +16 -0
- data/lib/merb/rack_adapter.rb +37 -0
- data/lib/merb/request.rb +421 -0
- data/lib/merb/router.rb +576 -0
- data/lib/merb/{merb_server.rb → server.rb} +275 -71
- data/lib/merb/session.rb +10 -10
- data/lib/merb/session/cookie_store.rb +125 -0
- data/lib/merb/session/{merb_mem_cache_session.rb → mem_cache_session.rb} +22 -9
- data/lib/merb/session/{merb_memory_session.rb → memory_session.rb} +15 -11
- data/lib/merb/template.rb +35 -8
- data/lib/merb/template/erubis.rb +16 -10
- data/lib/merb/template/haml.rb +33 -20
- data/lib/merb/template/markaby.rb +16 -14
- data/lib/merb/template/xml_builder.rb +8 -4
- data/lib/merb/test/{merb_fake_request.rb → fake_request.rb} +11 -5
- data/lib/merb/test/helper.rb +31 -0
- data/lib/merb/test/hpricot.rb +136 -0
- data/lib/merb/test/{merb_multipart.rb → multipart.rb} +1 -1
- data/lib/merb/test/rspec.rb +93 -0
- data/lib/merb/{merb_upload_handler.rb → upload_handler.rb} +5 -6
- data/lib/merb/{merb_upload_progress.rb → upload_progress.rb} +1 -1
- data/lib/merb/{merb_view_context.rb → view_context.rb} +27 -42
- data/lib/{merb_tasks.rb → tasks.rb} +0 -0
- data/lib/tasks/merb.rake +21 -11
- data/merb_default_generators/model/USAGE +0 -0
- data/merb_default_generators/model/model_generator.rb +16 -0
- data/merb_default_generators/model/templates/new_model_template.erb +5 -0
- data/merb_default_generators/resource_controller/USAGE +0 -0
- data/merb_default_generators/resource_controller/resource_controller_generator.rb +26 -0
- data/merb_default_generators/resource_controller/templates/controller.rb +30 -0
- data/merb_default_generators/resource_controller/templates/edit.html.erb +1 -0
- data/merb_default_generators/resource_controller/templates/helper.rb +5 -0
- data/merb_default_generators/resource_controller/templates/index.html.erb +1 -0
- data/merb_default_generators/resource_controller/templates/new.html.erb +1 -0
- data/merb_default_generators/resource_controller/templates/show.html.erb +1 -0
- data/merb_generators/controller/USAGE +5 -0
- data/merb_generators/controller/controller_generator.rb +16 -0
- data/merb_generators/controller/templates/controller.rb +8 -0
- data/merb_generators/controller/templates/helper.rb +5 -0
- data/merb_generators/controller/templates/index.html.erb +3 -0
- data/merb_generators/resource/USAGE +0 -0
- data/merb_generators/resource/resource_generator.rb +60 -0
- data/rspec_generators/merb_controller_test/merb_controller_test_generator.rb +67 -0
- data/rspec_generators/merb_controller_test/templates/controller_spec.rb +8 -0
- data/rspec_generators/merb_controller_test/templates/edit_spec.rb +12 -0
- data/rspec_generators/merb_controller_test/templates/helper_spec.rb +5 -0
- data/rspec_generators/merb_controller_test/templates/index_spec.rb +12 -0
- data/rspec_generators/merb_controller_test/templates/new_spec.rb +12 -0
- data/rspec_generators/merb_controller_test/templates/show_spec.rb +5 -0
- data/rspec_generators/merb_model_test/merb_model_test_generator.rb +26 -0
- data/rspec_generators/merb_model_test/templates/model_spec_template.erb +7 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test_unit_generators/merb_controller_test/merb_controller_test_generator.rb +53 -0
- data/test_unit_generators/merb_controller_test/templates/functional_test.rb +17 -0
- data/test_unit_generators/merb_controller_test/templates/helper_test.rb +9 -0
- data/test_unit_generators/merb_model_test/merb_model_test_generator.rb +29 -0
- data/test_unit_generators/merb_model_test/templates/model_test_unit_template.erb +9 -0
- metadata +172 -94
- data/examples/README_EXAMPLES +0 -10
- data/examples/skeleton/Rakefile +0 -68
- data/examples/skeleton/dist/app/views/layout/application.herb +0 -12
- data/examples/skeleton/dist/conf/database.yml +0 -23
- data/examples/skeleton/dist/conf/merb.yml +0 -57
- data/examples/skeleton/dist/conf/merb_init.rb +0 -24
- data/examples/skeleton/dist/conf/router.rb +0 -22
- data/examples/skeleton/dist/conf/upload.conf +0 -5
- data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +0 -14
- data/examples/skeleton/script/new_migration +0 -21
- data/lib/merb/core_ext/merb_string.rb +0 -18
- data/lib/merb/merb_controller.rb +0 -206
- data/lib/merb/merb_dispatcher.rb +0 -87
- data/lib/merb/merb_exceptions.rb +0 -319
- data/lib/merb/merb_part_controller.rb +0 -42
- data/lib/merb/merb_plugins.rb +0 -293
- data/lib/merb/merb_request.rb +0 -165
- data/lib/merb/merb_router.rb +0 -309
- data/lib/merb/merb_yaml_store.rb +0 -31
- data/lib/merb/mixins/render_mixin.rb +0 -283
- data/lib/merb/mixins/responder_mixin.rb +0 -159
- data/lib/merb/session/merb_ar_session.rb +0 -131
- data/lib/merb/vendor/paginator/README.txt +0 -84
- data/lib/merb/vendor/paginator/paginator.rb +0 -124
- data/lib/tasks/db.rake +0 -55
@@ -0,0 +1,29 @@
|
|
1
|
+
module Merb
|
2
|
+
module WebControllerMixin
|
3
|
+
|
4
|
+
def request
|
5
|
+
@web_controller.request
|
6
|
+
end
|
7
|
+
|
8
|
+
def params
|
9
|
+
@web_controller.params
|
10
|
+
end
|
11
|
+
|
12
|
+
def cookies
|
13
|
+
@web_controller.cookies
|
14
|
+
end
|
15
|
+
|
16
|
+
def headers
|
17
|
+
@web_controller.headers
|
18
|
+
end
|
19
|
+
|
20
|
+
def session
|
21
|
+
@web_controller.session
|
22
|
+
end
|
23
|
+
|
24
|
+
def response
|
25
|
+
@web_controller.response
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class Mongrel::HttpResponse
|
2
2
|
NO_CLOSE_STATUS_FORMAT = "HTTP/1.1 %d %s\r\n".freeze
|
3
3
|
def send_status_no_connection_close(content_length=@body.length)
|
4
|
-
|
4
|
+
unless @status_sent
|
5
5
|
@header['Content-Length'] = content_length unless @status == 304
|
6
6
|
write(NO_CLOSE_STATUS_FORMAT % [@status, Mongrel::HTTP_STATUS_CODES[@status]])
|
7
7
|
@status_sent = true
|
@@ -10,7 +10,18 @@ class Mongrel::HttpResponse
|
|
10
10
|
end
|
11
11
|
|
12
12
|
class MerbHandler < Mongrel::HttpHandler
|
13
|
-
@@file_only_methods
|
13
|
+
@@file_only_methods = ["GET","HEAD"]
|
14
|
+
@@path_prefix = nil
|
15
|
+
@@path_prefix_original = nil
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# Use :path_prefix in merb.yml to set this
|
19
|
+
def path_prefix() @@path_prefix_original end
|
20
|
+
def path_prefix=(prefix)
|
21
|
+
@@path_prefix_original = prefix
|
22
|
+
@@path_prefix = (prefix.is_a?(String) ? /^#{prefix.escape_regexp}/ : prefix)
|
23
|
+
end
|
24
|
+
end
|
14
25
|
|
15
26
|
# Take the name of a directory and use that as the doc root or public
|
16
27
|
# directory of your site. This is set to the root of your merb app + '/public'
|
@@ -28,58 +39,59 @@ class MerbHandler < Mongrel::HttpHandler
|
|
28
39
|
# into Merb::RouteMatcher to let it decide which controller and method will
|
29
40
|
# serve the request.
|
30
41
|
# 4. After the controller has done its thing, we check for the X-SENDFILE
|
31
|
-
# header. if you set this header to the path
|
42
|
+
# header. if you set this header to the path of a file in your controller
|
32
43
|
# then mongrel will serve the file directly and your controller can go on
|
33
44
|
# processing other requests.
|
34
|
-
def process(request, response)
|
35
|
-
|
36
|
-
start = Time.now
|
45
|
+
def process(request, response)
|
46
|
+
start = Time.now
|
37
47
|
benchmarks = {}
|
38
48
|
|
39
|
-
if response.socket.closed?
|
40
|
-
return
|
41
|
-
end
|
49
|
+
return if response.socket.closed?
|
42
50
|
|
43
|
-
MERB_LOGGER.info("\nRequest: REQUEST_URI: #{
|
51
|
+
MERB_LOGGER.info("\nRequest: REQUEST_URI: #{
|
52
|
+
request.params[Mongrel::Const::REQUEST_URI]} (#{Time.now.strftime("%Y-%m-%d %H:%M:%S")})")
|
53
|
+
|
54
|
+
# Truncate the request URI if there's a path prefix so that an app can be
|
55
|
+
# hosted inside a subdirectory, for example.
|
56
|
+
if @@path_prefix
|
57
|
+
if request.params[Mongrel::Const::PATH_INFO] =~ @@path_prefix
|
58
|
+
MERB_LOGGER.info("Path prefix #{@@path_prefix.inspect} removed from PATH_INFO and REQUEST_URI.")
|
59
|
+
request.params[Mongrel::Const::PATH_INFO].sub!(@@path_prefix, '')
|
60
|
+
request.params[Mongrel::Const::REQUEST_URI].sub!(@@path_prefix, '')
|
61
|
+
path_info = request.params[Mongrel::Const::PATH_INFO]
|
62
|
+
else
|
63
|
+
raise "Path prefix is set to '#{@@path_prefix.inspect}', but is not in the REQUEST_URI. "
|
64
|
+
end
|
65
|
+
else
|
66
|
+
path_info = request.params[Mongrel::Const::PATH_INFO]
|
67
|
+
end
|
44
68
|
|
45
69
|
# Rails style page caching. Check the public dir first for .html pages and
|
46
70
|
# serve directly. Otherwise fall back to Merb routing and request
|
47
71
|
# dispatching.
|
48
|
-
path_info = request.params[Mongrel::Const::PATH_INFO]
|
49
72
|
page_cached = path_info + ".html"
|
50
73
|
get_or_head = @@file_only_methods.include? request.params[Mongrel::Const::REQUEST_METHOD]
|
51
74
|
|
52
|
-
if get_or_head
|
75
|
+
if get_or_head && @files.can_serve(path_info)
|
53
76
|
# File exists as-is so serve it up
|
54
77
|
MERB_LOGGER.info("Serving static file: #{path_info}")
|
55
78
|
@files.process(request,response)
|
56
|
-
elsif get_or_head
|
79
|
+
elsif get_or_head && @files.can_serve(page_cached)
|
57
80
|
# Possible cached page, serve it up
|
58
81
|
MERB_LOGGER.info("Serving static file: #{page_cached}")
|
59
82
|
request.params[Mongrel::Const::PATH_INFO] = page_cached
|
60
83
|
@files.process(request,response)
|
61
84
|
else
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
85
|
+
# Let Merb:Dispatcher find the route and call the filter chain and action
|
86
|
+
controller, action = Merb::Dispatcher.handle(request, response)
|
87
|
+
benchmarks.merge!(controller._benchmarks)
|
88
|
+
benchmarks[:controller] = controller.class.to_s
|
89
|
+
benchmarks[:action] = action
|
66
90
|
|
67
|
-
|
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")
|
72
|
-
rescue Object => e
|
73
|
-
response.start(500) do |head,out|
|
74
|
-
head["Content-Type"] = "text/html"
|
75
|
-
MERB_LOGGER.info(Merb.exception(e))
|
76
|
-
out << Merb.html_exception(e)
|
77
|
-
end
|
78
|
-
return
|
79
|
-
end
|
91
|
+
MERB_LOGGER.info("Routing to controller: #{controller.class} action: #{action}\nRoute Recognition & Parsing HTTP Input took: #{benchmarks[:setup_time]} seconds")
|
80
92
|
|
81
93
|
sendfile, clength = nil
|
82
|
-
response.status
|
94
|
+
response.status = controller.status
|
83
95
|
# Check for the X-SENDFILE header from your Merb::Controller and serve
|
84
96
|
# the file directly instead of buffering.
|
85
97
|
controller.headers.each do |k, v|
|
@@ -96,8 +108,9 @@ class MerbHandler < Mongrel::HttpHandler
|
|
96
108
|
|
97
109
|
if sendfile
|
98
110
|
benchmarks[:sendfile_time] = Time.now - start
|
99
|
-
MERB_LOGGER.info("X-SENDFILE: #{sendfile}\nComplete Request took: #{
|
100
|
-
|
111
|
+
MERB_LOGGER.info("X-SENDFILE: #{sendfile}\nComplete Request took: #{
|
112
|
+
benchmarks[:sendfile_time]} seconds")
|
113
|
+
file_status = File.stat(sendfile)
|
101
114
|
response.status = 200
|
102
115
|
# Set the last modified times as well and etag for all files
|
103
116
|
response.header[Mongrel::Const::LAST_MODIFIED] = file_status.mtime.httpdate
|
@@ -129,9 +142,17 @@ class MerbHandler < Mongrel::HttpHandler
|
|
129
142
|
total_request_time = Time.now - start
|
130
143
|
benchmarks[:total_request_time] = total_request_time
|
131
144
|
|
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")
|
145
|
+
MERB_LOGGER.info("Request Times: #{benchmarks.inspect}\nResponse status: #{response.status}\nComplete Request took: #{total_request_time} seconds, #{1.0/total_request_time} Requests/Second\n\n")
|
134
146
|
end
|
135
|
-
|
136
|
-
|
137
|
-
|
147
|
+
rescue Object => e
|
148
|
+
# if an exception is raised here then something is
|
149
|
+
# wrong with the dispatcher code so we shouldn't pass the
|
150
|
+
# exception back in or we might end up in a loop
|
151
|
+
response.send_status(500)
|
152
|
+
response.send_header
|
153
|
+
response.write("500 Internal Server Error")
|
154
|
+
MERB_LOGGER.error(Merb.exception(e))
|
155
|
+
ensure
|
156
|
+
MERB_LOGGER.flush
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Merb
|
2
|
+
class PartController < AbstractController
|
3
|
+
self._template_root = File.expand_path(self._template_root / "../app/parts/views")
|
4
|
+
include Merb::WebControllerMixin
|
5
|
+
|
6
|
+
def initialize(web_controller)
|
7
|
+
@web_controller = web_controller
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def dispatch(action=:to_s)
|
12
|
+
old_action = params[:action]
|
13
|
+
params[:action] = action
|
14
|
+
super(action)
|
15
|
+
params[:action] = old_action
|
16
|
+
@_body
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/merb/plugins.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Merb
|
2
|
+
module Plugins
|
3
|
+
def self.config
|
4
|
+
@config ||= File.exists?(MERB_ROOT / "config" / "plugins.yml") ? YAML.load(File.read(MERB_ROOT / "config" / "plugins.yml")) || {} : {}
|
5
|
+
end
|
6
|
+
|
7
|
+
@rakefiles = []
|
8
|
+
def self.rakefiles
|
9
|
+
@rakefiles
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.add_rakefiles(*rakefiles)
|
13
|
+
@rakefiles += rakefiles
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Merb
|
2
|
+
module Rack
|
3
|
+
|
4
|
+
class RequestWrapper
|
5
|
+
def initialize(env)
|
6
|
+
@env = env
|
7
|
+
end
|
8
|
+
|
9
|
+
def params
|
10
|
+
@env
|
11
|
+
end
|
12
|
+
|
13
|
+
def body
|
14
|
+
@env['rack.input']
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Adapter
|
19
|
+
def call(env)
|
20
|
+
env["PATH_INFO"] ||= ""
|
21
|
+
env["SCRIPT_NAME"] ||= ""
|
22
|
+
if env["REQUEST_URI"] =~ %r{(https?://)[^/](.*)}
|
23
|
+
env["REQUEST_URI"] = $2
|
24
|
+
end
|
25
|
+
request = RequestWrapper.new(env)
|
26
|
+
response = StringIO.new
|
27
|
+
begin
|
28
|
+
controller, action = ::Merb::Dispatcher.handle(request, response)
|
29
|
+
rescue Object => e
|
30
|
+
return [500, {"Content-Type"=>"text/html"}, "Internal Server Error"]
|
31
|
+
end
|
32
|
+
|
33
|
+
[controller.status, controller.headers, controller.body]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/merb/request.rb
ADDED
@@ -0,0 +1,421 @@
|
|
1
|
+
module Merb
|
2
|
+
class Request
|
3
|
+
attr_accessor :env, :session
|
4
|
+
|
5
|
+
# by setting these to false, auto-parsing is disabled; this way you can do your own parsing instead
|
6
|
+
cattr_accessor :parse_multipart_params, :parse_json_params, :parse_xml_params
|
7
|
+
self.parse_multipart_params = true
|
8
|
+
self.parse_json_params = true
|
9
|
+
self.parse_xml_params = true
|
10
|
+
|
11
|
+
def initialize(http_request)
|
12
|
+
@env = http_request.params
|
13
|
+
@body = http_request.body
|
14
|
+
end
|
15
|
+
|
16
|
+
METHODS = %w{get post put delete head}
|
17
|
+
|
18
|
+
def method
|
19
|
+
@method ||= begin
|
20
|
+
request_method = @env['REQUEST_METHOD'].downcase.to_sym
|
21
|
+
case request_method
|
22
|
+
when :get, :head, :put, :delete
|
23
|
+
request_method
|
24
|
+
when :post
|
25
|
+
m = body_and_query_params['_method']
|
26
|
+
m.downcase! if m
|
27
|
+
METHODS.include?(m) ? m.to_sym : :post
|
28
|
+
else
|
29
|
+
raise "Unknown REQUEST_METHOD: #{@env['REQUEST_METHOD']}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# create predicate methods for querying the REQUEST_METHOD
|
35
|
+
# get? post? head? put? etc
|
36
|
+
METHODS.each do |m|
|
37
|
+
define_method("#{m}?") { method == m.to_sym }
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# FIXME: symbolize_keys on params is a potential problem. symbols are
|
43
|
+
# not garbage collected so a malicious user could send many large query
|
44
|
+
# keys to Merb forcing it to eat up memeory.
|
45
|
+
|
46
|
+
# A hash of parameters passed from the URL like ?blah=hello
|
47
|
+
def query_params
|
48
|
+
@query_params ||= self.class.query_parse(query_string || '')
|
49
|
+
end
|
50
|
+
|
51
|
+
# A hash of parameters passed in the body of the request.
|
52
|
+
#
|
53
|
+
# Ajax calls from prototype.js and other libraries
|
54
|
+
# pass content this way.
|
55
|
+
def body_params
|
56
|
+
@body_params ||= begin
|
57
|
+
if content_type && content_type.match(Merb::Const::FORM_URL_ENCODED_REGEXP) # or content_type.nil?
|
58
|
+
self.class.query_parse(raw_post)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def body_and_query_params
|
64
|
+
# ^-- FIXME a better name for this method
|
65
|
+
@body_and_query_params ||= begin
|
66
|
+
h = query_params
|
67
|
+
h.merge!(body_params) if body_params
|
68
|
+
h
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def multipart_params
|
73
|
+
@multipart_params ||= begin
|
74
|
+
if Merb::Const::MULTIPART_REGEXP =~ content_type
|
75
|
+
self.class.parse_multipart(@body, $1, content_length)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def json_params
|
81
|
+
@json_params ||= begin
|
82
|
+
if [Merb::Const::APPLICATION_JSON, Merb::Const::TEXT_JSON].include?(content_type)
|
83
|
+
JSON.parse(raw_post)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def xml_params
|
89
|
+
@xml_params ||= begin
|
90
|
+
if [Merb::Const::APPLICATION_XML, Merb::Const::TEXT_XML].include?(content_type)
|
91
|
+
Hash.from_xml(raw_post) rescue Mash.new
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
public
|
97
|
+
|
98
|
+
def params
|
99
|
+
@params ||= begin
|
100
|
+
h = route_params.to_mash.merge(body_and_query_params)
|
101
|
+
h.merge!(multipart_params) if self.class.parse_multipart_params && multipart_params
|
102
|
+
h.merge!(json_params) if self.class.parse_json_params && json_params
|
103
|
+
h.merge!(xml_params) if self.class.parse_xml_params && xml_params
|
104
|
+
h
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def cookies
|
109
|
+
@cookies ||= self.class.query_parse(@env[Merb::Const::HTTP_COOKIE], ';,')
|
110
|
+
end
|
111
|
+
|
112
|
+
def route
|
113
|
+
@route ||= Merb::Router.routes[route_index]
|
114
|
+
end
|
115
|
+
|
116
|
+
# returns two objects, route_index and route_params
|
117
|
+
def route_match
|
118
|
+
@route_match ||= Merb::Router.match(self, body_and_query_params)
|
119
|
+
end
|
120
|
+
private :route_match
|
121
|
+
|
122
|
+
def route_index
|
123
|
+
route_match.first
|
124
|
+
end
|
125
|
+
|
126
|
+
def route_params
|
127
|
+
route_match.last
|
128
|
+
end
|
129
|
+
|
130
|
+
def controller_name
|
131
|
+
route_params[:controller]
|
132
|
+
end
|
133
|
+
|
134
|
+
def controller_class
|
135
|
+
begin
|
136
|
+
cnt = controller_name.to_const_string
|
137
|
+
rescue ::String::InvalidPathConversion
|
138
|
+
raise ControllerExceptions::NotFound
|
139
|
+
end
|
140
|
+
if !Controller._subclasses.include?(cnt)
|
141
|
+
raise ControllerExceptions::NotFound, "Controller '#{cnt}' not found"
|
142
|
+
end
|
143
|
+
|
144
|
+
begin
|
145
|
+
if cnt == "Application"
|
146
|
+
raise ControllerExceptions::NotFound, "The 'Application' controller has no public actions"
|
147
|
+
end
|
148
|
+
return Object.full_const_get(cnt)
|
149
|
+
rescue NameError
|
150
|
+
raise ControllerExceptions::NotFound
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def action
|
155
|
+
route_params[:action]
|
156
|
+
end
|
157
|
+
|
158
|
+
def raw_post
|
159
|
+
@body.rewind
|
160
|
+
@body.read
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns true if the request is an Ajax request.
|
164
|
+
#
|
165
|
+
# Also aliased as the more memorable ajax? and xhr?.
|
166
|
+
def xml_http_request?
|
167
|
+
not /XMLHttpRequest/i.match(@env['HTTP_X_REQUESTED_WITH']).nil?
|
168
|
+
end
|
169
|
+
alias xhr? :xml_http_request?
|
170
|
+
alias ajax? :xml_http_request?
|
171
|
+
|
172
|
+
# returns the remote IP address if it can find it.
|
173
|
+
def remote_ip
|
174
|
+
return @env['HTTP_CLIENT_IP'] if @env.include?('HTTP_CLIENT_IP')
|
175
|
+
|
176
|
+
if @env.include?(Merb::Const::HTTP_X_FORWARDED_FOR) then
|
177
|
+
remote_ips = @env[Merb::Const::HTTP_X_FORWARDED_FOR].split(',').reject do |ip|
|
178
|
+
ip =~ /^unknown$|^(127|10|172\.16|192\.168)\./i
|
179
|
+
end
|
180
|
+
|
181
|
+
return remote_ips.first.strip unless remote_ips.empty?
|
182
|
+
end
|
183
|
+
|
184
|
+
return @env[Merb::Const::REMOTE_ADDR]
|
185
|
+
end
|
186
|
+
|
187
|
+
# returns either 'https://' or 'http://' depending on
|
188
|
+
# the HTTPS header
|
189
|
+
def protocol
|
190
|
+
ssl? ? 'https://' : 'http://'
|
191
|
+
end
|
192
|
+
|
193
|
+
# returns true if the request is an SSL request
|
194
|
+
def ssl?
|
195
|
+
@env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
|
196
|
+
end
|
197
|
+
|
198
|
+
# returns the request HTTP_REFERER.
|
199
|
+
def referer
|
200
|
+
@env['HTTP_REFERER']
|
201
|
+
end
|
202
|
+
|
203
|
+
# returns he request uri.
|
204
|
+
def uri
|
205
|
+
@env['REQUEST_URI']
|
206
|
+
end
|
207
|
+
|
208
|
+
def user_agent
|
209
|
+
@env['HTTP_USER_AGENT']
|
210
|
+
end
|
211
|
+
|
212
|
+
def server_name
|
213
|
+
@env['SERVER_NAME']
|
214
|
+
end
|
215
|
+
|
216
|
+
def accept_encoding
|
217
|
+
@env['HTTP_ACCEPT_ENCODING']
|
218
|
+
end
|
219
|
+
|
220
|
+
def script_name
|
221
|
+
@env['SCRIPT_NAME']
|
222
|
+
end
|
223
|
+
|
224
|
+
def cache_control
|
225
|
+
@env['HTTP_CACHE_CONTROL']
|
226
|
+
end
|
227
|
+
|
228
|
+
def accept_language
|
229
|
+
@env['HTTP_ACCEPT_LANGUAGE']
|
230
|
+
end
|
231
|
+
|
232
|
+
def host
|
233
|
+
@env['HTTP_HOST']
|
234
|
+
end
|
235
|
+
|
236
|
+
def server_software
|
237
|
+
@env['SERVER_SOFTWARE']
|
238
|
+
end
|
239
|
+
|
240
|
+
def keep_alive
|
241
|
+
@env['HTTP_KEEP_ALIVE']
|
242
|
+
end
|
243
|
+
|
244
|
+
def accept_charset
|
245
|
+
@env['HTTP_ACCEPT_CHARSET']
|
246
|
+
end
|
247
|
+
|
248
|
+
def version
|
249
|
+
@env['HTTP_VERSION']
|
250
|
+
end
|
251
|
+
|
252
|
+
def gateway
|
253
|
+
@env['GATEWAY_INTERFACE']
|
254
|
+
end
|
255
|
+
|
256
|
+
def accept
|
257
|
+
@env['HTTP_ACCEPT']
|
258
|
+
end
|
259
|
+
|
260
|
+
def connection
|
261
|
+
@env['HTTP_CONNECTION']
|
262
|
+
end
|
263
|
+
|
264
|
+
def query_string
|
265
|
+
@env['QUERY_STRING']
|
266
|
+
end
|
267
|
+
|
268
|
+
def content_type
|
269
|
+
@env['CONTENT_TYPE']
|
270
|
+
end
|
271
|
+
|
272
|
+
def content_length
|
273
|
+
@content_length ||= @env[Merb::Const::CONTENT_LENGTH].to_i
|
274
|
+
end
|
275
|
+
|
276
|
+
# Returns the uri without the query string. Strips trailing '/' and reduces
|
277
|
+
# duplicate '/' to a single '/'
|
278
|
+
def path
|
279
|
+
path = (uri ? uri.split('?').first : '').sub(/\/+/, '/')
|
280
|
+
path = path[0..-2] if (path[-1] == ?/) && path.size > 1
|
281
|
+
path
|
282
|
+
end
|
283
|
+
|
284
|
+
# returns the PATH_INFO
|
285
|
+
def path_info
|
286
|
+
@path_info ||= Mongrel::HttpRequest.unescape(@env['PATH_INFO'])
|
287
|
+
end
|
288
|
+
|
289
|
+
# returns the port the server is running on
|
290
|
+
def port
|
291
|
+
@env['SERVER_PORT'].to_i
|
292
|
+
end
|
293
|
+
|
294
|
+
# returns the full hostname including port
|
295
|
+
def host
|
296
|
+
@env['HTTP_X_FORWARDED_HOST'] || @env['HTTP_HOST']
|
297
|
+
end
|
298
|
+
|
299
|
+
# returns an array of all the subdomain parts of the host.
|
300
|
+
def subdomains(tld_length = 1)
|
301
|
+
parts = host.split('.')
|
302
|
+
parts[0..-(tld_length+2)]
|
303
|
+
end
|
304
|
+
|
305
|
+
# returns the full domain name without the port number.
|
306
|
+
def domain(tld_length = 1)
|
307
|
+
host.split('.').last(1 + tld_length).join('.').sub(/:\d+$/,'')
|
308
|
+
end
|
309
|
+
|
310
|
+
class << self
|
311
|
+
# parses a query string or the payload of a POST
|
312
|
+
# request into the params hash. So for example:
|
313
|
+
# /foo?bar=nik&post[title]=heya&post[body]=whatever
|
314
|
+
# parses into:
|
315
|
+
# {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever'}}
|
316
|
+
def query_parse(qs, d = '&;')
|
317
|
+
(qs||'').split(/[#{d}] */n).inject({}) { |h,p|
|
318
|
+
normalize_params(h, *Mongrel::HttpRequest.unescape(p).split('=',2))
|
319
|
+
}
|
320
|
+
end
|
321
|
+
|
322
|
+
NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
|
323
|
+
CONTENT_TYPE_REGEX = /Content-Type: (.*)\r\n/ni.freeze
|
324
|
+
FILENAME_REGEX = /Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
|
325
|
+
CRLF = "\r\n".freeze
|
326
|
+
EOL = CRLF
|
327
|
+
def parse_multipart(request,boundary, content_length)
|
328
|
+
boundary = "--#{boundary}"
|
329
|
+
paramhsh = {}
|
330
|
+
buf = ""
|
331
|
+
input = request
|
332
|
+
input.binmode if defined? input.binmode
|
333
|
+
boundary_size = boundary.size + EOL.size
|
334
|
+
bufsize = 16384
|
335
|
+
content_length -= boundary_size
|
336
|
+
status = input.read(boundary_size)
|
337
|
+
raise EOFError, "bad content body" unless status == boundary + EOL
|
338
|
+
rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
|
339
|
+
loop {
|
340
|
+
head = nil
|
341
|
+
body = ''
|
342
|
+
filename = content_type = name = nil
|
343
|
+
read_size = 0
|
344
|
+
until head && buf =~ rx
|
345
|
+
i = buf.index("\r\n\r\n")
|
346
|
+
if( i == nil && read_size == 0 && content_length == 0 )
|
347
|
+
content_length = -1
|
348
|
+
break
|
349
|
+
end
|
350
|
+
if !head && i
|
351
|
+
head = buf.slice!(0, i+2) # First \r\n
|
352
|
+
buf.slice!(0, 2) # Second \r\n
|
353
|
+
filename = head[FILENAME_REGEX, 1]
|
354
|
+
content_type = head[CONTENT_TYPE_REGEX, 1]
|
355
|
+
name = head[NAME_REGEX, 1]
|
356
|
+
|
357
|
+
if filename && !filename.empty?
|
358
|
+
body = Tempfile.new(:Merb)
|
359
|
+
body.binmode if defined? body.binmode
|
360
|
+
end
|
361
|
+
next
|
362
|
+
end
|
363
|
+
|
364
|
+
# Save the read body part.
|
365
|
+
if head && (boundary_size+4 < buf.size)
|
366
|
+
body << buf.slice!(0, buf.size - (boundary_size+4))
|
367
|
+
end
|
368
|
+
|
369
|
+
read_size = bufsize < content_length ? bufsize : content_length
|
370
|
+
if( read_size > 0 )
|
371
|
+
c = input.read(read_size)
|
372
|
+
raise EOFError, "bad content body" if c.nil? || c.empty?
|
373
|
+
buf << c
|
374
|
+
content_length -= c.size
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
# Save the rest.
|
379
|
+
if i = buf.index(rx)
|
380
|
+
body << buf.slice!(0, i)
|
381
|
+
buf.slice!(0, boundary_size+2)
|
382
|
+
|
383
|
+
content_length = -1 if $1 == "--"
|
384
|
+
end
|
385
|
+
|
386
|
+
if filename && !filename.empty?
|
387
|
+
body.rewind
|
388
|
+
data = {
|
389
|
+
:filename => File.basename(filename),
|
390
|
+
:content_type => content_type,
|
391
|
+
:tempfile => body,
|
392
|
+
:size => File.size(body)
|
393
|
+
}
|
394
|
+
else
|
395
|
+
data = body
|
396
|
+
end
|
397
|
+
paramhsh = normalize_params(paramhsh,name,data)
|
398
|
+
break if buf.empty? || content_length == -1
|
399
|
+
}
|
400
|
+
paramhsh
|
401
|
+
end
|
402
|
+
|
403
|
+
def normalize_params(parms, name, val=nil)
|
404
|
+
name =~ %r([\[\]]*([^\[\]]+)\]*)
|
405
|
+
key = $1 || ''
|
406
|
+
after = $' || ''
|
407
|
+
|
408
|
+
if after == ""
|
409
|
+
parms[key] = val
|
410
|
+
elsif after == "[]"
|
411
|
+
(parms[key] ||= []) << val
|
412
|
+
else
|
413
|
+
parms[key] ||= {}
|
414
|
+
parms[key] = normalize_params(parms[key], after, val)
|
415
|
+
end
|
416
|
+
parms
|
417
|
+
end
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|