lib/roda.rb in roda-1.0.0 vs lib/roda.rb in roda-1.1.0
- old
+ new
@@ -8,10 +8,11 @@
class Roda
# Error class raised by Roda
class RodaError < StandardError; end
if defined?(RUBY_ENGINE) && RUBY_ENGINE != 'ruby'
+ # :nocov:
# A thread safe cache class, offering only #[] and #[]= methods,
# each protected by a mutex. Used on non-MRI where Hash is not
# thread safe.
class RodaCache
# Create a new thread safe cache.
@@ -28,10 +29,11 @@
# Make setting value in underlying hash thread safe.
def []=(key, value)
@mutex.synchronize{@hash[key] = value}
end
end
+ # :nocov:
else
# Hashes are already thread-safe in MRI, due to the GVL, so they
# can safely be used as a cache.
RodaCache = Hash
end
@@ -49,13 +51,14 @@
# methods are added by Roda::RodaPlugins::Base::ResponseClassMethods.
class RodaResponse < ::Rack::Response;
@roda_class = ::Roda
end
- @builder = ::Rack::Builder.new
+ @app = nil
@middleware = []
@opts = {}
+ @route_block = nil
# Module in which all Roda plugins should be stored. Also contains logic for
# registering and loading plugins.
module RodaPlugins
# Stores registered plugins
@@ -84,18 +87,23 @@
# The base plugin for Roda, implementing all default functionality.
# Methods are put into a plugin so future plugins can easily override
# them and call super to get the default behavior.
module Base
+ SESSION_KEY = 'rack.session'.freeze
+
# Class methods for the Roda class.
module ClassMethods
# The rack application that this class uses.
attr_reader :app
# The settings/options hash for the current class.
attr_reader :opts
+ # The route block that this class uses.
+ attr_reader :route_block
+
# Call the internal rack application with the given environment.
# This allows the class itself to be used as a rack application.
# However, for performance, it's better to use #app to get direct
# access to the underlying rack app.
def call(env)
@@ -120,21 +128,18 @@
# end
def hash_matcher(key, &block)
request_module{define_method(:"match_#{key}", &block)}
end
- # When inheriting Roda, setup a new rack app builder, copy the
- # default middleware and opts into the subclass, and set the
- # request and response classes in the subclasses to be subclasses
- # of the request and responses classes in the parent class. This
- # makes it so child classes inherit plugins from their parent,
- # but using plugins in child classes does not affect the parent.
+ # When inheriting Roda, copy the shared data into the subclass,
+ # and setup the request and response subclasses.
def inherited(subclass)
super
- subclass.instance_variable_set(:@builder, ::Rack::Builder.new)
subclass.instance_variable_set(:@middleware, @middleware.dup)
subclass.instance_variable_set(:@opts, opts.dup)
+ subclass.instance_variable_set(:@route_block, @route_block)
+ subclass.send(:build_rack_app)
request_class = Class.new(self::RodaRequest)
request_class.roda_class = subclass
request_class.match_pattern_cache = thread_safe_cache
subclass.const_set(:RodaRequest, request_class)
@@ -233,13 +238,12 @@
# end
#
# This should only be called once per class, and if called multiple
# times will overwrite the previous routing.
def route(&block)
- @middleware.each{|a, b| @builder.use(*a, &b)}
- @builder.run lambda{|env| new.call(env, &block)}
- @app = @builder.to_app
+ @route_block = block
+ build_rack_app
end
# A new thread safe cache instance. This is a method so it can be
# easily overridden for alternative implementations.
def thread_safe_cache
@@ -250,14 +254,25 @@
# called before calling #route to have an effect. Example:
#
# Roda.use Rack::Session::Cookie, :secret=>ENV['secret']
def use(*args, &block)
@middleware << [args, block]
+ build_rack_app
end
private
+ # Build the rack app to use
+ def build_rack_app
+ if block = @route_block
+ builder = Rack::Builder.new
+ @middleware.each{|a, b| builder.use(*a, &b)}
+ builder.run lambda{|env| allocate.call(env, &block)}
+ @app = builder.to_app
+ end
+ end
+
# Backbone of the request_module and response_module support.
def module_include(type, mod)
if type == :response
klass = self::RodaResponse
iv = :@response_module
@@ -268,11 +283,13 @@
if mod
raise RodaError, "can't provide both argument and block to response_module" if block_given?
klass.send(:include, mod)
else
- unless mod = instance_variable_get(iv)
+ if instance_variable_defined?(iv)
+ mod = instance_variable_get(iv)
+ else
mod = instance_variable_set(iv, Module.new)
klass.send(:include, mod)
end
mod.module_eval(&Proc.new) if block_given?
@@ -282,12 +299,10 @@
end
end
# Instance methods for the Roda class.
module InstanceMethods
- SESSION_KEY = 'rack.session'.freeze
-
# Create a request and response of the appopriate
# class, the instance_exec the route block with
# the request, handling any halts. This is not usually
# called directly.
def call(env, &block)
@@ -928,19 +943,37 @@
def empty?
@body.empty?
end
# Return the rack response array of status, headers, and body
- # for the current response. Example:
+ # for the current response. If the status has not been set,
+ # uses a 200 status if the body has been written to, otherwise
+ # uses a 404 status. Adds the Content-Length header to the
+ # size of the response body.
#
- # response.finish # => [200, {'Content-Type'=>'text/html'}, []]
+ # Example:
+ #
+ # response.finish
+ # # => [200,
+ # # {'Content-Type'=>'text/html', 'Content-Length'=>'0'},
+ # # []]
def finish
b = @body
s = (@status ||= b.empty? ? 404 : 200)
- [s, @headers, b]
+ h = @headers
+ h[CONTENT_LENGTH] = @length.to_s
+ [s, h, b]
end
+ # Return the rack response array using a given body. Assumes a
+ # 200 response status unless status has been explicitly set,
+ # and doesn't add the Content-Length header or use the existing
+ # body.
+ def finish_with_body(body)
+ [@status || 200, @headers, body]
+ end
+
# Set the Location header to the given path, and the status
# to the given status. Example:
#
# response.redirect('foo', 301)
# response.redirect('bar')
@@ -955,19 +988,15 @@
# response.set_cookie('foo', :value=>'bar', :domain=>'example.org')
def set_cookie(key, value)
::Rack::Utils.set_cookie_header!(@headers, key, value)
end
- # Write to the response body. Updates Content-Length header
- # with the size of the string written. Returns nil. Example:
+ # Write to the response body. Returns nil.
#
# response.write('foo')
- # response['Content-Length'] # =>'3'
def write(str)
s = str.to_s
-
@length += s.bytesize
- @headers[CONTENT_LENGTH] = @length.to_s
@body << s
nil
end
end
end