module Bezel::Plug
A step of an endpoint's pipeline: takes the connection and returns it
changed, as Elixir's Plug does. A plug that answers the request calls
Conn#halt (or one of Conn#text, Conn#html, Conn#json, Conn#redirect,
Conn#send_resp, which halt), and the plugs after it do not run.
Plugs are Shareable values, made once when the endpoint starts and
used by every request: their settings are fields, their state per
request goes in the Conn.
A plug that must act after the rest of the pipeline (logging the
status, a time limit) overrides wrap, which is given the rest as a
procedure. The rest does not raise: an exception a later plug raised
comes back as the connection's Conn#error (the connection halted),
which wrap may inspect, or replace by clearing error and answering
itself (out.with(error: nil).html(page, status: 404)); an endpoint's
"no route" Bezel::NotFound comes back so too.
data Hello(name: String)
include Bezel::Plug
def call(conn: Bezel::Conn) -> Bezel::Conn = conn.text("hello #{name}")
end
conn = Bezel::Plug.run([Hello.new("you")], Bezel::Conn.build(HTTP::Server::Request.get("/")))
conn.to_response.body # => HTTP::Content::Text(text: "hello you")
Classes and modules
Bezel::Plug::AcceptsChooses the response's format among formats (["html", "json"]) and sets Conn#format (:html): the _format parameter when there is one, else the request's Accept (its media ranges by their quality; */* or no Accept takes the first of formats). A request that accepts none of them, or names another _format, is Bezel::NotAcceptable (406).Bezel::Plug::CSRFProtects a browser pipeline against cross-site request forgery (after Plug::Session):Bezel::Plug::FlashCarries the flash between requests in the session (after Plug::Session): the messages a request put (Conn#put_flash) are the next request's conn.flash[:kind], and then gone. Messages shown in the same request use Bezel::Flash#now.Bezel::Plug::HeadRuns a HEAD request as a GET, so that the routes and actions of GET answer it (the server sends no body for it).Bezel::Plug::HostAuthorizationRefuses a request whose Host header is not one of the application's hosts (a 403), against DNS rebinding and links built from a forged Host (Conn#url). A host is a name (shop.example), a name with a leading dot for it and its subdomains (.shop.example), or an address; the port is ignored and case does not matter.Bezel::Plug::LoggerLogs one line per request, once the rest of the pipeline has answered, with the status the client gets (Conn#final_status: 404 when nothing answered, the error's when a plug raised), the method and path as this plug received them (before Plug::Head or Plug::MethodOverride change the method), and the request's id when Plug::RequestId ran before it: to standard error, or to on_event when given (for structured logs). Lines are written at the info level, so a LOG_LEVEL of warn or error silences them. The path is written percent-decoded, as Conn#path; control characters in it are escaped, so that a request cannot forge a line.Bezel::Plug::MethodOverrideLets an HTML form send PUT, PATCH or DELETE: a POST whose body has _method set to one of them (in any case) gets that method. It reads the body's parameters, so it comes after Bezel::Plug::Parsers; the query's _method is not believed (a link could carry it).Bezel::Plug::ParsersReads the request's body into its parameters (Conn#params, and Conn#body_params alone) by its Content-Type:Bezel::Plug::RequestIdGives each request an id: the request's X-Request-Id when it is a sane one (20 to 200 characters of letters, digits, +, /, =, _ and -, as a proxy in front makes), else a new random one. The id is Conn#request_id and goes back in the response's X-Request-Id, so that a log line and a client's report can be matched.Bezel::Plug::SecureHeadersSets the response headers that keep browsers from misreading or framing the application's pages: X-Content-Type-Options: nosniff, X-Frame-Options (SAMEORIGIN by default), Referrer-Policy (strict-origin-when-cross-origin), and, when given, a Content-Security-Policy and Strict-Transport-Security (hsts:, a max-age in seconds; only for an application served over HTTPS). A header the application sets itself later in the pipeline wins.Bezel::Plug::SessionLoads the request's session from store (conn.session) and, once the rest of the pipeline has answered, saves it when it changed (a cookie store sets its cookie). Not saved when a plug raised: the error page leaves the session as it was (so a first visit's CSRF secret is made again on the next request).Bezel::Plug::StaticServes the files under the directory from at the path at (at: "/", from: "priv/static" serves priv/static/app.css as /app.css), for GET and HEAD; any other request, or a path with no such file, goes on to the next plug. A directory is never listed.Bezel::Plug::TimeoutGives the rest of the pipeline limit to answer a request; past it, the rest is cancelled (at its next wait: a computation that never waits runs to its end first) and the request is answered 503 Service Unavailable (Bezel::TimeoutError).
Class methods
def self.run(plugs: Array[Bezel::Plug], conn: Bezel::Conn) -> Bezel::Conn
Runs plugs on conn in order, until one halts. A StandardError
a plug raises stops the pipeline: the result is the connection as
that plug received it (with what the plugs before it did: the
request id, headers), halted, the exception its error.
def self.run(plugs: Array[Bezel::Plug], conn: Bezel::Conn, rest: (Bezel::Conn) -> Bezel::Conn) -> Bezel::Conn
Runs plugs on conn in order, then rest, until one halts: the
plugs' wraps are around rest too.
Methods
abstract def call(conn: Bezel::Conn) -> Bezel::Conn
The connection changed by this plug.
def wrap(conn: Bezel::Conn, rest: (Bezel::Conn) -> Bezel::Conn) -> Bezel::Conn
Runs this plug, then rest (the plugs after it) unless it halted.
Override it to act around the rest: its result is the request's.