messua/mware

Some basic middlware layers.

See the documentation for Layer and even more so the Stack example for demonstrations of how to put these together.

Types

For specifying where the log_layer() should output its logs to.`

pub type LogTarget {
  Stdout
  Stderr
  File(path: String)
}

Constructors

  • Stdout

    The standard output.

  • Stderr

    The standard error stream.

  • File(path: String)

    Append to the file at the given path.

Functions

pub fn make_blacklist_layer(
  addrs: List(IpAddr),
) -> fn(
  MRequest(a),
  fn(MRequest(a)) -> Result(Response(ResponseData), Err),
) -> Result(Response(ResponseData), Err)

Return a layer that rejects requests from all listed addresses with an unceremonious 404.

You can use handle.parse_ip() to help read a list of strings (like from a configuration file).

Plans for a future version include a way to specify a range of IP addresses.

Examples

import gleam/list
import gleam/result
import messua/handle
import messua/mware

let assert Ok(blacklist) = [
  "173.194.219.100",
  "173.194.219.101",
  "173.194.219.102",
  "173.194.219.113",
  "173.194.219.138",
  "173.194.219.139",
]
|> list.map(handle.parse_ip)
|> result.all()

let blacklist_layer = mware.make_blacklist_layer(blacklist)
pub fn make_compression_layer(
  min_size: Int,
) -> fn(
  MRequest(a),
  fn(MRequest(a)) -> Result(Response(ResponseData), Err),
) -> Result(Response(ResponseData), Err)

Return a Layer that compresses outgoing responses if they are at least min_size bytes and the incoming request contained an Accept-Encoding: deflate header.

Like ok.compress_body(), this does not do anything to responses generated with ok.serve_file() or ok.serve_dir().

pub fn make_log_layer(
  target: LogTarget,
) -> fn(
  MRequest(a),
  fn(MRequest(a)) -> Result(Response(ResponseData), Err),
) -> Result(Response(ResponseData), Err)

Return a Layer function that logs to the specified target.

Currently, logging to a file opens the file for append each time. This is clearly not the most efficient method, but I don’t know enough Erlang yet to do better.

TODO: Learn some Erlang and do better.

Safety

If you specify a path that cannot be written to, the server will crash upon startup.

pub fn make_rate_limit_layer(
  requests: Int,
  over_seconds: Int,
  cull_expired_counters_every: Int,
) -> fn(
  MRequest(a),
  fn(MRequest(a)) -> Result(Response(ResponseData), Err),
) -> Result(Response(ResponseData), Err)

Return a layer that limits requests from any given IP address to requests requests every over_seconds seconds. Excess requests will be rejected with a a 429.

10 requests every two seconds and 20 requests every four seconds both limit to the same average rate (one request every 200 ms), but the latter setting allows for larger “bursts” of requests (with, of course, a correspondingly longer cooldown).

The cull_expired_counters_every parameter controls how often the layer will crawl through and throw away records from clients that haven’t send a request in over_seconds seconds or more.

Examples

import messua
import messua/mware
import messua/rr

fn inner_handler(req: rr.MRequest) -> rr.MResponse {
  // Your routing and business logic goes here.
}

// 2 requests/second, with bursts up to 12 requests.
let limit_layer = mware.make_rate_limit_layer(12, 6, 1024)

// Wrap our `inner_handler` in our `limit_layer` to make our
// composite handler.
let handler = limit_layer(_, inner_handler)

messua.default()
  |> messua.start(handler)
pub fn make_static_header_layer(
  headers: List(#(String, String)),
) -> fn(
  MRequest(a),
  fn(MRequest(a)) -> Result(Response(ResponseData), Err),
) -> Result(Response(ResponseData), Err)

Adds the given list of #(name, value) headers to each response.

Search Document