messua/layer

A Layer is a Handler function with another “inner” handler (which itself might be another layer). This is the natural format for “middleware”.

The layer function may pre-process or alter the request before passing it to the inner handler; it may process or alter the response returned from the inner handler; it may also choose to generate its own response and not even call the inner handler.

The stack() function can be used to compose multiple layers; here’s an example which may help convey the structure.

Dramatis functionae:

But in this example, we will just set each up to log a message to the web server’s console, so we can see in which order things happen.

import messua/handle.{type Handler}
import messua/minc.{type Incoming}
import messua/mout.{type Outgoing}


fn blacklist(in: Incoming(Nil), inner: Handler(Nil)) -> Outgoing {
  io.println("blacklist layer receives Incoming")
  let out = inner(in)
  io.println("blacklist layer returns Outgoing")
  out
}

fn auth(in: Incoming(Nil), inner: Handler(Nil)) -> Outgoing {
  io.println("auth layer receives Incoming")
  let out = inner(in)
  io.println("auth layer returns Outgoing")
  out
}

fn handler(_in: Incoming(Nil)) -> Outgoing {
  io.println("handler processes Incoming and returns Outgoing")
  mout.ok()
}

We set up and listen with our handler stack function thus:

pub fn main() {
  let handler_stack = layer.stack(handler, [auth, blacklist])

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

And when the server fields a request, it prints this out to the console:

blacklist layer receives Incoming
auth layer receives Incoming
handler processes Incoming and returns Outgoing
auth layer returns Outgoing
blacklist layer returns Outgoing

So you can see the layered structure of the request handler is such:

       |                      ^
    Incoming              Outgoing
       v                      |
  ----------------------------------
  |          blacklist()           |
  | pre-handling     post-handling |
  ----------------------------------
       |                      ^
    Incoming              Outgoing
       v                      |
  ----------------------------------
  |             auth()             |
  | pre-handling     post-handling |
  ----------------------------------
       |                      ^
    Incoming              Outgoing
       v                      |
  ----------------------------------
  |    |      handler()       ^    |
  |    |                      |    |
  |    ---> innermost logic ---    |
  ----------------------------------

And of course, at any point a layer could choose to return a response without passing the incoming request to the layer below. To simulate that, we rewrite our auth() function to simulate authentication failure.


fn auth(_in: Incoming(Nil), _inner: Handler(Nil)) -> Outgoing {
  io.println("auth layer receives Incoming")
  io.println("authentication error!")
  mout.fail()
  |> mout.with_status(403)
}

So the innermost handler function never gets called:

blacklist layer receives Incoming
auth layer receives Incoming
authentication error!
blacklist layer returns Outgoing

Types

A handler function with its own innner handler function. The Layer is free to pass the (possibly modified) Incoming request to this inner function, or generate a response of its own.

pub type Layer(state) =
  fn(
    minc.Incoming(state),
    fn(minc.Incoming(state)) -> Result(
      response.Response(mist.ResponseData),
      fail.Failure,
    ),
  ) -> Result(response.Response(mist.ResponseData), fail.Failure)

Values

pub fn make_blacklist_layer(
  addrs: List(addr.IpAddr),
) -> fn(
  minc.Incoming(state),
  fn(minc.Incoming(state)) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
) -> Result(response.Response(mist.ResponseData), fail.Failure)

Return a Layer that will automatically reject (with a 404) any request from any of the specified addrs.

This is just the simplest thing that could function, and doesn’t make any affordances for specifying IP ranges or looking at any other feature of the incoming request.

pub fn make_static_header_layer(
  headers: List(#(String, String)),
) -> fn(
  minc.Incoming(state),
  fn(minc.Incoming(state)) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
) -> Result(response.Response(mist.ResponseData), fail.Failure)

Return a Layer that will append a series of #(name, value) headers to any outgoing reply.

pub fn stack(
  base: fn(minc.Incoming(state)) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
  layers: List(
    fn(
      minc.Incoming(state),
      fn(minc.Incoming(state)) -> Result(
        response.Response(mist.ResponseData),
        fail.Failure,
      ),
    ) -> Result(
      response.Response(mist.ResponseData),
      fail.Failure,
    ),
  ),
) -> fn(minc.Incoming(state)) -> Result(
  response.Response(mist.ResponseData),
  fail.Failure,
)

Combine a stack of Layers and an innermost Handler function into a single, combined, handler function.

The ordering of the layers list is such that the last function in the list is the first to receive the incoming request, and the last to return the outgoing response.

pub fn update_incoming(
  in: minc.Incoming(state),
  update: fn(request.Request(mist.Connection)) -> request.Request(
    mist.Connection,
  ),
) -> minc.Incoming(state)

Update or alter the incoming request.

Search Document