web_push/clients/
isahc_client.rs

1use async_trait::async_trait;
2use futures_lite::AsyncReadExt;
3use http::header::RETRY_AFTER;
4use isahc::HttpClient;
5
6use crate::clients::request_builder;
7use crate::clients::{WebPushClient, MAX_RESPONSE_SIZE};
8use crate::error::{RetryAfter, WebPushError};
9use crate::message::WebPushMessage;
10
11/// An async client for sending the notification payload. This client is expensive to create, and
12/// should be reused.
13///
14/// This client is thread-safe. Clones of this client will share the same underlying resources,
15/// so cloning is a cheap and effective method to provide access to the client.
16///
17/// This client is built on [`isahc`](https://crates.io/crates/isahc), and will therefore work on any async executor.
18#[derive(Clone)]
19pub struct IsahcWebPushClient {
20    client: HttpClient,
21}
22
23impl Default for IsahcWebPushClient {
24    fn default() -> Self {
25        Self::new().unwrap()
26    }
27}
28
29impl From<HttpClient> for IsahcWebPushClient {
30    /// Creates a new client from a custom Isahc HTTP client.
31    fn from(client: HttpClient) -> Self {
32        Self { client }
33    }
34}
35
36impl IsahcWebPushClient {
37    /// Creates a new client. Can fail under resource depletion.
38    pub fn new() -> Result<Self, WebPushError> {
39        Ok(Self {
40            client: HttpClient::new()?,
41        })
42    }
43}
44
45#[async_trait]
46impl WebPushClient for IsahcWebPushClient {
47    /// Sends a notification. Never times out.
48    async fn send(&self, message: WebPushMessage) -> Result<(), WebPushError> {
49        trace!("Message: {:?}", message);
50
51        let request = request_builder::build_request::<isahc::AsyncBody>(message);
52
53        trace!("Request: {:?}", request);
54
55        let requesting = self.client.send_async(request);
56
57        let response = requesting.await?;
58
59        trace!("Response: {:?}", response);
60
61        let retry_after = response
62            .headers()
63            .get(RETRY_AFTER)
64            .and_then(|ra| ra.to_str().ok())
65            .and_then(RetryAfter::from_str);
66
67        let response_status = response.status();
68        trace!("Response status: {}", response_status);
69
70        let mut body = Vec::new();
71        if response
72            .into_body()
73            .take(MAX_RESPONSE_SIZE as u64 + 1)
74            .read_to_end(&mut body)
75            .await?
76            > MAX_RESPONSE_SIZE
77        {
78            return Err(WebPushError::InvalidResponse); // TODO: Use new error code
79        }
80        trace!("Body: {:?}", body);
81
82        trace!("Body text: {:?}", std::str::from_utf8(&body));
83
84        let response = request_builder::parse_response(response_status, body.to_vec());
85
86        trace!("Response: {:?}", response);
87
88        if let Err(WebPushError::ServerError(None)) = response {
89            Err(WebPushError::ServerError(retry_after))
90        } else {
91            Ok(response?)
92        }
93    }
94}