Writing the response
What a route may return, and the helpers that build it:
render,redirect,halt,json,status.
A route block returns its response. There is no render-and-return-nil, no implicit template lookup, and no
response object to mutate.
What a block may return
| Return | Becomes |
|---|---|
String | 200 text/html; charset=utf-8 with that body |
render :name, ... | a String (the rendered page) — same as above |
json(value) / text(s) / html(s) | a Melee::Response with that content type |
Melee::Response.stream { |out| ... } | a chunked response |
| anything else | to_s, sent as HTML |
redirect and halt do not return: they unwind the block immediately.
get "/notes" do
render :notes, notes: db.query("SELECT id, text FROM notes ORDER BY id")
end
status and header
get "/feed.xml" do
status 200
header "Cache-Control", "no-store"
text db.first("SELECT body FROM feed")["body"].to_s
end
status(Integer) and header(name, value) set the status and headers of the response the block is about to
return. Both stringify the value. They apply to a returned String, render, json, text and html.
They do not apply to redirect or halt, which build their own response and ignore anything set
earlier. A redirect therefore carries only Location and Content-Length.
json, text, html
get "/api/notes" do
json({ "notes" => db.query("SELECT id, text FROM notes") })
end
| Call | Returns | Content-Type |
|---|---|---|
json(value) | Melee::Response | application/json (JSON.generate(value)) |
text(string) | Melee::Response | text/plain; charset=utf-8 |
html(string) | Melee::Response | text/html; charset=utf-8 |
json accepts Hashes, Arrays, Strings, Integers, Floats, true, false, nil. Not your own classes:
build a Hash first.
All three take an optional type: to override the content type, for the formats that are text under another
name:
get "/card.vcf" do
text build_vcard, type: "text/vcard; charset=utf-8"
end
redirect
post "/notes" do
db.run "INSERT INTO notes (text) VALUES (?)", params.fetch(:text)
redirect "/notes"
end
redirect(to) sends 303 See Other with a Location header and an empty body. Always 303, for every
method. redirect back uses the Referer header, falling back to "/" — back is a plain method
returning a String.
halt
get "/d/:token" do
halt 404 unless secure_equal?(params.fetch(:token), setting("display_token"))
render :display, layout: false
end
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
halt(status = 200, body = "", type: nil) stops the request with a text/plain response carrying body,
or type: if you give one. It works inside a route, inside a before filter, and inside any method a route
calls. It discards a status or header set earlier in the request.
Streaming
get "/export.csv" do
Melee::Response.stream(content_type: "text/csv; charset=utf-8",
headers: { "Content-Disposition" => "attachment; filename=notes.csv" }) do |out|
out << "id,text\n"
db.query("SELECT id, text FROM notes").each { |r| out << "#{r["id"]},#{r["text"]}\n" }
end
end
Melee::Response.stream(content_type: "text/plain; charset=utf-8", status: 200, headers: {}) { |out| ... }
returns a Melee::Response. out accepts << with a String and nothing else. The headers set with
header are not merged into a streamed response — pass them in the headers: keyword instead. An exception
raised inside the block ends the response early and is logged; the client has already had a 200. Under
melee dev the whole stream is buffered before anything is sent.
Errors
An exception that escapes the block is logged and turned into a 500 — either your error handler’s output
or Something went wrong. See app.md.
What is not here
There is no send_file: static files belong in public/, which the server sends without waiting for the
app. There is no content_type setter (use type: on text/html/json, or header "Content-Type", ...),
no attachment, no last_modified/etag, and no cookie API beyond session — set one with
header "Set-Cookie", ... if you must.