Browse the docs

silk.http.body.

Read and write the body of an HTTP/1 message: fixed length, chunked, or until the connection closes.

When to use#

After you parse a head with silk.http.head, call selectRequest or selectResponse to find how the body is framed. Then make a Decoder and call Decoder.step with the bytes after the head. To send a body, check the head with validateOutgoingRequest or validateOutgoingResponse, make an Encoder, and call Encoder.step, Encoder.beginFinish, and Encoder.continueFinish.

Details#

make allocates all the memory. After that, step does not allocate and does not keep your buffers. Each call returns how many input bytes it used and how many output bytes it wrote, and the next action in ProgressState. The decoder removes the chunk framing and gives only the payload. This module does not decompress content: see silk.http.content.

Errors#

  • BodyError tells which rule failed, in which part, and the byte offset on the wire. After an error, the decoder or encoder stays failed.

  • OutOfMemoryError from make if the Allocator fails.

Gotchas#

A complete body does not mean that the connection is good. Check the connection separately before you use it again. A response with CloseDelimited framing ends only when the connection closes, so give finalInput as true at the end of the transport.

Examples#

Decode a chunked response#

silk
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.core.effect { Effect }
import silk.http.message { Method }
import silk.http.body {
  BodyError,
  Decoder,
  Framing,
  Limits as BodyLimits,
  Progress,
  ProgressState,
  Selection,
  TrailerPolicy,
  selectResponse
}
import silk.http.head {
  Limits,
  ParseError,
  ParsedResponse,
  ResponseHead,
  ResponseParser,
  parseResponse
}
import silk.http.headers { Limits as ValueLimits }
import silk.core.result { Result }
import silk.collections.slice { Slice }

fn values() -> ValueLimits {
  return ValueLimits {
    maxMethodBytes: 16,
    maxTargetBytes: 1024,
    maxNameBytes: 64,
    maxValueBytes: 1024,
    maxFields: 32,
    maxFieldBytes: 4096,
    maxOwnedBytes: 8192,
  }
}

fn headLimits() -> Limits {
  return Limits {
    maxHeadBytes: 4096,
    maxStartLineBytes: 1024,
    maxFieldLineBytes: 1024,
    maxOwnedBytes: 8192,
    values: values(),
  }
}

fn bodyLimits() -> BodyLimits {
  return BodyLimits {
    maxWireBytes: 65536,
    maxPayloadBytes: 65536,
    maxChunkBytes: 4096,
    maxChunks: 64,
    maxChunkLineBytes: 256,
    maxExtensionBytes: 1024,
    maxTrailerBytes: 1024,
    maxTrailerFields: 8,
    maxOwnedBytes: 16384,
    trailerValues: values(),
  }
}

effect fn program() -> i32
  ! OutOfMemoryError {
  let mut allocator = Allocator.systemAllocatorProvider()
  let wire = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n"
  let parsed = run parseResponse(&wire, headLimits())
    |> Effect.provideMut<Allocator>(&mut allocator)
  let result = match move parsed {
    Result<ParsedResponse, ParseError>.Success { value } => move value
    Result<ParsedResponse, ParseError>.Failure { error: _ } => {
      return 1
    }
  }
  let head = match ResponseParser.head(&result.parser) {
    Result<ResponseHead, ParseError>.Success { value } => value
    Result<ResponseHead, ParseError>.Failure { error: _ } => {
      return 1
    }
  }
  let get = Method.get()
  let selection = match selectResponse(head, &get, TrailerPolicy.defaultPolicy()) {
    Result<Selection, BodyError>.Success { value } => move value
    Result<Selection, BodyError>.Failure { error: _ } => {
      return 1
    }
  }
  let chunked = match Selection.framing(&selection) {
    Framing.Chunked => true
    _ => false
  }
  if !chunked {
    return 1
  }
  let made = run Decoder.make(move selection, bodyLimits(), TrailerPolicy.defaultPolicy())
    |> Effect.provideMut<Allocator>(&mut allocator)
  let mut decoder = match move made {
    Result<Decoder, BodyError>.Success { value } => move value
    Result<Decoder, BodyError>.Failure { error: _ } => {
      return 1
    }
  }

  // The body starts after the head.
  let body = Slice.view<u8>(&wire, result.progress.consumed, wire.length - result.progress.consumed)
  let mut payload: [u8; 16] = [0; 16]
  let progress = match Decoder.step(&mut decoder, body, &mut payload, true) {
    Result<Progress, BodyError>.Success { value } => move value
    Result<Progress, BodyError>.Failure { error: _ } => {
      return 1
    }
  }
  if progress.state != ProgressState.Complete || progress.written != 5 || payload[0] != 104 {
    return 1
  }
  return 0
}

effect fn failed<E>(error: E) -> i32 {
  drop error
  return 1
}

pub fn main() -> i32 {
  return run Effect.catchAll(program(), failed)
}

Import as Decoder with import silk.http.body { Decoder }.

Public declarations: 22.

Framing#

silk
pub union Framing

How the end of a body is found.

Empty#

silk
Empty

There is no body, for example in a response to HEAD, or with status 204 or 304.

Fixed#

silk
Fixed

The body has the length in Content-Length.

Field length#

silk
pub length: u64

The length of the body, in bytes.

Chunked#

silk
Chunked

The body is sent in chunks, with Transfer-Encoding: chunked.

CloseDelimited#

silk
CloseDelimited

The body ends when the connection closes. The connection cannot be used again.

Tunnel#

silk
Tunnel

The bytes after the head are a tunnel, for example after a successful CONNECT. They are not an HTTP body.

Implementation Copy for Framing#

silk
impl Copy for Framing

Anomaly#

silk
pub enum Anomaly

A problem in a response with no body. The body is still empty, but do not use the connection again.

None#

silk
None

There is no problem.

ProhibitedFraming#

silk
ProhibitedFraming

A 1xx or 204 response has Content-Length or Transfer-Encoding.

Http10TransferEncoding#

silk
Http10TransferEncoding

An HTTP/1.0 response has Transfer-Encoding.

Selection#

silk
pub struct Selection<'head>

The framing of a body, from selectRequest or selectResponse. Give it to Decoder.make or Encoder.make.

Implementation Selection<'head>#

silk
impl<'head> Selection<'head>

Method framing#

silk
pub fn framing(self: &Self) -> Framing

Returns how the end of the body is found.

Method anomaly#

silk
pub fn anomaly(self: &Self) -> Anomaly

Returns a problem that prevents use of the connection again, if any.

Limits#

silk
pub struct Limits

The maximum sizes for a body and its framing.

Field maxWireBytes#

silk
pub maxWireBytes: u64

The maximum number of bytes on the wire, including the chunk framing.

Field maxPayloadBytes#

silk
pub maxPayloadBytes: u64

The maximum number of payload bytes.

Field maxChunkBytes#

silk
pub maxChunkBytes: usize

The maximum size of one chunk.

Field maxChunks#

silk
pub maxChunks: usize

The maximum number of chunks that are not empty.

Field maxChunkLineBytes#

silk
pub maxChunkLineBytes: usize

The maximum length of one chunk size line.

Field maxExtensionBytes#

silk
pub maxExtensionBytes: u64

The maximum total length of all chunk extensions.

Field maxTrailerBytes#

silk
pub maxTrailerBytes: usize

The maximum size of the trailer section.

Field maxTrailerFields#

silk
pub maxTrailerFields: usize

The maximum number of trailer fields.

Field maxOwnedBytes#

silk
pub maxOwnedBytes: usize

The maximum memory of a decoder or an encoder.

Field trailerValues#

silk
pub trailerValues: ValueLimits

The limits for each trailer name and value.

Implementation Copy for Limits#

silk
impl Copy for Limits

ProgressState#

silk
pub enum ProgressState

What to do after a step.

NeedInput#

silk
NeedInput

Give more input.

NeedOutput#

silk
NeedOutput

Give more space in the output buffer.

Complete#

silk
Complete

The body is complete.

Tunnel#

silk
Tunnel

The bytes are a tunnel, not an HTTP body.

Progress#

silk
pub struct Progress

The result of a successful step.

Field consumed#

silk
pub consumed: usize

The number of input bytes that this call used.

Field written#

silk
pub written: usize

The number of output bytes that this call wrote.

Field totalWire#

silk
pub totalWire: u64

The total number of wire bytes so far.

Field totalPayload#

silk
pub totalPayload: u64

The total number of payload bytes so far.

Field state#

silk
pub state: ProgressState

What to do next.

CompletionKind#

silk
pub enum CompletionKind

How a body ended. See Completion.kind.

Delimited#

silk
Delimited

The framing marked the end: the length, the last chunk, or no body.

CloseDelimited#

silk
CloseDelimited

The connection closed.

Tunnel#

silk
Tunnel

The bytes were a tunnel.

Completion#

silk
pub struct Completion<'owner>

Proof that a body is complete. Get it from Decoder.completion or Encoder.completion.

Implementation Completion<'owner>#

silk
impl<'owner> Completion<'owner>

Method kind#

silk
pub fn kind(self: &Self) -> CompletionKind

Returns how the body ended.

BodyComponent#

silk
pub enum BodyComponent

The part of the body work where an error occurred.

Selection#

silk
Selection

The selection of the framing from the head.

ContentLength#

silk
ContentLength

The Content-Length field.

TransferEncoding#

silk
TransferEncoding

The Transfer-Encoding field.

ChunkLine#

silk
ChunkLine

A chunk size line, with its extensions.

ChunkPayload#

silk
ChunkPayload

The bytes of a chunk.

ChunkDelimiter#

silk
ChunkDelimiter

The CRLF after the bytes of a chunk.

TrailerName#

silk
TrailerName

The name of a trailer field.

TrailerValue#

silk
TrailerValue

The value of a trailer field.

TrailerPolicy#

silk
TrailerPolicy

The check of a trailer name against the TrailerPolicy.

Decoder#

silk
Decoder

The decoder.

Encoder#

silk
Encoder

The encoder.

Discard#

silk
Discard

A Decoder.discard call.

Implementation Debug for BodyComponent#

silk
impl Debug for BodyComponent

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant, such as Selection.

BodyLimitKind#

silk
pub enum BodyLimitKind

The limit that a body exceeded. Each value names a field of Limits.

WireBytes#

silk
WireBytes

The cumulative wire-byte budget.

PayloadBytes#

silk
PayloadBytes

The cumulative payload-byte budget.

ChunkBytes#

silk
ChunkBytes

The per-chunk payload-byte budget.

Chunks#

silk
Chunks

The nonempty chunk-count budget.

ChunkLineBytes#

silk
ChunkLineBytes

The chunk-line byte budget.

ExtensionBytes#

silk
ExtensionBytes

The cumulative extension-byte budget.

TrailerBytes#

silk
TrailerBytes

The trailer-section byte budget.

TrailerFields#

silk
TrailerFields

The trailer field-count budget.

TrailerNameBytes#

silk
TrailerNameBytes

trailerValues.maxNameBytes.

TrailerValueBytes#

silk
TrailerValueBytes

trailerValues.maxValueBytes.

TrailerFieldBytes#

silk
TrailerFieldBytes

trailerValues.maxFieldBytes.

OwnedBytes#

silk
OwnedBytes

The owned-storage byte budget.

Implementation Debug for BodyLimitKind#

silk
impl Debug for BodyLimitKind

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant, such as WireBytes.

BodyReason#

silk
pub union BodyReason

The rule that failed.

InvalidFraming#

silk
InvalidFraming

The head does not allow this framing.

ConflictingFraming#

silk
ConflictingFraming

The head has both Content-Length and Transfer-Encoding.

UnsupportedTransferCoding#

silk
UnsupportedTransferCoding

A transfer coding other than chunked is used.

InvalidLength#

silk
InvalidLength

The Content-Length value is not valid.

SizeOverflow#

silk
SizeOverflow

A size calculation overflowed.

ChunkSyntax#

silk
ChunkSyntax

A chunk size line or a chunk delimiter is not valid.

TrailerSyntax#

silk
TrailerSyntax

A trailer field is not valid.

TrailerPolicy#

silk
TrailerPolicy

A trailer name is not allowed by the TrailerPolicy.

Truncated#

silk
Truncated

The input ended before the body was complete.

FixedLengthUnderrun#

silk
FixedLengthUnderrun

The encoder finished before it got Content-Length bytes.

FixedLengthOverrun#

silk
FixedLengthOverrun

The encoder got more than Content-Length bytes.

UnexpectedPayload#

silk
UnexpectedPayload

The encoder got bytes for a message with no body.

UnsupportedTunnelEncoding#

silk
UnsupportedTunnelEncoding

An encoder cannot send tunnel bytes as a body.

DiscardLimit#

silk
DiscardLimit

A Decoder.discard call would read more than its maxDiscardWireBytes.

Field allowed#

silk
pub allowed: u64

The value of the limit.

Field attempted#

silk
pub attempted: u64

The size that was found.

LimitExceeded#

silk
LimitExceeded

The body exceeded a limit in Limits.

Field limit#

silk
pub limit: BodyLimitKind

The limit that was exceeded.

Field allowed#

silk
pub allowed: u64

The value of the limit.

Field attempted#

silk
pub attempted: u64

The size that was found.

InvalidState#

silk
InvalidState

The call is not valid now, for example a step after the body is complete or after an error.

Implementation Copy for BodyReason#

silk
impl Copy for BodyReason

Implementation Debug for BodyReason#

silk
impl Debug for BodyReason

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant and its fields, such as InvalidFraming.

BodyError#

silk
pub struct BodyError

The error from body work. After it, the decoder or encoder stays failed.

Field reason#

silk
pub reason: BodyReason

The rule that failed.

Field component#

silk
pub component: BodyComponent

The part where it failed.

Field wireOffset#

silk
pub wireOffset: u64

The byte offset on the wire, from the start of the body.

Field consumed#

silk
pub consumed: usize

The number of input bytes that this call used before the error.

Field written#

silk
pub written: usize

The number of output bytes that this call wrote before the error.

Field totalWire#

silk
pub totalWire: u64

The total number of wire bytes before the error.

Field totalPayload#

silk
pub totalPayload: u64

The total number of payload bytes before the error.

Field fieldIndex#

silk
pub fieldIndex: Option<usize>

The position of the trailer field, from 0, when the error is in a trailer.

Implementation Debug for BodyError#

silk
impl Debug for BodyError

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the fields, as BodyError { reason: ..., component: ..., ... }.

TrailerPolicy#

silk
pub union TrailerPolicy<'names>

The trailer fields that a chunked body can have.

Default#

silk
Default

Allow trailers, but not fields that change the framing, routing, or authentication, such as Content-Length or Host.

Explicit#

silk
Explicit

Allow only the trailers with these names.

Implementation Copy for TrailerPolicy<'names>#

silk
impl<'names> Copy for TrailerPolicy<'names>

Implementation TrailerPolicy<'names>#

silk
impl<'names> TrailerPolicy<'names>

Associated function defaultPolicy#

silk
pub fn defaultPolicy() -> TrailerPolicy<'names>

Returns the Default policy.

Associated function fromNames#

silk
pub fn fromNames(
    names: &'names [string<'names>],
  ) -> Result<TrailerPolicy<'names>, BodyError>

Returns a policy that allows only names. It fails if a name is not a token, or is a field that is never allowed in a trailer.

selectRequest#

silk
pub fn selectRequest<'value>(
  head: RequestHead<'value>,
  policy: TrailerPolicy,
) -> Result<Selection<'value>, BodyError>

Returns the framing of a received request body.

A request without Content-Length or Transfer-Encoding has no body.

selectResponse#

silk
pub fn selectResponse<'value>(
  head: ResponseHead<'value>,
  requestMethod: &Method,
  policy: TrailerPolicy,
) -> Result<Selection<'value>, BodyError>

Returns the framing of a received response body. requestMethod is the method of the request.

Details#

A response to HEAD, a 1xx, 204, or 304 response has no body. A 2xx response to CONNECT is a tunnel. A response without Content-Length or Transfer-Encoding ends when the connection closes.

validateOutgoingRequest#

silk
pub fn validateOutgoingRequest<'value>(
  head: RequestHead<'value>,
  policy: TrailerPolicy,
) -> Result<Selection<'value>, BodyError>

Checks the framing fields of a request that you send, and returns the framing for Encoder.make.

validateOutgoingResponse#

silk
pub fn validateOutgoingResponse<'value>(
  head: ResponseHead<'value>,
  requestMethod: &Method,
  policy: TrailerPolicy,
) -> Result<Selection<'value>, BodyError>

Checks the framing fields of a response that you send, and returns the framing for Encoder.make. A 1xx, 204, or 2xx CONNECT response cannot have Content-Length or Transfer-Encoding.

Decoder#

silk
pub struct Decoder

Reads a body from wire bytes. Make it with Decoder.make.

Implementation Decoder#

silk
impl Decoder

Associated function make#

silk
pub effect fn make<'head>(
    selection: Selection<'head>,
    limits: Limits,
    policy: TrailerPolicy,
  ) -> Result<Decoder, BodyError> ! OutOfMemoryError ? &mut Allocator

Returns a decoder for the framing in selection, and allocates all its memory.

Errors#

  • LimitExceeded with OwnedBytes if the memory is more than maxOwnedBytes.

  • OutOfMemoryError if the Allocator fails.

Method step#

silk
pub fn step(
    self: &mut Self,
    input: &[u8],
    output: &mut [u8],
    finalInput: bool,
  ) -> Result<Progress, BodyError>

Reads wire bytes from input, and writes payload bytes to output.

Details#

The decoder uses only the input bytes whose payload fits in output. Give the rest again in the next call. Set finalInput to true when the transport has no more bytes. For a body that ends with the connection, this completes the body. For other bodies, it makes an incomplete body fail with Truncated.

Method discard#

silk
pub fn discard(
    self: &mut Self,
    input: &[u8],
    finalInput: bool,
    maxDiscardWireBytes: u64,
  ) -> Result<Progress, BodyError>

Reads and drops body bytes, for example to use the connection again without the body.

maxDiscardWireBytes limits the total wire bytes of all discard calls. If more are needed, it fails with DiscardLimit.

Method completion#

silk
pub fn completion<'owner>(self: &'owner Self) -> Option<Completion<'owner>>

Returns proof that the body is complete, or None.

Method trailers#

silk
pub fn trailers<'owner>(self: &'owner Self) -> Option<Trailers<'owner>>

Returns the trailer fields of a complete chunked body, or None. They are separate from the fields of the head.

Method abandon#

silk
pub fn abandon(self: &mut Self) -> ()

Stops the decoder. Later calls fail. It does no I/O.

Trailers#

silk
pub struct Trailers<'owner>

The trailer fields of a chunked body. They borrow the decoder.

Implementation Trailers<'owner>#

silk
impl<'owner> Trailers<'owner>

Method count#

silk
pub fn count(self: &Self) -> usize

Returns the number of trailer fields.

Method requiredOwnedBytes#

silk
pub fn requiredOwnedBytes(self: &Self) -> Result<usize, BodyError>

Returns the memory that Trailers.copy needs.

Method fields#

silk
pub fn fields(self: &'owner Self) -> TrailerIterator<'owner>

Returns an iterator over the trailer fields, in order.

Method copy#

silk
pub effect fn copy(
    self: &'owner Self,
    limits: ValueLimits,
  ) -> Result<OwnedTrailers, BodyError> ! OutOfMemoryError ? &mut Allocator

Copies the trailers into new memory, so that they do not borrow the decoder.

OwnedTrailers#

silk
pub struct OwnedTrailers

Trailer fields that own their memory. Get a Trailers from it with OwnedTrailers.view.

Implementation OwnedTrailers#

silk
impl OwnedTrailers

Method ownedBytes#

silk
pub fn ownedBytes(self: &Self) -> usize

Returns the memory that this copy uses, in bytes.

Method view#

silk
pub fn view<'owner>(self: &'owner Self) -> Trailers<'owner>

Returns the trailers as a Trailers that borrows this value.

TrailerIterator#

silk
pub struct TrailerIterator<'owner>

An iterator over trailer fields, in order.

Implementation TrailerIterator<'owner>#

silk
impl<'owner> TrailerIterator<'owner>

Method next#

silk
pub fn next(self: &mut Self) -> Option<Header<'owner>>

Returns the next trailer field, or None at the end.

Encoder#

silk
pub struct Encoder

Writes a body with its framing. Make it with Encoder.make.

Implementation Encoder#

silk
impl Encoder

Associated function make#

silk
pub effect fn make<'head>(
    selection: Selection<'head>,
    limits: Limits,
    policy: TrailerPolicy,
  ) -> Result<Encoder, BodyError> ! OutOfMemoryError ? &mut Allocator

Returns an encoder for the framing in selection, and allocates all its memory.

Errors#

  • UnsupportedTunnelEncoding for Tunnel framing.

  • OutOfMemoryError if the Allocator fails.

Method step#

silk
pub fn step(self: &mut Self, input: &[u8], output: &mut [u8]) -> Result<Progress, BodyError>

Takes payload bytes from input, and writes framed bytes to output.

For chunked framing, the encoder writes a chunk for the bytes that it takes. With NeedOutput, send the output and call again.

Method beginFinish#

silk
pub fn beginFinish<'value>(
    self: &mut Self,
    trailers: &Headers<'value>,
  ) -> Result<(), BodyError>

Starts the end of the body, with trailers for a chunked body. Then call Encoder.continueFinish.

Errors#

  • FixedLengthUnderrun if a fixed-length body did not get all its bytes.

  • TrailerPolicy or TrailerSyntax if a trailer is not valid. Nothing is written.

Method continueFinish#

silk
pub fn continueFinish(self: &mut Self, output: &mut [u8]) -> Result<Progress, BodyError>

Writes the end of the body, such as the last chunk and the trailers, to output. Call it again while the state is NeedOutput.

Method completion#

silk
pub fn completion<'owner>(self: &'owner Self) -> Option<Completion<'owner>>

Returns proof that the whole body was written, or None.

Method abandon#

silk
pub fn abandon(self: &mut Self) -> ()

Stops the encoder without writing more bytes. Later calls fail. The body on the wire is incomplete.

Category: HTTP.

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2.

View the source of this page