glomp/frame

Encode and decode STOMP frames yourself, without any sockets. The client in glomp uses this module, so most users never need it. You can use this if you are writing your own STOMP transport, or a broker!

Types

A STOMP command.

pub type Command {
  Connect
  Stomp
  Send
  Subscribe
  Unsubscribe
  Ack
  Nack
  Begin
  Commit
  Abort
  Disconnect
  Connected
  Message
  Receipt
  ServerError
}

Constructors

  • Connect
  • Stomp

    Another name for CONNECT, added in STOMP 1.2.

  • Send
  • Subscribe
  • Unsubscribe
  • Ack
  • Nack
  • Begin
  • Commit
  • Abort
  • Disconnect
  • Connected
  • Message
  • Receipt
  • ServerError

    The ERROR command, which a broker sends when something went wrong.

An error from turning bytes into a frame. After any of these errors the decoder cannot find where the next frame starts, so the stream has to be closed.

pub type DecodeError {
  UnknownCommand(command: String)
  HeadNotUtf8
  MalformedHeader(line: String)
  UndefinedEscape(sequence: String)
  InvalidContentLength(value: String)
  MissingTerminator
  HeadTooLarge(limit: Int)
  BodyTooLarge(limit: Int)
}

Constructors

  • UnknownCommand(command: String)

    The first line is not a known STOMP command.

  • HeadNotUtf8

    The command or headers are not UTF-8.

  • MalformedHeader(line: String)

    A header line is missing its colon or its name.

  • UndefinedEscape(sequence: String)

    A header uses an escape that STOMP does not define. The valid escapes are \n, \c and \\, plus \r in 1.2.

  • InvalidContentLength(value: String)

    The content-length header is not a valid number of bytes.

  • MissingTerminator

    The body does not end with a NUL byte.

  • HeadTooLarge(limit: Int)
  • BodyTooLarge(limit: Int)

A decoder turns bytes from the network into frames. It holds the bytes that have arrived and how far it got through the next frame.

Create one with decoder. Pass it bytes with feed, then call next until it returns Incomplete:

let decoder = frame.decoder(frame.default_limits) |> frame.feed(bytes)

case frame.next(decoder) {
  Ok(frame.Decoded(frame:, decoder:)) -> use_frame(frame, decoder)
  Ok(frame.Incomplete(decoder:)) -> wait_for_more_bytes(decoder)
  Error(error) -> close_the_connection(error)
}

Each step returns a new Decoder, so always keep the latest one. After a DecodeError, the decoder cannot read any more of the stream.

pub opaque type Decoder

A frame, which is one unit of STOMP: a command, some headers and a body.

Its headers are unescaped and kept in the order they were sent.

pub type Frame {
  Frame(
    command: Command,
    headers: List(#(String, String)),
    body: BitArray,
  )
}

Constructors

  • Frame(
      command: Command,
      headers: List(#(String, String)),
      body: BitArray,
    )
pub type Limits {
  Limits(max_head_bytes: Int, max_body_bytes: Int)
}

Constructors

  • Limits(max_head_bytes: Int, max_body_bytes: Int)

What next gives back. It is either a frame, or a sign that more bytes are needed.

Both variants carry the updated Decoder. Use it for the next call to feed or next.

pub type Step {
  Decoded(frame: Frame, decoder: Decoder)
  Incomplete(decoder: Decoder)
}

Constructors

  • Decoded(frame: Frame, decoder: Decoder)

    A frame. Call next again to get the one after it.

  • Incomplete(decoder: Decoder)

    The buffer does not hold a whole frame yet. Call feed with more bytes.

pub type Version {
  Stomp11
  Stomp12
}

Constructors

  • Stomp11
  • Stomp12

Values

pub fn command_to_string(command: Command) -> String

Get the command’s name on the transport, such as "SEND".

pub fn decoder(limits: Limits) -> Decoder

Create a decoder that reads STOMP 1.2. Change it with set_version.

pub const default_limits: Limits

The default limits, which allow 64 KiB of headers and a 100 MiB body.

pub fn encode(frame: Frame, version: Version) -> BitArray

Encode a frame.

Headers are escaped for version. CONNECT and CONNECTED headers are written as they are, because STOMP 1.0 brokers expect that.

Only the headers you give are written. A NUL byte marks the end of a frame, so a body that contains one needs content-length. The reader then knows how far to read.

pub fn feed(decoder: Decoder, bytes: BitArray) -> Decoder

Add bytes that arrived from the network.

pub fn header(frame: Frame, name: String) -> Result(String, Nil)

Get the first header called name.

pub const heart_beat: BitArray

A heart-beat, which is a single line feed.

pub fn next(decoder: Decoder) -> Result(Step, DecodeError)

Take the next frame from the buffer. It skips heart-beats.

pub fn set_version(decoder: Decoder, version: Version) -> Decoder

Set the version to read by. Call it after the CONNECTED frame, because that frame says which version the broker chose.

In 1.1, a carriage return is part of the data, and the \r escape is invalid.

✨ Search Document