HTTP Wrapper Type RFC & Implementation (#2912)

## Motivation and Context
Implementation of the HTTP request wrapper type from RFC

## Description
RFC + implementation of HttpRequest

## Testing
- IT/UT
- Separate PR which uses this type in implementation of the SDK


----

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._

---------

Co-authored-by: John DiSanti <jdisanti@amazon.com>
This commit is contained in:
Russell Cohen 2023-10-10 09:03:22 -04:00 committed by GitHub
parent bf824532a0
commit 7b30ffcbf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 778 additions and 2 deletions

View File

@ -37,7 +37,7 @@ jobs:
pushd design &>/dev/null
cargo install mdbook
cargo install mdbook-mermaid
cargo install --locked mdbook-mermaid
mdbook build --dest-dir ../../output
popd &>/dev/null

View File

@ -63,6 +63,7 @@
- [RFC-0034: Smithy Orchestrator](./rfcs/rfc0034_smithy_orchestrator.md)
- [RFC-0035: Collection Defaults](./rfcs/rfc0035_collection_defaults.md)
- [RFC-0036: HTTP Dependency Exposure](./rfcs/rfc0036_http_dep_elimination.md)
- [RFC-0037: The HTTP Wrapper](./rfcs/rfc0037_http_wrapper.md)
- [Contributing](./contributing/overview.md)
- [Writing and debugging a low-level feature that relies on HTTP](./contributing/writing_and_debugging_a_low-level_feature_that_relies_on_HTTP.md)

View File

@ -43,3 +43,7 @@
- [RFC-0031: Providing Fallback Credentials on Timeout](./rfc0031_providing_fallback_credentials_on_timeout.md)
- [RFC-0032: Better Constraint Violations](./rfc0032_better_constraint_violations.md)
- [RFC-0033: Improving access to request IDs in SDK clients](./rfc0033_improve_sdk_request_id_access.md)
- [RFC-0034: The Orchestrator Architecture](./rfc0034_smithy_orchestrator.md)
- [RFC-0035: Sensible Defaults for Collection Values](./rfc0035_collection_defaults.md)
- [RFC-0036: Enabling HTTP crate upgrades in the future](./rfc0036_http_dep_elimination.md)
- [RFC-0037: The HTTP wrapper type](./rfc0037_http_wrapper_type.md)

View File

@ -0,0 +1,111 @@
<!-- Give your RFC a descriptive name saying what it would accomplish or what feature it defines -->
RFC: The HTTP Wrapper Type
=============
<!-- RFCs start with the "RFC" status and are then either "Implemented" or "Rejected". -->
> Status: RFC
>
> Applies to: client
<!-- A great RFC will include a list of changes at the bottom so that the implementor can be sure they haven't missed anything -->
For a summarized list of proposed changes, see the [Changes Checklist](#changes-checklist) section.
<!-- Insert a short paragraph explaining, at a high level, what this RFC is for -->
This RFC defines the API of our wrapper types around `http::Request` and `http::Response`. For more information about why we are wrapping these types, see [RFC 0036: The HTTP Dependency](./rfc0036_http_dep_elimination.md).
<!-- The "Terminology" section is optional but is really useful for defining the technical terms you're using in the RFC -->
Terminology
-----------
- `Extensions` / "Request Extensions": The `http` crate Request/Response types include a typed property bag to store additional metadata along with the request.
<!-- Explain how users will use this new feature and, if necessary, how this compares to the current user experience -->
The user experience if this RFC is implemented
----------------------------------------------
In the current version of the SDK, external customers and internal code interacts directly with the [`http`](https://crates.io/crates/http) crate. Once this RFC is implemented, interactions at the **public** API level will occur with our own `http` types instead.
Our types aim to be nearly drop-in-compatible for types in the `http` crate, however:
1. We will not expose existing HTTP types in public APIs in ways that are ossified.
2. When possible, we aim to simplify the APIs to make them easier to use.
3. We will add SDK specific helper functionality when appropriate, e.g. first-level support for applying an endpoint to a request.
<!-- Explain the implementation of this new feature -->
How to actually implement this RFC
----------------------------------
We will need to add two types, `HttpRequest` and `HttpResponse`.
#### To string or not to String
Our header library restricts header names and values to `String`s (UTF-8).
Although the `http` library is very precise in its representation—it allows for `HeaderValue`s that are both a super and subset of `String`—a superset because headers support arbitrary binary data but a subset because headers cannot contain control characters like `\n`.
Although technically allowed, headers containing arbitrary binary data are not widely supported. Generally, Smithy protocols will use base-64 encoding when storing binary data in headers.
Finally, it's nicer for users if they can stay in "string land". Because of this, HttpRequest and Response expose header names and values as strings. Internally, the current design uses `HeaderName` and `HeaderValue`, however, there is a gate on construction that enforces that values are valid UTF-8.
**This is a one way door because `.as_str()` would panic in the future if we allow non-string values into headers.**
#### Where should these types live?
These types will be used by all orchestrator functionality, so they will be housed in `aws-smithy-runtime-api`
#### What's in and what's out?
At the onset, these types focus on supporting the most ossified usages: `&mut` modification of HTTP types. They **do not**
support construction of HTTP types, other than `impl From<http::Request>` and `From<http::Response>`. We will also make it
possible to use `http::HeaderName` / `http::HeaderValue` in a zero-cost way.
#### The `AsHeaderComponent` trait
All header insertion methods accept `impl AsHeaderComponent`. This allows us to provide a nice user experience while taking
advantage of zero-cost usage of `'static str`. We will seal this trait to prevent external usage. We will have separate implementation for:
- `&'static str`
- `String`
- http03x::HeaderName
#### Additional Functionality
Our wrapper type will add the following additional functionality:
1. Support for `self.try_clone()`
2. Support for `&mut self.apply_endpoint(...)`
#### Handling failure
There is no stdlib type that cleanly defines what may be placed into headers—String is too broad (even if we restrict to ASCII). This RFC proposes moving fallibility to the APIs:
```rust,ignore
impl HeadersMut<'_> {
pub fn try_insert(
&mut self,
key: impl AsHeaderComponent,
value: impl AsHeaderComponent,
) -> Result<Option<String>, BoxError> {
// ...
}
}
```
This allows us to offer user-friendly types while still avoiding runtime panics. We also offer `insert` and `append` which panic on invalid values.
#### Request Extensions
There is ongoing work which MAY restrict HTTP extensions to clone types. We will preempt that by:
1. Preventing `Extensions` from being present when initially constructing our HTTP request wrapper.
2. Forbidding non-clone extensions from being inserted into the wrapped request.
This also enables supporting request extensions for different downstream providers by allowing cloning into different extension types.
#### Proposed Implementation
<details>
<summary>Proposed Implementation of `request`</summary>
```rust,ignore
{{#include ../../../rust-runtime/aws-smithy-runtime-api/src/client/http/request.rs}}
```
</details>
### Future Work
Currently, the only way to construct `Request` is from a compatible type (e.g. `http03x::Request`)
Changes checklist
-----------------
- [x] Implement initial implementation and test it against the SDK as written
- [ ] Add test suite of `HTTP` wrapper
- [ ] External design review
- [x] Update the SigV4 crate to remove `http` API dependency
- [ ] Update the SDK to use the new type (breaking change)

View File

@ -20,7 +20,7 @@ aws-smithy-async = { path = "../aws-smithy-async" }
aws-smithy-http = { path = "../aws-smithy-http" }
aws-smithy-types = { path = "../aws-smithy-types" }
bytes = "1"
http = "0.2.3"
http = "0.2.9"
pin-project-lite = "0.2"
tokio = { version = "1.25", features = ["sync"] }
tracing = "0.1"

View File

@ -50,6 +50,9 @@
//! [`tower`]: https://crates.io/crates/tower
//! [`aws-smithy-runtime`]: https://crates.io/crates/aws-smithy-runtime
pub mod request;
pub mod response;
use crate::client::orchestrator::{HttpRequest, HttpResponse};
use crate::client::runtime_components::RuntimeComponents;
use crate::impl_shared_conversions;

View File

@ -0,0 +1,651 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
//! Http Request Types
use aws_smithy_http::body::SdkBody;
use http as http0;
use http::header::{InvalidHeaderName, InvalidHeaderValue, ToStrError};
use http::uri::InvalidUri;
use http0::header::Iter;
use http0::uri::PathAndQuery;
use http0::{Extensions, HeaderMap, Method};
use std::borrow::Cow;
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr;
#[derive(Debug)]
/// An HTTP Request Type
pub struct Request<B = SdkBody> {
body: B,
uri: Uri,
method: Method,
extensions: Extensions,
headers: Headers,
}
/// A Request URI
#[derive(Debug, Clone)]
pub struct Uri {
as_string: String,
parsed: http0::Uri,
}
impl Uri {
/// Sets `endpoint` as the endpoint for a URL.
///
/// An `endpoint` MUST contain a scheme and authority.
/// An `endpoint` MAY contain a port and path.
///
/// An `endpoint` MUST NOT contain a query
pub fn set_endpoint(&mut self, endpoint: &str) -> Result<(), HttpError> {
let endpoint: http0::Uri = endpoint.parse().map_err(HttpError::invalid_uri)?;
let endpoint = endpoint.into_parts();
let authority = endpoint
.authority
.ok_or_else(|| HttpError::new("endpoint must contain authority"))?;
let scheme = endpoint
.scheme
.ok_or_else(|| HttpError::new("endpoint must have scheme"))?;
let new_uri = http0::Uri::builder()
.authority(authority)
.scheme(scheme)
.path_and_query(merge_paths(endpoint.path_and_query, &self.parsed).as_ref())
.build()
.map_err(HttpError::new)?;
self.as_string = new_uri.to_string();
self.parsed = new_uri;
Ok(())
}
}
fn merge_paths(endpoint_path: Option<PathAndQuery>, uri: &http0::Uri) -> Cow<'_, str> {
let uri_path_and_query = uri.path_and_query().map(|pq| pq.as_str()).unwrap_or("");
let endpoint_path = match endpoint_path {
None => return Cow::Borrowed(uri_path_and_query),
Some(path) => path,
};
if let Some(query) = endpoint_path.query() {
tracing::warn!(query = %query, "query specified in endpoint will be ignored during endpoint resolution");
}
let endpoint_path = endpoint_path.path();
if endpoint_path.is_empty() {
Cow::Borrowed(uri_path_and_query)
} else {
let ep_no_slash = endpoint_path.strip_suffix('/').unwrap_or(endpoint_path);
let uri_path_no_slash = uri_path_and_query
.strip_prefix('/')
.unwrap_or(uri_path_and_query);
Cow::Owned(format!("{}/{}", ep_no_slash, uri_path_no_slash))
}
}
impl TryFrom<String> for Uri {
type Error = HttpError;
fn try_from(value: String) -> Result<Self, Self::Error> {
let parsed = value.parse().map_err(HttpError::invalid_uri)?;
Ok(Uri {
as_string: value,
parsed,
})
}
}
impl<'a> TryFrom<&'a str> for Uri {
type Error = HttpError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
Self::try_from(value.to_string())
}
}
impl From<http0::Uri> for Uri {
fn from(value: http::Uri) -> Self {
Self {
as_string: value.to_string(),
parsed: value,
}
}
}
impl<B> TryInto<http0::Request<B>> for Request<B> {
type Error = HttpError;
fn try_into(self) -> Result<http::Request<B>, Self::Error> {
self.into_http03x()
}
}
impl<B> Request<B> {
/// Converts this request into an http 0.x request.
///
/// Depending on the internal storage type, this operation may be free or it may have an internal
/// cost.
pub fn into_http03x(self) -> Result<http0::Request<B>, HttpError> {
let mut req = http::Request::builder()
.uri(self.uri.parsed)
.method(self.method)
.body(self.body)
.expect("known valid");
let mut headers = HeaderMap::new();
headers.extend(
self.headers
.headers
.into_iter()
.map(|(k, v)| (k, v.into_http03x())),
);
*req.headers_mut() = headers;
*req.extensions_mut() = self.extensions;
Ok(req)
}
/// Returns a GET request with no URI
pub fn new(body: B) -> Self {
Self {
body,
uri: Uri::from(http0::Uri::from_static("/")),
method: Method::GET,
extensions: Default::default(),
headers: Default::default(),
}
}
/// Returns a reference to the header map
pub fn headers(&self) -> &Headers {
&self.headers
}
/// Returns a mutable reference to the header map
pub fn headers_mut(&mut self) -> &mut Headers {
&mut self.headers
}
/// Returns the body associated with the request
pub fn body(&self) -> &B {
&self.body
}
/// Returns a mutable reference to the body
pub fn body_mut(&mut self) -> &mut B {
&mut self.body
}
/// Returns the method associated with this request
pub fn method(&self) -> &str {
self.method.as_str()
}
/// Returns the URI associated with this request
pub fn uri(&self) -> &str {
&self.uri.as_string
}
/// Returns a mutable reference the the URI of this http::Request
pub fn uri_mut(&mut self) -> &mut Uri {
&mut self.uri
}
/// Sets the URI of this request
pub fn set_uri<U>(&mut self, uri: U) -> Result<(), U::Error>
where
U: TryInto<Uri>,
{
let uri = uri.try_into()?;
self.uri = uri;
Ok(())
}
/// Adds an extension to the request extensions
pub fn add_extension<T: Send + Sync + Clone + 'static>(&mut self, extension: T) {
self.extensions.insert(extension);
}
}
impl Request<SdkBody> {
/// Attempts to clone this request
///
/// If the body is cloneable, this will clone the request. Otherwise `None` will be returned
pub fn try_clone(&self) -> Option<Self> {
let body = self.body().try_clone()?;
Some(Self {
body,
uri: self.uri.clone(),
method: self.method.clone(),
extensions: Extensions::new(),
headers: self.headers.clone(),
})
}
/// Replaces this requests body with [`SdkBody::taken()`]
pub fn take_body(&mut self) -> SdkBody {
std::mem::replace(self.body_mut(), SdkBody::taken())
}
}
impl<B> TryFrom<http0::Request<B>> for Request<B> {
type Error = HttpError;
fn try_from(value: http::Request<B>) -> Result<Self, Self::Error> {
if let Some(e) = value
.headers()
.values()
.filter_map(|value| value.to_str().err())
.next()
{
Err(HttpError::header_was_not_a_string(e))
} else {
let (parts, body) = value.into_parts();
let mut string_safe_headers: HeaderMap<HeaderValue> = Default::default();
string_safe_headers.extend(
parts
.headers
.into_iter()
.map(|(k, v)| (k, HeaderValue::from_http03x(v).expect("validated above"))),
);
Ok(Self {
body,
uri: parts.uri.into(),
method: parts.method.clone(),
extensions: parts.extensions,
headers: Headers {
headers: string_safe_headers,
},
})
}
}
}
/// An immutable view of request headers
#[derive(Clone, Default, Debug)]
pub struct Headers {
headers: HeaderMap<HeaderValue>,
}
impl<'a> IntoIterator for &'a Headers {
type Item = (&'a str, &'a str);
type IntoIter = HeadersIter<'a>;
fn into_iter(self) -> Self::IntoIter {
HeadersIter {
inner: self.headers.iter(),
}
}
}
/// An Iterator over headers
pub struct HeadersIter<'a> {
inner: Iter<'a, HeaderValue>,
}
impl<'a> Iterator for HeadersIter<'a> {
type Item = (&'a str, &'a str);
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(k, v)| (k.as_str(), v.as_ref()))
}
}
impl Headers {
/// Returns the value for a given key
///
/// If multiple values are associated, the first value is returned
/// See [HeaderMap::get]
pub fn get(&self, key: impl AsRef<str>) -> Option<&str> {
self.headers.get(key.as_ref()).map(|v| v.as_ref())
}
/// Returns an iterator over the headers
pub fn iter(&self) -> HeadersIter<'_> {
HeadersIter {
inner: self.headers.iter(),
}
}
/// Returns the total number of **values** stored in the map
pub fn len(&self) -> usize {
self.headers.len()
}
/// Returns true if there are no headers
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns true if this header is present
pub fn contains_key(&self, key: &str) -> bool {
self.headers.contains_key(key)
}
/// Insert a value into the headers structure.
///
/// This will *replace* any existing value for this key. Returns the previous associated value if any.
///
/// # Panics
/// If the key or value are not valid ascii, this function will panic.
pub fn insert(
&mut self,
key: impl AsHeaderComponent,
value: impl AsHeaderComponent,
) -> Option<String> {
self.try_insert(key, value)
.expect("HeaderName or HeaderValue was invalid")
}
/// Insert a value into the headers structure.
///
/// This will *replace* any existing value for this key. Returns the previous associated value if any.
///
/// If the key or value are not valid ascii, an error is returned
pub fn try_insert(
&mut self,
key: impl AsHeaderComponent,
value: impl AsHeaderComponent,
) -> Result<Option<String>, HttpError> {
let key = header_name(key.into_maybe_static()?)?;
let value = header_value(value.into_maybe_static()?)?;
Ok(self
.headers
.insert(key, value)
.map(|old_value| old_value.into()))
}
/// Appends a value to a given key
///
/// If the key or value are NOT valid ascii, an error is returned
pub fn try_append(
&mut self,
key: impl AsHeaderComponent,
value: impl AsHeaderComponent,
) -> Result<bool, HttpError> {
let key = header_name(key.into_maybe_static()?)?;
let value = header_value(value.into_maybe_static()?)?;
Ok(self.headers.append(key, value))
}
/// Removes all headers with a given key
///
/// If there are multiple entries for this key, the first entry is returned
pub fn remove(&mut self, key: &str) -> Option<HeaderValue> {
self.headers.remove(key)
}
/// Appends a value to a given key
///
/// # Panics
/// If the key or value are NOT valid ascii, this function will panic
pub fn append(&mut self, key: impl AsHeaderComponent, value: impl AsHeaderComponent) -> bool {
self.try_append(key, value)
.expect("HeaderName or HeaderValue was invalid")
}
}
use sealed::AsHeaderComponent;
mod sealed {
use super::*;
/// Trait defining things that may be converted into a header component (name or value)
pub trait AsHeaderComponent {
/// If the component can be represented as a Cow<'static, str>, return it
fn into_maybe_static(self) -> Result<MaybeStatic, HttpError>;
/// If a component is already internally represented as a `http03x::HeaderName`, return it
fn repr_as_http03x_header_name(self) -> Result<http0::HeaderName, Self>
where
Self: Sized,
{
Err(self)
}
}
impl AsHeaderComponent for &'static str {
fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
Ok(Cow::Borrowed(self))
}
}
impl AsHeaderComponent for String {
fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
Ok(Cow::Owned(self))
}
}
impl AsHeaderComponent for Cow<'static, str> {
fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
Ok(self)
}
}
impl AsHeaderComponent for http0::HeaderValue {
fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
Ok(Cow::Owned(
self.to_str()
.map_err(HttpError::header_was_not_a_string)?
.to_string(),
))
}
}
impl AsHeaderComponent for http0::HeaderName {
fn into_maybe_static(self) -> Result<MaybeStatic, HttpError> {
Ok(self.to_string().into())
}
fn repr_as_http03x_header_name(self) -> Result<http0::HeaderName, Self>
where
Self: Sized,
{
Ok(self)
}
}
}
mod header_value {
use super::http0;
use std::str::Utf8Error;
/// HeaderValue type
///
/// **Note**: Unlike `HeaderValue` in `http`, this only supports UTF-8 header values
#[derive(Debug, Clone)]
pub struct HeaderValue {
_private: http0::HeaderValue,
}
impl HeaderValue {
pub(crate) fn from_http03x(value: http0::HeaderValue) -> Result<Self, Utf8Error> {
let _ = std::str::from_utf8(value.as_bytes())?;
Ok(Self { _private: value })
}
pub(crate) fn into_http03x(self) -> http0::HeaderValue {
self._private
}
}
impl AsRef<str> for HeaderValue {
fn as_ref(&self) -> &str {
std::str::from_utf8(self._private.as_bytes())
.expect("unreachable—only strings may be stored")
}
}
impl From<HeaderValue> for String {
fn from(value: HeaderValue) -> Self {
value.as_ref().to_string()
}
}
}
use crate::box_error::BoxError;
pub use header_value::HeaderValue;
impl HeaderValue {
/// Returns the string representation of this header value
pub fn as_str(&self) -> &str {
self.as_ref()
}
}
impl FromStr for HeaderValue {
type Err = HttpError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
HeaderValue::try_from(s.to_string())
}
}
impl TryFrom<String> for HeaderValue {
type Error = HttpError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Ok(HeaderValue::from_http03x(
http0::HeaderValue::try_from(value).map_err(HttpError::invalid_header_value)?,
)
.expect("input was a string"))
}
}
type MaybeStatic = Cow<'static, str>;
#[derive(Debug)]
/// An error occurred constructing an Http Request.
///
/// This is normally due to configuration issues, internal SDK bugs, or other user error.
pub struct HttpError(BoxError);
impl HttpError {
// TODO(httpRefactor): Add better error internals
fn new<E: Into<Box<dyn Error + Send + Sync + 'static>>>(err: E) -> Self {
HttpError(err.into())
}
fn invalid_header_value(err: InvalidHeaderValue) -> Self {
Self(err.into())
}
fn header_was_not_a_string(err: ToStrError) -> Self {
Self(err.into())
}
fn invalid_header_name(err: InvalidHeaderName) -> Self {
Self(err.into())
}
fn invalid_uri(err: InvalidUri) -> Self {
Self(err.into())
}
}
impl Display for HttpError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "an error occurred creating an HTTP Request")
}
}
impl Error for HttpError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(self.0.as_ref())
}
}
fn header_name(name: impl AsHeaderComponent) -> Result<http0::HeaderName, HttpError> {
name.repr_as_http03x_header_name().or_else(|name| {
name.into_maybe_static().and_then(|cow| match cow {
Cow::Borrowed(staticc) => Ok(http0::HeaderName::from_static(staticc)),
Cow::Owned(s) => http0::HeaderName::try_from(s).map_err(HttpError::invalid_header_name),
})
})
}
fn header_value(value: MaybeStatic) -> Result<HeaderValue, HttpError> {
let header = match value {
Cow::Borrowed(b) => http0::HeaderValue::from_static(b),
Cow::Owned(s) => {
http0::HeaderValue::try_from(s).map_err(HttpError::invalid_header_value)?
}
};
HeaderValue::from_http03x(header).map_err(HttpError::new)
}
#[cfg(test)]
mod test {
use aws_smithy_http::body::SdkBody;
use http::header::{AUTHORIZATION, CONTENT_LENGTH};
use http::{HeaderValue, Uri};
#[test]
fn headers_can_be_any_string() {
let _: HeaderValue = "😹".parse().expect("can be any string");
let _: HeaderValue = "abcd".parse().expect("can be any string");
let _ = "a\nb"
.parse::<HeaderValue>()
.expect_err("cannot contain control characters");
}
#[test]
fn request_can_be_created() {
let req = http::Request::builder()
.uri("http://foo.com")
.body(SdkBody::from("hello"))
.unwrap();
let mut req = super::Request::try_from(req).unwrap();
req.headers_mut().insert("a", "b");
assert_eq!(req.headers().get("a").unwrap(), "b");
req.headers_mut().append("a", "c");
assert_eq!(req.headers().get("a").unwrap(), "b");
let http0 = req.into_http03x().unwrap();
assert_eq!(http0.uri(), "http://foo.com");
}
#[test]
fn uri_mutations() {
let req = http::Request::builder()
.uri("http://foo.com")
.body(SdkBody::from("hello"))
.unwrap();
let mut req = super::Request::try_from(req).unwrap();
assert_eq!(req.uri(), "http://foo.com/");
req.set_uri("http://bar.com").unwrap();
assert_eq!(req.uri(), "http://bar.com");
let http0 = req.into_http03x().unwrap();
assert_eq!(http0.uri(), "http://bar.com");
}
#[test]
#[should_panic]
fn header_panics() {
let req = http::Request::builder()
.uri("http://foo.com")
.body(SdkBody::from("hello"))
.unwrap();
let mut req = super::Request::try_from(req).unwrap();
let _ = req
.headers_mut()
.try_insert("a\nb", "a\nb")
.expect_err("invalid header");
let _ = req.headers_mut().insert("a\nb", "a\nb");
}
#[test]
fn try_clone_clones_all_data() {
let request = ::http::Request::builder()
.uri(Uri::from_static("https://www.amazon.com"))
.method("POST")
.header(CONTENT_LENGTH, 456)
.header(AUTHORIZATION, "Token: hello")
.body(SdkBody::from("hello world!"))
.expect("valid request");
let request: super::Request = request.try_into().unwrap();
let cloned = request.try_clone().expect("request is cloneable");
assert_eq!("https://www.amazon.com/", cloned.uri());
assert_eq!("POST", cloned.method());
assert_eq!(2, cloned.headers().len());
assert_eq!("Token: hello", cloned.headers().get(AUTHORIZATION).unwrap(),);
assert_eq!("456", cloned.headers().get(CONTENT_LENGTH).unwrap());
assert_eq!("hello world!".as_bytes(), cloned.body().bytes().unwrap());
}
}

View File

@ -0,0 +1,6 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
//! Http Response Types