merb 0.3.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +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
|
+
|