Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract Tracker API Client packages #689

Closed
josecelano opened this issue Feb 9, 2024 · 1 comment · Fixed by #1335
Closed

Extract Tracker API Client packages #689

josecelano opened this issue Feb 9, 2024 · 1 comment · Fixed by #1335
Assignees
Labels
Code Cleanup / Refactoring Tidying and Making Neat
Milestone

Comments

@josecelano
Copy link
Member

josecelano commented Feb 9, 2024

Relates to: torrust/torrust-index#806
Relates to: #255 (comment)

We are using a Tracker API Client in test code.

See: https://github.com/torrust/torrust-tracker/blob/develop/tests/servers/api/v1/client.rs

That client is duplicated in the Index because the Index calls the Tracker API:

https://github.com/torrust/torrust-index/blob/develop/src/tracker/api.rs

We should extract that login into an independent package so that the Index can use it and we remove that duplicate code.

Before extracting the package we should clean it and make it ready for production. Currently, is not ready because

  • The URL schema is hardcoded to http://.
  • It panics if the HTTP fails. We should return an error and let the user handle it.
  • We should decide if we expose request errors or we wrap them.

I propose to create this packages:

  • folder (crates.io package name)
  • packages/api-types (torrust-tracker-api-types)
  • packages/api-client (torrust-tracker-api-client) <- depends on torrust-tracker-api-types
  • packages/api-server (torrust-tracker-api-server) <- depends on torrust-tracker-api-types. Maybe in the future

IMPORTANT: the implementation in the Index was improved. We have to apply those improvements to this client.

@josecelano josecelano added the Code Cleanup / Refactoring Tidying and Making Neat label Feb 9, 2024
@josecelano josecelano self-assigned this Feb 9, 2024
josecelano added a commit to torrust/torrust-index that referenced this issue Feb 12, 2024
3f629c2 refactor: [#473] tracker API client. Remove duplicate code (Jose Celano)
7256b46 feat: [#473] add timeout to Tracker API Client requests (Jose Celano)

Pull request description:

  Ass default timeout of 5 seconds for Tracker API requests.

  In the future, we could use the official Tracker API client. See torrust/torrust-tracker#689.

  However, it's also fine to use this reduced client. Because we do not need all the PAI endpoints.

ACKs for top commit:
  josecelano:
    ACK 3f629c2

Tree-SHA512: fc22ceac6ef234fb1bd20b6ae5099cea17ddfe2cb599faa3ad6f3e2ae04d93043e6af0b9a85dc41785f0a99bf7ace13f8e5e0cb656058390bf20585b236bf5e6
@cgbosse cgbosse added this to the v3.1.0 milestone May 24, 2024
@josecelano josecelano removed their assignment Jun 11, 2024
@josecelano josecelano self-assigned this Feb 27, 2025
@josecelano
Copy link
Member Author

The current implementation in the Index with some improvements like a timeout for requests:

use std::time::Duration;

use reqwest::{Error, Response};
use url::Url;
pub struct ConnectionInfo {
    /// The URL of the tracker API. Eg: <https://tracker:1212>.
    pub url: Url,
    /// The token used to authenticate with the tracker API.
    pub token: String,
}

impl ConnectionInfo {
    #[must_use]
    pub fn new(url: Url, token: String) -> Self {
        Self { url, token }
    }
}

const TOKEN_PARAM_NAME: &str = "token";
const API_PATH: &str = "api/v1";
const TOTAL_REQUEST_TIMEOUT_IN_SECS: u64 = 5;

pub struct Client {
    pub connection_info: ConnectionInfo,
    api_base_url: Url,
    client: reqwest::Client,
    token_param: [(String, String); 1],
}

impl Client {
    /// # Errors
    ///
    /// Will fails if it can't build a HTTP client with a timeout.
    ///
    /// # Panics
    ///
    /// Will panic if the API base URL is not valid.
    pub fn new(connection_info: ConnectionInfo) -> Result<Self, Error> {
        let api_base_url = connection_info.url.join(API_PATH).expect("valid URL API path");
        let client = reqwest::Client::builder()
            .timeout(Duration::from_secs(TOTAL_REQUEST_TIMEOUT_IN_SECS))
            .build()?;
        let token_param = [(TOKEN_PARAM_NAME.to_string(), connection_info.token.to_string())];

        Ok(Self {
            connection_info,
            api_base_url,
            client,
            token_param,
        })
    }

    /// Add a torrent to the tracker whitelist.
    ///
    /// # Errors
    ///
    /// Will return an error if the HTTP request fails.
    pub async fn whitelist_torrent(&self, info_hash: &str) -> Result<Response, Error> {
        let request_url = format!("{}/whitelist/{}", self.api_base_url, info_hash);

        self.client.post(request_url).query(&self.token_param).send().await
    }

    /// Remove a torrent from the tracker whitelist.
    ///
    /// # Errors
    ///
    /// Will return an error if the HTTP request fails.
    pub async fn remove_torrent_from_whitelist(&self, info_hash: &str) -> Result<Response, Error> {
        let request_url = format!("{}/whitelist/{}", self.api_base_url, info_hash);

        self.client.delete(request_url).query(&self.token_param).send().await
    }

    /// Retrieve a new tracker key.
    ///
    /// # Errors
    ///
    /// Will return an error if the HTTP request fails.
    pub async fn retrieve_new_tracker_key(&self, token_valid_seconds: u64) -> Result<Response, Error> {
        let request_url = format!("{}/key/{}", self.api_base_url, token_valid_seconds);

        self.client.post(request_url).query(&self.token_param).send().await
    }

    /// Retrieve the info for one torrent.
    ///
    /// # Errors
    ///
    /// Will return an error if the HTTP request fails.
    pub async fn get_torrent_info(&self, info_hash: &str) -> Result<Response, Error> {
        let request_url = format!("{}/torrent/{}", self.api_base_url, info_hash);

        self.client.get(request_url).query(&self.token_param).send().await
    }

    /// Retrieve the info for multiple torrents at the same time.
    ///
    /// # Errors
    ///
    /// Will return an error if the HTTP request fails.
    pub async fn get_torrents_info(&self, info_hashes: &[String]) -> Result<Response, Error> {
        let request_url = format!("{}/torrents", self.api_base_url);

        let mut query_params: Vec<(String, String)> = Vec::with_capacity(info_hashes.len() + 1);

        query_params.push((TOKEN_PARAM_NAME.to_string(), self.connection_info.token.clone()));

        for info_hash in info_hashes {
            query_params.push(("info_hash".to_string(), info_hash.clone()));
        }

        self.client.get(request_url).query(&query_params).send().await
    }
}

josecelano added a commit to josecelano/torrust-tracker that referenced this issue Feb 28, 2025
- Add a timeout to the requests.
- Return an error in the construction if it can't build the HTTP client.
- Extract constants.
@josecelano josecelano linked a pull request Feb 28, 2025 that will close this issue
josecelano added a commit that referenced this issue Feb 28, 2025
89b0bfd refactor: [#689] improve REST API client (Jose Celano)

Pull request description:

  Refactor: improve REST API client:

  - Add a timeout to the requests.
  - Return an error in the constructor if it can't build the HTTP client.
  - Extract constants.

ACKs for top commit:
  josecelano:
    ACK 89b0bfd

Tree-SHA512: 2b552e2ed6ce56587f5a364ed1b3fcc4b20d16f2ea5229e33594f2f35c5c25835ae84f8864a1d1114e906fc9af2b4a6f82d287c30d4798795b2a590d13909046
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Code Cleanup / Refactoring Tidying and Making Neat
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

2 participants