ffsend_api/
client.rs

1use std::time::Duration;
2
3use reqwest::{
4    self,
5    blocking::{
6        Client as ReqwestClient, ClientBuilder as ReqwestClientBuilder, Request as ReqwestRequest,
7        RequestBuilder as ReqwestRequestBuilder, Response as ReqwestResponse,
8    },
9    IntoUrl,
10};
11#[cfg(feature = "send3")]
12use websocket::{
13    self, client::sync::Client as WsClient, result::WebSocketResult as WsResult,
14    stream::sync::NetworkStream as WsNetworkStream,
15};
16
17/// The protocol to report to the server when uploading through a websocket.
18#[cfg(feature = "send3")]
19const WEBSOCKET_PROTOCOL: &str = "ffsend";
20
21/// A networking client for ffsend actions.
22pub struct Client {
23    /// The client configuration to use for built reqwest clients.
24    config: ClientConfig,
25
26    /// The inner reqwest client.
27    reqwest: ReqwestClient,
28}
29
30impl Client {
31    /// Construct the ffsend client with the given `config`.
32    ///
33    /// If this client is used for transfering files, `transfer` should be true.
34    // TODO: properly handle errors, do not unwrap
35    pub fn new(config: ClientConfig, transfer: bool) -> Self {
36        // Set up the reqwest client
37        let mut builder = ReqwestClientBuilder::new();
38        match config.timeout {
39            Some(timeout) if !transfer => builder = builder.timeout(timeout),
40            _ => {}
41        }
42        match config.transfer_timeout {
43            Some(timeout) if transfer => builder = builder.timeout(timeout),
44            _ => {}
45        }
46        let reqwest = builder.build().expect("failed to build reqwest client");
47
48        // Build the client
49        Self { config, reqwest }
50    }
51
52    /// Create a HTTP GET request through this client, returning a `RequestBuilder`.
53    pub fn get<U: IntoUrl>(&self, url: U) -> ReqwestRequestBuilder {
54        self.configure(self.reqwest.get(url))
55    }
56
57    /// Create a HTTP GET request through this client, returning a `RequestBuilder`.
58    pub fn post<U: IntoUrl>(&self, url: U) -> ReqwestRequestBuilder {
59        self.configure(self.reqwest.post(url))
60    }
61
62    /// Execute the given reqwest request through the internal reqwest client.
63    // TODO: remove this, as the timeout can't be configured?
64    pub fn execute(&self, request: ReqwestRequest) -> reqwest::Result<ReqwestResponse> {
65        self.reqwest.execute(request)
66    }
67
68    /// Construct a websocket client connected to the given `url` using the wrapped configuration.
69    #[cfg(feature = "send3")]
70    pub fn websocket(&self, url: &str) -> WsResult<WsClient<Box<dyn WsNetworkStream + Send>>> {
71        // Build the websocket client
72        let mut builder = websocket::ClientBuilder::new(url)
73            .expect("failed to set up websocket builder")
74            .add_protocol(WEBSOCKET_PROTOCOL);
75
76        // Configure basic HTTP authentication
77        if let Some((user, password)) = &self.config.basic_auth {
78            let mut headers = websocket::header::Headers::new();
79            headers.set(websocket::header::Authorization(websocket::header::Basic {
80                username: user.to_owned(),
81                password: password.to_owned(),
82            }));
83            builder = builder.custom_headers(&headers);
84        }
85
86        // Build and connect the client
87        builder.connect(None)
88    }
89
90    /// Configure the given reqwest client to match the configuration.
91    fn configure(&self, mut client: ReqwestRequestBuilder) -> ReqwestRequestBuilder {
92        // Configure basic HTTP authentication
93        if let Some((user, password)) = &self.config.basic_auth {
94            client = client.basic_auth(user, password.to_owned());
95        }
96
97        client
98    }
99}
100
101/// Configurable properties for networking client used for ffsend actions.
102#[derive(Clone, Debug, Builder)]
103pub struct ClientConfig {
104    /// Connection timeout for control requests.
105    #[builder(default = "Some(Duration::from_secs(30))")]
106    timeout: Option<Duration>,
107
108    /// Connection timeout specific to file transfers.
109    #[builder(default = "Some(Duration::from_secs(86400))")]
110    transfer_timeout: Option<Duration>,
111
112    /// Basic HTTP authentication credentials.
113    ///
114    /// Consists of a username, and an optional password.
115    #[builder(default)]
116    basic_auth: Option<(String, Option<String>)>,
117    // TODO: proxy settings
118}
119
120impl ClientConfig {
121    /// Construct a ffsend client based on this configuration.
122    ///
123    /// If this client is used for transfering files, `transfer` should be true.
124    pub fn client(self, transfer: bool) -> Client {
125        Client::new(self, transfer)
126    }
127}
128
129impl Default for ClientConfig {
130    fn default() -> Self {
131        Self {
132            timeout: None,
133            transfer_timeout: None,
134            basic_auth: None,
135        }
136    }
137}