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