Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reading the request

Every helper that reads the request being served: params, session, headers, cookies, bodies.

Every helper here is a top-level method that reads the request being served. One request per process, so there is nothing to pass around and no argument to thread through.

params, form, query

get "/notes/:id" do
  id = params.fetch(:id).to_i
  "note #{id} sorted by #{params.fetch(:sort, "date")}"
end

params merges three sources, later ones winning: path params, then the query string, then urlencoded body fields. form is the body fields only; query is the query string only. All three return a Melee::Params, a Symbol-or-String view over a Hash{String => String}.

CallReturns
params[:name]String or nil — nil when the key is absent
params.fetch(:name)String, "" when absent — never nil, never raises
params.fetch(:name, "date")String, the default when absent
params.key?(:name)true / false
params.to_hHash{String => String}
`params.each {k, v
params.empty?true / false

The idiom. Use params.fetch(:x) whenever you want a String — it always gives one, so .to_i, .strip, .match? and interpolation are safe. Use params[:x] only when absent and empty must be told apart. Do not write params[:x].to_s; fetch already did that.

Values are always Strings. Convert explicitly: params.fetch(:n).to_i, params.fetch(:price).to_f. Validate before you trust:

post "/notes" do
  halt 400, "Date must be YYYY-MM-DD" unless params.fetch(:on_date).match?(/\A\d{4}-\d\d-\d\d\z/)
  db.run "INSERT INTO notes (on_date, text) VALUES (?, ?)", params.fetch(:on_date), params.fetch(:text)
  redirect "/notes"
end

Only application/x-www-form-urlencoded bodies are parsed into form. Multipart file uploads are not supported; a multipart body leaves form empty and stays available as request.body.

Values arrive already decoded. To go the other way — putting a value into a URL you build — use the two top-level helpers: url_encode(String) -> String and url_decode(String) -> String.

redirect "/search?q=#{url_encode(params.fetch(:q))}"

request

request returns the Melee::Request.

CallReturns
request.methodString, upper case: "GET", "POST", …
request.pathString, no query string
request.queryString, the raw query string, "" when there is none
request.hostString
request.schemeString, "http" or "https"
request.base_urlString, e.g. "https://kitchen.example.com"
request.remote_addrString, the client address
request.header("Accept")String or nil, case-insensitive
request.headersArray[[String, String]] in wire order
request.content_typeString, "" when the header is absent
request.bodyString, "" when there is no body
request.jsonthe parsed body; raises JSON::ParserError on bad input
request.cookiesHash{String => String}
request.get?, request.post?true / false
request.idInteger, the request id that appears in the logs

There is no request.ip; the name is request.remote_addr.

post "/webhook", csrf: false do
  payload = request.json
  halt 400 unless payload.is_a?(Hash)
  log.info "webhook", kind: payload["type"].to_s
  text "ok"
end

request.json returns whatever the body decoded to — Hash, Array, String, Integer, nil. Index hashes by String keys (payload["type"], not payload[:type]) and narrow with is_a? before doing arithmetic on a value.

session

session is a signed cookie holding Strings only.

post "/login" do
  if secure_equal?(params.fetch(:secret), ENV["ADMIN_SECRET"].to_s)
    session[:admin] = "1"
    redirect "/admin"
  else
    status 401
    render :login, error: "That secret is wrong."
  end
end
CallReturns
session[:user]String or nil
session[:user] = "ada"stores value.to_s; assigning nil deletes the key
session.delete(:user)same as assigning nil
session.clearempties it
session.to_hHash{String => String}
session.csrf_tokenString, minted on first read

The cookie is only rewritten when you touched the session. Details, size limits and the CSRF rules are in security.md.

Configuration

ENV["NAME"] reads a variable set with melee env NAME value. It is String or nil, so write ENV["NAME"].to_s. Melee.env("NAME", "default") returns the default when the variable is missing or empty. For values the app itself writes and reads back — tokens, last-run times — use setting (see database.md).