Class: Rack::Builder

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/builder.rb

Overview

Rack::Builder provides a domain-specific language (DSL) to construct Rack applications. It is primarily used to parse config.ru files which instantiate several middleware and a final application which are hosted by a Rack-compatible web server.

Example:

app = Rack::Builder.new do
  use Rack::CommonLogger
  map "/ok" do
    run lambda { |env| [200, {'content-type' => 'text/plain'}, ['OK']] }
  end
end

run app

Or

app = Rack::Builder.app do
  use Rack::CommonLogger
  run lambda { |env| [200, {'content-type' => 'text/plain'}, ['OK']] }
end

run app

use adds middleware to the stack, run dispatches to an application. You can use map to construct a Rack::URLMap in a convenient way.

Constant Summary collapse

UTF_8_BOM =
'\xef\xbb\xbf'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(default_app = nil, **options, &block) ⇒ Builder

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 evaluated in the context of the instance.



116
117
118
119
120
121
122
123
124
125
# File 'lib/rack/builder.rb', line 116

def initialize(default_app = nil, **options, &block)
  @use = []
  @map = nil
  @run = default_app
  @warmup = nil
  @freeze_app = false
  @options = options

  instance_eval(&block) if block_given?
end

Instance Attribute Details

#optionsObject (readonly)

Any options provided to the Rack::Builder instance at initialization. These options can be server-specific. Some general options are:

  • :isolation: One of process, thread or fiber. The execution isolation model to use.



132
133
134
# File 'lib/rack/builder.rb', line 132

def options
  @options
end

Class Method Details

.app(default_app = nil, &block) ⇒ Object

Create a new Rack::Builder instance and return the Rack application generated from it.



136
137
138
# File 'lib/rack/builder.rb', line 136

def self.app(default_app = nil, &block)
  self.new(default_app, &block).to_app
end

.load_file(path, **options) ⇒ Object

Load the given file as a rackup file, treating the contents as if specified inside a Rack::Builder block.

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

use Rack::ContentLength
require './app.rb'
run App


87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/rack/builder.rb', line 87

def self.load_file(path, **options)
  config = ::File.read(path)
  config.slice!(/\A#{UTF_8_BOM}/) if config.encoding == Encoding::UTF_8

  if config[/^#\\(.*)/]
    fail "Parsing options from the first comment line is no longer supported: #{path}"
  end

  config.sub!(/^__END__\n.*\Z/m, '')

  return new_from_string(config, path, **options)
end

.new_from_string(builder_script, path = "(rackup)", **options) ⇒ Object

Evaluate the given builder_script string in the context of a Rack::Builder block, returning a Rack application.



102
103
104
105
106
107
108
109
110
111
# File 'lib/rack/builder.rb', line 102

def self.new_from_string(builder_script, path = "(rackup)", **options)
  builder = self.new(**options)

  # We want to build a variant of TOPLEVEL_BINDING with self as a Rack::Builder instance.
  # We cannot use instance_eval(String) as that would resolve constants differently.
  binding = BUILDER_TOPLEVEL_BINDING.call(builder)
  eval(builder_script, binding, path)

  return builder.to_app
end

.parse_file(path, **options) ⇒ Object

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.

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.

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
# is a 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 is a Rack application


65
66
67
68
69
70
71
72
# File 'lib/rack/builder.rb', line 65

def self.parse_file(path, **options)
  if path.end_with?('.ru')
    return self.load_file(path, **options)
  else
    require path
    return Object.const_get(::File.basename(path, '.rb').split('_').map(&:capitalize).join(''))
  end
end

Instance Method Details

#call(env) ⇒ Object

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.



276
277
278
# File 'lib/rack/builder.rb', line 276

def call(env)
  to_app.call(env)
end

#freeze_appObject

Freeze the app (set using run) and all middleware instances when building the application in to_app.



259
260
261
# File 'lib/rack/builder.rb', line 259

def freeze_app
  @freeze_app = true
end

#map(path, &block) ⇒ Object

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.

class App
  def call(env)
    [200, {'content-type' => 'text/plain'}, ["Hello World"]]
  end
end

class Heartbeat
  def call(env)
    [200, { "content-type" => "text/plain" }, ["OK"]]
  end
end

app = Rack::Builder.app do
  map '/heartbeat' do
    run Heartbeat.new
  end
  run App.new
end

run app

The use method can also be used inside the block to specify middleware to run under a specific path:

app = Rack::Builder.app do
  map '/heartbeat' do
    use Middleware
    run Heartbeat.new
  end
  run App.new
end

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.



252
253
254
255
# File 'lib/rack/builder.rb', line 252

def map(path, &block)
  @map ||= {}
  @map[path] = block
end

#run(app = nil, &block) ⇒ Object

Takes a block or argument that is an object that responds to #call and returns a Rack response.

You can use a block:

run do |env|
  [200, { "content-type" => "text/plain" }, ["Hello World!"]]
end

You can also provide a lambda:

run lambda { |env| [200, { "content-type" => "text/plain" }, ["OK"]] }

You can also provide a class instance:

class Heartbeat
  def call(env)
   [200, { "content-type" => "text/plain" }, ["OK"]]
  end
end

run Heartbeat.new

Raises:

  • (ArgumentError)


193
194
195
196
197
# File 'lib/rack/builder.rb', line 193

def run(app = nil, &block)
  raise ArgumentError, "Both app and block given!" if app && block_given?

  @run = app || block
end

#to_appObject

Return the Rack application generated by this instance.



264
265
266
267
268
269
270
271
# File 'lib/rack/builder.rb', line 264

def to_app
  app = @map ? generate_map(@run, @map) : @run
  fail "missing run or map statement" unless app
  app.freeze if @freeze_app
  app = @use.reverse.inject(app) { |a, e| e[a].tap { |x| x.freeze if @freeze_app } }
  @warmup.call(app) if @warmup
  app
end

#use(middleware, *args, &block) ⇒ Object

Specifies middleware to use in a stack.

class Middleware
  def initialize(app)
    @app = app
  end

  def call(env)
    env["rack.some_header"] = "setting an example"
    @app.call(env)
  end
end

use Middleware
run lambda { |env| [200, { "content-type" => "text/plain" }, ["OK"]] }

All requests through to this application will first be processed by the middleware class. The call method in this example sets an additional environment key which then can be referenced in the application if required.



159
160
161
162
163
164
165
# File 'lib/rack/builder.rb', line 159

def use(middleware, *args, &block)
  if @map
    mapping, @map = @map, nil
    @use << proc { |app| generate_map(app, mapping) }
  end
  @use << proc { |app| middleware.new(app, *args, &block) }
end

#warmup(prc = nil, &block) ⇒ Object

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)
  client.get('/')
end

use SomeMiddleware
run MyApp


209
210
211
# File 'lib/rack/builder.rb', line 209

def warmup(prc = nil, &block)
  @warmup = prc || block
end