diff --git a/src/bin/http_tracker_client.rs b/src/bin/http_tracker_client.rs index 4ca19480..0de04054 100644 --- a/src/bin/http_tracker_client.rs +++ b/src/bin/http_tracker_client.rs @@ -1,96 +1,7 @@ -//! HTTP Tracker client: -//! -//! Examples: -//! -//! `Announce` request: -//! -//! ```text -//! cargo run --bin http_tracker_client announce http://127.0.0.1:7070 9c38422213e30bff212b30c360d26f9a02136422 | jq -//! ``` -//! -//! `Scrape` request: -//! -//! ```text -//! cargo run --bin http_tracker_client scrape http://127.0.0.1:7070 9c38422213e30bff212b30c360d26f9a02136422 | jq -//! ``` -use std::str::FromStr; - -use anyhow::Context; -use clap::{Parser, Subcommand}; -use reqwest::Url; -use torrust_tracker::shared::bit_torrent::info_hash::InfoHash; -use torrust_tracker::shared::bit_torrent::tracker::http::client::requests::announce::QueryBuilder; -use torrust_tracker::shared::bit_torrent::tracker::http::client::responses::announce::Announce; -use torrust_tracker::shared::bit_torrent::tracker::http::client::responses::scrape; -use torrust_tracker::shared::bit_torrent::tracker::http::client::{requests, Client}; - -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -struct Args { - #[command(subcommand)] - command: Command, -} - -#[derive(Subcommand, Debug)] -enum Command { - Announce { tracker_url: String, info_hash: String }, - Scrape { tracker_url: String, info_hashes: Vec }, -} +//! Program to make request to HTTP trackers. +use torrust_tracker::console::clients::http::app; #[tokio::main] async fn main() -> anyhow::Result<()> { - let args = Args::parse(); - - match args.command { - Command::Announce { tracker_url, info_hash } => { - announce_command(tracker_url, info_hash).await?; - } - Command::Scrape { - tracker_url, - info_hashes, - } => { - scrape_command(&tracker_url, &info_hashes).await?; - } - } - Ok(()) -} - -async fn announce_command(tracker_url: String, info_hash: String) -> anyhow::Result<()> { - let base_url = Url::parse(&tracker_url).context("failed to parse HTTP tracker base URL")?; - let info_hash = - InfoHash::from_str(&info_hash).expect("Invalid infohash. Example infohash: `9c38422213e30bff212b30c360d26f9a02136422`"); - - let response = Client::new(base_url) - .announce(&QueryBuilder::with_default_values().with_info_hash(&info_hash).query()) - .await; - - let body = response.bytes().await.unwrap(); - - let announce_response: Announce = serde_bencode::from_bytes(&body) - .unwrap_or_else(|_| panic!("response body should be a valid announce response, got: \"{:#?}\"", &body)); - - let json = serde_json::to_string(&announce_response).context("failed to serialize scrape response into JSON")?; - - println!("{json}"); - - Ok(()) -} - -async fn scrape_command(tracker_url: &str, info_hashes: &[String]) -> anyhow::Result<()> { - let base_url = Url::parse(tracker_url).context("failed to parse HTTP tracker base URL")?; - - let query = requests::scrape::Query::try_from(info_hashes).context("failed to parse infohashes")?; - - let response = Client::new(base_url).scrape(&query).await; - - let body = response.bytes().await.unwrap(); - - let scrape_response = scrape::Response::try_from_bencoded(&body) - .unwrap_or_else(|_| panic!("response body should be a valid scrape response, got: \"{:#?}\"", &body)); - - let json = serde_json::to_string(&scrape_response).context("failed to serialize scrape response into JSON")?; - - println!("{json}"); - - Ok(()) + app::run().await } diff --git a/src/console/clients/http/app.rs b/src/console/clients/http/app.rs new file mode 100644 index 00000000..80db0723 --- /dev/null +++ b/src/console/clients/http/app.rs @@ -0,0 +1,100 @@ +//! HTTP Tracker client: +//! +//! Examples: +//! +//! `Announce` request: +//! +//! ```text +//! cargo run --bin http_tracker_client announce http://127.0.0.1:7070 9c38422213e30bff212b30c360d26f9a02136422 | jq +//! ``` +//! +//! `Scrape` request: +//! +//! ```text +//! cargo run --bin http_tracker_client scrape http://127.0.0.1:7070 9c38422213e30bff212b30c360d26f9a02136422 | jq +//! ``` +use std::str::FromStr; + +use anyhow::Context; +use clap::{Parser, Subcommand}; +use reqwest::Url; + +use crate::shared::bit_torrent::info_hash::InfoHash; +use crate::shared::bit_torrent::tracker::http::client::requests::announce::QueryBuilder; +use crate::shared::bit_torrent::tracker::http::client::responses::announce::Announce; +use crate::shared::bit_torrent::tracker::http::client::responses::scrape; +use crate::shared::bit_torrent::tracker::http::client::{requests, Client}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + #[command(subcommand)] + command: Command, +} + +#[derive(Subcommand, Debug)] +enum Command { + Announce { tracker_url: String, info_hash: String }, + Scrape { tracker_url: String, info_hashes: Vec }, +} + +/// # Errors +/// +/// Will return an error if the command fails. +pub async fn run() -> anyhow::Result<()> { + let args = Args::parse(); + + match args.command { + Command::Announce { tracker_url, info_hash } => { + announce_command(tracker_url, info_hash).await?; + } + Command::Scrape { + tracker_url, + info_hashes, + } => { + scrape_command(&tracker_url, &info_hashes).await?; + } + } + + Ok(()) +} + +async fn announce_command(tracker_url: String, info_hash: String) -> anyhow::Result<()> { + let base_url = Url::parse(&tracker_url).context("failed to parse HTTP tracker base URL")?; + let info_hash = + InfoHash::from_str(&info_hash).expect("Invalid infohash. Example infohash: `9c38422213e30bff212b30c360d26f9a02136422`"); + + let response = Client::new(base_url) + .announce(&QueryBuilder::with_default_values().with_info_hash(&info_hash).query()) + .await; + + let body = response.bytes().await.unwrap(); + + let announce_response: Announce = serde_bencode::from_bytes(&body) + .unwrap_or_else(|_| panic!("response body should be a valid announce response, got: \"{:#?}\"", &body)); + + let json = serde_json::to_string(&announce_response).context("failed to serialize scrape response into JSON")?; + + println!("{json}"); + + Ok(()) +} + +async fn scrape_command(tracker_url: &str, info_hashes: &[String]) -> anyhow::Result<()> { + let base_url = Url::parse(tracker_url).context("failed to parse HTTP tracker base URL")?; + + let query = requests::scrape::Query::try_from(info_hashes).context("failed to parse infohashes")?; + + let response = Client::new(base_url).scrape(&query).await; + + let body = response.bytes().await.unwrap(); + + let scrape_response = scrape::Response::try_from_bencoded(&body) + .unwrap_or_else(|_| panic!("response body should be a valid scrape response, got: \"{:#?}\"", &body)); + + let json = serde_json::to_string(&scrape_response).context("failed to serialize scrape response into JSON")?; + + println!("{json}"); + + Ok(()) +} diff --git a/src/console/clients/http/mod.rs b/src/console/clients/http/mod.rs new file mode 100644 index 00000000..309be628 --- /dev/null +++ b/src/console/clients/http/mod.rs @@ -0,0 +1 @@ +pub mod app; diff --git a/src/console/clients/mod.rs b/src/console/clients/mod.rs index 55ece612..278b736e 100644 --- a/src/console/clients/mod.rs +++ b/src/console/clients/mod.rs @@ -1,2 +1,3 @@ //! Console clients. pub mod checker; +pub mod http;