Use procedural macros to define events
The construction of an EventHandler object should not be directly done by the client. Instead, it is easier to use procedural macro to automatize the process, hence exposing a nice and simple interface. Such use of procedural macros requires to crate an additional crate.
This commit is contained in:
parent
ccda4b1517
commit
789455668c
17 changed files with 1190 additions and 76 deletions
152
opensmtpd/src/lib.rs
Normal file
152
opensmtpd/src/lib.rs
Normal file
|
@ -0,0 +1,152 @@
|
|||
mod entry;
|
||||
mod errors;
|
||||
mod event_handlers;
|
||||
|
||||
use log::{debug, error, warn};
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
|
||||
pub use crate::entry::{Entry, Event};
|
||||
pub use crate::errors::Error;
|
||||
pub use crate::event_handlers::{EventHandler, MatchEvent};
|
||||
pub use opensmtpd_derive::event;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! handlers {
|
||||
( $( $x:expr ),* ) => {
|
||||
{
|
||||
let mut temp_vec = Vec::new();
|
||||
$(
|
||||
temp_vec.push(($x)());
|
||||
)*
|
||||
temp_vec
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
struct SessionHandler {
|
||||
entry_rx: mpsc::Receiver<Entry>,
|
||||
event_handlers: Vec<EventHandler>,
|
||||
}
|
||||
|
||||
impl SessionHandler {
|
||||
fn new(entry_rx: mpsc::Receiver<Entry>, handlers_rx: mpsc::Receiver<EventHandler>) -> Self {
|
||||
debug!(
|
||||
"New thread for session {}",
|
||||
thread::current().name().unwrap()
|
||||
);
|
||||
let mut event_handlers = Vec::new();
|
||||
for h in handlers_rx.iter() {
|
||||
debug!("Event handler registered");
|
||||
event_handlers.push(h);
|
||||
}
|
||||
SessionHandler {
|
||||
entry_rx,
|
||||
event_handlers,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_entries(&self) {
|
||||
for e in self.entry_rx.iter() {
|
||||
for h in self.event_handlers.iter() {
|
||||
if h.is_callable(e.event.clone()) {
|
||||
h.call(&e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SmtpIn {
|
||||
sessions: HashMap<u64, (mpsc::Sender<Entry>, thread::JoinHandle<()>)>,
|
||||
event_handlers: Vec<EventHandler>,
|
||||
}
|
||||
|
||||
impl SmtpIn {
|
||||
/// Read a line from the standard input.
|
||||
/// Since EOF should not append, it is considered as an error.
|
||||
fn read() -> Result<String, Error> {
|
||||
let mut input = String::new();
|
||||
let nb = io::stdin().read_line(&mut input)?;
|
||||
match nb {
|
||||
0 => Err(Error::new("end of file")),
|
||||
_ => Ok(input),
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatch the entry into its session's thread. If such thread does not
|
||||
/// already exists, creates it.
|
||||
fn dispatch(&mut self, input: &str) -> Result<(), Error> {
|
||||
let entry = Entry::from_str(input)?;
|
||||
let id = entry.session_id;
|
||||
let disconnect = entry.event == Event::LinkDisconnect;
|
||||
let channel = match self.sessions.get(&id) {
|
||||
Some((r, _)) => r,
|
||||
None => {
|
||||
let (handlers_tx, handlers_rx) = mpsc::channel();
|
||||
let (entry_tx, entry_rx) = mpsc::channel();
|
||||
let name = entry.session_id.to_string();
|
||||
let handle = thread::Builder::new().name(name).spawn(move || {
|
||||
SessionHandler::new(entry_rx, handlers_rx).read_entries();
|
||||
})?;
|
||||
for h in self.event_handlers.iter() {
|
||||
handlers_tx.send(h.clone())?;
|
||||
}
|
||||
self.sessions.insert(entry.session_id, (entry_tx, handle));
|
||||
let (r, _) = self.sessions.get(&entry.session_id).unwrap();
|
||||
r
|
||||
}
|
||||
};
|
||||
channel.send(entry)?;
|
||||
if disconnect {
|
||||
let _ = self.sessions.remove(&id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Allow each child thread to exit gracefully. First, the session table is
|
||||
/// drained so all the references to the senders are dropped, which will
|
||||
/// cause the receivers threads to exit. Then, we uses the join handlers in
|
||||
/// order to wait for the actual exit.
|
||||
fn graceful_exit_children(&mut self) {
|
||||
let mut handles = Vec::new();
|
||||
for (_, (_, h)) in self.sessions.drain() {
|
||||
handles.push(h);
|
||||
}
|
||||
for h in handles {
|
||||
let _ = h.join();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
SmtpIn {
|
||||
sessions: HashMap::new(),
|
||||
event_handlers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event_handlers(&mut self, handlers: Vec<EventHandler>) -> &mut Self {
|
||||
self.event_handlers = handlers.clone();
|
||||
self
|
||||
}
|
||||
|
||||
/// Run the infinite loop that will read and process input from stdin.
|
||||
pub fn run(&mut self) {
|
||||
loop {
|
||||
let line = match SmtpIn::read() {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
self.graceful_exit_children();
|
||||
error!("{}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
match self.dispatch(&line) {
|
||||
Ok(_) => {}
|
||||
Err(e) => warn!("{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in a new issue