Skip to main content

sentry/transports/
reqwest.rs

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