mist

A (hopefully) nice, pure Gleam web server

Installation

This package can be added to your Gleam project:

gleam add mist

and its documentation can be found at https://hexdocs.pm/mist.

Usage

Right now there are a few options. Let’s say you want a “simple” HTTP server that you can customize to your heart’s content. In that case, you want:

import mist
import gleam/bit_builder
import gleam/erlang/process
import gleam/http/response

pub fn main() {
  assert Ok(_) =
    mist.run_service(
      8080,
      fn(_req) {
        response.new(200)
        |> response.set_body(bit_builder.from_bit_string(<<
          "hello, world!":utf8,
        >>))
      },
      max_body_limit: 4_000_000
    )
  process.sleep_forever()
}

Maybe you also want to work with websockets. Maybe those should only be upgradable at a certain endpoint. For that, you can use handler.with_func. The websocket methods help you build a handler with connect/disconnect handlers. You can use these to, for example, track connected clients. For example:

import mist
import gleam/bit_builder
import gleam/erlang/process
import gleam/http.{Get, Post}
import gleam/http/request
import gleam/http/response
import gleam/result
import glisten/socket/transport
import mist/handler.{Response, Upgrade}
import mist/http.{BitBuilderBody}
import mist/websocket

pub fn main() {
  assert Ok(_) =
    mist.serve(
      8080,
      handler.with_func(fn(req) {
        case req.method, request.path_segments(req) {
          Get, ["echo", "test"] ->
            websocket.echo_handler
            |> websocket.with_handler
            |> Upgrade
          Post, ["echo", "body"] ->
            req
            |> http.read_body(transport.tcp())
            |> result.map(fn(req) {
              response.new(200)
              |> response.set_body(BitBuilderBody(bit_builder.from_bit_string(
                req.body,
              )))
              |> response.prepend_header(
                "content-type",
                request.get_header(req, "content-type")
                |> result.unwrap("application/octet-stream"),
              )
            })
            |> result.unwrap(
              response.new(400)
              |> response.set_body(BitBuilderBody(bit_builder.new())),
            )
            |> Response
          Get, ["home"] ->
            response.new(200)
            |> response.set_body(BitBuilderBody(bit_builder.from_bit_string(<<
              "sup home boy":utf8,
            >>)))
            |> Response
          _, _ ->
            response.new(200)
            |> response.set_body(BitBuilderBody(bit_builder.from_bit_string(<<
              "Hello, world!":utf8,
            >>)))
            |> Response
        }
      }),
    )
  process.sleep_forever()
}

You might also want to use SSL. You can do that with the following options.

With run_service_ssl:

import mist
import gleam/bit_builder
import gleam/erlang/process
import gleam/http/response

pub fn main() {
  assert Ok(_) =
    mist.run_service_ssl(
      port: 8080,
      certfile: "/path/to/server.crt",
      certkey: "/path/to/server.key",
      handler: fn(_req) {
        response.new(200)
        |> response.set_body(bit_builder.from_bit_string(<<
          "hello, world!":utf8,
        >>))
      },
      max_body_limit: 4_000_000
    )
  process.sleep_forever()
}

With serve_ssl:

pub fn main() {
  assert Ok(_) =
    mist.serve_ssl(
      port: 8080,
      certfile: "...",
      certkey: "...",
      handler.with_func(fn(req) {
        todo
      }
    )
  // ...
}

There is some initial support for sending files as well:

import gleam/bit_builder
import gleam/bit_string
import gleam/erlang/process
import gleam/http/request.{Request}
import gleam/http/response
import gleam/int
import gleam/string
import mist
import mist/file
import mist/handler.{Response}
import mist/http.{BitBuilderBody, Body, FileBody}

pub fn main() {
  assert Ok(_) =
    mist.serve(
      8080,
      handler.with_func(fn(req: Request(Body)) {
        case request.path_segments(req) {
          ["static", ..path] -> {
            // verify, validate, etc
            let file_path =
              path
              |> string.join("/")
              |> string.append("/", _)
              |> bit_string.from_string
            let size = file.size(file_path)
            assert Ok(fd) = file.open(file_path)
            response.new(200)
            |> response.set_body(FileBody(fd, int.to_string(size), 0, size))
            |> Response
          }
          _ ->
            response.new(404)
            |> response.set_body(BitBuilderBody(bit_builder.new()))
            |> Response
        }
      }),
    )
  process.sleep_forever()
}

If you need something a little more complex or custom, you can always use the helpers exported by the various glisten/mist modules.

Benchmarks

These are currently located here