sentry/transports/
reqwest.rs

1use std::time::Duration;
2
3use reqwest::{header as ReqwestHeaders, Client as ReqwestClient, Proxy, StatusCode};
4
5use super::tokio_thread::TransportThread;
6
7use crate::{sentry_debug, ClientOptions, Envelope, Transport};
8
9/// A [`Transport`] that sends events via the [`reqwest`] library.
10///
11/// When the `transport` feature is enabled this will currently
12/// be the default transport.  This is separately enabled by the
13/// `reqwest` feature flag.
14#[cfg_attr(doc_cfg, doc(cfg(feature = "reqwest")))]
15pub struct ReqwestHttpTransport {
16    thread: TransportThread,
17}
18
19impl ReqwestHttpTransport {
20    /// Creates a new Transport.
21    pub fn new(options: &ClientOptions) -> Self {
22        Self::new_internal(options, None)
23    }
24
25    /// Creates a new Transport that uses the specified [`ReqwestClient`].
26    pub fn with_client(options: &ClientOptions, client: ReqwestClient) -> Self {
27        Self::new_internal(options, Some(client))
28    }
29
30    fn new_internal(options: &ClientOptions, client: Option<ReqwestClient>) -> Self {
31        let client = client.unwrap_or_else(|| {
32            let mut builder = reqwest::Client::builder();
33            if options.accept_invalid_certs {
34                builder = builder.danger_accept_invalid_certs(true);
35            }
36            if let Some(url) = options.http_proxy.as_ref() {
37                match Proxy::http(url.as_ref()) {
38                    Ok(proxy) => {
39                        builder = builder.proxy(proxy);
40                    }
41                    Err(err) => {
42                        sentry_debug!("invalid proxy: {:?}", err);
43                    }
44                }
45            };
46            if let Some(url) = options.https_proxy.as_ref() {
47                match Proxy::https(url.as_ref()) {
48                    Ok(proxy) => {
49                        builder = builder.proxy(proxy);
50                    }
51                    Err(err) => {
52                        sentry_debug!("invalid proxy: {:?}", err);
53                    }
54                }
55            };
56            builder.build().unwrap()
57        });
58        let dsn = options.dsn.as_ref().unwrap();
59        let user_agent = options.user_agent.clone();
60        let auth = dsn.to_auth(Some(&user_agent)).to_string();
61        let url = dsn.envelope_api_url().to_string();
62
63        let thread = TransportThread::new(move |envelope, mut rl| {
64            let mut body = Vec::new();
65            envelope.to_writer(&mut body).unwrap();
66            let request = client.post(&url).header("X-Sentry-Auth", &auth).body(body);
67
68            // NOTE: because of lifetime issues, building the request using the
69            // `client` has to happen outside of this async block.
70            async move {
71                match request.send().await {
72                    Ok(response) => {
73                        let headers = response.headers();
74
75                        if let Some(sentry_header) = headers
76                            .get("x-sentry-rate-limits")
77                            .and_then(|x| x.to_str().ok())
78                        {
79                            rl.update_from_sentry_header(sentry_header);
80                        } else if let Some(retry_after) = headers
81                            .get(ReqwestHeaders::RETRY_AFTER)
82                            .and_then(|x| x.to_str().ok())
83                        {
84                            rl.update_from_retry_after(retry_after);
85                        } else if response.status() == StatusCode::TOO_MANY_REQUESTS {
86                            rl.update_from_429();
87                        }
88
89                        match response.text().await {
90                            Err(err) => {
91                                sentry_debug!("Failed to read sentry response: {}", err);
92                            }
93                            Ok(text) => {
94                                sentry_debug!("Get response: `{}`", text);
95                            }
96                        }
97                    }
98                    Err(err) => {
99                        sentry_debug!("Failed to send envelope: {}", err);
100                    }
101                }
102                rl
103            }
104        });
105        Self { thread }
106    }
107}
108
109impl Transport for ReqwestHttpTransport {
110    fn send_envelope(&self, envelope: Envelope) {
111        self.thread.send(envelope)
112    }
113    fn flush(&self, timeout: Duration) -> bool {
114        self.thread.flush(timeout)
115    }
116
117    fn shutdown(&self, timeout: Duration) -> bool {
118        self.flush(timeout)
119    }
120}