pass_it_on/interfaces/
http.rs

1//! HTTP [`Interface`] and [`InterfaceConfig`] implementation
2//!
3//! # Server Configuration Example
4//! ## Configuration for Localhost
5//! ```toml
6//! [[server.interface]]
7//! type = "http"
8//! host = "http://localhost"
9//! port = 8080
10//! ```
11//!
12//! ## Configuration with TLS
13//! ```toml
14//! [[server.interface]]
15//! type = "http"
16//! host = "example.com"
17//! port = 8080
18//! tls = true
19//! tls_cert_path = "/path/to/certificate/cert.pem"
20//! tls_key_path = "/path/to/private/key/key.pem"
21//! ```
22//!
23//! # Client Configuration Example
24//! ```toml
25//! [[client.interface]]
26//! type = "http"
27//! host = "127.0.0.1"
28//! port = 8080
29//! ```
30
31#[cfg(feature = "http-client")]
32pub(crate) mod http_client;
33#[cfg(feature = "http-server")]
34pub(crate) mod http_server;
35
36use crate::interfaces::{Interface, InterfaceConfig};
37use crate::notifications::Notification;
38use crate::{Error, CRATE_VERSION};
39use async_trait::async_trait;
40use tracing::debug;
41use serde::{Deserialize, Serialize};
42use std::net::SocketAddr;
43use std::path::PathBuf;
44use tokio::sync::{broadcast, mpsc, watch};
45use url::{ParseError, Url};
46
47const DEFAULT_HOST: &str = "http://0.0.0.0";
48const HTTP: &str = "http";
49const HTTPS: &str = "https";
50const DEFAULT_PORT: u16 = 8080;
51const BASE_PATH: &str = "pass-it-on";
52const NOTIFICATION_PATH: &str = "notification";
53const VERSION_PATH: &str = "version";
54
55#[derive(Debug, Deserialize, Serialize)]
56struct Version {
57    version: String,
58}
59
60/// Data structure to represent the HTTP Socket [`Interface`].
61#[derive(Debug, Clone)]
62pub struct HttpSocketInterface {
63    host: Url,
64    tls: bool,
65    port: u16,
66    tls_cert_path: Option<PathBuf>,
67    tls_key_path: Option<PathBuf>,
68}
69
70/// Data structure to represent the HTTP Socket [`InterfaceConfig`].
71#[derive(Debug, Deserialize, PartialEq, Eq, Hash, Clone)]
72#[serde(default)]
73pub(crate) struct HttpSocketConfigFile {
74    pub host: String,
75    pub tls: Option<bool>,
76    pub port: i64,
77    pub tls_cert_path: Option<String>,
78    pub tls_key_path: Option<String>,
79}
80
81impl Version {
82    fn new() -> Self {
83        Self { version: CRATE_VERSION.to_string() }
84    }
85}
86
87impl HttpSocketInterface {
88    /// Create a new `HttpSocketInterface`.
89    pub fn new<P: AsRef<str>>(host_url: &Url, cert_path: Option<P>, key_path: Option<P>) -> Self {
90        let host = host_url.clone();
91        let tls = host.scheme().eq_ignore_ascii_case(HTTPS);
92        let port = host.port().unwrap_or(DEFAULT_PORT);
93        let tls_cert_path = cert_path.map(|p| PathBuf::from(p.as_ref()));
94        let tls_key_path = key_path.map(|p| PathBuf::from(p.as_ref()));
95        Self { host, tls, port, tls_cert_path, tls_key_path }
96    }
97
98    /// Return the IP address.
99    pub fn host(&self) -> &str {
100        self.host.as_str()
101    }
102
103    /// Return the IP address if it exists or the default address(127.0.0.1).
104    pub fn sockets(&self) -> Result<Vec<SocketAddr>, Error> {
105        Ok(self.host.socket_addrs(|| Some(self.port()))?)
106    }
107
108    /// Return the port.
109    pub fn port(&self) -> u16 {
110        self.port
111    }
112
113    /// Return if interface should use TLS
114    pub fn tls(&self) -> bool {
115        self.tls
116    }
117
118    /// Return path the TLS certificate
119    pub fn tls_cert_path(&self) -> &Option<PathBuf> {
120        &self.tls_cert_path
121    }
122
123    /// Return path the TLS private key
124    pub fn tls_key_path(&self) -> &Option<PathBuf> {
125        &self.tls_key_path
126    }
127}
128
129impl Default for HttpSocketConfigFile {
130    fn default() -> Self {
131        Self {
132            host: DEFAULT_HOST.into(),
133            tls: None,
134            port: DEFAULT_PORT as i64,
135            tls_cert_path: None,
136            tls_key_path: None,
137        }
138    }
139}
140
141impl Default for HttpSocketInterface {
142    fn default() -> Self {
143        Self {
144            host: Url::parse(DEFAULT_HOST).unwrap(),
145            tls: false,
146            port: DEFAULT_PORT,
147            tls_cert_path: None,
148            tls_key_path: None,
149        }
150    }
151}
152
153impl TryFrom<&HttpSocketConfigFile> for HttpSocketInterface {
154    type Error = Error;
155
156    fn try_from(value: &HttpSocketConfigFile) -> Result<Self, Self::Error> {
157        if !(value.port < u16::MAX as i64 && value.port > u16::MIN as i64) {
158            return Err(Error::InvalidPortNumber(value.port));
159        }
160        let mut url = parse_url(value.host.as_str())?;
161        if let Some(explict_tls) = value.tls {
162            match explict_tls {
163                true => url.set_scheme(HTTPS),
164                false => url.set_scheme(HTTP),
165            }
166            .expect("TryFrom HttpSocketConfigFile Unable to set url scheme");
167        }
168
169        url.set_port(Some(value.port as u16)).unwrap();
170        Ok(HttpSocketInterface::new(&url, value.tls_cert_path.as_ref(), value.tls_key_path.as_ref()))
171    }
172}
173
174#[typetag::deserialize(name = "http")]
175impl InterfaceConfig for HttpSocketConfigFile {
176    fn to_interface(&self) -> Result<Box<dyn Interface + Send>, Error> {
177        Ok(Box::new(HttpSocketInterface::try_from(self)?))
178    }
179}
180
181#[async_trait]
182impl Interface for HttpSocketInterface {
183    #[cfg(feature = "http-server")]
184    async fn receive(&self, interface_tx: mpsc::Sender<String>, shutdown: watch::Receiver<bool>) -> Result<(), Error> {
185        use crate::interfaces::http::http_server::start_monitoring;
186
187        if self.tls && (self.tls_cert_path().is_none() || self.tls_cert_path().is_none()) {
188            Err(Error::InvalidInterfaceConfiguration(
189                "Both tls_cert_path and tls_cert_path must be provided for a TLS server".into(),
190            ))
191        } else {
192            for socket in self.sockets()? {
193                let tls = self.tls;
194                let itx = interface_tx.clone();
195                let srx = shutdown.clone();
196                let cert_path = self.tls_cert_path.clone();
197                let key_path = self.tls_key_path.clone();
198                tokio::spawn(async move { start_monitoring(itx, srx, socket, tls, cert_path, key_path).await });
199            }
200            Ok(())
201        }
202    }
203
204    #[cfg(not(feature = "http-server"))]
205    async fn receive(
206        &self,
207        _interface_tx: mpsc::Sender<String>,
208        _shutdown: watch::Receiver<bool>,
209    ) -> Result<(), Error> {
210        Err(Error::DisabledInterfaceFeature("http-server".to_string()))
211    }
212
213    #[cfg(feature = "http-client")]
214    async fn send(
215        &self,
216        interface_rx: broadcast::Receiver<Notification>,
217        shutdown: watch::Receiver<bool>,
218    ) -> Result<(), Error> {
219        use crate::interfaces::http::http_client::start_sending;
220
221        let mut url = self.host.clone();
222        url.set_path(format!("{}/{}", BASE_PATH, NOTIFICATION_PATH).as_str());
223        debug!("Sending notification to: {}", url.as_str());
224
225        tokio::spawn(async move { start_sending(interface_rx, shutdown, url.as_str()).await });
226        Ok(())
227    }
228
229    #[cfg(not(feature = "http-client"))]
230    async fn send(
231        &self,
232        _interface_rx: broadcast::Receiver<Notification>,
233        _shutdown: watch::Receiver<bool>,
234    ) -> Result<(), Error> {
235        Err(Error::DisabledInterfaceFeature("http-client".to_string()))
236    }
237}
238
239fn parse_url(value: &str) -> Result<Url, Error> {
240    match Url::parse(value) {
241        Ok(url) => Ok(url),
242        Err(ParseError::RelativeUrlWithoutBase) => parse_url(format!("{}://{}", HTTP, value).as_str()),
243        Err(error) => Err(error.into()),
244    }
245}