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