Files
aria2-rust-pro/crates/aria2-rust-pro-protocol/src/tracker/reqwest_transport.rs
T

108 lines
3.6 KiB
Rust

use super::{Client, TransportError, TransportErrorKind};
use crate::tracker::{
TrackerRequestModel, TrackerResponseModel, TrackerScrapeModel, TrackerTransport,
};
/// `reqwest`-backed HTTP tracker transport.
#[derive(Clone, Debug)]
pub struct ReqwestTrackerTransport {
/// Shared blocking HTTP client used for announce and scrape requests.
client: Client,
}
impl ReqwestTrackerTransport {
/// Creates a tracker transport backed by a default blocking `reqwest` client.
///
/// # Errors
///
/// Returns an error when the HTTP client cannot be constructed.
pub fn new() -> Result<Self, TransportError> {
let client = Client::builder()
.build()
.map_err(|error| tracker_transport_error(TransportErrorKind::Io, error.to_string()))?;
Ok(Self { client })
}
/// Fetches the raw response bytes for one tracker announce or scrape URL.
fn fetch_bytes(&self, url: &str) -> Result<Vec<u8>, TransportError> {
let response = self
.client
.get(url)
.send()
.map_err(map_reqwest_tracker_error)?;
let status = response.status();
if !status.is_success() {
return Err(tracker_transport_error(
TransportErrorKind::ProtocolViolation,
format!(
"tracker request failed with http status {}",
status.as_u16()
),
));
}
response
.bytes()
.map(Vec::from)
.map_err(map_reqwest_tracker_error)
}
}
impl Default for ReqwestTrackerTransport {
fn default() -> Self {
Self::new().expect("reqwest tracker transport should build")
}
}
impl TrackerTransport for ReqwestTrackerTransport {
fn announce(
&self,
request: &TrackerRequestModel,
) -> Result<TrackerResponseModel, TransportError> {
let url = request.announce_url().map_err(|error| {
tracker_transport_error(TransportErrorKind::ProtocolViolation, error.to_string())
})?;
let bytes = self.fetch_bytes(&url)?;
TrackerResponseModel::from_announce_bytes(&bytes).map_err(|error| {
tracker_transport_error(
TransportErrorKind::ProtocolViolation,
format!("invalid tracker announce payload: {error}"),
)
})
}
fn scrape(&self, announce_url: &str) -> Result<TrackerScrapeModel, TransportError> {
let url = super::parsing::tracker_scrape_url(announce_url);
let bytes = self.fetch_bytes(&url)?;
TrackerResponseModel::from_scrape_bytes(&bytes).map_err(|error| {
tracker_transport_error(
TransportErrorKind::ProtocolViolation,
format!("invalid tracker scrape payload: {error}"),
)
})
}
}
/// Builds a transport error with tracker-specific context already normalized.
fn tracker_transport_error(kind: TransportErrorKind, message: impl Into<String>) -> TransportError {
TransportError {
kind,
message: message.into(),
source: None,
context: None,
}
}
/// Maps a reqwest tracker fetch failure into the transport error model.
fn map_reqwest_tracker_error(error: reqwest::Error) -> TransportError {
let kind = if error.is_timeout() {
TransportErrorKind::Timeout
} else if error.is_connect() {
TransportErrorKind::NotConnected
} else if error.is_decode() {
TransportErrorKind::ProtocolViolation
} else {
TransportErrorKind::Io
};
tracker_transport_error(kind, error.to_string())
}