Skip to content

Reading the request body in the orchestrator #2729

@jdisanti

Description

@jdisanti

A use-case that comes up with Glacier and other services that need a content SHA-256 as a header is that the request body needs to get read so that it can be hashed before signing. A LoadedRequestBody config bag mechanism for this was added in #2704, but the downside of it is that it always reads the entire request payload into memory, even when the stream is a local file that can be replayed.

This issue tracks adding a mechanism for reading the request body in chunks as a first-class part of the orchestrator. This could be done by adding the following Interceptor trait methods:

/// Represents a chunk of a request body.
pub enum RequestBodyChunk<'a> {
    /// A chunk of the request body.
    Chunk(&'a [u8]),
    /// Indicates that the request body is finished.
    EndOfStream,
}
    /// Informs the orchestrator whether the request body needs to be read before signing.
    ///
    /// When set to `true`, the orchestrator will call the [`Interceptor::accept_request_body_chunk_before_signing`]
    /// hook for every chunk of the request body.
    ///
    /// The orchestrator will attempt to clone the streaming request body in order to call
    /// `read_request_body_before_signing` for each chunk in that request body. This clone
    /// generally succeeds for streaming files. **Important:** If the clone fails, the entire
    /// request body will be read into memory since there will be no other way to replay the
    /// request body for the actual request.
    ///
    /// Defaults to `false`.
    fn needs_read_request_body(
        &self,
        context: &BeforeTransmitInterceptorContextRef<'_>,
        cfg: &ConfigBag,
    ) -> bool {
        let (_, _) = (context, cfg);
        false
    }

    /// A hook that can be used to read the request body before signing.
    ///
    /// This hook is only called if `needs_read_request_body` is true for any interceptor.
    /// If another interceptor returns true, this will be called for this interceptor even
    /// if this interceptor returned false, so the implementation should be able to handle
    /// that scenario.
    ///
    /// This hook is called repeatedly with chunks of the request body until the entire
    /// request body has been read. These chunks are passed in as the `body_chunk` argument.
    /// The request state can be used to store data in between calls.
    fn accept_request_body_chunk_before_signing(
        &self,
        context: &BeforeTransmitInterceptorContextRef<'_>,
        cfg: &mut ConfigBag,
        body_chunk: RequestBodyChunk<'_>,
    ) -> Result<(), BoxError> {
        let (_, _, _) = (context, cfg, body_chunk);
        Ok(())
    }

Similarly, these could be added to the HttpRequestSigner if reading the body is required for the actual signing implementation:

    /// Informs the orchestrator whether the request body needs to be read before signing.
    ///
    /// When set to `true`, the orchestrator will call the [`HttpRequestSigner::accept_request_body_chunk`]
    /// hook for every chunk of the request body.
    ///
    /// The orchestrator will attempt to clone the streaming request body in order to call
    /// `read_request_body_before_signing` for each chunk in that request body. This clone
    /// generally succeeds for streaming files. **Important:** If the clone fails, the entire
    /// request body will be read into memory since there will be no other way to replay the
    /// request body for the actual request.
    ///
    /// Defaults to `false`.
    fn needs_read_request_body(
        &self,
        identity: &Identity,
        auth_scheme_endpoint_config: AuthSchemeEndpointConfig<'_>,
        config_bag: &ConfigBag,
    ) -> bool {
        let (_, _, _) = (identity, auth_scheme_endpoint_config, config_bag);
        false
    }

    fn accept_request_body_chunk(
        &self,
        identity: &Identity,
        auth_scheme_endpoint_config: AuthSchemeEndpointConfig<'_>,
        config_bag: &mut ConfigBag,
        body_chunk: RequestBodyChunk<'_>,
    ) -> Result<(), BoxError> {
        let (_, _, _, _) = (
            identity,
            auth_scheme_endpoint_config,
            config_bag,
            body_chunk,
        );
        Ok(())
    }

Notably, the signer should NOT rely on an interceptor to do its request body reading since there can be multiple authentication schemes, and not all of them may need that.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions