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#
BodyErrortells which rule failed, in which part, and the byte offset on the wire. After an error, the decoder or encoder stays failed.OutOfMemoryErrorfrommakeif theAllocatorfails.
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#
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#
pub union FramingHow the end of a body is found.
Empty#
EmptyThere is no body, for example in a response to HEAD, or with status 204 or 304.
Fixed#
FixedThe body has the length in Content-Length.
Field length#
pub length: u64The length of the body, in bytes.
Chunked#
ChunkedThe body is sent in chunks, with Transfer-Encoding: chunked.
CloseDelimited#
CloseDelimitedThe body ends when the connection closes. The connection cannot be used again.
Tunnel#
TunnelThe bytes after the head are a tunnel, for example after a successful CONNECT. They are not
an HTTP body.
Implementation Copy for Framing#
impl Copy for FramingAnomaly#
pub enum AnomalyA problem in a response with no body. The body is still empty, but do not use the connection again.
None#
NoneThere is no problem.
ProhibitedFraming#
ProhibitedFramingA 1xx or 204 response has Content-Length or Transfer-Encoding.
Http10TransferEncoding#
Http10TransferEncodingAn HTTP/1.0 response has Transfer-Encoding.
Selection#
pub struct Selection<'head>The framing of a body, from selectRequest or selectResponse. Give it to Decoder.make
or Encoder.make.
Implementation Selection<'head>#
impl<'head> Selection<'head>Method framing#
pub fn framing(self: &Self) -> FramingReturns how the end of the body is found.
Method anomaly#
pub fn anomaly(self: &Self) -> AnomalyReturns a problem that prevents use of the connection again, if any.
Limits#
pub struct LimitsThe maximum sizes for a body and its framing.
Field maxWireBytes#
pub maxWireBytes: u64The maximum number of bytes on the wire, including the chunk framing.
Field maxPayloadBytes#
pub maxPayloadBytes: u64The maximum number of payload bytes.
Field maxChunkBytes#
pub maxChunkBytes: usizeThe maximum size of one chunk.
Field maxChunks#
pub maxChunks: usizeThe maximum number of chunks that are not empty.
Field maxChunkLineBytes#
pub maxChunkLineBytes: usizeThe maximum length of one chunk size line.
Field maxExtensionBytes#
pub maxExtensionBytes: u64The maximum total length of all chunk extensions.
Field maxTrailerBytes#
pub maxTrailerBytes: usizeThe maximum size of the trailer section.
Field maxTrailerFields#
pub maxTrailerFields: usizeThe maximum number of trailer fields.
Field maxOwnedBytes#
pub maxOwnedBytes: usizeThe maximum memory of a decoder or an encoder.
Field trailerValues#
pub trailerValues: ValueLimitsThe limits for each trailer name and value.
Implementation Copy for Limits#
impl Copy for LimitsProgressState#
pub enum ProgressStateWhat to do after a step.
NeedInput#
NeedInputGive more input.
NeedOutput#
NeedOutputGive more space in the output buffer.
Complete#
CompleteThe body is complete.
Tunnel#
TunnelThe bytes are a tunnel, not an HTTP body.
Progress#
pub struct ProgressThe result of a successful step.
Field consumed#
pub consumed: usizeThe number of input bytes that this call used.
Field written#
pub written: usizeThe number of output bytes that this call wrote.
Field totalWire#
pub totalWire: u64The total number of wire bytes so far.
Field totalPayload#
pub totalPayload: u64The total number of payload bytes so far.
Field state#
pub state: ProgressStateWhat to do next.
CompletionKind#
pub enum CompletionKindHow a body ended. See Completion.kind.
Delimited#
DelimitedThe framing marked the end: the length, the last chunk, or no body.
CloseDelimited#
CloseDelimitedThe connection closed.
Tunnel#
TunnelThe bytes were a tunnel.
Completion#
pub struct Completion<'owner>Proof that a body is complete. Get it from Decoder.completion or Encoder.completion.
Implementation Completion<'owner>#
impl<'owner> Completion<'owner>Method kind#
pub fn kind(self: &Self) -> CompletionKindReturns how the body ended.
BodyComponent#
pub enum BodyComponentThe part of the body work where an error occurred.
Selection#
SelectionThe selection of the framing from the head.
ContentLength#
ContentLengthThe Content-Length field.
TransferEncoding#
TransferEncodingThe Transfer-Encoding field.
ChunkLine#
ChunkLineA chunk size line, with its extensions.
ChunkPayload#
ChunkPayloadThe bytes of a chunk.
ChunkDelimiter#
ChunkDelimiterThe CRLF after the bytes of a chunk.
TrailerName#
TrailerNameThe name of a trailer field.
TrailerValue#
TrailerValueThe value of a trailer field.
TrailerPolicy#
TrailerPolicyThe check of a trailer name against the TrailerPolicy.
Decoder#
DecoderThe decoder.
Encoder#
EncoderThe encoder.
Discard#
DiscardA Decoder.discard call.
Implementation Debug for BodyComponent#
impl Debug for BodyComponentOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as Selection.
BodyLimitKind#
pub enum BodyLimitKindThe limit that a body exceeded. Each value names a field of Limits.
WireBytes#
WireBytesThe cumulative wire-byte budget.
PayloadBytes#
PayloadBytesThe cumulative payload-byte budget.
ChunkBytes#
ChunkBytesThe per-chunk payload-byte budget.
Chunks#
ChunksThe nonempty chunk-count budget.
ChunkLineBytes#
ChunkLineBytesThe chunk-line byte budget.
ExtensionBytes#
ExtensionBytesThe cumulative extension-byte budget.
TrailerBytes#
TrailerBytesThe trailer-section byte budget.
TrailerFields#
TrailerFieldsThe trailer field-count budget.
TrailerNameBytes#
TrailerNameBytestrailerValues.maxNameBytes.
TrailerValueBytes#
TrailerValueBytestrailerValues.maxValueBytes.
TrailerFieldBytes#
TrailerFieldBytestrailerValues.maxFieldBytes.
OwnedBytes#
OwnedBytesThe owned-storage byte budget.
Implementation Debug for BodyLimitKind#
impl Debug for BodyLimitKindOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant, such as WireBytes.
BodyReason#
pub union BodyReasonThe rule that failed.
InvalidFraming#
InvalidFramingThe head does not allow this framing.
ConflictingFraming#
ConflictingFramingThe head has both Content-Length and Transfer-Encoding.
UnsupportedTransferCoding#
UnsupportedTransferCodingA transfer coding other than chunked is used.
InvalidLength#
InvalidLengthThe Content-Length value is not valid.
SizeOverflow#
SizeOverflowA size calculation overflowed.
ChunkSyntax#
ChunkSyntaxA chunk size line or a chunk delimiter is not valid.
TrailerSyntax#
TrailerSyntaxA trailer field is not valid.
TrailerPolicy#
TrailerPolicyA trailer name is not allowed by the TrailerPolicy.
Truncated#
TruncatedThe input ended before the body was complete.
FixedLengthUnderrun#
FixedLengthUnderrunThe encoder finished before it got Content-Length bytes.
FixedLengthOverrun#
FixedLengthOverrunThe encoder got more than Content-Length bytes.
UnexpectedPayload#
UnexpectedPayloadThe encoder got bytes for a message with no body.
UnsupportedTunnelEncoding#
UnsupportedTunnelEncodingAn encoder cannot send tunnel bytes as a body.
DiscardLimit#
DiscardLimitA Decoder.discard call would read more than its maxDiscardWireBytes.
Field allowed#
pub allowed: u64The value of the limit.
Field attempted#
pub attempted: u64The size that was found.
LimitExceeded#
LimitExceededThe body exceeded a limit in Limits.
Field limit#
pub limit: BodyLimitKindThe limit that was exceeded.
Field allowed#
pub allowed: u64The value of the limit.
Field attempted#
pub attempted: u64The size that was found.
InvalidState#
InvalidStateThe call is not valid now, for example a step after the body is complete or after an error.
Implementation Copy for BodyReason#
impl Copy for BodyReasonImplementation Debug for BodyReason#
impl Debug for BodyReasonOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the name of the variant and its fields, such as InvalidFraming.
BodyError#
pub struct BodyErrorThe error from body work. After it, the decoder or encoder stays failed.
Field reason#
pub reason: BodyReasonThe rule that failed.
Field component#
pub component: BodyComponentThe part where it failed.
Field wireOffset#
pub wireOffset: u64The byte offset on the wire, from the start of the body.
Field consumed#
pub consumed: usizeThe number of input bytes that this call used before the error.
Field written#
pub written: usizeThe number of output bytes that this call wrote before the error.
Field totalWire#
pub totalWire: u64The total number of wire bytes before the error.
Field totalPayload#
pub totalPayload: u64The total number of payload bytes before the error.
Field fieldIndex#
pub fieldIndex: Option<usize>The position of the trailer field, from 0, when the error is in a trailer.
Implementation Debug for BodyError#
impl Debug for BodyErrorOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the fields, as BodyError { reason: ..., component: ..., ... }.
TrailerPolicy#
pub union TrailerPolicy<'names>The trailer fields that a chunked body can have.
Default#
DefaultAllow trailers, but not fields that change the framing, routing, or authentication, such as
Content-Length or Host.
Explicit#
ExplicitAllow only the trailers with these names.
Implementation Copy for TrailerPolicy<'names>#
impl<'names> Copy for TrailerPolicy<'names>Implementation TrailerPolicy<'names>#
impl<'names> TrailerPolicy<'names>Associated function defaultPolicy#
pub fn defaultPolicy() -> TrailerPolicy<'names>Returns the Default policy.
Associated function fromNames#
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#
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#
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#
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#
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#
pub struct DecoderReads a body from wire bytes. Make it with Decoder.make.
Implementation Decoder#
impl DecoderAssociated function make#
pub effect fn make<'head>(
selection: Selection<'head>,
limits: Limits,
policy: TrailerPolicy,
) -> Result<Decoder, BodyError> ! OutOfMemoryError ? &mut AllocatorReturns a decoder for the framing in selection, and allocates all its memory.
Errors#
LimitExceededwithOwnedBytesif the memory is more thanmaxOwnedBytes.OutOfMemoryErrorif theAllocatorfails.
Method step#
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#
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#
pub fn completion<'owner>(self: &'owner Self) -> Option<Completion<'owner>>Returns proof that the body is complete, or None.
Method trailers#
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#
pub fn abandon(self: &mut Self) -> ()Stops the decoder. Later calls fail. It does no I/O.
Trailers#
pub struct Trailers<'owner>The trailer fields of a chunked body. They borrow the decoder.
Implementation Trailers<'owner>#
impl<'owner> Trailers<'owner>Method count#
pub fn count(self: &Self) -> usizeReturns the number of trailer fields.
Method requiredOwnedBytes#
pub fn requiredOwnedBytes(self: &Self) -> Result<usize, BodyError>Returns the memory that Trailers.copy needs.
Method fields#
pub fn fields(self: &'owner Self) -> TrailerIterator<'owner>Returns an iterator over the trailer fields, in order.
Method copy#
pub effect fn copy(
self: &'owner Self,
limits: ValueLimits,
) -> Result<OwnedTrailers, BodyError> ! OutOfMemoryError ? &mut AllocatorCopies the trailers into new memory, so that they do not borrow the decoder.
OwnedTrailers#
pub struct OwnedTrailersTrailer fields that own their memory. Get a Trailers from it with OwnedTrailers.view.
Implementation OwnedTrailers#
impl OwnedTrailersMethod ownedBytes#
pub fn ownedBytes(self: &Self) -> usizeReturns the memory that this copy uses, in bytes.
Method view#
pub fn view<'owner>(self: &'owner Self) -> Trailers<'owner>Returns the trailers as a Trailers that borrows this value.
TrailerIterator#
pub struct TrailerIterator<'owner>An iterator over trailer fields, in order.
Implementation TrailerIterator<'owner>#
impl<'owner> TrailerIterator<'owner>Method next#
pub fn next(self: &mut Self) -> Option<Header<'owner>>Returns the next trailer field, or None at the end.
Encoder#
pub struct EncoderWrites a body with its framing. Make it with Encoder.make.
Implementation Encoder#
impl EncoderAssociated function make#
pub effect fn make<'head>(
selection: Selection<'head>,
limits: Limits,
policy: TrailerPolicy,
) -> Result<Encoder, BodyError> ! OutOfMemoryError ? &mut AllocatorReturns an encoder for the framing in selection, and allocates all its memory.
Errors#
UnsupportedTunnelEncodingforTunnelframing.OutOfMemoryErrorif theAllocatorfails.
Method step#
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#
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#
FixedLengthUnderrunif a fixed-length body did not get all its bytes.TrailerPolicyorTrailerSyntaxif a trailer is not valid. Nothing is written.
Method continueFinish#
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#
pub fn completion<'owner>(self: &'owner Self) -> Option<Completion<'owner>>Returns proof that the whole body was written, or None.
Method abandon#
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.