teloxide_core/
net.rs

1//! Network-specific API.
2
3use std::time::Duration;
4
5pub use self::download::{download_file, download_file_stream, Download};
6
7pub(crate) use self::{
8    request::{request_json, request_multipart},
9    telegram_response::TelegramResponse,
10};
11
12mod download;
13mod request;
14mod telegram_response;
15
16/// The default Telegram API URL.
17pub const TELEGRAM_API_URL: &str = "https://api.telegram.org";
18
19/// Constructs a network client from the `TELOXIDE_PROXY` environmental
20/// variable.
21///
22/// This function passes the value of `TELOXIDE_PROXY` into
23/// [`reqwest::Proxy::all`], if it exists, otherwise returns the default
24/// client.
25///
26/// ## Note
27///
28/// The created client will have safe settings, meaning that it will be able to
29/// work in long time durations, see the [issue 223].
30///
31/// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all
32/// [issue 223]: https://github.com/teloxide/teloxide/issues/223
33///
34/// ## Panics
35///
36/// If `TELOXIDE_PROXY` exists, but isn't correct url.
37#[must_use]
38pub fn client_from_env() -> reqwest::Client {
39    use reqwest::Proxy;
40
41    const TELOXIDE_PROXY: &str = "TELOXIDE_PROXY";
42
43    let builder = default_reqwest_settings();
44
45    match std::env::var(TELOXIDE_PROXY).ok() {
46        Some(proxy) => builder.proxy(Proxy::all(proxy).expect("reqwest::Proxy creation failed")),
47        None => builder,
48    }
49    .build()
50    .expect("creating reqwest::Client")
51}
52
53/// Returns a reqwest client builder with default settings.
54///
55/// Client built from default settings is supposed to work over long time
56/// durations, see the [issue 223].
57///
58/// The current settings are:
59///  - A connection timeout of 5 seconds.
60///  - A timeout of 17 seconds.
61///  - `tcp_nodelay` is on.
62///
63/// ## Notes
64///
65/// 1. The settings may change in the future.
66/// 2. If you are using the polling mechanism to get updates, the timeout
67///    configured in the client should be bigger than the polling timeout.
68/// 3. If you alter the current settings listed above, your bot will not be
69///    guaranteed to work over long time durations.
70///
71/// [issue 223]: https://github.com/teloxide/teloxide/issues/223
72pub fn default_reqwest_settings() -> reqwest::ClientBuilder {
73    reqwest::Client::builder()
74        .connect_timeout(Duration::from_secs(5))
75        .timeout(Duration::from_secs(17))
76        .tcp_nodelay(true)
77}
78
79/// Creates URL for making HTTPS requests. See the [Telegram documentation].
80///
81/// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests
82fn method_url(base: reqwest::Url, token: &str, method_name: &str) -> reqwest::Url {
83    let mut url = base;
84    {
85        let mut segments = url.path_segments_mut().expect("base URL cannot be a cannot-be-a-base");
86        segments.push(&format!("bot{token}"));
87        segments.push(method_name);
88    }
89    url
90}
91
92/// Creates URL for downloading a file. See the [Telegram documentation].
93///
94/// [Telegram documentation]: https://core.telegram.org/bots/api#file
95fn file_url(base: reqwest::Url, token: &str, file_path: &str) -> reqwest::Url {
96    let mut url = base;
97    {
98        let mut segments = url.path_segments_mut().expect("base URL cannot be a cannot-be-a-base");
99        segments.push("file");
100        segments.push(&format!("bot{token}"));
101        segments.push(file_path);
102    }
103    url
104}
105
106#[cfg(test)]
107mod tests {
108    use crate::net::*;
109
110    #[test]
111    fn method_url_test() {
112        let url = method_url(
113            reqwest::Url::parse(TELEGRAM_API_URL).unwrap(),
114            "535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao",
115            "methodName",
116        );
117
118        assert_eq!(
119            url.as_str(),
120            "https://api.telegram.org/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/methodName"
121        );
122    }
123
124    #[test]
125    fn method_url_with_custom_url_test() {
126        let url = method_url(
127            reqwest::Url::parse("https://example.com/telegram").unwrap(),
128            "535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao",
129            "methodName",
130        );
131
132        assert_eq!(
133	    url.as_str(),
134	    "https://example.com/telegram/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/methodName"
135	);
136    }
137
138    #[test]
139    fn file_url_test() {
140        let url = file_url(
141            reqwest::Url::parse(TELEGRAM_API_URL).unwrap(),
142            "535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao",
143            "AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ",
144        );
145
146        assert_eq!(
147            url.as_str(),
148            "https://api.telegram.org/file/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ"
149        );
150    }
151
152    #[test]
153    fn file_url_with_custom_url_test() {
154        let url = file_url(
155            reqwest::Url::parse("https://example.com/telegram").unwrap(),
156            "535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao",
157            "AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ",
158        );
159
160        assert_eq!(
161	    url.as_str(),
162	    "https://example.com/telegram/file/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ"
163	);
164    }
165}