messua/rr

Requests and Responses.

Types

Naturally, a function that accepts an MRequest and returns an MResponse is considered a request Handler.

pub type Handler(t) =
  fn(MRequest(t)) -> MResponse

A Layer is a Handler with another “inner” Handler it can optionally pass an incoming request through. This inner handler may itself be another layer; it is possible to form stacks arbitrarily deep this way (see the documentation for stack for an example of this).

(The inspiration for this particular type is the tower-layer Rust crate.)

Examples

A layer that logs requests to stdout might look like this:

import gleam/int
import gleam/io
import gleam/string_builder

import gleam/http

fn handler(req: MRequest(State)) -> MResponse {
// ...Routing and handling goes here.  
}

fn log_layer(req: MRequest(State), next: Handler(State)) -> MResponse {
  // Get source, method, path from request.
  let source = case info(req) {
    Ok(info) -> string_builder.from_strings([
      string.inspect(info.ip_address), ":", int.to_string(info.port)
    ])
    Error(_) -> string_builder.from_string("[ source ??? ]")
  }
  
  let inner_req = inner(req)
  let url = string_builder.from_strings([
    string.inspect(inner_req.method),
    " ",
    string.inspect(inner_req.path)
  ])

  // Call inner handling function, get response.
  let mresp = next(req)

  // Note response status.
  let result_msg = case mresp {
    Ok(resp) -> string_builder.from_string(int.to_string(resp.status))
    Err(e) -> string_builder.from_string(int.to_string(e.status))
  }

  // Print a request log entry with the source, method, path fo the
  // request and the status code of the associated response.
  string_builder.append(source, " ")
  |> string_builder.append_builder(url)
  |> string_builder.append(" ")
  |> string_builder.append(result_msg)
  |> string_builder.to_string()
  |> io.println()

  // Return the response.
  mresp
}

messua.default()
|> messua.start(log_layer(_, handler))
pub type Layer(t) =
  fn(MRequest(t), Handler(t)) -> MResponse

A request with some user-defined server state.

You should never have to manually construct these; they will be constructed by the function returned by make_mist_handler, below.

pub opaque type MRequest(t)

A result type with Errors that will automatically become responses.

pub type MResponse =
  Result(response.Response(mist.ResponseData), err.Err)

Functions

pub fn info(req: MRequest(a)) -> Result(ConnectionInfo, Nil)

Get the underlying connection’s ConnectionInfo.

pub fn inner(req: MRequest(a)) -> Request(Connection)

Access the underlying Request.

pub fn make_mist_handler(
  state: a,
  handle: fn(MRequest(a)) -> Result(Response(ResponseData), Err),
) -> fn(Request(Connection)) -> Response(ResponseData)
pub fn stack(
  base: fn(MRequest(a)) -> Result(Response(ResponseData), Err),
  layers: List(
    fn(
      MRequest(a),
      fn(MRequest(a)) -> Result(Response(ResponseData), Err),
    ) -> Result(Response(ResponseData), Err),
  ),
) -> fn(MRequest(a)) -> Result(Response(ResponseData), Err)

Compose a stack of nested Layers into a Handler that can be passed to messua.start().

The front of the list is the “bottom” of the stack; the end of the list is the top.

This example is long, but it shows how to stack up some middleware:

import messua
import messua/errs
import messua/handle.{type SockAddr}
import messua/ok
import messua/rr.{type Handler, type MRequest, type MResponse}

fn auth_key_is_valid(key: String) -> Bool {
  todo
  // Implementation details elided.
}

fn sock_addr_is_blacklisted(addr: SockAddr) -> Bool {
  todo
  // Implementation details elided.
}

fn innermost_handler(req: MRequest) -> MResponse {
  todo
  // All your business routing and handling goes here.
}

fn auth_layer(req: MRequest, inner: Handler) -> MResponse {
  use auth_key <- handle.require_header("authorization")
  case auth_key_is_valid(auth_key) {
    True -> inner(req)
    False -> errs.unauthorized() |> Error()
  }
}

fn add_headers_layer(req: MRequest, inner: Handler) -> MResponse {
  inner(req)
  |> ok.with_header("server", "super-cool-messua-powered-thing/v1.0")
}

fn blacklist_layer(req: MRequest, inner: Handler) -> MResponse {
  use client_addr <- handle.require_client(req)
  case sock_addr_is_blacklisted(client_addr) {
    True -> errs.not_found() |> Error()
    False -> next(req)
  }
}

fn main() {
  let middleware_stack = [auth_layer, add_headers_layer, blacklist_layer]

  let handler = rr.stack(innermost_handler, middleware_stack)

  messua.default()
  |> messua.start(handler)
}

The request flow is thus: Request Flow

pub fn state(req: MRequest(a)) -> a

Retrieve the state that’s been injected into the MRequest.

Search Document