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            timeout: 60000,
110            redirect_limit: 10,
111            user_agent: None,
112            #[cfg(feature = "ohttp")]
113            ohttp_channel: None,
114        }
115    }
116}
117
118#[cfg(test)]
119mod test {
120    use url::Url;
121
122    use super::*;
123    use crate::settings;
124
125    #[test]
126    fn test_user_agent() {
127        let mut req = Request::get(Url::parse("http://example.com/").unwrap());
128        // No default user agent
129        let client = Client::new(ClientSettings::default());
130        client.set_user_agent(&mut req).unwrap();
131        assert_eq!(req.headers.get(USER_AGENT), None);
132        // Global user-agent set
133        settings::set_global_default_user_agent("global-user-agent".into());
134        let client = Client::new(ClientSettings::default());
135        let mut req = Request::get(Url::parse("http://example.com/").unwrap());
136        client.set_user_agent(&mut req).unwrap();
137        assert_eq!(req.headers.get(USER_AGENT), Some("global-user-agent"));
138        // ClientSettings overrides that
139        let client = Client::new(ClientSettings {
140            user_agent: Some("client-settings-user-agent".into()),
141            ..ClientSettings::default()
142        });
143        let mut req = Request::get(Url::parse("http://example.com/").unwrap());
144        client.set_user_agent(&mut req).unwrap();
145        assert_eq!(
146            req.headers.get(USER_AGENT),
147            Some("client-settings-user-agent")
148        );
149        // Request header overrides that
150        let mut req = Request::get(Url::parse("http://example.com/").unwrap());
151        req.headers
152            .insert(USER_AGENT, "request-user-agent")
153            .unwrap();
154        client.set_user_agent(&mut req).unwrap();
155        assert_eq!(req.headers.get(USER_AGENT), Some("request-user-agent"));
156    }
157}