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
-
Bytes(bytes_tree.BytesTree)A body of binary data stored as a
BytesTree.If you have a
BitArrayyou can use thebytes_tree.from_bit_arrayfunction to convert it. -
Text(String)A body of unicode text sent as UTF-8.
-
EmptyNo body. The response is sent with a
content-lengthof 0. -
File(@internal File)A body of the contents of a file created with the
filefunction.Large files are safe to send this way as they are never held in memory whole. See
filefor how each protocol sends them. -
Streaming(@internal Streaming)A body written a chunk at a time created with the
stream_responsefunction. -
Sse(@internal Sse)A Server-Sent Events stream created with the
ssefunction. -
Websocket(@internal Websocket)A WebSocket created with the
websocketfunction.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
-
BodyTooLargeThe body is larger than the limit that was given.
-
InvalidBodyThe 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
-
NormalClosureThe connection did what it was for and is closing normally (1000).
-
GoingAwayThe endpoint is going away, from a server shutdown or a client navigating away (1001).
-
ProtocolErrorThe other end broke the protocol (1002).
-
UnsupportedDataData arrived that this endpoint cannot accept (1003).
-
InvalidPayloadDataA message did not match the type it declared such as a text frame that is not UTF-8 (1007).
-
PolicyViolationThe other end broke your rules when no more specific code applies (1008).
-
MessageTooBigA message was larger than this endpoint will handle (1009).
-
MandatoryExtensionAn extension the client required was not negotiated (1010).
-
InternalErrorSomething went wrong on this side (1011).
-
ServiceRestartThe server is restarting and clients may reconnect shortly (1012).
-
TryAgainLaterThe server is overloaded and the client should retry later (1013).
-
BadGatewayAn 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
-
NoCloseReasonClose 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
-
NotFoundThere is nothing at the given path.
-
IsDirectoryThe path is a directory.
-
AccessDeniedThe server is not permitted to read the file.
-
UnknownErrorThe file could not be opened or measured for a reason ewe does not name.
-
InvalidOffsetThe offset is negative or past the end of the file.
-
InvalidLimitThe 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.
Noneleaves 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.
Noneleaves 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_markrather 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.1isIpV4(127, 0, 0, 1). -
IpV6(Int, Int, Int, Int, Int, Int, Int, Int)An IPv6 address, represented as its eight groups.
::1isIpV6(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
-
ConnectionClosedThe client is gone so nothing further can be written.
-
StreamResetThe client cancelled this HTTP/2 stream while the rest of the connection carries on.
-
SendTimedOutThe 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
-
OutOfBuffersThe kernel has no socket buffer space or memory left to take the write.
-
TooManyOpenFilesThe node is at its file descriptor limit or the whole host is.
-
NetworkDownThe interface the connection runs over is down.
-
NetworkUnreachableThere is no route to the client’s network.
-
HostUnreachableThe client’s network is reachable but the client’s host is not.
-
MessageTooLargeThe write is larger than the socket will send in one piece.
-
PermissionDeniedThe socket refused the write on permission grounds.
-
WouldBlockThe write would have blocked and the socket is not willing to.
-
InterruptedA signal arrived mid write. Nothing was sent.
-
NotSupportedThe socket does not support the write as it was made.
-
IoErrorThe write failed below the socket in the network stack or the device.
-
UnknownReasonThe 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
-
RsaPrivateKeyTraditional RSA key.
-
EcPrivateKeyElliptic curve key.
-
DsaPrivateKeyDSA key.
-
PrivateKeyInfoPKCS#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_initis called once, with a subject that the rest of your program sends messages to, and returns the starting state.handleris called for each message that arrives on that subject.on_closeis 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_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_initis 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.handleris called for each frame from the client and each message the selector picks up.on_closeis 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))