merb-core 0.9.8 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/CONTRIBUTORS +33 -0
  2. data/README +7 -3
  3. data/Rakefile +3 -3
  4. data/lib/merb-core.rb +165 -94
  5. data/lib/merb-core/bootloader.rb +469 -100
  6. data/lib/merb-core/config.rb +79 -3
  7. data/lib/merb-core/constants.rb +24 -2
  8. data/lib/merb-core/controller/abstract_controller.rb +172 -67
  9. data/lib/merb-core/controller/exceptions.rb +50 -6
  10. data/lib/merb-core/controller/merb_controller.rb +215 -108
  11. data/lib/merb-core/controller/mime.rb +36 -12
  12. data/lib/merb-core/controller/mixins/authentication.rb +52 -7
  13. data/lib/merb-core/controller/mixins/conditional_get.rb +14 -0
  14. data/lib/merb-core/controller/mixins/controller.rb +90 -58
  15. data/lib/merb-core/controller/mixins/render.rb +34 -10
  16. data/lib/merb-core/controller/mixins/responder.rb +40 -16
  17. data/lib/merb-core/controller/template.rb +37 -16
  18. data/lib/merb-core/core_ext/hash.rb +9 -0
  19. data/lib/merb-core/core_ext/kernel.rb +92 -41
  20. data/lib/merb-core/dispatch/dispatcher.rb +29 -45
  21. data/lib/merb-core/dispatch/request.rb +186 -82
  22. data/lib/merb-core/dispatch/router.rb +141 -53
  23. data/lib/merb-core/dispatch/router/behavior.rb +296 -139
  24. data/lib/merb-core/dispatch/router/resources.rb +51 -19
  25. data/lib/merb-core/dispatch/router/route.rb +76 -23
  26. data/lib/merb-core/dispatch/session.rb +80 -36
  27. data/lib/merb-core/dispatch/session/container.rb +31 -15
  28. data/lib/merb-core/dispatch/session/cookie.rb +51 -22
  29. data/lib/merb-core/dispatch/session/memcached.rb +10 -6
  30. data/lib/merb-core/dispatch/session/memory.rb +17 -5
  31. data/lib/merb-core/dispatch/session/store_container.rb +21 -9
  32. data/lib/merb-core/dispatch/worker.rb +16 -2
  33. data/lib/merb-core/gem_ext/erubis.rb +4 -0
  34. data/lib/merb-core/plugins.rb +13 -0
  35. data/lib/merb-core/rack.rb +1 -0
  36. data/lib/merb-core/rack/adapter.rb +1 -0
  37. data/lib/merb-core/rack/adapter/abstract.rb +95 -17
  38. data/lib/merb-core/rack/adapter/irb.rb +50 -5
  39. data/lib/merb-core/rack/application.rb +27 -5
  40. data/lib/merb-core/rack/handler/mongrel.rb +6 -6
  41. data/lib/merb-core/rack/helpers.rb +33 -0
  42. data/lib/merb-core/rack/middleware/conditional_get.rb +1 -1
  43. data/lib/merb-core/rack/middleware/path_prefix.rb +3 -3
  44. data/lib/merb-core/rack/middleware/static.rb +11 -7
  45. data/lib/merb-core/server.rb +134 -69
  46. data/lib/merb-core/tasks/gem_management.rb +153 -80
  47. data/lib/merb-core/tasks/merb_rake_helper.rb +12 -4
  48. data/lib/merb-core/tasks/stats.rake +1 -1
  49. data/lib/merb-core/test/helpers/mock_request_helper.rb +29 -22
  50. data/lib/merb-core/test/helpers/request_helper.rb +1 -1
  51. data/lib/merb-core/test/helpers/route_helper.rb +50 -4
  52. data/lib/merb-core/test/matchers/request_matchers.rb +2 -36
  53. data/lib/merb-core/test/matchers/view_matchers.rb +32 -22
  54. data/lib/merb-core/test/run_specs.rb +6 -5
  55. data/lib/merb-core/test/test_ext/rspec.rb +6 -19
  56. data/lib/merb-core/version.rb +1 -1
  57. metadata +5 -4
@@ -3,10 +3,24 @@ module Merb
3
3
 
4
4
  attr_accessor :thread
5
5
 
6
+ # ==== Returns
7
+ # Merb::Worker:: instance of a worker.
8
+ #
9
+ # @api private
10
+ def self.start
11
+ new
12
+ end
13
+
14
+ # Creates a new worker thread that loops over the work queue.
15
+ #
16
+ # @api private
6
17
  def initialize
7
18
  @thread = Thread.new { loop { process_queue } }
8
19
  end
9
20
 
21
+ # Processes tasks in the Merb::Dispatcher.work_queue.
22
+ #
23
+ # @api private
10
24
  def process_queue
11
25
  begin
12
26
  while blk = Merb::Dispatcher.work_queue.pop
@@ -21,8 +35,8 @@ module Merb
21
35
  rescue Exception => e
22
36
  Merb.logger.warn! %Q!Worker Thread Crashed with Exception:\n#{Merb.exception(e)}\nRestarting Worker Thread!
23
37
  retry
24
- end
38
+ end
25
39
  end
26
40
 
27
41
  end
28
- end
42
+ end
@@ -1,5 +1,9 @@
1
1
  require 'erubis'
2
2
  module Erubis
3
+ # This adds support for embedding the return value of a block call:
4
+ # <%= foo do %>...<% end =%>
5
+ #
6
+ # @api private
3
7
  module Basic::Converter
4
8
  def convert_input(src, input)
5
9
  pat = @pattern
@@ -2,10 +2,15 @@ module Merb
2
2
 
3
3
  module Plugins
4
4
 
5
+ # Returns the configuration settings hash for plugins. This is prepopulated from
6
+ # Merb.root / "config/plugins.yml" if it is present.
7
+ #
5
8
  # ==== Returns
6
9
  # Hash::
7
10
  # The configuration loaded from Merb.root / "config/plugins.yml" or, if
8
11
  # the load fails, an empty hash whose default value is another Hash.
12
+ #
13
+ # @api plugin
9
14
  def self.config
10
15
  @config ||= begin
11
16
  # this is so you can do Merb.plugins.config[:helpers][:awesome] = "bar"
@@ -25,12 +30,16 @@ module Merb
25
30
 
26
31
  # ==== Returns
27
32
  # Array(String):: All Rakefile load paths Merb uses for plugins.
33
+ #
34
+ # @api plugin
28
35
  def self.rakefiles
29
36
  Merb.rakefiles
30
37
  end
31
38
 
32
39
  # ==== Returns
33
40
  # Array(String):: All Generator load paths Merb uses for plugins.
41
+ #
42
+ # @api plugin
34
43
  def self.generators
35
44
  Merb.generators
36
45
  end
@@ -49,6 +58,8 @@ module Merb
49
58
  # if defined(Merb::Plugins)
50
59
  # Merb::Plugins.add_rakefiles "merb_sequel" / "merbtasks"
51
60
  # end
61
+ #
62
+ # @api plugin
52
63
  def self.add_rakefiles(*rakefiles)
53
64
  Merb.add_rakefiles(*rakefiles)
54
65
  end
@@ -60,6 +71,8 @@ module Merb
60
71
  #
61
72
  # This is the recommended way to register your plugin's generators
62
73
  # in Merb.
74
+ #
75
+ # @api plugin
63
76
  def self.add_generators(*generators)
64
77
  Merb.add_generators(*generators)
65
78
  end
@@ -23,5 +23,6 @@ module Merb
23
23
  autoload :ConditionalGet, 'merb-core/rack/middleware/conditional_get'
24
24
  autoload :Csrf, 'merb-core/rack/middleware/csrf'
25
25
  autoload :StreamWrapper, 'merb-core/rack/stream_wrapper'
26
+ autoload :Helpers, 'merb-core/rack/helpers'
26
27
  end # Rack
27
28
  end # Merb
@@ -5,6 +5,7 @@ module Merb
5
5
  class Adapter
6
6
 
7
7
  class << self
8
+ # Get a rack adapter by id.
8
9
  # ==== Parameters
9
10
  # id<String>:: The identifier of the Rack adapter class to retrieve.
10
11
  #
@@ -2,30 +2,83 @@ module Merb
2
2
  module Rack
3
3
  class AbstractAdapter
4
4
 
5
+ # This method is designed to be overridden in a rack adapter. It
6
+ # will be called to start a server created with the new_server method.
7
+ # This is called from the AbstractAdapter start method.
8
+ #
9
+ # @api plugin
10
+ # @overridable
11
+ def self.start_server
12
+ raise NotImplemented
13
+ end
14
+
15
+ # This method is designed to be overridden in a rack adapter. It will
16
+ # be called to create a new instance of the server for the adapter to
17
+ # start. The adapter should attempt to bind to a port at this point.
18
+ # This is called from the AbstractAdapter start method.
19
+ #
20
+ # ==== Parameters
21
+ # port<Integer>:: The port the server should listen on
22
+ #
23
+ # @api plugin
24
+ # @overridable
25
+ def self.new_server(port)
26
+ raise NotImplemented
27
+ end
28
+
29
+ # This method is designed to be overridden in a rack adapter. It will
30
+ # be called to stop the adapter server.
31
+ #
32
+ # ==== Parameters
33
+ # status<Integer>:: The exit status the adapter should exit with.
34
+ #
35
+ # ==== Returns
36
+ # Boolean:: True if the server was properly stopped.
37
+ #
38
+ # @api plugin
39
+ # @overridable
40
+ def self.stop(status)
41
+ raise NotImplemented
42
+ end
43
+
5
44
  # Spawn a new worker process at a port.
45
+ #
46
+ # ==== Parameters
47
+ # port<Integer>:: The port to start the worker process on.
48
+ #
49
+ # @api private
6
50
  def self.spawn_worker(port)
7
- child_pid = Kernel.fork
8
- start_at_port(port, @opts) unless child_pid
51
+ worker_pid = Kernel.fork
52
+ start_at_port(port, @opts) unless worker_pid
9
53
 
10
- # If we have a child_pid, we're in the parent. If we're
11
- throw(:new_worker) unless child_pid
54
+ # If we have a worker_pid, we're in the parent.
55
+ throw(:new_worker) unless worker_pid
12
56
 
13
- @pids[port] = child_pid
14
- $CHILDREN = @pids.values
57
+ @pids[port] = worker_pid
58
+ $WORKERS = @pids.values
15
59
  end
16
60
 
17
61
  # The main start method for bootloaders that support forking.
62
+ # This method launches the adapters which inherit using the
63
+ # new_server and start_server methods. This method should not
64
+ # be overridden in adapters which want to fork.
65
+ #
66
+ # ==== Parameters
67
+ # opts<Hash>:: A hash of options
68
+ # socket: the socket to bind to
69
+ # port: the port to bind to
70
+ # cluster: the number
71
+ #
72
+ # @api private
18
73
  def self.start(opts={})
19
74
  @opts = opts
20
- $CHILDREN ||= []
75
+ $WORKERS ||= []
21
76
  parent = nil
22
77
 
23
78
  @pids = {}
24
79
  port = (opts[:socket] || opts[:port]).to_i
25
80
  max_port = Merb::Config[:cluster] ? Merb::Config[:cluster] - 1 : 0
26
81
 
27
- Merb.logger.warn! "Cluster: #{max_port}"
28
-
29
82
  # If we only have a single merb, just start it up and dispense with
30
83
  # the spawner/worker setup.
31
84
  if max_port == 0
@@ -36,7 +89,7 @@ module Merb
36
89
  $0 = process_title(:spawner, port)
37
90
 
38
91
  # For each port, spawn a new worker. The parent will continue in
39
- # the loop, while the child will throw :new_worker and be booted
92
+ # the loop, while the worker will throw :new_worker and be booted
40
93
  # out of the loop.
41
94
  catch(:new_worker) do
42
95
  0.upto(max_port) do |i|
@@ -64,7 +117,7 @@ module Merb
64
117
  ensure
65
118
  # If there was no worker with that PID, the status was non-0
66
119
  # (we send back a status of 128 when ABRT is called on a
67
- # child, and Merb.fatal! exits with a status of 1), or if
120
+ # worker, and Merb.fatal! exits with a status of 1), or if
68
121
  # Merb is in the process of exiting, *then* don't respawn.
69
122
  # Note that processes killed with kill -9 will return no
70
123
  # exitstatus, and we respawn them.
@@ -88,11 +141,21 @@ module Merb
88
141
 
89
142
  end
90
143
 
144
+ # Fork a server on the specified port and start the app.
145
+ #
146
+ # ==== Parameters
147
+ # port<Integer>:: The port to start the server on
148
+ # opts<Hash>:: The hash of options, defaults to the @opts
149
+ # instance variable.
150
+ #
151
+ # @api private
91
152
  def self.start_at_port(port, opts = @opts)
92
153
  at_exit do
93
154
  Merb::Server.remove_pid(port)
94
155
  end
95
156
 
157
+ Merb::Worker.start
158
+
96
159
  # If Merb is daemonized, trap INT. If it's not daemonized,
97
160
  # we let the master process' ctrl-c control the cluster
98
161
  # of workers.
@@ -111,6 +174,7 @@ module Merb
111
174
  # In daemonized mode or not, support HUPing the process to
112
175
  # restart it.
113
176
  Merb.trap('HUP') do
177
+ Merb.exiting = true
114
178
  stop
115
179
  Merb.logger.warn! "Exiting port #{port} on #{Process.pid}\n"
116
180
  exit_process
@@ -118,6 +182,7 @@ module Merb
118
182
 
119
183
  # ABRTing the process will kill it, and it will not be respawned.
120
184
  Merb.trap('ABRT') do
185
+ Merb.exiting = true
121
186
  stopped = stop(128)
122
187
  Merb.logger.warn! "Exiting port #{port}\n" if stopped
123
188
  exit_process(128)
@@ -129,9 +194,9 @@ module Merb
129
194
  # Store the PID for this worker
130
195
  Merb::Server.store_pid(port)
131
196
 
132
- Merb::Config[:log_delimiter] = "#{$0} ~ "
197
+ Merb::Config[:log_delimiter] = "#{process_title(:worker, port)} ~ "
133
198
 
134
- Merb.logger = nil
199
+ Merb.reset_logger!
135
200
  Merb.logger.warn!("Starting #{self.name.split("::").last} at port #{port}")
136
201
 
137
202
  # If we can't connect to the port, keep trying until we can. Print
@@ -144,8 +209,8 @@ module Merb
144
209
  new_server(port)
145
210
  rescue Errno::EADDRINUSE
146
211
  unless printed_warning
147
- Merb.logger.warn! "Couldn't bind to port #{port}."
148
- Merb.logger.warn! "Waiting for it to become available"
212
+ Merb.logger.warn! "Port #{port} is in use, " \
213
+ "Waiting for it to become available."
149
214
  printed_warning = true
150
215
  end
151
216
 
@@ -163,11 +228,24 @@ module Merb
163
228
  start_server
164
229
  end
165
230
 
166
- # This can be overridden in adapters, but shouldn't need to be.
231
+ # Exit the process with the specified status.
232
+ #
233
+ # ==== Parameters
234
+ # status<Integer>:: The exit code of the process.
235
+ #
236
+ # @api private
167
237
  def self.exit_process(status = 0)
168
238
  exit(status)
169
239
  end
170
240
 
241
+ # Set the process title.
242
+ #
243
+ # ==== Parameters
244
+ # whoami<Symbol>:: Either :spawner for the master process or :worker for any of the worker
245
+ # processes.
246
+ # port<Integer>:: The base port that the app is running on.
247
+ #
248
+ # @api private
171
249
  def self.process_title(whoami, port)
172
250
  name = Merb::Config[:name]
173
251
  app = "merb#{" : #{name}" if (name && name != "merb")}"
@@ -179,7 +257,7 @@ module Merb
179
257
  "socket#{'s' if max_port > 0 && whoami != :worker} #{numbers} "\
180
258
  "#{file ? file : "#{Merb.log_path}/#{name}.#{port}.sock"}"
181
259
  else
182
- "port#{'s' if max_port > 0 && whoami != :worker}"
260
+ "port#{'s' if max_port > 0 && whoami != :worker} #{port}"
183
261
  end
184
262
  "#{app} : #{whoami} (#{listening_on})"
185
263
  end
@@ -1,15 +1,60 @@
1
1
  module Merb
2
2
  module Rack
3
3
  class Console
4
- # ==== Parameters
5
- # name<~to_sym, Hash>:: The name of the route to generate.
6
- # params<Hash>:: The params to use in the route generation.
4
+ # There are three possible ways to use this method. First, if you have a named route,
5
+ # you can specify the route as the first parameter as a symbol and any paramters in a
6
+ # hash. Second, you can generate the default route by just passing the params hash,
7
+ # just passing the params hash. Finally, you can use the anonymous parameters. This
8
+ # allows you to specify the parameters to a named route in the order they appear in the
9
+ # router.
10
+ #
11
+ # ==== Parameters(Named Route)
12
+ # name<Symbol>::
13
+ # The name of the route.
14
+ # args<Hash>::
15
+ # Parameters for the route generation.
16
+ #
17
+ # ==== Parameters(Default Route)
18
+ # args<Hash>::
19
+ # Parameters for the route generation. This route will use the default route.
20
+ #
21
+ # ==== Parameters(Anonymous Parameters)
22
+ # name<Symbol>::
23
+ # The name of the route.
24
+ # args<Array>::
25
+ # An array of anonymous parameters to generate the route
26
+ # with. These parameters are assigned to the route parameters
27
+ # in the order that they are passed.
7
28
  #
8
29
  # ==== Returns
9
30
  # String:: The generated URL.
10
31
  #
11
- # ==== Alternatives
12
- # If name is a hash, it will be merged with params.
32
+ # ==== Examples
33
+ # Named Route
34
+ #
35
+ # Merb::Router.prepare do
36
+ # match("/articles/:title").to(:controller => :articles, :action => :show).name("articles")
37
+ # end
38
+ #
39
+ # url(:articles, :title => "new_article")
40
+ #
41
+ # Default Route
42
+ #
43
+ # Merb::Router.prepare do
44
+ # default_routes
45
+ # end
46
+ #
47
+ # url(:controller => "articles", :action => "new")
48
+ #
49
+ # Anonymous Paramters
50
+ #
51
+ # Merb::Router.prepare do
52
+ # match("/articles/:year/:month/:title").to(:controller => :articles, :action => :show).name("articles")
53
+ # end
54
+ #
55
+ # url(:articles, 2008, 10, "test_article")
56
+ #
57
+ # @api public
13
58
  def url(name, *args)
14
59
  args << {}
15
60
  Merb::Router.url(name, *args)
@@ -2,24 +2,46 @@ module Merb
2
2
  module Rack
3
3
  class Application
4
4
 
5
+ # The main rack application call method. This is the entry point from rack (and the webserver)
6
+ # to your application.
7
+ #
8
+ # ==== Parameters
9
+ # env<Hash>:: A rack request of parameters.
10
+ #
11
+ # ==== Returns
12
+ # <Array>:: A rack response of [status<Integer>, headers<Hash>, body<String, Stream>]
13
+ #
14
+ # @api private
5
15
  def call(env)
6
16
  begin
7
- controller = ::Merb::Dispatcher.handle(Merb::Request.new(env))
17
+ rack_response = ::Merb::Dispatcher.handle(Merb::Request.new(env))
8
18
  rescue Object => e
9
- return [500, {Merb::Const::CONTENT_TYPE => "text/html"}, e.message + "<br/>" + e.backtrace.join("<br/>")]
19
+ return [500, {Merb::Const::CONTENT_TYPE => Merb::Const::TEXT_SLASH_HTML}, e.message + Merb::Const::BREAK_TAG + e.backtrace.join(Merb::Const::BREAK_TAG)]
10
20
  end
11
- Merb.logger.info "\n\n"
21
+ Merb.logger.info Merb::Const::DOUBLE_NEWLINE
12
22
  Merb.logger.flush
13
23
 
14
24
  # unless controller.headers[Merb::Const::DATE]
15
25
  # require "time"
16
26
  # controller.headers[Merb::Const::DATE] = Time.now.rfc2822.to_s
17
27
  # end
18
- controller.rack_response
28
+ rack_response
19
29
  end
20
30
 
31
+ # Determines whether this request is a "deferred_action", usually a long request.
32
+ # Rack uses this method to detemine whether to use an evented request or a deferred
33
+ # request in evented rack handlers.
34
+ #
35
+ # ==== Parameters
36
+ # env<Hash>:: The rack request
37
+ #
38
+ # ==== Returns
39
+ # Boolean::
40
+ # True if the request should be deferred.
41
+ #
42
+ # @api private
21
43
  def deferred?(env)
22
- path = env[Merb::Const::PATH_INFO] ? env[Merb::Const::PATH_INFO].chomp('/') : ""
44
+ path = env[Merb::Const::PATH_INFO] ? env[Merb::Const::PATH_INFO].chomp(Merb::Const::SLASH) : Merb::Const::EMPTY_STRING
23
45
  if path =~ Merb.deferred_actions
24
46
  Merb.logger.info! "Deferring Request: #{path}"
25
47
  true
@@ -54,10 +54,10 @@ module Merb
54
54
  # response<HTTPResponse>:: The response object to write response to.
55
55
  def process(request, response)
56
56
  env = {}.replace(request.params)
57
- env.delete "HTTP_CONTENT_TYPE"
58
- env.delete "HTTP_CONTENT_LENGTH"
57
+ env.delete Merb::Const::HTTP_CONTENT_TYPE
58
+ env.delete Merb::Const::HTTP_CONTENT_LENGTH
59
59
 
60
- env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
60
+ env[Merb::Const::SCRIPT_NAME] = Merb::Const::EMPTY_STRING if env[Merb::Const::SCRIPT_NAME] == Merb::Const::SLASH
61
61
 
62
62
  env.update({"rack.version" => [0,1],
63
63
  "rack.input" => request.body || StringIO.new(""),
@@ -69,8 +69,8 @@ module Merb
69
69
 
70
70
  "rack.url_scheme" => "http"
71
71
  })
72
- env["QUERY_STRING"] ||= ""
73
- env.delete "PATH_INFO" if env["PATH_INFO"] == ""
72
+ env[Merb::Const::QUERY_STRING] ||= ""
73
+ env.delete Merb::Const::PATH_INFO if env[Merb::Const::PATH_INFO] == Merb::Const::EMPTY_STRING
74
74
 
75
75
  status, headers, body = @app.call(env)
76
76
 
@@ -93,4 +93,4 @@ module Merb
93
93
  end
94
94
  end
95
95
  end
96
- end
96
+ end