Repeated Content-Length or Transfer-Encoding fields are rejected before the
application handler runs, including identical repetitions and case variants.
A request carrying both fields is also rejected, in either order. These cases
close the connection at the parser boundary; they do not produce an application
response. Applications still own their body-size limits and supported encodings.
Use mist.websocket_with_options when clients must not control how much
message data a connection retains. Its callbacks match mist.websocket:
mist.websocket_with_options(
request: req,
on_init: fn(_connection) { #(Nil, None) },
on_close: fn(_state) { Nil },
handler: handle_ws_message,
options: mist.WebsocketOptions(
max_frame_bytes: 65_536,
max_message_bytes: 262_144,
compression: mist.CompressionDisabled,
),
)Both limits must be positive. The frame limit applies to its declared payload; the message limit includes all continuation fragments, even across TCP reads. Oversized input closes with code 1009 before the application receives it. Detected framing violations or compressed frames close with code 1002. Bounded connections do not negotiate compression and reject compressed frames before decompression. Other protocol validation retains the existing decoder's behavior.
These are per-connection input limits. Applications must separately bound
connection count, queued output, and data retained by their callbacks. A socket
send timeout does not mean that a remote application consumed the output.
The existing mist.websocket API retains its previous behavior.
To follow along with the example below, you can create a new project and add the dependencies as follows:
$ gleam new <your_project>
$ cd <your_project>
$ gleam add mist logging gleam_erlang gleam_httpThe main entrypoint for your application is mist.start. The argument to this
function is generated from the opaque Builder type. It can be constructed with
the mist.new function, and fed updated configuration options with the
associated methods (demonstrated in the examples below).
import gleam/bit_array
import gleam/bytes_tree
import gleam/erlang/process.{type Subject}
import gleam/http/request.{type Request}
import gleam/http/response.{type Response}
import gleam/int
import gleam/io
import gleam/list
import gleam/option.{None}
import gleam/result
import gleam/string
import logging
import mist.{type Connection, type ResponseData}
const index = "<html lang='en'>
<head>
<title>Mist Example</title>
</head>
<body>
Hello, world!
</body>
</html>"
pub fn main() {
logging.configure()
logging.set_level(logging.Debug)
let not_found =
response.new(404)
|> response.set_body(mist.Bytes(bytes_tree.new()))
let assert Ok(_) =
fn(req: Request(Connection)) -> Response(ResponseData) {
let _ = case mist.get_connection_info(req.body) {
Ok(info) -> {
logging.log(
logging.Info,
"Got a request from: " <> mist.connection_info_to_string(info),
)
}
Error(_nil) -> {
logging.log(logging.Info, "Failed to get connection info")
}
}
case request.path_segments(req) {
[] ->
response.new(200)
|> response.prepend_header("my-value", "abc")
|> response.prepend_header("my-value", "123")
|> response.set_body(mist.Bytes(bytes_tree.from_string(index)))
["ws"] ->
mist.websocket(
request: req,
on_init: fn(_conn) { #(Nil, None) },
on_close: fn(_state) { io.println("goodbye!") },
handler: handle_ws_message,
)
["echo"] -> echo_body(req)
["chunk"] ->
mist.chunked(
req,
response.new(200),
fn(subj) {
process.spawn(fn() { send_chunks(subj) })
Nil
},
fn(state, msg, connection) {
case msg {
Data(str) -> {
let assert Ok(_nil) =
mist.send_chunk(connection, bit_array.from_string(str))
mist.chunk_continue(state)
}
Done -> {
mist.chunk_stop()
}
}
},
)
["file", ..rest] -> serve_file(req, rest)
["stream"] -> handle_stream(req)
_ -> not_found
}
}
|> mist.new
|> mist.bind("localhost")
|> mist.with_ipv6
|> mist.port(4000)
|> mist.start
process.sleep_forever()
}
pub type MyMessage {
Broadcast(String)
}
fn handle_ws_message(state, message, conn) {
case message {
mist.Text("ping") -> {
let assert Ok(_) = mist.send_text_frame(conn, "pong")
mist.continue(state)
}
mist.Text(msg) -> {
logging.log(logging.Info, "Received text frame: " <> msg)
mist.continue(state)
}
mist.Binary(msg) -> {
logging.log(
logging.Info,
"Received binary frame ("
<> int.to_string(bit_array.byte_size(msg))
<> ")",
)
mist.continue(state)
}
mist.Custom(Broadcast(text)) -> {
let assert Ok(_) = mist.send_text_frame(conn, text)
mist.continue(state)
}
mist.Closed | mist.Shutdown -> mist.stop()
}
}
fn echo_body(request: Request(Connection)) -> Response(ResponseData) {
let content_type =
request
|> request.get_header("content-type")
|> result.unwrap("text/plain")
mist.read_body(request, 1024 * 1024 * 10)
|> result.map(fn(req) {
response.new(200)
|> response.set_body(mist.Bytes(bytes_tree.from_bit_array(req.body)))
|> response.set_header("content-type", content_type)
})
|> result.lazy_unwrap(fn() {
response.new(400)
|> response.set_body(mist.Bytes(bytes_tree.new()))
})
}
type ChunkMessage {
Data(data: String)
Done
}
fn send_chunks(subject: Subject(ChunkMessage)) {
["one", "two", "three"]
|> list.each(fn(data) {
process.sleep(2000)
process.send(subject, Data(data))
})
process.send(subject, Done)
}
fn serve_file(
_req: Request(Connection),
path: List(String),
) -> Response(ResponseData) {
let file_path = string.join(path, "/")
// Omitting validation for brevity
mist.send_file(file_path, offset: 0, limit: None)
|> result.map(fn(file) {
let content_type = guess_content_type(file_path)
response.new(200)
|> response.prepend_header("content-type", content_type)
|> response.set_body(file)
})
|> result.lazy_unwrap(fn() {
response.new(404)
|> response.set_body(mist.Bytes(bytes_tree.new()))
})
}
fn handle_stream(req: Request(Connection)) -> Response(ResponseData) {
let failed =
response.new(400) |> response.set_body(mist.Bytes(bytes_tree.new()))
case mist.stream(req) {
Ok(consume) -> {
case do_handle_stream(consume, <<>>) {
Ok(body) ->
response.new(200)
|> response.set_body(mist.Bytes(bytes_tree.from_bit_array(body)))
Error(_reason) -> failed
}
}
Error(_reason) -> {
failed
}
}
}
fn do_handle_stream(
consume: fn(Int) -> Result(mist.Chunk, mist.ReadError),
body: BitArray,
) -> Result(BitArray, Nil) {
case consume(1024) {
Ok(mist.Chunk(data, consume)) ->
do_handle_stream(consume, <<body:bits, data:bits>>)
Ok(mist.Done) -> Ok(body)
Error(_reason) -> Error(Nil)
}
}
fn guess_content_type(_path: String) -> String {
"application/octet-stream"
}