viaduct/
client.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use crate::{
6    header_names::USER_AGENT,
7    new_backend::get_backend,
8    settings::{validate_request, GLOBAL_SETTINGS},
9    Request, Response, Result,
10};
11
12/// HTTP Client
13///
14/// This represents the "new" API.
15/// See `README.md` for details about the transition from the old to new API.
16#[derive(Default)]
17pub struct Client {
18    settings: ClientSettings,
19}
20
21impl Client {
22    pub fn new(mut settings: ClientSettings) -> Self {
23        settings.update_from_global_settings();
24        Self { settings }
25    }
26
27    /// Create a client that uses OHTTP with the specified channel for all requests
28    #[cfg(feature = "ohttp")]
29    pub fn with_ohttp_channel(
30        channel: &str,
31        settings: ClientSettings,
32    ) -> Result<Self, crate::ViaductError> {
33        if !crate::ohttp::is_ohttp_channel_configured(channel) {
34            return Err(crate::ViaductError::OhttpChannelNotConfigured(
35                channel.to_string(),
36            ));
37        }
38        let mut client_settings = settings;
39        client_settings.ohttp_channel = Some(channel.to_string());
40        Ok(Self::new(client_settings))
41    }
42
43    fn set_user_agent(&self, request: &mut Request) -> Result<()> {
44        if let Some(user_agent) = &self.settings.user_agent {
45            request.headers.insert_if_missing(USER_AGENT, user_agent)?;
46        }
47        Ok(())
48    }
49
50    pub async fn send(&self, mut request: Request) -> Result<Response> {
51        validate_request(&request)?;
52        self.set_user_agent(&mut request)?;
53
54        // Check if this client should use OHTTP for all requests
55        #[cfg(feature = "ohttp")]
56        if let Some(channel) = &self.settings.ohttp_channel {
57            crate::debug!(
58                "Client configured for OHTTP channel '{}', processing request via OHTTP",
59                channel
60            );
61            return crate::ohttp::process_ohttp_request(request, channel, self.settings.clone())
62                .await;
63        }
64
65        // For non-OHTTP requests, use the normal backend
66        crate::debug!("Processing request via standard backend");
67        get_backend()?
68            .send_request(request, self.settings.clone())
69            .await
70    }
71
72    pub fn send_sync(&self, request: Request) -> Result<Response> {
73        pollster::block_on(self.send(request))
74    }
75}
76
77#[derive(Debug, uniffi::Record, Clone)]
78#[repr(C)]
79pub struct ClientSettings {
80    /// Timeout for the entire request in ms (0 indicates no timeout).
81    #[uniffi(default = 0)]
82    pub timeout: u32,
83    /// Maximum amount of redirects to follow (0 means redirects are not allowed)
84    #[uniffi(default = 10)]
85    pub redirect_limit: u32,
86    /// OHTTP channel to use for all requests (if any)
87    #[cfg(feature = "ohttp")]
88    pub ohttp_channel: Option<String>,
89    /// Client default user-agent.
90    ///
91    /// This overrides the global default user-agent and is used when no `User-agent` header is set
92    /// directly in the Request.
93    #[uniffi(default = None)]
94    pub user_agent: Option<String>,
95}
96
97impl ClientSettings {
98    pub fn update_from_global_settings(&mut self) {
99        let settings = GLOBAL_SETTINGS.read();
100        if self.user_agent.is_none() {
101            self.user_agent = settings.default_user_agent.clone();
102        }
103    }
104}
105
106impl Default for ClientSettings {
107    fn default() -> Self {
108        Self {
109            #[cfg(target_os = "ios")]
110            timeout: 7000,
111            #[cfg(not(target_os = "ios"))]
112            timeout: 10000,
113            redirect_limit: 10,
114            user_agent: None,
115            #[cfg(feature = "ohttp")]
116            ohttp_channel: None,
117        }
118    }
119}
120
121#[cfg(test)]
122mod test {
123    use url::Url;
124
125    use super::*;
126    use crate::settings;
127
128    #[test]
129    fn test_user_agent() {
130        let mut req = Request::get(Url::parse("http://example.com/").unwrap());
131        // No default user agent
132        let client = Client::new(ClientSettings::default());
133        client.set_user_agent(&mut req).unwrap();
134        assert_eq!(req.headers.get(USER_AGENT), None);
135        // Global user-agent set
136        settings::set_global_default_user_agent("global-user-agent".into());
137        let client = Client::new(ClientSettings::default());
138        let mut req = Request::get(Url::parse("http://example.com/").unwrap());
139        client.set_user_agent(&mut req).unwrap();
140        assert_eq!(req.headers.get(USER_AGENT), Some("global-user-agent"));
141        // ClientSettings overrides that
142        let client = Client::new(ClientSettings {
143            user_agent: Some("client-settings-user-agent".into()),
144            ..ClientSettings::default()
145        });
146        let mut req = Request::get(Url::parse("http://example.com/").unwrap());
147        client.set_user_agent(&mut req).unwrap();
148        assert_eq!(
149            req.headers.get(USER_AGENT),
150            Some("client-settings-user-agent")
151        );
152        // Request header overrides that
153        let mut req = Request::get(Url::parse("http://example.com/").unwrap());
154        req.headers
155            .insert(USER_AGENT, "request-user-agent")
156            .unwrap();
157        client.set_user_agent(&mut req).unwrap();
158        assert_eq!(req.headers.get(USER_AGENT), Some("request-user-agent"));
159    }
160}