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
57                .build()
58                .expect("Failed to build `reqwest` client as a TLS backend is not available. Enable either the `native-tls` or the `rustls` feature of the `sentry` crate.")
59        });
60        let dsn = options.dsn.as_ref().unwrap();
61        let user_agent = options.user_agent.clone();
62        let auth = dsn.to_auth(Some(&user_agent)).to_string();
63        let url = dsn.envelope_api_url().to_string();
64
65        let thread = TransportThread::new(move |envelope, mut rl| {
66            let mut body = Vec::new();
67            envelope.to_writer(&mut body).unwrap();
68            let request = client.post(&url).header("X-Sentry-Auth", &auth).body(body);
69
70            // NOTE: because of lifetime issues, building the request using the
71            // `client` has to happen outside of this async block.
72            async move {
73                match request.send().await {
74                    Ok(response) => {
75                        let headers = response.headers();
76
77                        if let Some(sentry_header) = headers
78                            .get("x-sentry-rate-limits")
79                            .and_then(|x| x.to_str().ok())
80                        {
81                            rl.update_from_sentry_header(sentry_header);
82                        } else if let Some(retry_after) = headers
83                            .get(ReqwestHeaders::RETRY_AFTER)
84                            .and_then(|x| x.to_str().ok())
85                        {
86                            rl.update_from_retry_after(retry_after);
87                        } else if response.status() == StatusCode::TOO_MANY_REQUESTS {
88                            rl.update_from_429();
89                        }
90
91                        match response.text().await {
92                            Err(err) => {
93                                sentry_debug!("Failed to read sentry response: {}", err);
94                            }
95                            Ok(text) => {
96                                sentry_debug!("Get response: `{}`", text);
97                            }
98                        }
99                    }
100                    Err(err) => {
101                        sentry_debug!("Failed to send envelope: {}", err);
102                    }
103                }
104                rl
105            }
106        });
107        Self { thread }
108    }
109}
110
111impl Transport for ReqwestHttpTransport {
112    fn send_envelope(&self, envelope: Envelope) {
113        self.thread.send(envelope)
114    }
115    fn flush(&self, timeout: Duration) -> bool {
116        self.thread.flush(timeout)
117    }
118
119    fn shutdown(&self, timeout: Duration) -> bool {
120        self.flush(timeout)
121    }
122}