summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2020-01-30 15:16:29 -0800
committerJeremy Evans <code@jeremyevans.net>2020-01-30 15:16:49 -0800
commitf2d2df4016a906beec755b63b4edfcc07b58ee05 (patch)
treebd909618b35c514c90d5dd4a3bd9eb5445e3abc4
parente9f26e74913d7c2ef31f4fa81ee8c0e8d568ffb7 (diff)
downloadrack-f2d2df4016a906beec755b63b4edfcc07b58ee05.tar.gz
Add documentation to BodyProxy and Builder
-rw-r--r--lib/rack/body_proxy.rb11
-rw-r--r--lib/rack/builder.rb77
2 files changed, 82 insertions, 6 deletions
diff --git a/lib/rack/body_proxy.rb b/lib/rack/body_proxy.rb
index ebb7db39..cfc0796a 100644
--- a/lib/rack/body_proxy.rb
+++ b/lib/rack/body_proxy.rb
@@ -1,17 +1,25 @@
# frozen_string_literal: true
module Rack
+ # Proxy for response bodies allowing calling a block when
+ # the response body is closed (after the response has been fully
+ # sent to the client).
class BodyProxy
+ # Set the response body to wrap, and the block to call when the
+ # response has been fully sent.
def initialize(body, &block)
@body = body
@block = block
@closed = false
end
+ # Return whether the wrapped body responds to the method.
def respond_to_missing?(method_name, include_all = false)
super or @body.respond_to?(method_name, include_all)
end
+ # If not already closed, close the wrapped body and
+ # then call the block the proxy was initialized with.
def close
return if @closed
@closed = true
@@ -22,10 +30,13 @@ module Rack
end
end
+ # Whether the proxy is closed. The proxy starts as not closed,
+ # and becomes closed on the first call to close.
def closed?
@closed
end
+ # Delegate missing methods to the wrapped body.
def method_missing(method_name, *args, &block)
@body.__send__(method_name, *args, &block)
end
diff --git a/lib/rack/builder.rb b/lib/rack/builder.rb
index ebfa1f11..c9c45048 100644
--- a/lib/rack/builder.rb
+++ b/lib/rack/builder.rb
@@ -35,6 +35,32 @@ module Rack
# https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom
UTF_8_BOM = '\xef\xbb\xbf'
+ # Parse the given config file to get a Rack application.
+ #
+ # If the config file ends in +.ru+, it is treated as a
+ # rackup file and the contents will be treated as if
+ # specified inside a Rack::Builder block, using the given
+ # options.
+ #
+ # If the config file does not end in +.ru+, it is
+ # required and Rack will use the basename of the file
+ # to guess which constant will be the Rack application to run.
+ # The options given will be ignored in this case.
+ #
+ # Examples:
+ #
+ # Rack::Builder.parse_file('config.ru')
+ # # Rack application built using Rack::Builder.new
+ #
+ # Rack::Builder.parse_file('app.rb')
+ # # requires app.rb, which can be anywhere in Ruby's
+ # # load path. After requiring, assumes App constant
+ # # contains Rack application
+ #
+ # Rack::Builder.parse_file('./my_app.rb')
+ # # requires ./my_app.rb, which should be in the
+ # # process's current directory. After requiring,
+ # # assumes MyApp constant contains Rack application
def self.parse_file(config, opts = Server::Options.new)
if config.end_with?('.ru')
return self.load_file(config, opts)
@@ -45,6 +71,25 @@ module Rack
end
end
+ # Load the given file as a rackup file, treating the
+ # contents as if specified inside a Rack::Builder block.
+ #
+ # Treats the first comment at the beginning of a line
+ # that starts with a backslash as options similar to
+ # options passed on a rackup command line.
+ #
+ # Ignores content in the file after +__END__+, so that
+ # use of +__END__+ will not result in a syntax error.
+ #
+ # Example config.ru file:
+ #
+ # $ cat config.ru
+ #
+ # #\ -p 9393
+ #
+ # use Rack::ContentLength
+ # require './app.rb'
+ # run App
def self.load_file(path, opts = Server::Options.new)
options = {}
@@ -61,16 +106,23 @@ module Rack
return app, options
end
+ # Evaluate the given +builder_script+ string in the context of
+ # a Rack::Builder block, returning a Rack application.
def self.new_from_string(builder_script, file = "(rackup)")
eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
TOPLEVEL_BINDING, file, 0
end
+ # Initialize a new Rack::Builder instance. +default_app+ specifies the
+ # default application if +run+ is not called later. If a block
+ # is given, it is evaluted in the context of the instance.
def initialize(default_app = nil, &block)
@use, @map, @run, @warmup, @freeze_app = [], nil, default_app, nil, false
instance_eval(&block) if block_given?
end
+ # Create a new Rack::Builder instance and return the Rack application
+ # generated from it.
def self.app(default_app = nil, &block)
self.new(default_app, &block).to_app
end
@@ -121,7 +173,8 @@ module Rack
@run = app
end
- # Takes a lambda or block that is used to warm-up the application.
+ # Takes a lambda or block that is used to warm-up the application. This block is called
+ # before the Rack application is returned by to_app.
#
# warmup do |app|
# client = Rack::MockRequest.new(app)
@@ -134,25 +187,31 @@ module Rack
@warmup = prc || block
end
- # Creates a route within the application.
+ # Creates a route within the application. Routes under the mapped path will be sent to
+ # the Rack application specified by run inside the block. Other requests will be sent to the
+ # default application specified by run outside the block.
#
# Rack::Builder.app do
- # map '/' do
+ # map '/heartbeat' do
# run Heartbeat
# end
+ # run App
# end
#
- # The +use+ method can also be used here to specify middleware to run under a specific path:
+ # The +use+ method can also be used inside the block to specify middleware to run under a specific path:
#
# Rack::Builder.app do
- # map '/' do
+ # map '/heartbeat' do
# use Middleware
# run Heartbeat
# end
+ # run App
# end
#
- # This example includes a piece of middleware which will run before requests hit +Heartbeat+.
+ # This example includes a piece of middleware which will run before +/heartbeat+ requests hit +Heartbeat+.
#
+ # Note that providing a +path+ of +/+ will ignore any default application given in a +run+ statement
+ # outside the block.
def map(path, &block)
@map ||= {}
@map[path] = block
@@ -164,6 +223,7 @@ module Rack
@freeze_app = true
end
+ # Return the Rack application generated by this instance.
def to_app
app = @map ? generate_map(@run, @map) : @run
fail "missing run or map statement" unless app
@@ -173,12 +233,17 @@ module Rack
app
end
+ # Call the Rack application generated by this builder instance. Note that
+ # this rebuilds the Rack application and runs the warmup code (if any)
+ # every time it is called, so it should not be used if performance is important.
def call(env)
to_app.call(env)
end
private
+ # Generate a URLMap instance by generating new Rack applications for each
+ # map block in this instance.
def generate_map(default_app, mapping)
mapped = default_app ? { '/' => default_app } : {}
mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app }