parent
af6446b335
commit
ee3cdbf8bb
@ -0,0 +1,127 @@
|
||||
//! PCRE
|
||||
use super::*;
|
||||
use pcre::{
|
||||
Pcre,
|
||||
};
|
||||
use std::{
|
||||
sync::{
|
||||
Mutex,
|
||||
},
|
||||
error,
|
||||
fmt,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Regex(Mutex<Pcre>, String);
|
||||
|
||||
/// A regex error
|
||||
#[derive(Debug)]
|
||||
pub struct Error(pcre::CompilationError);
|
||||
|
||||
impl error::Error for Error{}
|
||||
|
||||
impl fmt::Display for Error
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
write!(f,"regex failed to compile: ")?;
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<pcre::CompilationError> for Error
|
||||
{
|
||||
#[inline] fn from(from: pcre::CompilationError) -> Self
|
||||
{
|
||||
Self(from)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Iter<'a>(Option<pcre::Match<'a>>, usize);
|
||||
|
||||
impl<'a> Iterator for Iter<'a>
|
||||
{
|
||||
type Item = &'a str;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(m) = &mut self.0 {
|
||||
if self.1 >= m.string_count() {
|
||||
None
|
||||
} else {
|
||||
let string = m.group(self.1);
|
||||
self.1 +=1;
|
||||
Some(string)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline] fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(self.len(), Some(self.len()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::iter::FusedIterator for Iter<'a>{}
|
||||
impl<'a> ExactSizeIterator for Iter<'a>
|
||||
{
|
||||
fn len(&self) -> usize
|
||||
{
|
||||
match &self.0 {
|
||||
Some(m) => m.string_count(),
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Regex
|
||||
{
|
||||
/// Create a new regular expression from a string
|
||||
pub fn new<I>(from: impl AsRef<str>, opt: I) -> Result<Self, Error>
|
||||
where I: IntoIterator<Item=pcre::CompileOption>
|
||||
{
|
||||
let string = from.as_ref();
|
||||
Ok(Self(Mutex::new(Pcre::compile_with_options(string, &opt.into_iter().collect())?), string.to_owned()))
|
||||
}
|
||||
|
||||
/// Test against string
|
||||
pub fn test(&self, string: impl AsRef<str>) -> bool
|
||||
{
|
||||
let mut regex = self.0.lock().unwrap();
|
||||
regex.exec(string.as_ref()).is_some()
|
||||
}
|
||||
|
||||
/// Get the groups of a match against string
|
||||
pub fn exec_ref<'a>(&mut self, string: &'a (impl AsRef<str> + ?Sized)) -> Iter<'a>
|
||||
{
|
||||
let regex = self.0.get_mut().unwrap();
|
||||
Iter(regex.exec(string.as_ref()), 0)
|
||||
}
|
||||
|
||||
/// Get the groups as an owned string vector from map against string
|
||||
pub fn exec(&self, string: impl AsRef<str>) -> Vec<String>
|
||||
{
|
||||
let mut regex = self.0.lock().unwrap();
|
||||
Iter(regex.exec(string.as_ref()), 0).map(std::borrow::ToOwned::to_owned).collect()
|
||||
}
|
||||
|
||||
/// Get the string used to create this regex
|
||||
#[inline] pub fn as_str(&self) -> &str{
|
||||
&self.1[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Regex> for Pcre
|
||||
{
|
||||
#[inline] fn from(from: Regex) -> Self
|
||||
{
|
||||
from.0.into_inner().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Regex
|
||||
{
|
||||
#[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
write!(f, "{}", self.1)
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
//! Internal errors
|
||||
use super::*;
|
||||
use std::{
|
||||
fmt,
|
||||
error,
|
||||
net::SocketAddr,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
Denied(SocketAddr, bool),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Error
|
||||
{
|
||||
/// Print this error as a warning
|
||||
#[inline(never)] #[cold] pub fn warn(self) -> Self
|
||||
{
|
||||
warn!("{}", self);
|
||||
self
|
||||
}
|
||||
|
||||
/// Print this error as info
|
||||
#[inline(never)] #[cold] pub fn info(self) -> Self
|
||||
{
|
||||
info!("{}", self);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error{}
|
||||
impl fmt::Display for Error
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
match self {
|
||||
Self::Denied(sock, true) => write!(f, "denied connection (explicit): {}", sock),
|
||||
Self::Denied(sock, _) => write!(f, "denied connection (implicit): {}", sock),
|
||||
_ => write!(f, "unknown error"),
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
//! Basic router
|
||||
use super::*;
|
||||
use hyper::{
|
||||
Method,
|
||||
};
|
||||
use std::{
|
||||
fmt,
|
||||
marker::Send,
|
||||
iter,
|
||||
};
|
||||
use tokio::{
|
||||
sync::{
|
||||
mpsc,
|
||||
broadcast,
|
||||
notify,
|
||||
},
|
||||
};
|
||||
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: impl Future) -> Result<usize, (usize, Vec<Index>)>
|
||||
{
|
||||
let string = uri.as_ref();
|
||||
let (tcx, trx) = broadcast::channel(1);
|
||||
tokio::pin!(timeout);
|
||||
let begin_to = tokio::sync::Barrier::new(self.routes.len()+1);
|
||||
let timeout = async {
|
||||
timeout.await;
|
||||
begin_to.wait().await;
|
||||
tcx.send(());
|
||||
};
|
||||
let mut timeouts = iter::once(trx)
|
||||
.chain(iter::repeat_with(|| tcx.subscribe()));
|
||||
let output = async {
|
||||
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 mut timeout = timeouts.next().unwrap();
|
||||
Some(async move {
|
||||
match tokio::select!{
|
||||
_ = timeout.recv() => {
|
||||
None
|
||||
}
|
||||
s = sender.send(string.to_owned()) => {
|
||||
Some(s)
|
||||
}
|
||||
} {
|
||||
Some(Err(er)) => {
|
||||
warn!("{:?}: Dispatch failed on hooked route for {}", i, er.0);
|
||||
Err(i)
|
||||
},
|
||||
Some(_) => Ok(()),
|
||||
None => {
|
||||
warn!("{:?}: Dispatch timed out on hooked route", i);
|
||||
Err(i)
|
||||
},
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
})).await.into_iter()
|
||||
.filter_map(|res| {
|
||||
if res.is_ok() {
|
||||
success+=1;
|
||||
}
|
||||
res.err()
|
||||
}).collect();
|
||||
(success, vec)
|
||||
};
|
||||
tokio::pin!(output);
|
||||
let (_, (success, vec)) = future::join(timeout, output).await;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue