sentry/transports/
reqwest.rs1use 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#[cfg_attr(doc_cfg, doc(cfg(feature = "reqwest")))]
17pub struct ReqwestHttpTransport {
18 thread: TransportThread,
19}
20
21impl ReqwestHttpTransport {
22 pub fn new(options: &ClientOptions) -> Self {
24 Self::new_internal(options, None)
25 }
26
27 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 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}