108 lines
3.6 KiB
Rust
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())
|
|
}
|