ewe

Types

The body of a HTTP response to be sent to the client.

The Streaming, Sse and Websocket variants are created by the functions rather than directly.

pub type Body {
  Bytes(bytes_tree.BytesTree)
  Text(String)
  Empty
  File(@internal File)
  Streaming(@internal Streaming)
  Sse(@internal Sse)
  Websocket(@internal Websocket)
}

Constructors

  • A body of binary data stored as a BytesTree.

    If you have a BitArray you can use the bytes_tree.from_bit_array function to convert it.

  • Text(String)

    A body of unicode text sent as UTF-8.

  • Empty

    No body. The response is sent with a content-length of 0.

  • File(@internal File)

    A body of the contents of a file created with the file function.

    Large files are safe to send this way as they are never held in memory whole. See file for how each protocol sends them.

  • Streaming(@internal Streaming)

    A body written a chunk at a time created with the stream_response function.

  • Sse(@internal Sse)

    A Server-Sent Events stream created with the sse function.

  • Websocket(@internal Websocket)

    A WebSocket created with the websocket function.

    The connection stops being HTTP once the handshake has been sent so it will never carry another request.

The reason a request body could not be read.

pub type BodyError {
  BodyTooLarge
  InvalidBody
}

Constructors

  • BodyTooLarge

    The body is larger than the limit that was given.

  • InvalidBody

    The body could not be read to the end. The connection dropped, the read timed out or the chunked framing was malformed.

The configuration of a server.

Create one with new, adjust it with the builder functions, then give it to start or supervised.

pub opaque type Builder

The certificate authority a client’s certificate has to be signed by, given to with_client_verification.

pub type ClientVerification {
  CaCertFile(path: String)
  CaCertData(certs: List(BitArray))
}

Constructors

  • CaCertFile(path: String)

    Path to a PEM file holding the CA certificate.

  • CaCertData(certs: List(BitArray))

    In-memory DER-encoded CA certificates.

The status code a close frame carries.

The codes that exist only to be reported locally, such as 1005 and 1006, are absent, as sending one is a protocol violation.

pub type CloseCode {
  NormalClosure
  GoingAway
  ProtocolError
  UnsupportedData
  InvalidPayloadData
  PolicyViolation
  MessageTooBig
  MandatoryExtension
  InternalError
  ServiceRestart
  TryAgainLater
  BadGateway
  ApplicationCode(code: Int)
}

Constructors

  • NormalClosure

    The connection did what it was for and is closing normally (1000).

  • GoingAway

    The endpoint is going away, from a server shutdown or a client navigating away (1001).

  • ProtocolError

    The other end broke the protocol (1002).

  • UnsupportedData

    Data arrived that this endpoint cannot accept (1003).

  • InvalidPayloadData

    A message did not match the type it declared such as a text frame that is not UTF-8 (1007).

  • PolicyViolation

    The other end broke your rules when no more specific code applies (1008).

  • MessageTooBig

    A message was larger than this endpoint will handle (1009).

  • MandatoryExtension

    An extension the client required was not negotiated (1010).

  • InternalError

    Something went wrong on this side (1011).

  • ServiceRestart

    The server is restarting and clients may reconnect shortly (1012).

  • TryAgainLater

    The server is overloaded and the client should retry later (1013).

  • BadGateway

    An upstream server answered badly (1014).

  • ApplicationCode(code: Int)

    An application specific code which must be between 3000 and 4999.

The reason a WebSocket is being closed, sent to the client in the close frame.

pub type CloseReason {
  NoCloseReason
  CloseReason(code: CloseCode, reason: String)
}

Constructors

  • NoCloseReason

    Close without saying why.

  • CloseReason(code: CloseCode, reason: String)

    Close with a status code and a description, which may be empty.

The connection a request arrived on.

This is the body of the request given to your handler. Pass it to read_body or read_body_chunk to read the request body, or to file to send a file back.

pub type Connection =
  @internal Connection

The reason a file could not be prepared by the file function.

pub type FileError {
  NotFound
  IsDirectory
  AccessDenied
  UnknownError
  InvalidOffset
  InvalidLimit
}

Constructors

  • NotFound

    There is nothing at the given path.

  • IsDirectory

    The path is a directory.

  • AccessDenied

    The server is not permitted to read the file.

  • UnknownError

    The file could not be opened or measured for a reason ewe does not name.

  • InvalidOffset

    The offset is negative or past the end of the file.

  • InvalidLimit

    The limit is negative.

The limits and timeouts applied to every HTTP/1 connection.

Sizes are in bytes and timeouts in milliseconds. Build one by updating default_http1_options so you only state the ones you care about.

A value outside the range a field accepts is replaced with the default and logged as a warning when the server starts.

Examples

Http1Options(..ewe.default_http1_options(), max_headers: 50)
pub type Http1Options {
  Http1Options(
    max_request_line: Int,
    max_header_line: Int,
    max_headers: Int,
    max_chunk_size_line: Int,
    idle_timeout: Int,
    body_read_timeout: Int,
    auto_drain_limit: Int,
    auto_drain_chunk_bytes: Int,
  )
}

Constructors

  • Http1Options(
      max_request_line: Int,
      max_header_line: Int,
      max_headers: Int,
      max_chunk_size_line: Int,
      idle_timeout: Int,
      body_read_timeout: Int,
      auto_drain_limit: Int,
      auto_drain_chunk_bytes: Int,
    )

    Arguments

    max_request_line

    The longest request line accepted. A longer one is refused with status code 414: URI Too Long.

    max_header_line

    The longest single header line accepted. A longer one is refused with status code 431: Request Header Fields Too Large.

    max_headers

    The most header fields a request may carry. More than this is refused with status code 431: Request Header Fields Too Large.

    max_chunk_size_line

    The longest chunk size line accepted in a chunked body. A longer one is refused with status code 413: Content Too Large.

    idle_timeout

    How long a connection may sit without sending anything before it is closed.

    body_read_timeout

    How long a single read of a request body waits for the client.

    auto_drain_limit

    How much of a body the handler never read is drained so that the connection can be reused. A larger body closes the connection instead.

    auto_drain_chunk_bytes

    How much of that drain is read at a time.

The limits and timeouts applied to every HTTP/2 connection.

Sizes are in bytes and timeouts in milliseconds. Build one by updating default_http2_options so you only state the ones you care about.

A value outside the range a field accepts is replaced with the default and logged as a warning when the server starts.

Examples

Http2Options(..ewe.default_http2_options(), max_concurrent_streams: Some(100))
pub type Http2Options {
  Http2Options(
    max_concurrent_streams: option.Option(Int),
    initial_window_size: Int,
    max_frame_size: Int,
    max_header_list_size: option.Option(Int),
    header_table_size: Int,
    max_continuation_frames: Int,
    max_header_block_bytes: Int,
    rapid_reset_window: Int,
    rapid_reset_threshold: Int,
    handshake_timeout: Int,
    drain_timeout: Int,
    recv_window_low_water_mark: Int,
    recv_window_high_water_mark: Int,
    file_read_threshold: Int,
    body_read_timeout: Int,
  )
}

Constructors

  • Http2Options(
      max_concurrent_streams: option.Option(Int),
      initial_window_size: Int,
      max_frame_size: Int,
      max_header_list_size: option.Option(Int),
      header_table_size: Int,
      max_continuation_frames: Int,
      max_header_block_bytes: Int,
      rapid_reset_window: Int,
      rapid_reset_threshold: Int,
      handshake_timeout: Int,
      drain_timeout: Int,
      recv_window_low_water_mark: Int,
      recv_window_high_water_mark: Int,
      file_read_threshold: Int,
      body_read_timeout: Int,
    )

    Arguments

    max_concurrent_streams

    The most streams a client may have open at once. None leaves it unlimited.

    initial_window_size

    How much response body a stream may have in flight before the client has to allow more. Must be within 0 and 2147483647.

    max_frame_size

    The largest frame the server accepts. Must be within 16384 and 16777215.

    max_header_list_size

    The largest header list the server accepts. None leaves it unlimited.

    header_table_size

    How much HPACK dynamic table the server keeps for decoding.

    max_continuation_frames

    The most CONTINUATION frames one header sequence may span.

    max_header_block_bytes

    The most bytes of HEADERS and CONTINUATION one header block may total, counted before it is decoded.

    rapid_reset_window

    The window over which client stream resets are counted.

    rapid_reset_threshold

    How many resets within that window trip a GOAWAY which is what stops Rapid Reset (CVE-2023-44487) costing more than it should.

    handshake_timeout

    How long a connection may sit in the preface and SETTINGS handshake before it is dropped.

    drain_timeout

    How long a draining connection waits for its streams to finish after GOAWAY before closing.

    recv_window_low_water_mark

    Once a receive window falls to this it is topped straight back up to recv_window_high_water_mark rather than trickling small updates.

    recv_window_high_water_mark

    What a receive window is topped up to. The wider the gap from the low mark the fewer WINDOW_UPDATE round trips a large body costs.

    file_read_threshold

    Files at or below this size are read into memory and framed like any other body. Larger ones are streamed from disk instead.

    body_read_timeout

    How long a single read of a request body waits for the client.

An IP address.

pub type IpAddress {
  IpV4(Int, Int, Int, Int)
  IpV6(Int, Int, Int, Int, Int, Int, Int, Int)
}

Constructors

  • IpV4(Int, Int, Int, Int)

    An IPv4 address, represented as its four bytes. 127.0.0.1 is IpV4(127, 0, 0, 1).

  • IpV6(Int, Int, Int, Int, Int, Int, Int, Int)

    An IPv6 address, represented as its eight groups. ::1 is IpV6(0, 0, 0, 0, 0, 0, 0, 1).

The result of a single call to read_body_chunk.

pub type ReadEvent {
  Chunk(data: BitArray, request: request.Request(Connection))
  Done(request: request.Request(Nil))
}

Constructors

  • Chunk(data: BitArray, request: request.Request(Connection))

    A piece of the body along with the request to pass to the next call.

  • Done(request: request.Request(Nil))

    The body has been read to the end.

    Any trailer fields are appended to the request’s headers and the request no longer carries a connection as there is nothing left to read from it.

A handle for writing the body of a streamed response, given to the handler by stream_response.

pub type ResponseWriter =
  @internal ResponseWriter

The reason a write to the client did not go through.

pub type SendError {
  ConnectionClosed
  StreamReset
  SendTimedOut
  SocketError(reason: SocketReason)
}

Constructors

  • ConnectionClosed

    The client is gone so nothing further can be written.

  • StreamReset

    The client cancelled this HTTP/2 stream while the rest of the connection carries on.

  • SendTimedOut

    The client stopped reading for long enough that the write gave up which takes the connection with it.

  • SocketError(reason: SocketReason)

    The socket refused the write for a reason of its own.

The address a socket is bound to or the address of a connected peer.

pub type SocketAddress {
  TcpSocketAddress(ip_address: IpAddress, port: Int)
  UnixSocketAddress(path: String)
}

Constructors

  • TcpSocketAddress(ip_address: IpAddress, port: Int)

    An address and port on a TCP socket.

  • UnixSocketAddress(path: String)

    The path of a Unix domain socket.

What the socket said when it refused a write, carried by the SocketError variant of SendError.

pub type SocketReason {
  OutOfBuffers
  TooManyOpenFiles
  NetworkDown
  NetworkUnreachable
  HostUnreachable
  MessageTooLarge
  PermissionDenied
  WouldBlock
  Interrupted
  NotSupported
  IoError
  UnknownReason
}

Constructors

  • OutOfBuffers

    The kernel has no socket buffer space or memory left to take the write.

  • TooManyOpenFiles

    The node is at its file descriptor limit or the whole host is.

  • NetworkDown

    The interface the connection runs over is down.

  • NetworkUnreachable

    There is no route to the client’s network.

  • HostUnreachable

    The client’s network is reachable but the client’s host is not.

  • MessageTooLarge

    The write is larger than the socket will send in one piece.

  • PermissionDenied

    The socket refused the write on permission grounds.

  • WouldBlock

    The write would have blocked and the socket is not willing to.

  • Interrupted

    A signal arrived mid write. Nothing was sent.

  • NotSupported

    The socket does not support the write as it was made.

  • IoError

    The write failed below the socket in the network stack or the device.

  • UnknownReason

    The socket reported something ewe does not classify.

A handle for sending on an open Server-Sent Events stream.

pub type SseConnection =
  @internal SseConnection

A message on a Server-Sent Events stream.

Create one with event or comment, then set the rest of its fields with event_name, event_id and event_retry.

pub type SseEvent =
  @internal Event

What a Server-Sent Events stream does once the handler has dealt with a message.

Create one with sse_continue, sse_stop or sse_stop_abnormal.

pub opaque type SseNext(user_state)

The source of the TLS certificate and key given to with_tls.

pub type Tls {
  Disk(cert: String, key: String)
  Pem(cert: BitArray, key: BitArray)
  Der(cert: BitArray, key: BitArray, key_type: TlsKeyType)
}

Constructors

  • Disk(cert: String, key: String)

    Paths to PEM-encoded certificate and key files on disk.

  • Pem(cert: BitArray, key: BitArray)

    In-memory PEM-encoded certificate and key.

  • Der(cert: BitArray, key: BitArray, key_type: TlsKeyType)

    In-memory DER-encoded certificate and key.

The type of a DER-encoded private key needed by the Der variant of Tls.

pub type TlsKeyType {
  RsaPrivateKey
  EcPrivateKey
  DsaPrivateKey
  PrivateKeyInfo
}

Constructors

  • RsaPrivateKey

    Traditional RSA key.

  • EcPrivateKey

    Elliptic curve key.

  • DsaPrivateKey

    DSA key.

  • PrivateKeyInfo

    PKCS#8 key.

A handle for sending frames on an open WebSocket.

pub type WebsocketConnection =
  @internal WebsocketConnection

A message reaching a WebSocket handler either from the client or from the rest of your program.

Ping and pong frames are answered by the server and never reach the handler.

pub type WebsocketMessage(user_message) {
  TextFrame(text: String)
  BinaryFrame(data: BitArray)
  UserMessage(message: user_message)
}

Constructors

  • TextFrame(text: String)

    A text frame from the client with the valid UTF-8 payload.

  • BinaryFrame(data: BitArray)

    A binary frame from the client.

  • UserMessage(message: user_message)

    A message picked up by the selector given to on_init, sent by the rest of your program.

What a WebSocket does once the handler has dealt with a message.

Create one with websocket_continue, websocket_continue_with_selector, websocket_stop or websocket_stop_abnormal.

pub opaque type WebsocketNext(user_state, user_message)

Values

pub fn bind(builder: Builder, to interface: String) -> Builder

Set the network interface the server listens on. "127.0.0.1" and "localhost" are the loopback, "0.0.0.0" is every IPv4 interface, "::1" is the IPv6 loopback, and "::" is every IPv6 interface.

A server listens on either a network interface or a Unix socket so this undoes a previous call to unix.

Panics

Starting the server will panic if the interface is not "localhost" or a valid IPv4 or IPv6 address.

pub fn comment(text: String) -> SseEvent

Create a comment, which clients ignore.

Sending one every so often is the usual way to keep an idle stream from being closed by a proxy in between.

pub fn default_http1_options() -> Http1Options

Get the default HTTP/1 limits and timeouts to be adjusted and given to with_http1.

pub fn default_http2_options() -> Http2Options

Get the default HTTP/2 limits and timeouts to be adjusted and given to with_http2.

pub fn event(data: String) -> SseEvent

Create an event carrying the given data.

Data spanning several lines is sent as the repeated data: fields that the client joins back together.

Examples

event("Hello, Joe!")
|> event_name("greeting")
|> event_id("1")
pub fn event_id(event: SseEvent, id: String) -> SseEvent

Set the ID of an event. A reconnecting client sends the last ID it saw back in the last-event-id header.

pub fn event_name(event: SseEvent, name: String) -> SseEvent

Set the name of an event which clients use to route it to a listener.

pub fn event_retry(event: SseEvent, retry: Int) -> SseEvent

Set how long, in milliseconds, the client waits before reconnecting.

pub fn file(
  connection: Connection,
  path: String,
  offset offset: option.Option(Int),
  limit limit: option.Option(Int),
) -> Result(Body, FileError)

Create a response body from a file on the disc. Large files are safe to send this way as they are never held in memory whole.

The offset and limit are in bytes and serve a range of the file. Leave either as None to start at the beginning or to run to the end.

How the file reaches the client depends on the protocol. HTTP/1 lets the kernel copy it straight to the socket and falls back to reading it in 64kb pieces when TLS is in the way. HTTP/2 reads a file at or below the file_read_threshold of Http2Options into memory and frames it like any other body, and streams anything larger from the disc.

On HTTP/1 the file is opened here and stays open until the response has been written so only create a body you go on to return. One that is created and then thrown away holds its file open until it is garbage collected.

Examples

let assert Ok(body) =
  ewe.file(request.body, "/tmp/report.pdf", offset: None, limit: None)

response.new(200)
|> response.set_header("content-type", "application/pdf")
|> response.set_body(body)
pub fn finish_chunk(
  writer: ResponseWriter,
  chunk: BitArray,
) -> Result(Nil, SendError)

Send the last chunk of a streamed response body and close the body off.

pub fn finish_response(
  writer: ResponseWriter,
) -> Result(Nil, SendError)

Close off a streamed response body without sending any more data. Use finish_chunk instead if there is one last chunk to send.

pub fn force_ipv6(builder: Builder) -> Builder

Serve over IPv6.

bind must have been given an IPv6 address, or one of "localhost", "127.0.0.1" and "0.0.0.0", which are bound so that they work over either address family. The server crashes on start with any other IPv4 address and with any address at all if the system has no IPv6 support.

pub fn get_client_info(
  connection: Connection,
) -> Result(SocketAddress, Nil)

Get the address of the client at the other end of the connection.

Returns an error if the address could not be looked up such as when the connection has already closed.

pub fn get_server_info(
  listener: process.Subject(@internal Message),
) -> SocketAddress

Get the address the server is listening on. This is how you find the port picked by listening_random.

The server must be running. Pass the subject of the listener_name given to new.

Examples

process.named_subject(listener_name)
|> ewe.get_server_info
// -> TcpSocketAddress(IpV4(127, 0, 0, 1), 3000)
pub fn ip_address_to_string(address: IpAddress) -> String

Convert an IP address to the string form. IPv6 addresses are written in lowercase, with the longest run of zero groups collapsed to ::.

Examples

ip_address_to_string(IpV4(127, 0, 0, 1))
// -> "127.0.0.1"

ip_address_to_string(IpV6(0, 0, 0, 0, 0, 0, 0, 1))
// -> "::1"
pub fn listening(builder: Builder, on port: Int) -> Builder

Set the port the server listens on.

A server listens on either a network interface or a Unix socket so this undoes a previous call to unix.

pub fn listening_random(builder: Builder) -> Builder

Listen on port 0, which asks the operating system for any free port. This is useful in tests where a fixed port would clash.

Use get_server_info once the server is running to find the port it was given.

pub fn new(
  listener_name listener_name: process.Name(@internal Message),
  connection_factory_name connection_factory_name: process.Name(
    factory_supervisor.Message(
      @internal Socket,
      process.Subject(@internal Message(@internal Message)),
    ),
  ),
  handler handler: fn(request.Request(Connection)) -> response.Response(
    Body,
  ),
) -> Builder

Create a new server configuration. The handler is called for every request and the response it returns is sent to the client.

The two names are used by the acceptor pool to wire its listener and its connection factory together. Create them once where your program starts and pass them in here.

The server listens on 127.0.0.1:3000 and prints its address once started. Use bind, listening and on_start to change that.

Examples

pub fn main() {
  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()
}
pub fn on_start(
  builder: Builder,
  on_start: fn(http.Scheme, SocketAddress) -> Nil,
) -> Builder

Set the function to run once the server is listening. It is given the scheme and the address the server ended up on.

By default this prints the address. Use quiet to say nothing instead.

pub fn quiet(builder: Builder) -> Builder

Print nothing when the server starts by replacing the default on_start function with one that does nothing.

pub fn read_body(
  req: request.Request(Connection),
  limit limit: Int,
) -> Result(request.Request(BitArray), BodyError)

Read the entire request body into memory up to the given limit in bytes.

Any trailer fields a chunked request ends with are appended to the returned request’s headers.

Use read_body_chunk instead if the body may be too large to hold in memory.

Examples

case ewe.read_body(request, limit: 1_048_576) {
  Ok(request) -> handle(request.body)
  Error(_body_error) -> response.new(400) |> response.set_body(ewe.Empty)
}
pub fn read_body_chunk(
  req: request.Request(Connection),
  max_chunk_bytes max_chunk_bytes: Int,
  limit limit: Int,
) -> Result(ReadEvent, BodyError)

Read the request body a chunk at a time rather than holding all of it in memory taking up to max_chunk_bytes per call and refusing a body larger than limit bytes in total.

Each Chunk carries the request to use for the next call. Keep going until you get Done.

Examples

fn count(request: request.Request(ewe.Connection), total: Int) -> Int {
  case ewe.read_body_chunk(request, max_chunk_bytes: 4096, limit: 10_000_000) {
    Ok(ewe.Chunk(data:, request:)) ->
      count(request, total + bit_array.byte_size(data))
    Ok(ewe.Done(_request)) -> total
    Error(_body_error) -> total
  }
}
pub fn send_binary_frame(
  conn: WebsocketConnection,
  data: BitArray,
) -> Result(Nil, SendError)

Send a binary frame to the client.

pub fn send_chunk(
  writer: ResponseWriter,
  chunk: BitArray,
) -> Result(ResponseWriter, SendError)

Send one chunk of a streamed response body. The writer is handed back so that it can be threaded into the next call.

For the last chunk use finish_chunk instead, which closes the body off in the same write.

pub fn send_close_frame(
  conn: WebsocketConnection,
  reason: CloseReason,
) -> WebsocketNext(user_state, user_message)

Start the closing handshake and end the WebSocket.

Return the value this gives back from your handler. No frame can be sent after it.

Examples

ewe.send_close_frame(conn, ewe.CloseReason(ewe.GoingAway, "shutting down"))
pub fn send_error_to_string(error: SendError) -> String

Describe a SendError in a form that reads inside a log line.

Examples

send_error_to_string(ConnectionClosed)
// -> "the client is gone"
pub fn send_event(
  conn: SseConnection,
  event: SseEvent,
) -> Result(Nil, SendError)

Send an event to the client of a Server-Sent Events stream.

pub fn send_text_frame(
  conn: WebsocketConnection,
  text: String,
) -> Result(Nil, SendError)

Send a text frame to the client.

pub fn socket_reason_to_string(reason: SocketReason) -> String

Describe a SocketReason in a form that reads inside a log line.

Examples

socket_reason_to_string(NetworkDown)
// -> "the network is down"
pub fn sse(
  response: response.Response(a),
  on_init on_init: fn(process.Subject(user_message)) -> user_state,
  handler handler: fn(SseConnection, user_state, user_message) -> SseNext(
    user_state,
  ),
  on_close on_close: fn(SseConnection, user_state) -> Nil,
) -> response.Response(Body)

Set the body of a response to a Server-Sent Events stream which runs until the handler stops it or the client goes away.

  • on_init is called once, with a subject that the rest of your program sends messages to, and returns the starting state.
  • handler is called for each message that arrives on that subject.
  • on_close is called once, however the stream ended.

The content-type and cache-control headers the stream needs are set by ewe.

On HTTP/1.1 the connection can carry another request afterwards as long as the handler ended the stream itself and the client sent nothing during it.

Examples

response.new(200)
|> ewe.sse(
  on_init: fn(subject) {
    pubsub.subscribe(pubsub, subject)
    0
  },
  handler: fn(conn, sent, message) {
    case ewe.send_event(conn, ewe.event(message)) {
      Ok(Nil) -> ewe.sse_continue(sent + 1)
      Error(_send_error) -> ewe.sse_stop()
    }
  },
  on_close: fn(_conn, _sent) { Nil },
)
pub fn sse_continue(
  user_state: user_state,
) -> SseNext(user_state)

Carry on with the stream, handling further messages with the given state.

pub fn sse_stop() -> SseNext(user_state)

End the stream.

pub fn sse_stop_abnormal(reason: String) -> SseNext(user_state)

End the stream and exit the connection process abnormally with the given reason.

pub fn start(
  builder: Builder,
) -> Result(
  actor.Started(static_supervisor.Supervisor),
  actor.StartError,
)

Start the server, running the on_start function once it is listening.

The supervisor returned holds the acceptor pool. To put the server under a supervision tree use supervised instead.

pub fn stream_response(
  response: response.Response(a),
  handler: fn(ResponseWriter) -> Result(Nil, SendError),
) -> response.Response(Body)

Set the body of a response to one written a chunk at a time so that each chunk reaches the client as it is produced.

The handler is given a writer to send through and must finish the body with finish_chunk or finish_response. A handler that returns without calling either still has its body closed off but the connection is dropped instead of being reused for the next request.

Examples

response.new(200)
|> response.set_header("content-type", "text/plain")
|> ewe.stream_response(fn(writer) {
  use writer <- result.try(ewe.send_chunk(writer, <<"Hello, ":utf8>>))
  ewe.finish_chunk(writer, <<"Joe!":utf8>>)
})
pub fn supervised(
  builder: Builder,
) -> supervision.ChildSpecification(static_supervisor.Supervisor)

Create a child specification for the server so that it can be added to a supervision tree.

pub fn unix(builder: Builder, path: String) -> Builder

Listen on a Unix domain socket at the given path instead of on TCP.

A server listens on either a network interface or a Unix socket so this discards any interface, port and IPv6 setting made before it.

pub fn websocket(
  request request: request.Request(Connection),
  on_init on_init: fn(
    WebsocketConnection,
    process.Selector(user_message),
  ) -> #(user_state, process.Selector(user_message)),
  handler handler: fn(
    WebsocketConnection,
    user_state,
    WebsocketMessage(user_message),
  ) -> WebsocketNext(user_state, user_message),
  on_close on_close: fn(WebsocketConnection, user_state) -> Nil,
) -> response.Response(Body)

Upgrade the request to a WebSocket which runs until the handler stops it or the client goes away.

  • on_init is called once, with an empty selector to add whatever the rest of your program sends this connection to, and returns the starting state along with that selector.
  • handler is called for each frame from the client and each message the selector picks up.
  • on_close is called once, however the WebSocket ended.

A request that is not a valid handshake is answered with status code 400: Bad Request, and the handler is never run. WebSockets travel over extended CONNECT on HTTP/2, which ewe does not negotiate yet, so a request on an HTTP/2 connection is answered with status code 501: Not Implemented.

The connection stops being HTTP once the handshake has been sent so it will never carry another request.

Examples

ewe.websocket(
  request:,
  on_init: fn(_conn, selector) { #(0, selector) },
  handler: fn(conn, count, message) {
    case message {
      ewe.TextFrame(text) -> {
        let assert Ok(Nil) = ewe.send_text_frame(conn, text)
        ewe.websocket_continue(count + 1)
      }
      ewe.BinaryFrame(_data) | ewe.UserMessage(_message) ->
        ewe.websocket_continue(count)
    }
  },
  on_close: fn(_conn, _count) { Nil },
)
pub fn websocket_continue(
  user_state: user_state,
) -> WebsocketNext(user_state, user_message)

Carry on with the WebSocket handling further messages with the given state and the selector the connection already has.

pub fn websocket_continue_with_selector(
  user_state: user_state,
  selector: process.Selector(user_message),
) -> WebsocketNext(user_state, user_message)

Carry on with the WebSockets listening on the given selector from here on instead of the one the connection was started with.

pub fn websocket_stop() -> WebsocketNext(user_state, user_message)

End the WebSocket. To tell the client why first, use send_close_frame.

pub fn websocket_stop_abnormal(
  reason: String,
) -> WebsocketNext(user_state, user_message)

End the WebSocket and exit the connection process abnormally with the given reason.

pub fn with_client_verification(
  builder: Builder,
  ca_cert: ClientVerification,
) -> Builder

Require clients to present a certificate signed by the given authority. Clients that do not are refused.

This needs TLS which with_tls sets up.

pub fn with_http1(
  builder: Builder,
  options: Http1Options,
) -> Builder

Set the limits and timeouts applied to every HTTP/1 connection.

pub fn with_http2(
  builder: Builder,
  options: Http2Options,
) -> Builder

Set the limits and timeouts applied to every HTTP/2 connection.

pub fn with_tls(builder: Builder, tls: Tls) -> Builder

Serve over TLS with the given certificate and key.

This is also what offers HTTP/2 to clients through ALPN. Without TLS a client only gets HTTP/2 by opening the connection with the h2c preface.

Examples

ewe.with_tls(builder, ewe.Disk("cert.pem", "key.pem"))
ewe.with_tls(builder, ewe.Pem(cert, key))
ewe.with_tls(builder, ewe.Der(cert, key, ewe.RsaPrivateKey))
Search Document