You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
yuurei/src/web/route.rs

164 lines
3.7 KiB

//! Basic router
use super::*;
use hyper::{
Method,
};
use std::{
fmt,
marker::Send,
iter,
};
use tokio::{
sync::{
mpsc::{
self,
error::SendTimeoutError,
},
},
time,
};
use futures::{
future::{
self,
Future,
},
};
use generational_arena::{
Index,
Arena,
};
pub trait UriRoute
{
fn is_match(&self, uri: &str) -> bool;
#[inline] fn as_string(&self) -> &str
{
""
}
}
impl UriRoute for str
{
#[inline] fn is_match(&self, uri: &str) -> bool {
self.eq(uri)
}
#[inline] fn as_string(&self) -> &str {
self
}
}
impl<T: AsRef<str>> UriRoute for T
{
#[inline] fn is_match(&self, uri: &str) -> bool {
self.as_ref().eq(uri)
}
#[inline] fn as_string(&self) -> &str {
self.as_ref()
}
}
impl UriRoute for regex::Regex
{
#[inline] fn is_match(&self, uri: &str) -> bool {
self.test(uri)
}
#[inline] fn as_string(&self) -> &str {
self.as_str()
}
}
/// Contains a routing table
#[derive(Debug)]
pub struct Router
{
routes: Arena<(Option<Method>, OpaqueDebug<Box<dyn UriRoute + Send + 'static>>, mpsc::Sender<String>)>,
}
impl Router
{
/// Create an empty routing table
pub fn new() -> Self
{
Self{
routes: Arena::new(),
}
}
/// Push a new route into the router.
///
/// # Returns
/// The hook's new index, and the receiver that `dispatch()` sends to.
pub fn hook<Uri: UriRoute + Send + 'static>(&mut self, method: Option<Method>, uri: Uri) -> (Index, mpsc::Receiver<String>)
{
let (tx, rx) = mpsc::channel(config::get_or_default().dos_max);
(self.routes.insert((method, OpaqueDebug::new(Box::new(uri)), tx)), rx)
}
/// Dispatch the URI location across this router, sending to all that match it.
///
/// # Returns
/// When one or more dispatchers match but faile, `Err` is returned. Inside the `Err` tuple is the amount of successful dispatches, and also a vector containing the indecies of the failed hook sends.
pub async fn dispatch(&mut self, method: &Method, uri: impl AsRef<str>, timeout: Option<time::Duration>) -> Result<usize, (usize, Vec<Index>)>
{
let string = uri.as_ref();
let mut success=0usize;
let vec: Vec<_> =
future::join_all(self.routes.iter_mut()
.filter_map(|(i, (a_method, route, sender))| {
match a_method {
Some(x) if x != method => None,
_ => {
if route.is_match(string) {
trace!("{:?} @{}: -> {}",i, route.as_string(), string);
let timeout = timeout.clone();
macro_rules! send {
() => {
match timeout {
None => sender.send(string.to_owned()).await
.map_err(|e| SendTimeoutError::Closed(e.0)),
Some(time) => sender.send_timeout(string.to_owned(), time).await
}
}
};
Some(async move {
match send!() {
Err(SendTimeoutError::Closed(er)) => {
error!("{:?}: Dispatch failed on hooked route for {}", i, er);
Err(i)
},
Err(SendTimeoutError::Timeout(er)) => {
warn!("{:?}: Dispatch timed out on hooked route for {}", i, er);
Err(i)
},
_ => Ok(()),
}
})
} else {
None
}
},
}
})).await.into_iter()
.filter_map(|res| {
if res.is_ok() {
success+=1;
}
res.err()
}).collect();
if vec.len() > 0 {
Err((success, vec))
} else {
Ok(success)
}
}
/// Attempt to unhook these hooks. If one or more of the provided indecies does not exist in the routing table, it is ignored.
pub fn unhook<I>(&mut self, items: I)
where I: IntoIterator<Item = Index>
{
for item in items.into_iter() {
self.routes.remove(item);
}
}
}