Malachi.Console.HeaderDeadline (malachi v0.22.7)

View Source

One deadline for the request line and the whole header block of every request on a console connection, the guarantee Malachi.HTTP.Limits.recv_timeout_ms/0 describes and the dashboard keeps.

Bandit only has a per-read timeout, restarted on every chunk it reads, so a client that sends one header byte just inside it holds a connection for as long as the header limits allow, which is weeks (slowloris). This module closes that gap without changing Bandit:

  • It is the Thousand Island handler of the console endpoint, delegating every callback to Bandit's own delegating handler. When a connection starts, it spawns a watchdog process for it and registers the watchdog under the connection's pid in registry/0.
  • The watchdog is armed from the start: if the deadline passes, it closes the socket. A connection blocked reading headers sees its read fail and ends; one waiting between keep alive requests receives the exit of its own socket and stops with {:shutdown, :header_deadline}.
  • Malachi.Console.Router disarms it when a request reaches the plug, which is when every header has been read, and arms it again once the response has been sent, so the idle wait for the next request and that request's headers share one deadline, whether the next request arrives later or was already pipelined behind this one.

The watchdog is registered in a Registry rather than the process dictionary because Bandit clears the dictionary between keep alive requests. It monitors its connection and exits with it.

Only HTTP/1 is served (see Malachi.Console.Endpoint): under HTTP/2 a request runs in a process of its own, so the plug could not disarm the connection's watchdog.

Summary

Functions

Starts the calling connection's deadline again, if it has one. A response has been sent.

Returns a specification to start this module under a supervisor.

Stops the calling connection's deadline, if it has one. A request has finished its headers.

The name of the Registry the watchdogs are registered in, keyed by connection pid.

Functions

arm()

@spec arm() :: :ok

Starts the calling connection's deadline again, if it has one. A response has been sent.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

disarm()

@spec disarm() :: :ok

Stops the calling connection's deadline, if it has one. A request has finished its headers.

registry()

@spec registry() :: atom()

The name of the Registry the watchdogs are registered in, keyed by connection pid.

start_link(arg)

@spec start_link({handler_options :: term(), GenServer.options()}) ::
  GenServer.on_start()