merb-core 0.9.3 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (178) hide show
  1. data/LICENSE +1 -1
  2. data/README +3 -3
  3. data/Rakefile +144 -33
  4. data/bin/merb +0 -0
  5. data/bin/merb-specs +0 -0
  6. data/docs/bootloading.dox +1 -0
  7. data/docs/merb-core-call-stack-diagram.mmap +0 -0
  8. data/docs/merb-core-call-stack-diagram.pdf +0 -0
  9. data/docs/merb-core-call-stack-diagram.png +0 -0
  10. data/lib/merb-core.rb +159 -37
  11. data/lib/merb-core/autoload.rb +1 -0
  12. data/lib/merb-core/bootloader.rb +208 -92
  13. data/lib/merb-core/config.rb +20 -6
  14. data/lib/merb-core/controller/abstract_controller.rb +113 -61
  15. data/lib/merb-core/controller/exceptions.rb +28 -13
  16. data/lib/merb-core/controller/merb_controller.rb +73 -44
  17. data/lib/merb-core/controller/mime.rb +25 -7
  18. data/lib/merb-core/controller/mixins/authentication.rb +1 -1
  19. data/lib/merb-core/controller/mixins/controller.rb +44 -8
  20. data/lib/merb-core/controller/mixins/render.rb +191 -128
  21. data/lib/merb-core/controller/mixins/responder.rb +65 -63
  22. data/lib/merb-core/controller/template.rb +103 -54
  23. data/lib/merb-core/core_ext.rb +7 -12
  24. data/lib/merb-core/core_ext/kernel.rb +128 -136
  25. data/lib/merb-core/dispatch/cookies.rb +26 -4
  26. data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
  27. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +198 -0
  28. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
  29. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +92 -0
  30. data/lib/merb-core/dispatch/dispatcher.rb +156 -224
  31. data/lib/merb-core/dispatch/request.rb +126 -25
  32. data/lib/merb-core/dispatch/router.rb +61 -6
  33. data/lib/merb-core/dispatch/router/behavior.rb +122 -41
  34. data/lib/merb-core/dispatch/router/route.rb +147 -22
  35. data/lib/merb-core/dispatch/session.rb +52 -2
  36. data/lib/merb-core/dispatch/session/cookie.rb +4 -2
  37. data/lib/merb-core/dispatch/session/memcached.rb +38 -27
  38. data/lib/merb-core/dispatch/session/memory.rb +18 -11
  39. data/lib/merb-core/dispatch/worker.rb +28 -0
  40. data/lib/merb-core/gem_ext/erubis.rb +58 -0
  41. data/lib/merb-core/logger.rb +3 -31
  42. data/lib/merb-core/plugins.rb +25 -3
  43. data/lib/merb-core/rack.rb +18 -12
  44. data/lib/merb-core/rack/adapter.rb +10 -8
  45. data/lib/merb-core/rack/adapter/ebb.rb +2 -2
  46. data/lib/merb-core/rack/adapter/irb.rb +31 -21
  47. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
  48. data/lib/merb-core/rack/adapter/thin.rb +19 -9
  49. data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
  50. data/lib/merb-core/rack/application.rb +9 -84
  51. data/lib/merb-core/rack/middleware.rb +26 -0
  52. data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
  53. data/lib/merb-core/rack/middleware/profiler.rb +19 -0
  54. data/lib/merb-core/rack/middleware/static.rb +45 -0
  55. data/lib/merb-core/server.rb +27 -9
  56. data/lib/merb-core/tasks/audit.rake +68 -0
  57. data/lib/merb-core/tasks/merb.rb +1 -0
  58. data/lib/merb-core/tasks/merb_rake_helper.rb +12 -0
  59. data/lib/merb-core/tasks/stats.rake +71 -0
  60. data/lib/merb-core/test.rb +2 -1
  61. data/lib/merb-core/test/helpers/multipart_request_helper.rb +3 -3
  62. data/lib/merb-core/test/helpers/request_helper.rb +66 -24
  63. data/lib/merb-core/test/matchers/controller_matchers.rb +36 -4
  64. data/lib/merb-core/test/matchers/route_matchers.rb +12 -3
  65. data/lib/merb-core/test/matchers/view_matchers.rb +3 -3
  66. data/lib/merb-core/test/run_specs.rb +1 -0
  67. data/lib/merb-core/test/tasks/spectasks.rb +13 -5
  68. data/lib/merb-core/test/test_ext/string.rb +14 -0
  69. data/lib/merb-core/vendor/facets/dictionary.rb +3 -3
  70. data/lib/merb-core/vendor/facets/inflect.rb +82 -37
  71. data/lib/merb-core/version.rb +2 -2
  72. data/spec/private/config/config_spec.rb +39 -4
  73. data/spec/private/core_ext/kernel_spec.rb +3 -14
  74. data/spec/private/dispatch/bootloader_spec.rb +1 -1
  75. data/spec/private/dispatch/cookies_spec.rb +181 -69
  76. data/spec/private/dispatch/fixture/app/controllers/exceptions.rb +0 -2
  77. data/spec/private/dispatch/fixture/app/controllers/foo.rb +0 -2
  78. data/spec/private/dispatch/fixture/config/rack.rb +10 -0
  79. data/spec/private/dispatch/fixture/log/merb_test.log +7054 -1802
  80. data/spec/private/dispatch/route_params_spec.rb +2 -3
  81. data/spec/private/dispatch/session_mixin_spec.rb +47 -0
  82. data/spec/private/plugins/plugin_spec.rb +73 -59
  83. data/spec/private/router/behavior_spec.rb +60 -0
  84. data/spec/private/router/fixture/log/merb_test.log +1693 -0
  85. data/spec/private/router/route_spec.rb +414 -0
  86. data/spec/private/router/router_spec.rb +175 -0
  87. data/spec/private/vendor/facets/plural_spec.rb +564 -0
  88. data/spec/private/vendor/facets/singular_spec.rb +489 -0
  89. data/spec/public/abstract_controller/controllers/cousins.rb +41 -0
  90. data/spec/public/abstract_controller/controllers/helpers.rb +12 -2
  91. data/spec/public/abstract_controller/controllers/partial.rb +17 -2
  92. data/spec/public/abstract_controller/controllers/render.rb +16 -1
  93. data/spec/public/abstract_controller/controllers/views/helpers/capture_eq/index.erb +1 -0
  94. data/spec/public/abstract_controller/controllers/views/helpers/capture_with_args/index.erb +1 -0
  95. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_two_throw_contents/index.erb +1 -0
  96. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/_collection.erb +1 -0
  97. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/index.erb +1 -0
  98. data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/_partial.erb +1 -0
  99. data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/index.erb +1 -0
  100. data/spec/public/abstract_controller/filter_spec.rb +20 -1
  101. data/spec/public/abstract_controller/helper_spec.rb +10 -2
  102. data/spec/public/abstract_controller/partial_spec.rb +8 -0
  103. data/spec/public/abstract_controller/render_spec.rb +8 -0
  104. data/spec/public/abstract_controller/spec_helper.rb +7 -3
  105. data/spec/public/boot_loader/boot_loader_spec.rb +2 -2
  106. data/spec/public/controller/base_spec.rb +10 -2
  107. data/spec/public/controller/config/init.rb +6 -0
  108. data/spec/public/controller/controllers/authentication.rb +9 -11
  109. data/spec/public/controller/controllers/base.rb +2 -8
  110. data/spec/public/controller/controllers/cookies.rb +16 -0
  111. data/spec/public/controller/controllers/dispatcher.rb +35 -0
  112. data/spec/public/controller/controllers/display.rb +62 -14
  113. data/spec/public/controller/controllers/redirect.rb +36 -0
  114. data/spec/public/controller/controllers/responder.rb +37 -11
  115. data/spec/public/controller/controllers/views/layout/custom_arg.json.erb +1 -0
  116. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.html.erb +1 -0
  117. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.xml.erb +1 -0
  118. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/no_layout.html.erb +1 -0
  119. data/spec/public/controller/cookies_spec.rb +23 -0
  120. data/spec/public/controller/dispatcher_spec.rb +411 -0
  121. data/spec/public/controller/display_spec.rb +43 -10
  122. data/spec/public/controller/redirect_spec.rb +33 -0
  123. data/spec/public/controller/responder_spec.rb +79 -11
  124. data/spec/public/controller/spec_helper.rb +3 -1
  125. data/spec/public/controller/url_spec.rb +10 -0
  126. data/spec/public/core/merb_core_spec.rb +11 -0
  127. data/spec/public/core_ext/fixtures/core_ext_dependency.rb +2 -0
  128. data/spec/public/core_ext/kernel_spec.rb +9 -0
  129. data/spec/public/core_ext/spec_helper.rb +1 -0
  130. data/spec/public/directory_structure/directory/log/merb_test.log +3729 -272
  131. data/spec/public/directory_structure/directory_spec.rb +3 -4
  132. data/spec/public/logger/logger_spec.rb +4 -4
  133. data/spec/public/reloading/directory/log/merb_test.log +288066 -15
  134. data/spec/public/reloading/reload_spec.rb +49 -27
  135. data/spec/public/request/multipart_spec.rb +26 -0
  136. data/spec/public/request/request_spec.rb +21 -2
  137. data/spec/public/router/fixation_spec.rb +27 -0
  138. data/spec/public/router/fixture/log/merb_test.log +30050 -0
  139. data/spec/public/router/nested_matches_spec.rb +97 -0
  140. data/spec/public/router/resource_spec.rb +1 -9
  141. data/spec/public/router/resources_spec.rb +0 -20
  142. data/spec/public/router/spec_helper.rb +27 -9
  143. data/spec/public/router/special_spec.rb +21 -8
  144. data/spec/public/template/template_spec.rb +17 -5
  145. data/spec/public/test/controller_matchers_spec.rb +10 -0
  146. data/spec/public/test/request_helper_spec.rb +29 -0
  147. data/spec/public/test/route_helper_spec.rb +18 -1
  148. data/spec/public/test/route_matchers_spec.rb +28 -1
  149. data/spec/public/test/view_matchers_spec.rb +3 -3
  150. data/spec/spec_helper.rb +56 -12
  151. metadata +89 -47
  152. data/lib/merb-core/core_ext/class.rb +0 -299
  153. data/lib/merb-core/core_ext/hash.rb +0 -426
  154. data/lib/merb-core/core_ext/mash.rb +0 -154
  155. data/lib/merb-core/core_ext/object.rb +0 -147
  156. data/lib/merb-core/core_ext/object_space.rb +0 -14
  157. data/lib/merb-core/core_ext/rubygems.rb +0 -28
  158. data/lib/merb-core/core_ext/set.rb +0 -46
  159. data/lib/merb-core/core_ext/string.rb +0 -89
  160. data/lib/merb-core/core_ext/time.rb +0 -13
  161. data/lib/merb-core/dispatch/exceptions.html.erb +0 -297
  162. data/spec/private/core_ext/class_spec.rb +0 -22
  163. data/spec/private/core_ext/hash_spec.rb +0 -522
  164. data/spec/private/core_ext/object_spec.rb +0 -121
  165. data/spec/private/core_ext/set_spec.rb +0 -26
  166. data/spec/private/core_ext/string_spec.rb +0 -167
  167. data/spec/private/core_ext/time_spec.rb +0 -16
  168. data/spec/private/dispatch/dispatch_spec.rb +0 -26
  169. data/spec/private/dispatch/fixture/log/development.log +0 -1
  170. data/spec/private/dispatch/fixture/log/merb.4000.pid +0 -1
  171. data/spec/private/dispatch/fixture/log/production.log +0 -1
  172. data/spec/private/dispatch/fixture/merb.4000.pid +0 -1
  173. data/spec/private/rack/application_spec.rb +0 -43
  174. data/spec/public/controller/log/merb.4000.pid +0 -1
  175. data/spec/public/directory_structure/directory/log/merb.4000.pid +0 -1
  176. data/spec/public/directory_structure/directory/merb.4000.pid +0 -1
  177. data/spec/public/reloading/directory/log/merb.4000.pid +0 -1
  178. data/spec/public/reloading/directory/merb.4000.pid +0 -1
@@ -1,241 +1,173 @@
1
- class Merb::Dispatcher
2
- DEFAULT_ERROR_TEMPLATE = File.expand_path(File.dirname(__FILE__) / 'exceptions.html')
3
-
4
- class << self
1
+ require Merb.framework_root / "merb-core" / "dispatch" / "default_exception" / "default_exception"
2
+ module Merb
3
+ class Dispatcher
4
+ class << self
5
+ include Merb::ControllerExceptions
5
6
 
6
- attr_accessor :use_mutex
7
+ attr_accessor :use_mutex
7
8
 
8
- @@mutex = Mutex.new
9
- Merb::Dispatcher.use_mutex = ::Merb::Config[:use_mutex]
9
+ @@mutex = Mutex.new
10
+ @@work_queue = Queue.new
10
11
 
11
- # This is where we grab the incoming request REQUEST_URI and use that in
12
- # the merb RouteMatcher to determine which controller and method to run.
13
- # Returns a 2 element tuple of: [controller, action]
14
- #
15
- # ControllerExceptions are rescued here and redispatched.
16
- #
17
- # ==== Parameters
18
- # rack_env<Rack::Environment>::
19
- # The rack environment, which is used to instantiate a Merb::Request
20
- # response<IO>::
21
- # An IO object to hold the response
22
- #
23
- # ==== Returns
24
- # Array[Merb::Controller, Symbol]::
25
- # An array containing the Merb::Controller and the action that was
26
- # dispatched to.
27
- def handle(rack_env)
28
- start = Time.now
29
- request = Merb::Request.new(rack_env)
30
-
31
- route_index, route_params = Merb::Router.match(request)
32
-
33
- if route_params.empty?
34
- raise ::Merb::ControllerExceptions::NotFound, "No routes match the request, #{request.uri}"
35
- end
36
- request.route_params = route_params
37
- request.params.merge! route_params
38
-
39
- controller_name = (route_params[:namespace] ? route_params[:namespace] + '/' : '') + route_params[:controller]
40
-
41
- unless controller_name
42
- raise Merb::ControllerExceptions::NotFound, "Route matched, but route did not specify a controller"
43
- end
44
-
45
- Merb.logger.debug("Routed to: #{request.route_params.inspect}")
46
-
47
- cnt = controller_name.snake_case.to_const_string
12
+ def work_queue
13
+ @@work_queue
14
+ end
15
+
16
+ Merb::Dispatcher.use_mutex = ::Merb::Config[:use_mutex]
17
+
18
+ # Dispatch the rack environment. ControllerExceptions are rescued here
19
+ # and redispatched.
20
+ #
21
+ # ==== Parameters
22
+ # rack_env<Rack::Environment>::
23
+ # The rack environment, which is used to instantiate a Merb::Request
24
+ #
25
+ # ==== Returns
26
+ # Merb::Controller::
27
+ # The Merb::Controller that was dispatched to
28
+ def handle(request)
29
+ start = Time.now
30
+ Merb.logger.info "Started request handling: #{start.to_s}"
31
+
32
+ request.find_route!
33
+ return redirect(request) if request.redirects?
34
+
35
+ klass = request.controller
36
+ Merb.logger.debug("Routed to: #{request.params.inspect}")
37
+
38
+ unless klass < Controller
39
+ raise NotFound,
40
+ "Controller '#{klass}' not found.\n" <<
41
+ "If Merb tries to look for a controller for static files, " <<
42
+ "you way need to check up your Rackup file, see the Problems " <<
43
+ "section at: http://wiki.merbivore.com/pages/rack-middleware"
44
+ end
48
45
 
49
- if !Merb::Controller._subclasses.include?(cnt)
50
- raise Merb::ControllerExceptions::NotFound, "Controller '#{cnt}' not found"
51
- end
52
- if cnt == "Application"
53
- raise Merb::ControllerExceptions::NotFound, "The 'Application' controller has no public actions"
54
- end
55
-
56
- begin
57
- klass = Object.full_const_get(cnt)
58
- rescue NameError
59
- raise Merb::ControllerExceptions::NotFound
60
- end
61
-
62
- Merb.logger.info("Params: #{klass._filter_params(request.params).inspect}")
63
-
64
- action = route_params[:action]
65
-
66
- if route_index && route = Merb::Router.routes[route_index]
67
- #Fixate the session ID if it is enabled on the route
68
- if route.allow_fixation? && request.params.key?(Merb::Controller._session_id_key)
69
- request.cookies[Merb::Controller._session_id_key] = request.params[Merb::Controller._session_id_key]
46
+ if klass.name == "Application"
47
+ raise NotFound, "The 'Application' controller has no public actions"
70
48
  end
71
- end
72
-
73
- controller = dispatch_action(klass, action, request)
74
- controller._benchmarks[:dispatch_time] = Time.now - start
75
- Merb.logger.info controller._benchmarks.inspect
76
- Merb.logger.flush
77
-
78
- controller
79
- # this is the custom dispatch_exception; it allows failures to still be dispatched
80
- # to the error controller
81
- rescue => exception
82
- Merb.logger.error(Merb.exception(exception))
83
- unless request.xhr?
84
- exception = controller_exception(exception)
85
- dispatch_exception(request, exception)
86
- else
87
- Struct.new(:headers, :status, :body).new({}, 500,
88
- <<-HERE
89
- #{exception.message}
90
-
91
- Params:
92
- #{(request.params || {}).map { |p,v| " #{p}: #{v}\n"}.join("\n")}
93
-
94
- Session:
95
- #{(request.session || {}).map { |p,v| " #{p}: #{v}\n"}.join("\n")}
96
-
97
- Cookies:
98
- #{(request.cookies || {}).map { |p,v| " #{p}: #{v}\n"}.join("\n")}
99
-
100
- Stacktrace:
101
- #{exception.backtrace.join("\n")}
102
- HERE
103
- )
104
- end
105
- end
49
+
50
+ controller = dispatch_action(klass, request.params[:action], request)
51
+ controller._benchmarks[:dispatch_time] = Time.now - start
52
+ Merb.logger.info controller._benchmarks.inspect
53
+ Merb.logger.flush
54
+ controller
55
+ rescue Object => exception
56
+ dispatch_exception(exception, request)
57
+ end
106
58
 
107
- private
108
- # Setup the controller and call the chosen action
109
- #
110
- # ==== Parameters
111
- # klass<Merb::Controller>:: The controller class to dispatch to.
112
- # action<Symbol>:: The action to dispatch.
113
- # request<Merb::Request>::
114
- # The Merb::Request object that was created in #handle
115
- # response<IO>:: The response object passed in from Mongrel
116
- # status<Integer>:: The status code to respond with.
117
- #
118
- # ==== Returns
119
- # Array[Merb::Controller, Symbol]::
120
- # An array containing the Merb::Controller and the action that was
121
- # dispatched to.
122
- def dispatch_action(klass, action, request, status=200)
123
- # build controller
124
- controller = klass.new(request, status)
125
- if use_mutex
126
- @@mutex.synchronize { controller._dispatch(action) }
127
- else
128
- controller._dispatch(action)
59
+ private
60
+ # Setup the controller and call the chosen action
61
+ #
62
+ # ==== Parameters
63
+ # klass<Merb::Controller>:: The controller class to dispatch to.
64
+ # action<Symbol>:: The action to dispatch.
65
+ # request<Merb::Request>::
66
+ # The Merb::Request object that was created in #handle
67
+ # status<Integer>:: The status code to respond with.
68
+ #
69
+ # ==== Returns
70
+ # Merb::Controller::
71
+ # The Merb::Controller that was dispatched to.
72
+ def dispatch_action(klass, action, request, status=200)
73
+ # build controller
74
+ controller = klass.new(request, status)
75
+ if use_mutex
76
+ @@mutex.synchronize { controller._dispatch(action) }
77
+ else
78
+ controller._dispatch(action)
79
+ end
80
+ controller
129
81
  end
130
- controller
131
- end
132
82
 
133
- # Re-route the current request to the Exception controller if it is
134
- # available, and try to render the exception nicely.
135
- #
136
- # You can handle exceptions by implementing actions for specific
137
- # exceptions such as not_found or for entire classes of exceptions
138
- # such as client_error
139
- #
140
- # If it is not available then just render a simple text error.
141
- #
142
- # ==== Parameters
143
- # request<Merb::Request>::
144
- # The request object associated with the failed request.
145
- # response<IO>::
146
- # The response object to put the response into.
147
- # exception<Object>::
148
- # The exception object that was created when trying to dispatch the
149
- # original controller.
150
- #
151
- # ==== Returns
152
- # Array[Merb::Controller, String]::
153
- # An array containing the Merb::Controller and the name of the exception
154
- # that triggrered #dispatch_exception. For instance, a NotFound exception
155
- # will be "not_found".
156
- def dispatch_exception(request, exception)
157
- exception_klass = exception.class
158
- begin
159
- klass = ::Exceptions rescue Merb::Controller
160
- request.params[:original_params] = request.params.dup rescue {}
161
- request.params[:original_session] = request.session.dup rescue {}
162
- request.params[:original_cookies] = request.cookies.dup rescue {}
163
- request.params[:exception] = exception
164
- request.params[:action] = exception_klass.name
165
-
166
- dispatch_action(klass, exception_klass.name, request, exception.class.status)
167
- rescue => dispatch_issue
168
- dispatch_issue = controller_exception(dispatch_issue)
169
- # when no action/template exist for an exception, or an
170
- # exception occurs on an InternalServerError the message is
171
- # rendered as simple text.
83
+ # Re-route the current request to the Exception controller if it is
84
+ # available, and try to render the exception nicely.
85
+ #
86
+ # You can handle exceptions by implementing actions for specific
87
+ # exceptions such as not_found or for entire classes of exceptions
88
+ # such as client_error. You can also implement handlers for
89
+ # exceptions outside the Merb exception hierarchy (e.g.
90
+ # StandardError is caught in standard_error).
91
+ #
92
+ # ==== Parameters
93
+ # request<Merb::Request>::
94
+ # The request object associated with the failed request.
95
+ # exception<Object>::
96
+ # The exception object that was created when trying to dispatch the
97
+ # original controller.
98
+ #
99
+ # ==== Returns
100
+ # Exceptions::
101
+ # The Merb::Controller that was dispatched to.
102
+ def dispatch_exception(exception, request)
103
+ Merb.logger.error(Merb.exception(exception))
104
+ request.exceptions = [exception]
172
105
 
173
- # ControllerExceptions raised from exception actions are
174
- # dispatched back into the Exceptions controller
175
- if dispatch_issue.is_a?(Merb::ControllerExceptions::NotFound)
176
- # If a handler for a specific exception is not found, keep retrying
177
- # with the more general cases until we reach the base exception.
178
- unless exception_klass == Merb::ControllerExceptions::Base
179
- exception_klass = exception_klass.superclass
180
- retry
106
+ begin
107
+ e = request.exceptions.first
108
+
109
+ if action_name = e.action_name
110
+ dispatch_action(Exceptions, action_name, request, e.class.status)
181
111
  else
182
- dispatch_default_exception(klass, request, exception)
183
- end
184
- elsif request.params[:exception].is_a?(dispatch_issue.class)
185
- dispatch_default_exception(klass, request, dispatch_issue)
186
- elsif dispatch_issue.is_a?(Merb::ControllerExceptions::InternalServerError)
187
- dispatch_default_exception(klass, request, dispatch_issue)
188
- else
189
- exception = dispatch_issue
190
- retry
112
+ dispatch_default_exception(request, e.class.status)
113
+ end
114
+ rescue Object => dispatch_issue
115
+ if e.same?(dispatch_issue)
116
+ dispatch_default_exception(request, e.class.status)
117
+ else
118
+ Merb.logger.error("Dispatching #{e.class} raised another error.")
119
+ Merb.logger.error(Merb.exception(dispatch_issue))
120
+
121
+ request.exceptions.unshift dispatch_issue
122
+ retry
123
+ end
191
124
  end
192
125
  end
193
- end
194
126
 
195
- # If no custom actions are available to render an exception then the errors
196
- # will end up here for processing
197
- #
198
- # ==== Parameters
199
- # klass<Merb::Controller>::
200
- # The class of the controller to use for exception dispatch.
201
- # request<Merb::Request>::
202
- # The Merb request that produced the original error.
203
- # response<IO>::
204
- # The response object that the response will be put into.
205
- # e<Exception>::
206
- # The exception that caused #dispatch_exception to be called.
207
- #
208
- # ==== Returns
209
- # Array[Merb::Controller, String]::
210
- # An array containing the Merb::Controller that was dispatched to and the
211
- # error's name. For instance, a NotFound error's name is "not_found".
212
- def dispatch_default_exception(klass, request, e)
213
- controller = klass.new(request, e.class.status)
214
- if e.is_a? Merb::ControllerExceptions::Redirection
215
- controller.headers.merge!('Location' => e.message)
216
- controller.body = %{ } #fix
217
- else
218
- controller.instance_variable_set("@exception", e) # for ERB
219
- controller.instance_variable_set("@exception_name", e.name.split("_").map {|x| x.capitalize}.join(" "))
220
- controller.body = controller.send(Merb::Template.template_for(DEFAULT_ERROR_TEMPLATE))
127
+ # If no custom actions are available to render an exception then the errors
128
+ # will end up here for processing
129
+ #
130
+ # ==== Parameters
131
+ # request<Merb::Request>::
132
+ # The Merb request that produced the original error.
133
+ # status<Integer>::
134
+ # The status code to return with the Exception.
135
+ #
136
+ # ==== Returns
137
+ # Merb::Dispatcher::DefaultException::
138
+ # The DefaultException that was dispatched to.
139
+ def dispatch_default_exception(request, status)
140
+ Merb::Dispatcher::DefaultException.new(request, status)._dispatch
141
+ end
142
+
143
+ # Set up a faux controller to do redirection from the router
144
+ #
145
+ # ==== Parameters
146
+ # request<Merb::Request>::
147
+ # The Merb::Request object that was created in #handle
148
+ # status<Integer>::
149
+ # The status code to return with the controller
150
+ # url<String>::
151
+ # The URL to return
152
+ #
153
+ # ==== Example
154
+ # r.match("/my/old/crusty/url").redirect("http://example.com/index.html")
155
+ #
156
+ # ==== Returns
157
+ # Merb::Controller::
158
+ # Merb::Controller set with redirect headers and a 301/302 status
159
+ def redirect(request)
160
+ status, url = request.redirect_status, request.redirect_url
161
+ controller = Merb::Controller.new(request, status)
162
+
163
+ Merb.logger.info("Dispatcher redirecting to: #{url} (#{status})")
164
+ Merb.logger.flush
165
+
166
+ controller.headers['Location'] = url
167
+ controller.body = "<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>"
168
+ controller
221
169
  end
222
- controller
223
- end
224
-
225
- # Wraps any non-ControllerException errors in an InternalServerError ready
226
- # for displaying over HTTP.
227
- #
228
- # ==== Parameters
229
- # e<Exception>::
230
- # The exception that caused #dispatch_exception to be called.
231
- #
232
- # ==== Returns
233
- # Merb::InternalServerError::
234
- # An internal server error wrapper for the exception.
235
- def controller_exception(e)
236
- e.kind_of?(Merb::ControllerExceptions::Base) ?
237
- e : Merb::ControllerExceptions::InternalServerError.new(e)
238
- end
239
170
 
171
+ end
240
172
  end
241
173
  end
@@ -1,15 +1,29 @@
1
+ require 'tempfile'
2
+
1
3
  module Merb
2
4
 
3
5
  class Request
4
6
  # def env def session def route_params
5
- attr_accessor :env, :session, :route_params
7
+ attr_accessor :env, :session, :exceptions, :route
8
+ attr_reader :route_params
6
9
 
7
- # by setting these to false, auto-parsing is disabled; this way you can do your own parsing instead
8
- cattr_accessor :parse_multipart_params, :parse_json_params, :parse_xml_params
10
+ # by setting these to false, auto-parsing is disabled; this way you can
11
+ # do your own parsing instead
12
+ cattr_accessor :parse_multipart_params, :parse_json_params,
13
+ :parse_xml_params
9
14
  self.parse_multipart_params = true
10
15
  self.parse_json_params = true
11
16
  self.parse_xml_params = true
12
17
 
18
+ # Flash, and some older browsers can't use arbitrary
19
+ # request methods -- i.e., are limited to GET/POST.
20
+ # These user-agents can make POST requests in combination
21
+ # with these overrides to participate fully in REST
22
+ # Common examples are _method or fb_sig_request_method
23
+ # in the params, or an X-HTTP-Method-Override header
24
+ cattr_accessor :http_method_overrides
25
+ self.http_method_overrides = []
26
+
13
27
  # Initial the request object.
14
28
  #
15
29
  # ==== Parameters
@@ -21,25 +35,46 @@ module Merb
21
35
  @route_params = {}
22
36
  end
23
37
 
24
- METHODS = %w{get post put delete head}
38
+ def controller
39
+ unless params[:controller]
40
+ raise ControllerExceptions::NotFound,
41
+ "Route matched, but route did not specify a controller.\n" +
42
+ "Did you forgot to add :controller => \"people\" or :controller " +
43
+ "segment to route definition?\nHere is what's specified:\n" +
44
+ request.route_params.inspect
45
+ end
46
+ path = [params[:namespace], params[:controller]].compact.join("/")
47
+ controller = path.snake_case.to_const_string
48
+
49
+ begin
50
+ Object.full_const_get(controller)
51
+ rescue NameError => e
52
+ msg = "Controller class not found for controller `#{path}'"
53
+ Merb.logger.warn!(msg)
54
+ raise ControllerExceptions::NotFound, msg
55
+ end
56
+ end
57
+
58
+ METHODS = %w{get post put delete head options}
25
59
 
26
60
  # ==== Returns
27
- # Symbol:: The name of the request method, e.g. :get.
61
+ # <Symbol>:: The name of the request method, e.g. :get.
28
62
  #
29
63
  # ==== Notes
30
- # If the method is post, then the +_method+ param will be checked for
31
- # masquerading method.
64
+ # If the method is post, then the blocks specified in
65
+ # http_method_overrides will be checked for the masquerading method.
66
+ # The block will get the controller yielded to it. The first matching workaround wins.
67
+ # To disable this behavior, set http_method_overrides = []
32
68
  def method
33
69
  @method ||= begin
34
70
  request_method = @env['REQUEST_METHOD'].downcase.to_sym
35
71
  case request_method
36
- when :get, :head, :put, :delete
72
+ when :get, :head, :put, :delete, :options
37
73
  request_method
38
74
  when :post
39
- if self.class.parse_multipart_params
40
- m = body_and_query_params.merge(multipart_params)['_method']
41
- else
42
- m = body_and_query_params['_method']
75
+ m = nil
76
+ self.class.http_method_overrides.each do |o|
77
+ m ||= o.call(self); break if m
43
78
  end
44
79
  m.downcase! if m
45
80
  METHODS.include?(m) ? m.to_sym : :post
@@ -54,6 +89,39 @@ module Merb
54
89
  METHODS.each do |m|
55
90
  class_eval "def #{m}?() method == :#{m} end"
56
91
  end
92
+
93
+ # Find route using requested URI and merges route
94
+ # parameters (:action, :controller and named segments)
95
+ # into request params hash.
96
+ def find_route!
97
+ @route, @route_params = Merb::Router.route_for(self)
98
+ @params.merge! @route_params
99
+ end
100
+
101
+ # Redirect status of route matched this request.
102
+ #
103
+ # ==== Returns
104
+ # Integer::
105
+ # The URL to redirect to if the route redirects
106
+ def redirect_status
107
+ route.redirect_status
108
+ end
109
+
110
+ # Returns redirect url of route matched this request.
111
+ #
112
+ # ==== Returns
113
+ # <String>:: redirect url of route matched this request
114
+ def redirect_url
115
+ route.redirect_url
116
+ end
117
+
118
+ # Returns true if matched route does immediate redirection.
119
+ #
120
+ # ==== Returns
121
+ # <Boolean>:: if matched route does immediate redirection.
122
+ def redirects?
123
+ route.redirects?
124
+ end
57
125
 
58
126
  private
59
127
 
@@ -77,7 +145,7 @@ module Merb
77
145
  end
78
146
 
79
147
  # ==== Returns
80
- # Hash::
148
+ # Mash::
81
149
  # The parameters gathered from the query string and the request body,
82
150
  # with parameters in the body taking precedence.
83
151
  def body_and_query_params
@@ -98,9 +166,9 @@ module Merb
98
166
  def multipart_params
99
167
  @multipart_params ||=
100
168
  begin
101
- # if the content-type is multipart and there's stuff in the body,
169
+ # if the content-type is multipart
102
170
  # parse the multipart. Otherwise return {}
103
- if (Merb::Const::MULTIPART_REGEXP =~ content_type && @body.size > 0)
171
+ if (Merb::Const::MULTIPART_REGEXP =~ content_type)
104
172
  self.class.parse_multipart(@body, $1, content_length)
105
173
  else
106
174
  {}
@@ -113,10 +181,17 @@ module Merb
113
181
 
114
182
  # ==== Returns
115
183
  # Hash:: Parameters from body if this is a JSON request.
184
+ #
185
+ # ==== Notes
186
+ # If the JSON object parses as a Hash, it will be merged with the
187
+ # parameters hash. If it parses to anything else (such as an Array, or
188
+ # if it inflates to an Object) it will be accessible via the inflated_object
189
+ # parameter.
116
190
  def json_params
117
191
  @json_params ||= begin
118
192
  if Merb::Const::JSON_MIME_TYPE_REGEXP.match(content_type)
119
- JSON.parse(raw_post)
193
+ jobj = JSON.parse(raw_post) rescue Mash.new
194
+ jobj.kind_of?(Hash) ? jobj : { :inflated_object => jobj }
120
195
  end
121
196
  end
122
197
  end
@@ -133,7 +208,7 @@ module Merb
133
208
 
134
209
  public
135
210
  # ==== Returns
136
- # Hash:: All request parameters.
211
+ # Mash:: All request parameters.
137
212
  #
138
213
  # ==== Notes
139
214
  # The order of precedence for the params is XML, JSON, multipart, body and
@@ -147,6 +222,15 @@ module Merb
147
222
  h
148
223
  end
149
224
  end
225
+
226
+ def message
227
+ return {} unless params[:_message]
228
+ begin
229
+ Marshal.load(Merb::Request.unescape(params[:_message]).unpack("m").first)
230
+ rescue ArgumentError
231
+ {}
232
+ end
233
+ end
150
234
 
151
235
  # Resets the params to a nil value.
152
236
  def reset_params!
@@ -156,7 +240,14 @@ module Merb
156
240
  # ==== Returns
157
241
  # Hash:: The cookies for this request.
158
242
  def cookies
159
- @cookies ||= self.class.query_parse(@env[Merb::Const::HTTP_COOKIE], ';,')
243
+ @cookies ||= begin
244
+ cookies = self.class.query_parse(@env[Merb::Const::HTTP_COOKIE], ';,')
245
+ if route && route.allow_fixation? && params.key?(Merb::Controller._session_id_key)
246
+ Merb.logger.info("Fixated session id: #{Merb::Controller._session_id_key}")
247
+ cookies[Merb::Controller._session_id_key] = params[Merb::Controller._session_id_key]
248
+ end
249
+ cookies
250
+ end
160
251
  end
161
252
 
162
253
  # ==== Returns
@@ -210,10 +301,16 @@ module Merb
210
301
  @env['HTTP_REFERER']
211
302
  end
212
303
 
304
+ # ==== Returns
305
+ # String:: The full URI, including protocol and host
306
+ def full_uri
307
+ protocol + host + uri
308
+ end
309
+
213
310
  # ==== Returns
214
311
  # String:: The request URI.
215
312
  def uri
216
- @env['REQUEST_URI'] || @env['REQUEST_PATH']
313
+ @env['REQUEST_PATH'] || @env['REQUEST_URI'] || path_info
217
314
  end
218
315
 
219
316
  # ==== Returns
@@ -366,7 +463,7 @@ module Merb
366
463
  class << self
367
464
 
368
465
  # ==== Parameters
369
- # value<Array, Hash, ~to_s>:: The value for the query string.
466
+ # value<Array, Hash, Dictionary ~to_s>:: The value for the query string.
370
467
  # prefix<~to_s>:: The prefix to add to the query string keys.
371
468
  #
372
469
  # ==== Returns
@@ -390,7 +487,7 @@ module Merb
390
487
  value.map { |v|
391
488
  params_to_query_string(v, "#{prefix}[]")
392
489
  } * "&"
393
- when Hash
490
+ when Hash, Dictionary
394
491
  value.map { |k, v|
395
492
  params_to_query_string(v, prefix ? "#{prefix}[#{Merb::Request.escape(k)}]" : Merb::Request.escape(k))
396
493
  } * "&"
@@ -424,18 +521,21 @@ module Merb
424
521
  # ==== Parameters
425
522
  # qs<String>:: The query string.
426
523
  # d<String>:: The query string divider. Defaults to "&".
524
+ # preserve_order<Boolean>:: Preserve order of args. Defaults to false.
427
525
  #
428
526
  # ==== Returns
429
- # Mash:: The parsed query string.
527
+ # Mash:: The parsed query string (Dictionary if preserve_order is set).
430
528
  #
431
529
  # ==== Examples
432
530
  # query_parse("bar=nik&post[body]=heya")
433
531
  # # => { :bar => "nik", :post => { :body => "heya" } }
434
- def query_parse(qs, d = '&;')
435
- (qs||'').split(/[#{d}] */n).inject({}) { |h,p|
532
+ def query_parse(qs, d = '&;', preserve_order = false)
533
+ qh = preserve_order ? Dictionary.new : {}
534
+ (qs||'').split(/[#{d}] */n).inject(qh) { |h,p|
436
535
  key, value = unescape(p).split('=',2)
437
536
  normalize_params(h, key, value)
438
- }.to_mash
537
+ }
538
+ preserve_order ? qh : qh.to_mash
439
539
  end
440
540
 
441
541
  NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
@@ -464,6 +564,7 @@ module Merb
464
564
  bufsize = 16384
465
565
  content_length -= boundary_size
466
566
  status = input.read(boundary_size)
567
+ return {} if status == nil || status.empty?
467
568
  raise ControllerExceptions::MultiPartParseError, "bad content body:\n'#{status}' should == '#{boundary + EOL}'" unless status == boundary + EOL
468
569
  rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
469
570
  loop {