use std::io::{self, Read};
use std::time::Duration;
use brazen::{Bytes, CanonicalError, ErrorKind, Method, Transport, TransportResponse, WireRequest};
mod idle;
use idle::IdleChunkReader;
pub struct HttpTransport {
agent: ureq::Agent,
}
impl HttpTransport {
pub fn new() -> Self {
let builder = ureq::Agent::config_builder().http_status_as_error(false);
#[cfg(feature = "native-certs")]
let builder = builder.tls_config(
ureq::tls::TlsConfig::builder()
.root_certs(ureq::tls::RootCerts::PlatformVerifier)
.build(),
);
HttpTransport {
agent: builder.build().into(),
}
}
}
impl Transport for HttpTransport {
fn send(&self, wire: WireRequest) -> Result<TransportResponse, CanonicalError> {
if let Some(spec) = wire.exec.clone() {
return super::exec::send_exec(&spec, &wire);
}
let t = wire.timeouts;
let resp = match wire.method {
Method::Get => {
let mut cfg = self.agent.get(&wire.url).config();
if let Some(secs) = t.connect {
cfg = cfg.timeout_connect(Some(Duration::from_secs(secs)));
}
if let Some(secs) = t.response {
cfg = cfg.timeout_recv_response(Some(Duration::from_secs(secs)));
}
let mut req = cfg.build();
for (name, value) in &wire.headers {
req = req.header(name, value);
}
req.call()
}
Method::Post => {
let mut cfg = self.agent.post(&wire.url).config();
if let Some(secs) = t.connect {
cfg = cfg.timeout_connect(Some(Duration::from_secs(secs)));
}
if let Some(secs) = t.response {
cfg = cfg.timeout_recv_response(Some(Duration::from_secs(secs)));
}
let mut req = cfg.build();
for (name, value) in &wire.headers {
req = req.header(name, value);
}
req.send(&wire.body[..])
}
}
.map_err(|e| transport_error(&error_chain(&e)))?;
let status = resp.status().as_u16();
let retry_after = resp
.headers()
.get("retry-after")
.and_then(|v| v.to_str().ok())
.map(str::to_owned);
let reader = resp.into_body().into_reader();
let body: Box<dyn Iterator<Item = io::Result<Bytes>>> = match t.idle {
Some(secs) => Box::new(IdleChunkReader::spawn(reader, Duration::from_secs(secs))),
None => Box::new(ChunkReader { reader }),
};
Ok(TransportResponse {
status,
body,
retry_after,
})
}
}
struct ChunkReader<R> {
reader: R,
}
impl<R: Read> Iterator for ChunkReader<R> {
type Item = io::Result<Bytes>;
fn next(&mut self) -> Option<Self::Item> {
let mut buf = vec![0u8; 8192];
match self.reader.read(&mut buf) {
Ok(0) => None,
Ok(n) => {
buf.truncate(n);
Some(Ok(buf))
}
Err(e) => Some(Err(e)),
}
}
}
fn error_chain(e: &dyn std::error::Error) -> String {
let mut out = e.to_string();
let mut cur = e.source();
while let Some(src) = cur {
out.push_str(": ");
out.push_str(&src.to_string());
cur = src.source();
}
out
}
fn transport_error(message: &str) -> CanonicalError {
CanonicalError {
kind: ErrorKind::Transport,
message: format!("HTTP transport: {message}"),
provider_detail: None,
retry_after_seconds: None,
}
}
#[cfg(test)]
mod tests;