![ewe](https://raw.githubusercontent.com/vshakitskiy/ewe/v5/public/banner.jpg)

# 🐑 ewe

ewe [/juː/] - fluffy HTTP/1 and HTTP/2 web server for Gleam.

[![Package Version](https://img.shields.io/hexpm/v/ewe)](https://hex.pm/packages/ewe)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/ewe/)

## Contents

- [Installation](#installation)
- [Usage](#usage)
  - [Getting Started](#getting-started)
  - [HTTPS](#https)
  - [HTTP/2](#http2)
  - [Sending a Response](#sending-a-response)
  - [Reading the Request Body](#reading-the-request-body)
  - [Streaming Bodies](#streaming-bodies)
  - [Serving Files](#serving-files)
  - [Client Address](#client-address)
  - [WebSocket](#websocket)
  - [Server-Sent Events](#server-sent-events)
  - [Connection Limits and Timeouts](#connection-limits-and-timeouts)
  - [Running Under Supervision](#running-under-supervision)
  - [Running as an OTP Application](#running-as-an-otp-application)
- [Examples](#examples)
- [API Reference](#api-reference)

Most section headings are links, each one opening the runnable example it is
based on.

<h2 id="installation">Installation</h2>

```sh
gleam add ewe@5 gleam_erlang gleam_otp gleam_http logging
```

<h2 id="usage">Usage</h2>

<h3 id="getting-started"><a target="_blank" href="https://github.com/vshakitskiy/ewe/blob/v5/examples/src/getting_started.gleam">Getting Started</a></h3>

A handler takes a [`request.Request(ewe.Connection)`](https://hexdocs.pm/ewe/ewe.html#Connection)
and returns a [`response.Response(ewe.Body)`](https://hexdocs.pm/ewe/ewe.html#Body).
The connection carried by the request is what [`ewe.read_body`](https://hexdocs.pm/ewe/ewe.html#read_body),
[`ewe.file`](https://hexdocs.pm/ewe/ewe.html#file) and [`ewe.websocket`](https://hexdocs.pm/ewe/ewe.html#websocket)
work on.

Instead of a port you can bind a unix domain socket with [`ewe.unix`](https://hexdocs.pm/ewe/ewe.html#unix),
or let the OS pick a free port with [`ewe.listening_random`](https://hexdocs.pm/ewe/ewe.html#listening_random)
and ask for the one it picked with [`ewe.get_server_info`](https://hexdocs.pm/ewe/ewe.html#get_server_info).


```gleam
import ewe
import gleam/erlang/process
import gleam/http/request
import gleam/http/response
import logging

pub fn main() {
  logging.configure()
  logging.set_level(logging.Info)

  // The acceptor pool wires the listener and the connection factory together
  // through process names. Create them where your program starts and pass them
  // in here.
  //
  let listener_name = process.new_name("listener_name")
  let connection_factory_name = process.new_name("connection_factory_name")

  let assert Ok(_) =
    ewe.new(listener_name:, connection_factory_name:, handler: handle_request)
    |> ewe.bind(to: "0.0.0.0")
    |> ewe.listening(on: 8080)
    |> ewe.start

  process.sleep_forever()
}

fn handle_request(
  _request: request.Request(ewe.Connection),
) -> response.Response(ewe.Body) {
  // When sending a body it is important to include a `content-type` header.
  // You never set `content-length` or `transfer-encoding` yourself, ewe frames
  // the response and writes them for you.
  //
  response.new(200)
  |> response.set_header("content-type", "text/plain; charset=utf-8")
  |> response.set_body(ewe.Text("Hello, World!"))
}
```

<h3 id="https"><a target="_blank" href="https://github.com/vshakitskiy/ewe/blob/v5/examples/src/https.gleam">HTTPS</a></h3>

Enable TLS with [`ewe.with_tls`](https://hexdocs.pm/ewe/ewe.html#with_tls), which
takes the certificate source as a [`ewe.Tls`](https://hexdocs.pm/ewe/ewe.html#Tls)
value. The certificate and key are validated on startup and the server crashes if
they are missing or invalid.

```gleam
ewe.new(listener_name:, connection_factory_name:, handler: handle_request)
|> ewe.bind(to: "0.0.0.0")
|> ewe.listening(on: 8080)
// Certificate and key files on disk.
|> ewe.with_tls(ewe.Disk("priv/localhost.crt", "priv/localhost.key"))
// Or PEM already in memory: ewe.Pem(cert, key)
// Or DER in memory:         ewe.Der(cert, key, ewe.RsaPrivateKey)
|> ewe.start
```

To refuse clients that do not present a certificate signed by an authority you
name, add [`ewe.with_client_verification`](https://hexdocs.pm/ewe/ewe.html#with_client_verification).
It needs TLS to be configured.

```gleam
|> ewe.with_tls(ewe.Disk("priv/localhost.crt", "priv/localhost.key"))
|> ewe.with_client_verification(ewe.CaCertFile("priv/ca.crt"))
```

<h3 id="http2">HTTP/2</h3>

HTTP/2 is always enabled on ewe. Over TLS ewe offers it through ALPN and a plain
connection is served as HTTP/2 when it opens with the HTTP/2 preface which is
what a client with prior knowledge sends. An `Upgrade: h2c` request is not
negotiated, it is answered as HTTP/1.1.

> [!NOTE]
> Extended CONNECT is not negotiated yet, so WebSockets over HTTP/2 are not
> supported.

<h3 id="sending-a-response"><a target="_blank" href="https://github.com/vshakitskiy/ewe/blob/v5/examples/src/sending_response.gleam">Sending a Response</a></h3>

A response body is one of the [`ewe.Body`](https://hexdocs.pm/ewe/ewe.html#Body)
variants. `Text`, `Bytes` and `Empty` are built by hand, the rest come from
[`ewe.file`](https://hexdocs.pm/ewe/ewe.html#file),
[`ewe.stream_response`](https://hexdocs.pm/ewe/ewe.html#stream_response),
[`ewe.sse`](https://hexdocs.pm/ewe/ewe.html#sse) and
[`ewe.websocket`](https://hexdocs.pm/ewe/ewe.html#websocket).

```gleam
import ewe
import gleam/bytes_tree
import gleam/crypto
import gleam/http/request
import gleam/http/response
import gleam/int
import gleam/result

fn handle_request(
  request: request.Request(ewe.Connection),
) -> response.Response(ewe.Body) {
  case request.path_segments(request) {
    ["hello", name] -> {
      // Text for text responses.
      response.new(200)
      |> response.set_header("content-type", "text/plain; charset=utf-8")
      |> response.set_body(ewe.Text("Hello, " <> name <> "!"))
    }
    ["bytes", amount] -> {
      // Bytes for binary responses built from a `BytesTree`.
      let body =
        int.parse(amount)
        |> result.unwrap(0)
        |> crypto.strong_random_bytes
        |> bytes_tree.from_bit_array
        |> ewe.Bytes

      response.new(200)
      |> response.set_header("content-type", "application/octet-stream")
      |> response.set_body(body)
    }
    _segments ->
      // Empty for responses with no body like 404 or 204.
      response.new(404)
      |> response.set_body(ewe.Empty)
  }
}
```

<h3 id="reading-the-request-body"><a target="_blank" href="https://github.com/vshakitskiy/ewe/blob/v5/examples/src/reading_body.gleam">Reading the Request Body</a></h3>

[`ewe.read_body`](https://hexdocs.pm/ewe/ewe.html#read_body) reads the whole body
into memory up to `limit` bytes. Trailer fields of a chunked request are appended
to the returned request's headers.

```gleam
fn handle_request(
  request: request.Request(ewe.Connection),
) -> response.Response(ewe.Body) {
  let content_type =
    request.get_header(request, "content-type")
    |> result.unwrap("application/octet-stream")

  case ewe.read_body(request, limit: 10_240) {
    Ok(req) ->
      response.new(200)
      |> response.set_header("content-type", content_type)
      |> response.set_body(ewe.Bytes(bytes_tree.from_bit_array(req.body)))
    Error(ewe.BodyTooLarge) ->
      response.new(413)
      |> response.set_header("content-type", "text/plain; charset=utf-8")
      |> response.set_body(ewe.Text("Body too large"))
    Error(ewe.InvalidBody) ->
      response.new(400)
      |> response.set_header("content-type", "text/plain; charset=utf-8")
      |> response.set_body(ewe.Text("Invalid request"))
  }
}
```

A body the handler never read is drained by the server so the connection can be
reused. One larger than `auto_drain_limit` closes the connection instead.

<h3 id="streaming-bodies"><a target="_blank" href="https://github.com/vshakitskiy/ewe/blob/v5/examples/src/streaming_bodies.gleam">Streaming Bodies</a></h3>

[`ewe.read_body_chunk`](https://hexdocs.pm/ewe/ewe.html#read_body_chunk) pulls up
to `max_chunk_bytes` per call rather than buffering everything. Each
[`ewe.Chunk`](https://hexdocs.pm/ewe/ewe.html#ReadEvent) carries the request to
feed into the next call.

Going the other way, [`ewe.stream_response`](https://hexdocs.pm/ewe/ewe.html#stream_response)
turns a response into a streamed one. Its handler owns an
[`ewe.ResponseWriter`](https://hexdocs.pm/ewe/ewe.html#ResponseWriter) and must
end by calling [`ewe.finish_chunk`](https://hexdocs.pm/ewe/ewe.html#finish_chunk)
or [`ewe.finish_response`](https://hexdocs.pm/ewe/ewe.html#finish_response) since
that is what closes the stream. The callback runs in the same connection process.

```gleam
fn handle_stream(
  req: request.Request(ewe.Connection),
  max_chunk_bytes: Int,
) -> response.Response(ewe.Body) {
  let content_type =
    request.get_header(req, "content-type")
    |> result.unwrap("application/octet-stream")

  response.new(200)
  |> response.set_header("content-type", content_type)
  |> ewe.stream_response(echo_body(req, _, max_chunk_bytes))
}

// Read the request body one chunk at a time and write each one back out.
//
fn echo_body(
  req: request.Request(ewe.Connection),
  writer: ewe.ResponseWriter,
  max_chunk_bytes: Int,
) -> Result(Nil, ewe.SendError) {
  case ewe.read_body_chunk(req, max_chunk_bytes:, limit: 10_485_760) {
    Ok(ewe.Chunk(data:, request:)) -> {
      use writer <- result.try(ewe.send_chunk(writer, data))
      echo_body(request, writer, max_chunk_bytes)
    }
    Ok(ewe.Done(_request)) -> ewe.finish_response(writer)
    Error(_body_error) -> ewe.finish_response(writer)
  }
}
```

<h3 id="serving-files"><a target="_blank" href="https://github.com/vshakitskiy/ewe/blob/v5/examples/src/serving_files.gleam">Serving Files</a></h3>

[`ewe.file`](https://hexdocs.pm/ewe/ewe.html#file) prepares a file as a response
body so you never read one in yourself. `offset` and `limit` serve a byte range,
which is what a range request needs. It takes the connection so it is the
request's body you pass in first.

```gleam
case ewe.file(request.body, resolved, offset: None, limit: None) {
  Ok(file) ->
    response.new(200)
    |> response.set_header("content-type", "application/octet-stream")
    |> response.set_body(file)
  Error(_error) -> not_found()
}
```

<h3 id="client-address"><a target="_blank" href="https://github.com/vshakitskiy/ewe/blob/v5/examples/src/client_info.gleam">Client Address</a></h3>

[`ewe.get_client_info`](https://hexdocs.pm/ewe/ewe.html#get_client_info) reads the
address a request came from off its connection as a
[`ewe.SocketAddress`](https://hexdocs.pm/ewe/ewe.html#SocketAddress). It fails
only when the socket is already gone.

```gleam
fn describe_client(connection: ewe.Connection) -> String {
  case ewe.get_client_info(connection) {
    Ok(ewe.TcpSocketAddress(ip_address:, port:)) -> {
      // An IPv6 address is bracketed so the port stays readable next to the
      // colons the address itself is full of.
      let host = case ip_address {
        ewe.IpV6(..) -> "[" <> ewe.ip_address_to_string(ip_address) <> "]"
        ewe.IpV4(..) -> ewe.ip_address_to_string(ip_address)
      }

      host <> ":" <> int.to_string(port)
    }
    Ok(ewe.UnixSocketAddress(path: "")) -> "unix socket"
    Ok(ewe.UnixSocketAddress(path:)) -> "unix:" <> path
    Error(Nil) -> "unknown"
  }
}
```

Behind a proxy this is the proxy's address rather than the browser's. The one the
proxy puts in `x-forwarded-for` is the address to use there. MDN's
[security and privacy concerns](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For#security_and_privacy_concerns)
is worth a read before you rely on it for anything since an address taken on
trust is an address anyone can choose.

<h3 id="websocket"><a target="_blank" href="https://github.com/vshakitskiy/ewe/blob/v5/examples/src/websocket.gleam">WebSocket</a></h3>

[`ewe.websocket`](https://hexdocs.pm/ewe/ewe.html#websocket) turns a request into
a WebSocket. A request that is not a valid handshake is answered with a 400 and
your handler never runs. Frames from the client and messages from the rest of
your program arrive as [`ewe.WebsocketMessage`](https://hexdocs.pm/ewe/ewe.html#WebsocketMessage)
values. Answer them with [`ewe.send_text_frame`](https://hexdocs.pm/ewe/ewe.html#send_text_frame)
or [`ewe.send_binary_frame`](https://hexdocs.pm/ewe/ewe.html#send_binary_frame)
and say what happens next with
[`ewe.WebsocketNext`](https://hexdocs.pm/ewe/ewe.html#WebsocketNext).

```gleam
fn handle_topic(
  req: request.Request(ewe.Connection),
  pubsub: Subject(pubsub.Message(Broadcast)),
  topic: String,
) -> response.Response(ewe.Body) {
  ewe.websocket(
    request: req,
    // Called once. The selector is where you add whatever the rest of your
    // program sends to this connection.
    on_init: fn(_conn, selector) {
      let client = process.new_subject()
      pubsub.subscribe(pubsub, topic:, client:)

      let state = WebsocketState(pubsub:, topic:, client:)
      let selector = process.select(selector, client)

      #(state, selector)
    },
    handler: handle_websocket_message,
    // Called once however the WebSocket ended.
    on_close: fn(_conn, state) {
      pubsub.unsubscribe(state.pubsub, topic: state.topic, client: state.client)
    },
  )
}

fn handle_websocket_message(
  conn: ewe.WebsocketConnection,
  state: WebsocketState,
  message: ewe.WebsocketMessage(Broadcast),
) -> ewe.WebsocketNext(WebsocketState, Broadcast) {
  case message {
    ewe.TextFrame(text) -> {
      pubsub.publish(state.pubsub, topic: state.topic, message: Text(text))
      ewe.websocket_continue(state)
    }

    ewe.BinaryFrame(data) -> {
      pubsub.publish(state.pubsub, topic: state.topic, message: Bytes(data))
      ewe.websocket_continue(state)
    }

    // A message from the rest of the program.
    ewe.UserMessage(broadcast) -> {
      let sent = case broadcast {
        Text(text) -> ewe.send_text_frame(conn, text)
        Bytes(data) -> ewe.send_binary_frame(conn, data)
      }

      case sent {
        Ok(Nil) -> ewe.websocket_continue(state)
        Error(_send_error) ->
          ewe.websocket_stop_abnormal("Failed to send a frame")
      }
    }
  }
}
```

Ping and pong frames are answered by the server and never reach the handler. To
start the closing handshake yourself, return
[`ewe.send_close_frame`](https://hexdocs.pm/ewe/ewe.html#send_close_frame) with a
[`ewe.CloseReason`](https://hexdocs.pm/ewe/ewe.html#CloseReason). No frame can be
sent after it!

<h3 id="server-sent-events"><a target="_blank" href="https://github.com/vshakitskiy/ewe/blob/v5/examples/src/sse.gleam">Server-Sent Events</a></h3>

[`ewe.sse`](https://hexdocs.pm/ewe/ewe.html#sse) turns a response into an SSE
stream which runs until the handler stops it or the client disconnects. `on_init`
receives the subject the rest of your program pushes messages to, `handler` is
called for each of those messages and `on_close` runs once the stream ends.
The `content-type` and `cache-control` headers the stream needs are set by ewe.

```gleam
response.new(200)
|> ewe.sse(
  on_init: fn(client) {
    pubsub.subscribe(pubsub, topic:, client:)

    client
  },
  handler: fn(conn, client, message) {
    case ewe.send_event(conn, ewe.event(message)) {
      Ok(Nil) -> ewe.sse_continue(client)
      Error(_send_error) -> ewe.sse_stop()
    }
  },
  on_close: fn(_conn, client) {
    pubsub.unsubscribe(pubsub, topic:, client:)
  },
)
```

An event is built with [`ewe.event`](https://hexdocs.pm/ewe/ewe.html#event) and
can carry a name, an id and a reconnection delay through
[`ewe.event_name`](https://hexdocs.pm/ewe/ewe.html#event_name),
[`ewe.event_id`](https://hexdocs.pm/ewe/ewe.html#event_id) and
[`ewe.event_retry`](https://hexdocs.pm/ewe/ewe.html#event_retry).
[`ewe.comment`](https://hexdocs.pm/ewe/ewe.html#comment) sends something clients
ignore which is the usual way to keep an idle stream from being closed by a
proxy.

<h3 id="connection-limits-and-timeouts">Connection Limits and Timeouts</h3>

Every connection is held to a set of limits and timeouts. Start with
[`ewe.default_http1_options`](https://hexdocs.pm/ewe/ewe.html#default_http1_options)
or [`ewe.default_http2_options`](https://hexdocs.pm/ewe/ewe.html#default_http2_options),
update the fields you care about and hand the result to
[`ewe.with_http1`](https://hexdocs.pm/ewe/ewe.html#with_http1) or
[`ewe.with_http2`](https://hexdocs.pm/ewe/ewe.html#with_http2). Sizes are in bytes
and timeouts in milliseconds.

```gleam
let http1 =
  ewe.Http1Options(
    ..ewe.default_http1_options(),
    // Refuse a request carrying more than 50 header fields with a 431.
    max_headers: 50,
    // Close a connection that sits idle for 30 seconds.
    idle_timeout: 30_000,
  )

let http2 =
  ewe.Http2Options(
    ..ewe.default_http2_options(),
    // Cap how many streams a client may have open at once.
    max_concurrent_streams: Some(100),
    // Trip a GOAWAY sooner on a client resetting streams in bulk.
    rapid_reset_threshold: 50,
  )

ewe.new(listener_name:, connection_factory_name:, handler: handle_request)
|> ewe.with_http1(http1)
|> ewe.with_http2(http2)
|> ewe.start
```

[`ewe.Http1Options`](https://hexdocs.pm/ewe/ewe.html#Http1Options):

| Field | Default | What it does |
| --- | --- | --- |
| `max_request_line` | `8192` | Longer request lines are refused with a 414. |
| `max_header_line` | `8192` | Longer header lines are refused with a 431. |
| `max_headers` | `100` | Requests carrying more header fields are refused with a 431. |
| `max_chunk_size_line` | `128` | Longest chunk size line in a chunked body. |
| `idle_timeout` | `10_000` | How long a connection may sit without sending anything. |
| `body_read_timeout` | `10_000` | How long a single body read waits for the client. |
| `auto_drain_limit` | `1_048_576` | An unread body larger than this closes the connection instead of being drained. |
| `auto_drain_chunk_bytes` | `65_536` | How much of that drain is read at a time. |

[`ewe.Http2Options`](https://hexdocs.pm/ewe/ewe.html#Http2Options), where a value
the protocol does not allow is replaced with the default rather than reaching a
peer:

| Field | Default | What it does |
| --- | --- | --- |
| `max_concurrent_streams` | `None` | How many streams a client may have open at once. |
| `initial_window_size` | `2_097_152` | How much response body a stream may have in flight. |
| `max_frame_size` | `16_384` | Largest frame accepted, between 16384 and 16777215. |
| `max_header_list_size` | `Some(32_768)` | Largest header list accepted. |
| `header_table_size` | `4096` | HPACK dynamic table kept for decoding. |
| `max_continuation_frames` | `100` | How many CONTINUATION frames one header sequence may span. |
| `max_header_block_bytes` | `65_536` | Bytes one header block may total before decoding. |
| `rapid_reset_window` | `10_000` | Window over which client stream resets are counted. |
| `rapid_reset_threshold` | `100` | Resets within that window that trip a GOAWAY which is what keeps Rapid Reset (CVE-2023-44487) in check. |
| `handshake_timeout` | `10_000` | How long a connection may sit in the preface and SETTINGS handshake. |
| `drain_timeout` | `4000` | How long a draining connection waits for its streams after GOAWAY. |
| `recv_window_low_water_mark` | `262_144` | Once a receive window falls to this it is topped back up. |
| `recv_window_high_water_mark` | `2_097_152` | What it is topped up to; a wider gap costs fewer WINDOW_UPDATE round trips. |
| `file_read_threshold` | `1_048_576` | Files at or below this are read into memory, larger ones are streamed from disk. |
| `body_read_timeout` | `10_000` | How long a single body read waits for the client. |

<h3 id="running-under-supervision">Running Under Supervision</h3>

[`ewe.start`](https://hexdocs.pm/ewe/ewe.html#start) runs the server on its own.
When it belongs to a supervision tree next to the rest of your program use
[`ewe.supervised`](https://hexdocs.pm/ewe/ewe.html#supervised) instead, which
returns a child specification.

```gleam
supervisor.new(supervisor.OneForAll)
|> supervisor.add(pubsub.worker(pubsub_name))
|> supervisor.add(
  ewe.new(listener_name:, connection_factory_name:, handler:)
  |> ewe.bind(to: "0.0.0.0")
  |> ewe.listening(on: 8080)
  |> ewe.supervised,
)
|> supervisor.start
```

The line printed on startup comes from [`ewe.on_start`](https://hexdocs.pm/ewe/ewe.html#on_start),
which receives the scheme and the address the server bound to. Replace it to log
it your own way or silence it with [`ewe.quiet`](https://hexdocs.pm/ewe/ewe.html#quiet).

<h3 id="running-as-an-otp-application">Running as an OTP Application</h3>

The examples start the server straight from `main` with a `let assert`, which is
the shortest thing that works while you are trying ewe out. A service is better
off letting the [OTP application](https://www.erlang.org/doc/apps/kernel/application.html)
controller own the supervision tree: it starts before anything else runs, it
brings the tree down in order on shutdown and it is what a release expects.

Point `application_start_module` at a module exporting `start/2` and `stop/1`:

```toml
[erlang]
application_start_module = "my_app"
```

`start` returns the pid of the top supervisor to the application controller,
which is the pid it supervises from there on.

```gleam
import gleam/erlang/atom
import gleam/erlang/process
import gleam/otp/actor
import gleam/otp/static_supervisor as supervisor

/// The Erlang/OTP application start callback. Starts the top supervisor and
/// hands its pid back to the application controller.
pub fn start(_type: a, _args: b) -> Result(process.Pid, actor.StartError) {
  let listener_name = process.new_name("listener_name")
  let connection_factory_name = process.new_name("connection_factory_name")

  case
    supervisor.new(supervisor.OneForOne)
    |> supervisor.add(
      ewe.new(listener_name:, connection_factory_name:, handler: handle_request)
      |> ewe.bind(to: "0.0.0.0")
      |> ewe.listening(on: 8080)
      |> ewe.supervised,
    )
    |> supervisor.start
  {
    Ok(actor.Started(pid:, ..)) -> Ok(pid)
    Error(reason) -> Error(reason)
  }
}

/// The Erlang/OTP application stop callback, called once every process in the
/// tree is down. Any final clean up goes here.
pub fn stop(_state: a) -> atom.Atom {
  atom.create("ok")
}

/// The application is already running by the time this is called, so all main
/// has left to do is keep the node alive.
pub fn main() {
  process.sleep_forever()
}
```

> [!NOTE]
> `main` still has to sleep. `gleam run` boots the application and then calls it,
> so without it the node exits as soon as it returns.

<h2 id="examples">Examples</h2>

Most sections above link to a runnable example. They live in
[examples](examples/), see [its README](examples/README.md) for how to run them.

<h2 id="api-reference">API Reference</h2>

For detailed API documentation, see [hexdocs.pm/ewe](https://hexdocs.pm/ewe/ewe.html).
