Skip to content

Commit

Permalink
Add documentation to BodyProxy and Builder
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremyevans committed Jan 30, 2020
1 parent e9f26e7 commit f2d2df4
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 6 deletions.
11 changes: 11 additions & 0 deletions lib/rack/body_proxy.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -22,10 +30,13 @@ def close
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
Expand Down
77 changes: 71 additions & 6 deletions lib/rack/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,32 @@ class Builder
# 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)
Expand All @@ -45,6 +71,25 @@ def self.parse_file(config, opts = Server::Options.new)
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 = {}

Expand All @@ -61,16 +106,23 @@ def self.load_file(path, opts = Server::Options.new)
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
Expand Down Expand Up @@ -121,7 +173,8 @@ def run(app)
@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)
Expand All @@ -134,25 +187,31 @@ def warmup(prc = nil, &block)
@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
Expand All @@ -164,6 +223,7 @@ def freeze_app
@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
Expand All @@ -173,12 +233,17 @@ def to_app
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 }
Expand Down

0 comments on commit f2d2df4

Please sign in to comment.