In a Rack config.ru, what do use, run and map do, and in which order does a request pass through the middleware?
answer
- config.ru is evaluated inside Rack::Builder
- use stacks, run sets the endpoint
- first use is the outermost layer
- map builds a Rack::URLMap
- SCRIPT_NAME grows, PATH_INFO shrinks
basics
~20 sconfig.ru is Ruby evaluated inside a Rack::Builder: use adds a middleware, run sets the innermost app, and map mounts a sub-app under a path prefix. The first use line is the outermost layer, so it sees the request first and the response last.
solid answer
~40 sPuma loads `config.ru` through `Rack::Builder.parse_file`, which evaluates the file inside a Builder instance. `use Klass, *args` records a middleware; `run app` (or `run { |env| ... }` since Rack 3) sets the endpoint; `map "/admin" do ... end` mounts a nested Builder under a prefix through `Rack::URLMap`. `to_app` wraps the endpoint in the `use` entries in reverse, so the first `use` line becomes the outermost object: the request flows top to bottom and the response unwinds bottom to top. `map` moves the matched prefix into `SCRIPT_NAME` and leaves the rest in `PATH_INFO`, trying the longest prefix first and answering 404 with `x-cascade: pass` when nothing matches. A file with neither `run` nor `map` fails with "missing run or map statement".
code
ruby · 15 lines# config.ru
require "rack"
require_relative "app"
require_relative "admin_app"
use Rack::Runtime # outermost: sees request first, response last
use Rack::ContentLength
map "/admin" do
run AdminApp.new # SCRIPT_NAME "/admin", PATH_INFO "/users"
end
map "/" do
run App.new
endgo deeper
Recall the three words: use adds a middleware, run sets the app that answers, map mounts an app under a path.
Explain how Rack::Builder turns the file into one nested object, why the first use line is outermost, and what SCRIPT_NAME and PATH_INFO look like inside a mapped app.
Reason about stack order in production: where error reporting, timing, static files and authentication go, and why building the stack once matters.
Treat config.ru as the composition root: decide which cross-cutting concerns live in Rack middleware rather than framework code, and keep the order documented so later additions do not silently bypass them.
## config.ru is Ruby inside a Builder `config.ru` is the conventional entry point of a Rack application. Puma reads `config.ru` by default, and the `rackup` executable - shipped in the separate `rackup` gem since Rack 3 - is the other common launcher. Puma hands the file to **`Rack::Builder.parse_file`**, which reads it and evaluates it with the binding of a fresh `Rack::Builder` instance. That is why `use`, `run`, `map`, `warmup` and `freeze_app` can be called bare: they are **instance methods of `Rack::Builder`**. Everything else in the file is plain Ruby - `require_relative`, constants, conditionals on `ENV`. Two details of the loader: a trailing `__END__` section is stripped, and a line starting `#\` (the old way of passing server options) now raises an error instead of being parsed. Since Rack 3, `config.ru` no longer gets Rack's modules loaded for free; write `require "rack"` or require the specific file, such as `require "rack/files"`. ## The DSL, one method at a time | Method | What it records | Returns (Rack 3.2) | |---|---|---| | `use Klass, *args, &blk` | a proc that will call `Klass.new(inner_app, *args, &blk)` | `nil` | | `run app` / `run { \|env\| ... }` | the innermost app; passing both an argument and a block raises `ArgumentError` | `nil` | | `map "/prefix" { ... }` | a block evaluated in a nested Builder, mounted with `Rack::URLMap` | `nil` | | `warmup { \|app\| ... }` | a callable run once with the finished app, e.g. to preload | - | | `freeze_app` | freeze the app and every middleware instance after building | - | ## The order: an onion built from the bottom `Rack::Builder#to_app` takes the endpoint (or the URLMap) and folds the recorded `use` procs over it **in reverse**: ```ruby use A use B run App # to_app builds A.new(B.new(App)) ``` 1. The server calls `A#call(env)` - the **first** `use` line is the **outermost** layer. 2. `A` calls `B`, `B` calls `App`. 3. `App` returns the triple to `B`, which returns (possibly edited) to `A`, which returns to the server. So the request passes the middleware **top to bottom** and the response passes them **bottom to top**. Practical consequences: - Put **exception reporters and timers** near the top, so they see everything below them. - Put middleware that **short-circuits** (static files, authentication, rate limits) as high as their dependencies allow, so rejected requests never reach the app. - A middleware that **depends on work another did** (for example reading a session another middleware loaded into `env`) must come **after** it. ## map and Rack::URLMap `map` blocks are collected and turned into a **`Rack::URLMap`**, which is itself a Rack app. For each request it: - tries the mapped locations **longest first**, so `/admin/reports` wins over `/admin`; - matches only on a path-segment boundary: `/admin` matches `/admin` and `/admin/users` but not `/administrator`; - moves the matched prefix into **`SCRIPT_NAME`** and leaves the remainder in **`PATH_INFO`** before calling the sub-app, so a request to `/admin/users` reaches the admin app with `SCRIPT_NAME` `"/admin"` and `PATH_INFO` `"/users"`; - can also match on host when a key is written as a full `http://host/path` URL; - returns `404` with `x-cascade: pass` when nothing matches. An app mounted this way must build links from `SCRIPT_NAME` plus its own path, or it will generate URLs without the prefix. ## How a server gets from the file to one object 1. Puma finds `config.ru` (its default rackup file) and calls `Rack::Builder.parse_file` on it. 2. For a `.ru` path, `load_file` reads the text, strips a UTF-8 byte-order mark and any `__END__` section, and evaluates it in a new Builder. 3. `to_app` returns the outermost object, runs a `warmup` callable if one was given, and freezes the stack if `freeze_app` was called. `parse_file` also accepts a `.rb` path: it requires the file and returns the constant named after it, so `hello_app.rb` yields `HelloApp`. ## Builder as an app, and building once `Rack::Builder` instances respond to `call`, but `Builder#call` runs `to_app` **on every request**, instantiating every middleware again. Use `Rack::Builder.app { ... }` or `Rack::Builder.new { ... }.to_app` to build the stack once. `parse_file` already returns the result of `to_app`. ## Common mistakes - A `use` line **after** the one that should wrap it, so an error reporter never sees an exception raised above it. - Assuming `map` passes the full path to the sub-app. - Forgetting `run` entirely, which fails with `missing run or map statement` when the app is built.
- Why is passing Rack::Builder.new { ... } itself to a server wasteful?`Rack::Builder#call` calls `to_app` every time, so every request instantiates the whole middleware stack again and throws it away. Build once with `Rack::Builder.app { ... }` or `.to_app`, and pass the resulting object. `parse_file` already returns a built app, so a normal `config.ru` load is unaffected.
- A sub-app mounted with map generates links that miss the /admin prefix. What is going on?`Rack::URLMap` moves the matched prefix into `SCRIPT_NAME` and leaves only the remainder in `PATH_INFO`. An app that builds URLs from `PATH_INFO` alone drops the mount point. Build links from `SCRIPT_NAME` plus the path, or use `Rack::Request#script_name` and `#path`, which combine both.
The stack is a set of nested envelopes: the first use line is the outer envelope, so it is opened first on the way in and sealed last on the way out.
saying these in an interview costs you the question
- Saying the last use line in config.ru is the first to see the request
- Believing map passes the full original path to the mounted app
- Treating config.ru as a YAML or INI file rather than Ruby
- Passing a Rack::Builder instance itself to the server as the app
- Assuming config.ru loads every Rack module without require "rack"