Skip to content

Commit dde3d8d

Browse files
committed
Merge #622: Add a new HTTP tracker client binary
129fd2f refactor: move upd tracker client (Jose Celano) 470e608 feat: a simple HTTP tracker client command (Jose Celano) Pull request description: You can execute it with: ```console cargo run --bin http_tracker_client https://tracker.torrust-demo.com 9c38422213e30bff212b30c360d26f9a02136422" ``` and the output should be something like: ```json { "complete": 1, "incomplete": 1, "interval": 300, "min interval": 300, "peers": [ { "ip": "90.XX.XX.167", "peer id": [ 45, 66, 76, 50, 52, 54, 51, 54, 51, 45, 51, 70, 41, 46, 114, 46, 68, 100, 74, 69 ], "port": 59568 } ] } ``` I have intentionally not used the same client in production and testing code. I do not want to use production code for testing purposes. In the future, we could extract the tracker clients to new packages (removing dependencies with the core tracker) so we can test them independently and use them in our tracker tests. ACKs for top commit: josecelano: ACK 129fd2f Tree-SHA512: 7d45b85940dd365e61622493351ace16498754b60a0b889d6d0d76236b66a0a7b729aedfa271dbf43df952dc248051e1f83ad82fdcb1658168a69af760aa1500
2 parents 44e8076 + 129fd2f commit dde3d8d

File tree

19 files changed

+944
-16
lines changed

19 files changed

+944
-16
lines changed

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,10 @@ rand = "0"
5454
reqwest = "0"
5555
serde = { version = "1", features = ["derive"] }
5656
serde_bencode = "0"
57+
serde_bytes = "0"
5758
serde_json = "1"
5859
serde_with = "3"
60+
serde_repr = "0"
5961
tdyne-peer-id = "1"
6062
tdyne-peer-id-registry = "0"
6163
thiserror = "1"
@@ -73,8 +75,6 @@ local-ip-address = "0"
7375
mockall = "0"
7476
once_cell = "1.18.0"
7577
reqwest = { version = "0", features = ["json"] }
76-
serde_bytes = "0"
77-
serde_repr = "0"
7878
serde_urlencoded = "0"
7979
torrust-tracker-test-helpers = { version = "3.0.0-alpha.12-develop", path = "packages/test-helpers" }
8080

share/default/config/tracker.development.sqlite3.toml

+5-5
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@ remove_peerless_torrents = true
1313
tracker_usage_statistics = true
1414

1515
[[udp_trackers]]
16-
bind_address = "0.0.0.0:6969"
17-
enabled = false
16+
bind_address = "0.0.0.0:0"
17+
enabled = true
1818

1919
[[http_trackers]]
20-
bind_address = "0.0.0.0:7070"
21-
enabled = false
20+
bind_address = "0.0.0.0:0"
21+
enabled = true
2222
ssl_cert_path = ""
2323
ssl_enabled = false
2424
ssl_key_path = ""
2525

2626
[http_api]
27-
bind_address = "127.0.0.1:1212"
27+
bind_address = "127.0.0.1:0"
2828
enabled = true
2929
ssl_cert_path = ""
3030
ssl_enabled = false

src/bin/http_tracker_client.rs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use std::env;
2+
use std::str::FromStr;
3+
4+
use reqwest::Url;
5+
use torrust_tracker::shared::bit_torrent::info_hash::InfoHash;
6+
use torrust_tracker::shared::bit_torrent::tracker::http::client::requests::announce::QueryBuilder;
7+
use torrust_tracker::shared::bit_torrent::tracker::http::client::responses::announce::Announce;
8+
use torrust_tracker::shared::bit_torrent::tracker::http::client::Client;
9+
10+
#[tokio::main]
11+
async fn main() {
12+
let args: Vec<String> = env::args().collect();
13+
if args.len() != 3 {
14+
eprintln!("Error: invalid number of arguments!");
15+
eprintln!("Usage: cargo run --bin http_tracker_client <HTTP_TRACKER_URL> <INFO_HASH>");
16+
eprintln!("Example: cargo run --bin http_tracker_client https://tracker.torrust-demo.com 9c38422213e30bff212b30c360d26f9a02136422");
17+
std::process::exit(1);
18+
}
19+
20+
let base_url = Url::parse(&args[1]).expect("arg 1 should be a valid HTTP tracker base URL");
21+
let info_hash = InfoHash::from_str(&args[2]).expect("arg 2 should be a valid infohash");
22+
23+
let response = Client::new(base_url)
24+
.announce(&QueryBuilder::with_default_values().with_info_hash(&info_hash).query())
25+
.await;
26+
27+
let body = response.bytes().await.unwrap();
28+
29+
let announce_response: Announce = serde_bencode::from_bytes(&body)
30+
.unwrap_or_else(|_| panic!("response body should be a valid announce response, got \"{:#?}\"", &body));
31+
32+
let json = serde_json::to_string(&announce_response).expect("announce response should be a valid JSON");
33+
34+
print!("{json}");
35+
}

src/servers/health_check_api/handlers.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use torrust_tracker_configuration::{Configuration, HttpApi, HttpTracker, UdpTrac
88

99
use super::resources::Report;
1010
use super::responses;
11-
use crate::shared::bit_torrent::udp::client::new_udp_tracker_client_connected;
11+
use crate::shared::bit_torrent::tracker::udp::client::new_udp_tracker_client_connected;
1212

1313
/// If port 0 is specified in the configuration the OS will automatically
1414
/// assign a free port. But we do now know in from the configuration.

src/servers/udp/server.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use crate::bootstrap::jobs::Started;
3434
use crate::core::Tracker;
3535
use crate::servers::signals::{shutdown_signal_with_message, Halted};
3636
use crate::servers::udp::handlers::handle_packet;
37-
use crate::shared::bit_torrent::udp::MAX_PACKET_SIZE;
37+
use crate::shared::bit_torrent::tracker::udp::MAX_PACKET_SIZE;
3838

3939
/// Error that can occur when starting or stopping the UDP server.
4040
///

src/shared/bit_torrent/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,4 @@
6969
//!Bencode & bdecode in your browser | <https://github.com/Chocobo1/bencode_online>
7070
pub mod common;
7171
pub mod info_hash;
72-
pub mod udp;
72+
pub mod tracker;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
pub mod requests;
2+
pub mod responses;
3+
4+
use std::net::IpAddr;
5+
6+
use requests::announce::{self, Query};
7+
use requests::scrape;
8+
use reqwest::{Client as ReqwestClient, Response, Url};
9+
10+
use crate::core::auth::Key;
11+
12+
/// HTTP Tracker Client
13+
pub struct Client {
14+
base_url: Url,
15+
reqwest: ReqwestClient,
16+
key: Option<Key>,
17+
}
18+
19+
/// URL components in this context:
20+
///
21+
/// ```text
22+
/// http://127.0.0.1:62304/announce/YZ....rJ?info_hash=%9C8B%22%13%E3%0B%FF%21%2B0%C3%60%D2o%9A%02%13d%22
23+
/// \_____________________/\_______________/ \__________________________________________________________/
24+
/// | | |
25+
/// base url path query
26+
/// ```
27+
impl Client {
28+
/// # Panics
29+
///
30+
/// This method fails if the client builder fails.
31+
#[must_use]
32+
pub fn new(base_url: Url) -> Self {
33+
Self {
34+
base_url,
35+
reqwest: reqwest::Client::builder().build().unwrap(),
36+
key: None,
37+
}
38+
}
39+
40+
/// Creates the new client binding it to an specific local address.
41+
///
42+
/// # Panics
43+
///
44+
/// This method fails if the client builder fails.
45+
#[must_use]
46+
pub fn bind(base_url: Url, local_address: IpAddr) -> Self {
47+
Self {
48+
base_url,
49+
reqwest: reqwest::Client::builder().local_address(local_address).build().unwrap(),
50+
key: None,
51+
}
52+
}
53+
54+
/// # Panics
55+
///
56+
/// This method fails if the client builder fails.
57+
#[must_use]
58+
pub fn authenticated(base_url: Url, key: Key) -> Self {
59+
Self {
60+
base_url,
61+
reqwest: reqwest::Client::builder().build().unwrap(),
62+
key: Some(key),
63+
}
64+
}
65+
66+
pub async fn announce(&self, query: &announce::Query) -> Response {
67+
self.get(&self.build_announce_path_and_query(query)).await
68+
}
69+
70+
pub async fn scrape(&self, query: &scrape::Query) -> Response {
71+
self.get(&self.build_scrape_path_and_query(query)).await
72+
}
73+
74+
pub async fn announce_with_header(&self, query: &Query, key: &str, value: &str) -> Response {
75+
self.get_with_header(&self.build_announce_path_and_query(query), key, value)
76+
.await
77+
}
78+
79+
pub async fn health_check(&self) -> Response {
80+
self.get(&self.build_path("health_check")).await
81+
}
82+
83+
/// # Panics
84+
///
85+
/// This method fails if there was an error while sending request.
86+
pub async fn get(&self, path: &str) -> Response {
87+
self.reqwest.get(self.build_url(path)).send().await.unwrap()
88+
}
89+
90+
/// # Panics
91+
///
92+
/// This method fails if there was an error while sending request.
93+
pub async fn get_with_header(&self, path: &str, key: &str, value: &str) -> Response {
94+
self.reqwest
95+
.get(self.build_url(path))
96+
.header(key, value)
97+
.send()
98+
.await
99+
.unwrap()
100+
}
101+
102+
fn build_announce_path_and_query(&self, query: &announce::Query) -> String {
103+
format!("{}?{query}", self.build_path("announce"))
104+
}
105+
106+
fn build_scrape_path_and_query(&self, query: &scrape::Query) -> String {
107+
format!("{}?{query}", self.build_path("scrape"))
108+
}
109+
110+
fn build_path(&self, path: &str) -> String {
111+
match &self.key {
112+
Some(key) => format!("{path}/{key}"),
113+
None => path.to_string(),
114+
}
115+
}
116+
117+
fn build_url(&self, path: &str) -> String {
118+
let base_url = self.base_url();
119+
format!("{base_url}{path}")
120+
}
121+
122+
fn base_url(&self) -> String {
123+
self.base_url.to_string()
124+
}
125+
}

0 commit comments

Comments
 (0)