#[cfg(feature = "hyper")]
#[cfg_attr(docsrs, doc(cfg(feature = "hyper")))]
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;
#[cfg(feature = "hyper")]
pub(crate) use self::hyper::TransportSettings;
#[cfg(feature = "hyper")]
#[cfg_attr(docsrs, doc(cfg(feature = "hyper")))]
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, ErrorKind, format_endpoint},
question::upsert,
redact::{self, Credentials, Outcome},
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) config: &'a Config,
}
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,
exchange.config.omit_endpoint_host(),
);
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.config.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.config.max_response_bytes()).to_string(),
)
.into());
}
Err(Failure::TooLarge { .. }) => {
Error::response_too_large(exchange.config.max_response_bytes())
}
Err(Failure::Error(error)) => redacted(error, exchange),
};
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))),
}
}
pub(crate) 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 {
cut(&render_uncut(error))
}
const CONNECTION_PREFIX: &str = "Connection error: ";
struct Uncut {
text: String,
ends: Vec<usize>,
}
fn render_uncut(error: &(dyn StdError + 'static)) -> Uncut {
let mut message = SafeText::after(String::from(CONNECTION_PREFIX), usize::MAX, Backslash::Keep);
let mut ends = Vec::new();
let mut character_bytes = [0; 4];
let mut link = Some(error);
for index in 0..8 {
let Some(current) = link else { break };
if index > 0 {
message.fixed(": ");
ends.push(message.byte_len());
}
for character in current.to_string().chars() {
message.untrusted(character.encode_utf8(&mut character_bytes), usize::MAX);
ends.push(message.byte_len());
}
link = current.source();
}
Uncut { text: message.into_string(), ends }
}
fn cut(uncut: &Uncut) -> String {
let mut message = String::from(CONNECTION_PREFIX);
let mut chars = 0;
let mut start = CONNECTION_PREFIX.len();
for &end in &uncut.ends {
let piece = &uncut.text[start..end];
let len = piece.chars().count();
if chars + len > text::MAX_MESSAGE_CHARS {
message.push('\u{2026}');
break;
}
message.push_str(piece);
chars += len;
start = end;
}
message
}
impl Uncut {
fn redacted(&self, credentials: &Credentials) -> Self {
let prefix = CONNECTION_PREFIX.len();
let mut found = credentials
.matches(&self.text[prefix..])
.map(|range| range.start + prefix..range.end + prefix);
let mut next = found.next();
let mut text = String::from(CONNECTION_PREFIX);
let mut ends = Vec::with_capacity(self.ends.len());
let mut start = prefix;
let mut index = 0;
while let Some(&end) = self.ends.get(index) {
match &next {
Some(first) if first.start < end => {
let mut group_end = first.end;
next = found.next();
while let Some(&piece_end) = self.ends.get(index) {
index += 1;
while let Some(another) = &next
&& another.start < piece_end
{
group_end = group_end.max(another.end);
next = found.next();
}
start = piece_end;
if piece_end >= group_end {
break;
}
}
text.push_str("***");
}
_ => {
text.push_str(&self.text[start..end]);
start = end;
index += 1;
}
}
ends.push(text.len());
}
Self { text, ends }
}
}
pub(crate) fn redacted(error: Error, exchange: Exchange<'_>) -> Error {
let Some(source) =
StdError::source(&error).filter(|_| matches!(error.kind(), ErrorKind::Connection))
else {
return error;
};
let credentials = Credentials::new(
exchange
.base_headers
.iter()
.chain(exchange.call_headers.iter().map(|(name, value)| (name, value))),
);
let message = error.to_string();
let transport_text = message.strip_prefix(CONNECTION_PREFIX).unwrap_or(&message);
match redact::copy_chain(source, transport_text, &credentials) {
Outcome::Kept => error,
Outcome::MessageOnly => {
let (_, source) = error.into_parts();
let redacted = credentials.redact(transport_text);
Error::connection(format!("{CONNECTION_PREFIX}{redacted}"), source)
}
Outcome::Replaced(link) => {
let message = cut(&render_uncut(&link).redacted(&credentials));
Error::connection(message, Some(Box::new(link)))
}
}
}
fn endpoint(exchange: Exchange<'_>) -> Box<str> {
format_endpoint(exchange.method, exchange.uri).into_boxed_str()
}
#[cfg(test)]
#[path = "mod_tests.rs"]
mod tests;