pass_it_on/interfaces/
http.rs1#[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#[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#[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 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 pub fn host(&self) -> &str {
100 self.host.as_str()
101 }
102
103 pub fn sockets(&self) -> Result<Vec<SocketAddr>, Error> {
105 Ok(self.host.socket_addrs(|| Some(self.port()))?)
106 }
107
108 pub fn port(&self) -> u16 {
110 self.port
111 }
112
113 pub fn tls(&self) -> bool {
115 self.tls
116 }
117
118 pub fn tls_cert_path(&self) -> &Option<PathBuf> {
120 &self.tls_cert_path
121 }
122
123 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}