-
Notifications
You must be signed in to change notification settings - Fork 214
Description
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.