mod hyper;
use std::{
convert::Infallible,
error::Error as StdError,
fmt,
future::{Future, poll_fn},
pin::Pin,
task::{Context, Poll},
time::Duration,
};
use bytes::Bytes;
use http::{
HeaderMap, HeaderName, HeaderValue, Method, Request, Response, StatusCode, Uri,
header::CONTENT_TYPE,
};
use http_body::{Frame, SizeHint};
use http_body_util::{BodyExt as _, LengthLimitError, Limited};
use tower_service::Service;
pub(crate) use self::hyper::TransportSettings;
pub use self::hyper::{HttpVersion, HyperResponseFuture, HyperTransport, ResponseBody};
use crate::{
config::Config,
constants::{
JSON_CONTENT_TYPE, PROTECTED_HEADERS, RETRY_COUNT_HEADER, RUNTIME_IDENTIFIER,
SDK_IDENTIFIER, TRANSPORT_HEADERS,
},
error::{ApiError, Error, format_endpoint},
question::upsert,
telemetry,
text::{self, Backslash, SafeText},
};
pub type BoxError = Box<dyn StdError + Send + Sync>;
#[derive(Clone, Default)]
pub struct Body {
data: Option<Bytes>,
}
impl Body {
#[must_use]
pub fn empty() -> Self {
Self { data: None }
}
#[must_use]
pub fn len(&self) -> usize {
self.data.as_ref().map_or(0, Bytes::len)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl From<Bytes> for Body {
fn from(bytes: Bytes) -> Self {
Self { data: (!bytes.is_empty()).then_some(bytes) }
}
}
impl fmt::Debug for Body {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.debug_struct("Body").field("len", &self.len()).finish()
}
}
impl http_body::Body for Body {
type Data = Bytes;
type Error = Infallible;
fn poll_frame(
self: Pin<&mut Self>,
_: &mut Context<'_>,
) -> Poll<Option<Result<Frame<Bytes>, Infallible>>> {
Poll::Ready(self.get_mut().data.take().map(|bytes| Ok(Frame::data(bytes))))
}
fn is_end_stream(&self) -> bool {
self.data.is_none()
}
fn size_hint(&self) -> SizeHint {
SizeHint::with_exact(self.len() as u64)
}
}
mod sealed {
pub trait Sealed {}
}
pub trait HttpService: sealed::Sealed + Clone + Send + Sync + 'static {
type ResponseBody: http_body::Body<Data: Send, Error: Into<BoxError>> + Send + 'static;
type Error: Into<BoxError>;
type Future: Future<Output = Result<Response<Self::ResponseBody>, Self::Error>> + Send;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>;
fn call(&mut self, request: Request<Body>) -> Self::Future;
}
impl<S, B> sealed::Sealed for S
where
S: Service<Request<Body>, Response = Response<B>> + Clone + Send + Sync + 'static,
S::Error: Into<BoxError>,
S::Future: Send,
B: http_body::Body<Data: Send, Error: Into<BoxError>> + Send + 'static,
{
}
impl<S, B> HttpService for S
where
S: Service<Request<Body>, Response = Response<B>> + Clone + Send + Sync + 'static,
S::Error: Into<BoxError>,
S::Future: Send,
B: http_body::Body<Data: Send, Error: Into<BoxError>> + Send + 'static,
{
type ResponseBody = B;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), S::Error>> {
Service::poll_ready(self, cx)
}
fn call(&mut self, request: Request<Body>) -> S::Future {
Service::call(self, request)
}
}
pub(crate) fn base_headers(config: &Config, with_body: bool) -> HeaderMap {
let defaults = config.default_headers();
let mut headers = HeaderMap::with_capacity(defaults.len() + PROTECTED_HEADERS.len() + 1);
for (name, value) in defaults {
if !is_sdk_owned(name, with_body) {
headers.append(name, value.clone());
}
}
let [authorization, accept, user_agent, sdk, runtime] = PROTECTED_HEADERS;
headers.insert(authorization, config.authorization().clone());
headers.insert(accept, JSON_CONTENT_TYPE);
headers.insert(user_agent, config.user_agent().clone());
headers.insert(sdk, SDK_IDENTIFIER);
if config.send_runtime_header() {
headers.insert(runtime, RUNTIME_IDENTIFIER.clone());
}
if with_body {
headers.insert(CONTENT_TYPE, JSON_CONTENT_TYPE);
}
headers
}
fn is_sdk_owned(name: &HeaderName, with_body: bool) -> bool {
PROTECTED_HEADERS.contains(name)
|| *name == RETRY_COUNT_HEADER
|| (with_body && *name == CONTENT_TYPE)
|| TRANSPORT_HEADERS.contains(name)
}
pub(crate) fn call_headers<'a, I>(
raw: I,
with_body: bool,
) -> Result<Vec<(HeaderName, HeaderValue)>, Error>
where
I: IntoIterator<Item = (&'a str, &'a str)>,
I::IntoIter: ExactSizeIterator,
{
let raw = raw.into_iter();
let mut parsed: Vec<(HeaderName, HeaderValue)> = Vec::with_capacity(raw.len());
for (name, value) in raw {
let (name, value) = parse_header(name, value, "").map_err(Error::invalid_request)?;
if is_sdk_owned(&name, with_body) {
continue;
}
upsert(&mut parsed, name, value);
}
Ok(parsed)
}
pub(crate) fn parse_header(
name: &str,
value: &str,
whose: &str,
) -> Result<(HeaderName, HeaderValue), String> {
let parsed = HeaderName::from_bytes(name.as_bytes()).map_err(|_| {
format!("The {whose}header name {} is not a valid HTTP header name.", text::quoted(name))
})?;
let value = HeaderValue::from_str(value).map_err(|_| {
format!(
"The value of the {whose}header {} is not a valid HTTP header value.",
text::quoted(name)
)
})?;
Ok((parsed, value))
}
#[derive(Clone, Copy)]
pub(crate) struct Exchange<'a> {
pub(crate) method: &'a Method,
pub(crate) uri: &'a Uri,
pub(crate) base_headers: &'a HeaderMap,
pub(crate) call_headers: &'a [(HeaderName, HeaderValue)],
pub(crate) deadline: Option<Duration>,
pub(crate) max_response_bytes: usize,
}
type Received = (StatusCode, HeaderMap, Bytes);
pub(crate) async fn attempt<S>(
service: &S,
exchange: Exchange<'_>,
retry: u32,
body: Option<Bytes>,
) -> Result<Received, Error>
where
S: HttpService,
{
let events = telemetry::Exchange::new(exchange.method, exchange.uri, retry);
let (started, exchanged) = {
let mut headers = exchange.base_headers.clone();
for (name, value) in exchange.call_headers {
headers.insert(name.clone(), value.clone());
}
if retry > 0 {
headers.insert(RETRY_COUNT_HEADER, HeaderValue::from(retry));
}
telemetry::sending(events, &headers, body.as_ref());
let started = telemetry::clock();
let mut request = Request::new(body.map_or_else(Body::empty, Body::from));
*request.method_mut() = exchange.method.clone();
*request.uri_mut() = exchange.uri.clone();
*request.headers_mut() = headers;
(started, exchange_once(service, request, exchange.max_response_bytes))
};
let outcome = match exchange.deadline {
Some(deadline) => tokio::time::timeout(deadline, exchanged)
.await
.unwrap_or_else(|_| Err(Failure::Error(Error::timeout(deadline)))),
None => exchanged.await,
};
let failure = match outcome {
Ok((status, headers, body)) => {
telemetry::responded(events, status, &headers, started);
telemetry::received(events, status, &headers, &body, started);
if status.is_success() {
return Ok((status, headers, body));
}
return Err(ApiError::new(status, body, headers, Some(endpoint(exchange))).into());
}
Err(Failure::TooLarge { status, headers }) if !status.is_success() => {
telemetry::responded(events, status, &headers, started);
return Err(ApiError::with_message(
status,
Bytes::new(),
headers,
Some(endpoint(exchange)),
Error::response_too_large(exchange.max_response_bytes).to_string(),
)
.into());
}
Err(Failure::TooLarge { .. }) => Error::response_too_large(exchange.max_response_bytes),
Err(Failure::Error(error)) => error,
};
telemetry::failed(events, &failure, started);
Err(failure)
}
enum Failure {
Error(Error),
TooLarge { status: StatusCode, headers: HeaderMap },
}
async fn exchange_once<S>(
service: &S,
request: Request<Body>,
limit: usize,
) -> Result<Received, Failure>
where
S: HttpService,
{
let called = {
let mut service = service.clone();
poll_fn(|cx| service.poll_ready(cx))
.await
.map_err(|error| Failure::Error(connection(error)))?;
service.call(request)
};
let response = called.await.map_err(|error| Failure::Error(connection(error)))?;
let (parts, body) = response.into_parts();
if http_body::Body::size_hint(&body).lower() > limit as u64 {
return Err(Failure::TooLarge { status: parts.status, headers: parts.headers });
}
match Limited::new(body, limit).collect().await {
Ok(collected) => Ok((parts.status, parts.headers, collected.to_bytes())),
Err(error) if error.is::<LengthLimitError>() => {
Err(Failure::TooLarge { status: parts.status, headers: parts.headers })
}
Err(error) => Err(Failure::Error(connection(error))),
}
}
fn connection(error: impl Into<BoxError>) -> Error {
match error.into().downcast::<Error>() {
Ok(ours) => *ours,
Err(other) => Error::connection(connection_message(&*other), Some(other)),
}
}
fn connection_message(error: &(dyn StdError + 'static)) -> String {
use fmt::Write as _;
let mut message = SafeText::after(
String::from("Connection error: "),
text::MAX_MESSAGE_CHARS,
Backslash::Keep,
);
let mut link = Some(error);
for index in 0..8 {
let Some(current) = link else { break };
if index > 0 {
message.fixed(": ");
}
write!(message.untrusted_writer(), "{current}")
.expect("invariant: the escaping writer never fails");
link = current.source();
}
message.into_string()
}
fn endpoint(exchange: Exchange<'_>) -> Box<str> {
format_endpoint(exchange.method, exchange.uri).into_boxed_str()
}
#[cfg(test)]
#[path = "mod_tests.rs"]
mod tests;