Skip to content

Commit 093e8c9

Browse files
committed
feat: extract new package bittorrent-tracker-client
This will allow other projects to reuse the tracker lib clients and console clients.
1 parent 25d9487 commit 093e8c9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+3325
-0
lines changed

Cargo.lock

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ axum-client-ip = "0"
3737
axum-extra = { version = "0", features = ["query"] }
3838
axum-server = { version = "0", features = ["tls-rustls"] }
3939
bittorrent-primitives = "0.1.0"
40+
bittorrent-tracker-client = { version = "3.0.0-develop", path = "packages/tracker-client" }
4041
camino = { version = "1", features = ["serde", "serde1"] }
4142
chrono = { version = "0", default-features = false, features = ["clock"] }
4243
clap = { version = "4", features = ["derive", "env"] }
@@ -100,6 +101,7 @@ members = [
100101
"packages/primitives",
101102
"packages/test-helpers",
102103
"packages/torrent-repository",
104+
"packages/tracker-client",
103105
]
104106

105107
[profile.dev]

packages/tracker-client/Cargo.toml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
[package]
2+
description = "A library with the primitive types shared by the Torrust tracker packages."
3+
keywords = ["bittorrent", "client", "tracker"]
4+
license = "LGPL-3.0"
5+
name = "bittorrent-tracker-client"
6+
readme = "README.md"
7+
8+
authors.workspace = true
9+
documentation.workspace = true
10+
edition.workspace = true
11+
homepage.workspace = true
12+
publish.workspace = true
13+
repository.workspace = true
14+
rust-version.workspace = true
15+
version.workspace = true
16+
17+
[dependencies]
18+
anyhow = "1"
19+
aquatic_udp_protocol = "0"
20+
bittorrent-primitives = "0.1.0"
21+
clap = { version = "4", features = ["derive", "env"] }
22+
derive_more = { version = "1", features = ["as_ref", "constructor", "from"] }
23+
futures = "0"
24+
futures-util = "0"
25+
hex-literal = "0"
26+
hyper = "1"
27+
percent-encoding = "2"
28+
reqwest = { version = "0", features = ["json"] }
29+
serde = { version = "1", features = ["derive"] }
30+
serde_bencode = "0"
31+
serde_bytes = "0"
32+
serde_json = { version = "1", features = ["preserve_order"] }
33+
serde_repr = "0"
34+
thiserror = "1"
35+
tokio = { version = "1", features = ["macros", "net", "rt-multi-thread", "signal", "sync"] }
36+
torrust-tracker-configuration = { version = "3.0.0-develop", path = "../configuration" }
37+
torrust-tracker-located-error = { version = "3.0.0-develop", path = "../located-error" }
38+
torrust-tracker-primitives = { version = "3.0.0-develop", path = "../primitives" }
39+
tracing = "0"
40+
tracing-subscriber = { version = "0", features = ["json"] }
41+
url = { version = "2", features = ["serde"] }
42+
zerocopy = "0.7"

packages/tracker-client/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# BitTorrent Tracker Client
2+
3+
A library an console applications to interact with a BitTorrent tracker.
4+
5+
> **Disclaimer**: This project is actively under development. We’re currently extracting and refining common types from the ][Torrust Tracker](https://github.com/torrust/torrust-tracker) to make them available to the BitTorrent community in Rust. While these types are functional, they are not yet ready for use in production or third-party projects.
6+
7+
## License
8+
9+
**Copyright (c) 2024 The Torrust Developers.**
10+
11+
This program is free software: you can redistribute it and/or modify it under the terms of the [GNU Lesser General Public License][LGPL_3_0] as published by the [Free Software Foundation][FSF], version 3.
12+
13+
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the [GNU Lesser General Public License][LGPL_3_0] for more details.
14+
15+
You should have received a copy of the *GNU Lesser General Public License* along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
Some files include explicit copyright notices and/or license notices.
18+
19+
### Legacy Exception
20+
21+
For prosperity, versions of Torrust BitTorrent Tracker Client that are older than five years are automatically granted the [MIT-0][MIT_0] license in addition to the existing [LGPL-3.0-only][LGPL_3_0] license.
22+
23+
[LGPL_3_0]: ./LICENSE
24+
[MIT_0]: ./docs/licenses/LICENSE-MIT_0
25+
[FSF]: https://www.fsf.org/
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
MIT No Attribution
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this
4+
software and associated documentation files (the "Software"), to deal in the Software
5+
without restriction, including without limitation the rights to use, copy, modify,
6+
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
7+
permit persons to whom the Software is furnished to do so.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
10+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
11+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
12+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
13+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
14+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//! Program to make request to HTTP trackers.
2+
use bittorrent_tracker_client::console::clients::http::app;
3+
4+
#[tokio::main]
5+
async fn main() -> anyhow::Result<()> {
6+
app::run().await
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//! Program to check running trackers.
2+
use bittorrent_tracker_client::console::clients::checker::app;
3+
4+
#[tokio::main]
5+
async fn main() {
6+
app::run().await.expect("Some checks fail");
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//! Program to make request to UDP trackers.
2+
use bittorrent_tracker_client::console::clients::udp::app;
3+
4+
#[tokio::main]
5+
async fn main() -> anyhow::Result<()> {
6+
app::run().await
7+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//! Program to run checks against running trackers.
2+
//!
3+
//! Run providing a config file path:
4+
//!
5+
//! ```text
6+
//! cargo run --bin tracker_checker -- --config-path "./share/default/config/tracker_checker.json"
7+
//! TORRUST_CHECKER_CONFIG_PATH="./share/default/config/tracker_checker.json" cargo run --bin tracker_checker
8+
//! ```
9+
//!
10+
//! Run providing the configuration:
11+
//!
12+
//! ```text
13+
//! TORRUST_CHECKER_CONFIG=$(cat "./share/default/config/tracker_checker.json") cargo run --bin tracker_checker
14+
//! ```
15+
//!
16+
//! Another real example to test the Torrust demo tracker:
17+
//!
18+
//! ```text
19+
//! TORRUST_CHECKER_CONFIG='{
20+
//! "udp_trackers": ["144.126.245.19:6969"],
21+
//! "http_trackers": ["https://tracker.torrust-demo.com"],
22+
//! "health_checks": ["https://tracker.torrust-demo.com/api/health_check"]
23+
//! }' cargo run --bin tracker_checker
24+
//! ```
25+
//!
26+
//! The output should be something like the following:
27+
//!
28+
//! ```json
29+
//! {
30+
//! "udp_trackers": [
31+
//! {
32+
//! "url": "144.126.245.19:6969",
33+
//! "status": {
34+
//! "code": "ok",
35+
//! "message": ""
36+
//! }
37+
//! }
38+
//! ],
39+
//! "http_trackers": [
40+
//! {
41+
//! "url": "https://tracker.torrust-demo.com/",
42+
//! "status": {
43+
//! "code": "ok",
44+
//! "message": ""
45+
//! }
46+
//! }
47+
//! ],
48+
//! "health_checks": [
49+
//! {
50+
//! "url": "https://tracker.torrust-demo.com/api/health_check",
51+
//! "status": {
52+
//! "code": "ok",
53+
//! "message": ""
54+
//! }
55+
//! }
56+
//! ]
57+
//! }
58+
//! ```
59+
use std::path::PathBuf;
60+
use std::sync::Arc;
61+
62+
use anyhow::{Context, Result};
63+
use clap::Parser;
64+
use tracing::level_filters::LevelFilter;
65+
66+
use super::config::Configuration;
67+
use super::console::Console;
68+
use super::service::{CheckResult, Service};
69+
use crate::console::clients::checker::config::parse_from_json;
70+
71+
#[derive(Parser, Debug)]
72+
#[clap(author, version, about, long_about = None)]
73+
struct Args {
74+
/// Path to the JSON configuration file.
75+
#[clap(short, long, env = "TORRUST_CHECKER_CONFIG_PATH")]
76+
config_path: Option<PathBuf>,
77+
78+
/// Direct configuration content in JSON.
79+
#[clap(env = "TORRUST_CHECKER_CONFIG", hide_env_values = true)]
80+
config_content: Option<String>,
81+
}
82+
83+
/// # Errors
84+
///
85+
/// Will return an error if the configuration was not provided.
86+
pub async fn run() -> Result<Vec<CheckResult>> {
87+
tracing_stdout_init(LevelFilter::INFO);
88+
89+
let args = Args::parse();
90+
91+
let config = setup_config(args)?;
92+
93+
let console_printer = Console {};
94+
95+
let service = Service {
96+
config: Arc::new(config),
97+
console: console_printer,
98+
};
99+
100+
service.run_checks().await.context("it should run the check tasks")
101+
}
102+
103+
fn tracing_stdout_init(filter: LevelFilter) {
104+
tracing_subscriber::fmt().with_max_level(filter).init();
105+
tracing::debug!("Logging initialized");
106+
}
107+
108+
fn setup_config(args: Args) -> Result<Configuration> {
109+
match (args.config_path, args.config_content) {
110+
(Some(config_path), _) => load_config_from_file(&config_path),
111+
(_, Some(config_content)) => parse_from_json(&config_content).context("invalid config format"),
112+
_ => Err(anyhow::anyhow!("no configuration provided")),
113+
}
114+
}
115+
116+
fn load_config_from_file(path: &PathBuf) -> Result<Configuration> {
117+
let file_content = std::fs::read_to_string(path).with_context(|| format!("can't read config file {path:?}"))?;
118+
119+
parse_from_json(&file_content).context("invalid config format")
120+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use std::sync::Arc;
2+
use std::time::Duration;
3+
4+
use anyhow::Result;
5+
use hyper::StatusCode;
6+
use reqwest::{Client as HttpClient, Response};
7+
use serde::Serialize;
8+
use thiserror::Error;
9+
use url::Url;
10+
11+
#[derive(Debug, Clone, Error, Serialize)]
12+
#[serde(into = "String")]
13+
pub enum Error {
14+
#[error("Failed to Build a Http Client: {err:?}")]
15+
ClientBuildingError { err: Arc<reqwest::Error> },
16+
#[error("Heath check failed to get a response: {err:?}")]
17+
ResponseError { err: Arc<reqwest::Error> },
18+
#[error("Http check returned a non-success code: \"{code}\" with the response: \"{response:?}\"")]
19+
UnsuccessfulResponse { code: StatusCode, response: Arc<Response> },
20+
}
21+
22+
impl From<Error> for String {
23+
fn from(value: Error) -> Self {
24+
value.to_string()
25+
}
26+
}
27+
28+
#[derive(Debug, Clone, Serialize)]
29+
pub struct Checks {
30+
url: Url,
31+
result: Result<String, Error>,
32+
}
33+
34+
pub async fn run(health_checks: Vec<Url>, timeout: Duration) -> Vec<Result<Checks, Checks>> {
35+
let mut results = Vec::default();
36+
37+
tracing::debug!("Health checks ...");
38+
39+
for url in health_checks {
40+
let result = match run_health_check(url.clone(), timeout).await {
41+
Ok(response) => Ok(response.status().to_string()),
42+
Err(err) => Err(err),
43+
};
44+
45+
let check = Checks { url, result };
46+
47+
if check.result.is_err() {
48+
results.push(Err(check));
49+
} else {
50+
results.push(Ok(check));
51+
}
52+
}
53+
54+
results
55+
}
56+
57+
async fn run_health_check(url: Url, timeout: Duration) -> Result<Response, Error> {
58+
let client = HttpClient::builder()
59+
.timeout(timeout)
60+
.build()
61+
.map_err(|e| Error::ClientBuildingError { err: e.into() })?;
62+
63+
let response = client
64+
.get(url.clone())
65+
.send()
66+
.await
67+
.map_err(|e| Error::ResponseError { err: e.into() })?;
68+
69+
if response.status().is_success() {
70+
Ok(response)
71+
} else {
72+
Err(Error::UnsuccessfulResponse {
73+
code: response.status(),
74+
response: response.into(),
75+
})
76+
}
77+
}

0 commit comments

Comments
 (0)