Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@ edition = "2021"
default = ["isahc-client"]
isahc-client = ["isahc", "futures-lite/futures-io"]
hyper-client = ["hyper", "hyper-tls"] # use features = ["hyper-client"], default-features = false for about 300kb size decrease
hyper-rustls-client = ["hyper", "hyper-rustls"] # pure-Rust TLS with RustCrypto, ideal for docker/musl builds

[dependencies]
hyper = { version = "0.14", features = ["client", "http1"], optional = true }
hyper-tls = { version = "0.5", optional = true }
hyper-rustls = { version = "0.24", default-features = false, features = ["http1", "native-tokio"], optional = true }
isahc = { version = "1.4.0", optional = true }
futures-lite = { version = "2.5.0", optional = true }
http = "0.2"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
jwt-simple = { version = "0.12.11", default-features = false, features = ["pure-rust"] }
ece = "2.2"
ece = { git = "https://github.com/buraktabn/rust-ece", default-features = false, features = ["backend-rustcrypto", "serializable-keys"] }
pem = "3.0.4"
sec1_decode = "0.1.0"
chrono = "0.4"
Expand Down
104 changes: 104 additions & 0 deletions src/clients/hyper_rustls_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use async_trait::async_trait;
use http::header::RETRY_AFTER;
use hyper::{body::HttpBody, client::HttpConnector, Body, Client, Request as HttpRequest};
use hyper_rustls::HttpsConnector;

use crate::{
clients::{request_builder, WebPushClient, MAX_RESPONSE_SIZE},
error::{RetryAfter, WebPushError},
message::WebPushMessage,
};

/// An async client for sending the notification payload using rustls for TLS.
///
/// This client is thread-safe. Clones of this client will share the same underlying resources,
/// so cloning is a cheap and effective method to provide access to the client.
///
/// This client is [`hyper`](https://crates.io/crates/hyper) based with [`rustls`](https://crates.io/crates/rustls)
/// for TLS, and will only work in Tokio contexts. This variant is ideal for docker/musl builds
/// that don't require native-tls.
#[derive(Clone)]
pub struct HyperRustlsWebPushClient {
client: Client<HttpsConnector<HttpConnector>>,
}

impl Default for HyperRustlsWebPushClient {
fn default() -> Self {
Self::new()
}
}

impl From<Client<HttpsConnector<HttpConnector>>> for HyperRustlsWebPushClient {
/// Creates a new client from a custom hyper HTTP client with rustls connector.
fn from(client: Client<HttpsConnector<HttpConnector>>) -> Self {
Self { client }
}
}

impl HyperRustlsWebPushClient {
/// Creates a new client with rustls for TLS.
pub fn new() -> Self {
let https = hyper_rustls::HttpsConnectorBuilder::new()
.with_native_roots()
.https_or_http()
.enable_http1()
.build();

Self {
client: Client::builder().build(https),
}
}
}

#[async_trait]
impl WebPushClient for HyperRustlsWebPushClient {
/// Sends a notification. Never times out.
async fn send(&self, message: WebPushMessage) -> Result<(), WebPushError> {
trace!("Message: {:?}", message);

let request: HttpRequest<Body> = request_builder::build_request(message);

debug!("Request: {:?}", request);

let requesting = self.client.request(request);

let response = requesting.await?;

trace!("Response: {:?}", response);

let retry_after = response
.headers()
.get(RETRY_AFTER)
.and_then(|ra| ra.to_str().ok())
.and_then(RetryAfter::from_str);

let response_status = response.status();
trace!("Response status: {}", response_status);

let mut chunks = response.into_body();
let mut body = Vec::new();
while let Some(chunk) = chunks.data().await {
body.extend(&chunk?);
if body.len() > MAX_RESPONSE_SIZE {
return Err(WebPushError::ResponseTooLarge);
}
}
trace!("Body: {:?}", body);

trace!("Body text: {:?}", std::str::from_utf8(&body));

let response = request_builder::parse_response(response_status, body.to_vec());

debug!("Response: {:?}", response);

if let Err(WebPushError::ServerError {
retry_after: None,
info,
}) = response
{
Err(WebPushError::ServerError { retry_after, info })
} else {
Ok(response?)
}
}
}
3 changes: 3 additions & 0 deletions src/clients/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub mod request_builder;
#[cfg(feature = "hyper-client")]
pub mod hyper_client;

#[cfg(feature = "hyper-rustls-client")]
pub mod hyper_rustls_client;

#[cfg(feature = "isahc-client")]
pub mod isahc_client;

Expand Down
2 changes: 1 addition & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl From<InvalidUri> for WebPushError {
}
}

#[cfg(feature = "hyper-client")]
#[cfg(any(feature = "hyper-client", feature = "hyper-rustls-client"))]
impl From<hyper::Error> for WebPushError {
fn from(_: hyper::Error) -> Self {
Self::Unspecified
Expand Down
7 changes: 5 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
//!
//! A library for creating and sending push notifications to a web browser. For
//! content payload encryption it uses [RFC8188](https://datatracker.ietf.org/doc/html/rfc8188).
//! The client is asynchronous and can run on any executor. An optional [`hyper`](https://crates.io/crates/hyper) based client is
//! available with the feature `hyper-client`.
//! The client is asynchronous and can run on any executor. Optional [`hyper`](https://crates.io/crates/hyper)
//! based clients are available with the features `hyper-client` (using native-tls) and
//! `hyper-rustls-client` (using rustls + pure-Rust crypto, ideal for docker/musl builds).
//!
//! # Example
//!
Expand Down Expand Up @@ -50,6 +51,8 @@ extern crate serde_derive;

#[cfg(feature = "hyper-client")]
pub use crate::clients::hyper_client::HyperWebPushClient;
#[cfg(feature = "hyper-rustls-client")]
pub use crate::clients::hyper_rustls_client::HyperRustlsWebPushClient;
#[cfg(feature = "isahc-client")]
pub use crate::clients::isahc_client::IsahcWebPushClient;
pub use crate::{
Expand Down