Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Middleware and error handling #9

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions examples/middleware.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use alcazar::error::MiddlewareError;
use alcazar::middleware::ProcessStrategy;
use alcazar::status_code::StatusCode;
use alcazar::{middleware::Middleware, prelude::*};
use std::{
io::{BufRead, BufReader, Write},
net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream},
};
use std::{thread::park_timeout, time::Duration};

async fn handler() -> StatusCode {
StatusCode::Ok
}

async fn first_middleware() -> Result<(), MiddlewareError> {
println!("first_middleware");
Ok(())
}

async fn second_middleware() -> Result<(), MiddlewareError> {
println!("second_middleware");
Ok(())
}

fn main() {
let router = Router::new().with_endpoint("/", &["get"], handler);

let mut middlewares = Vec::new();
middlewares.push(Middleware::new(ProcessStrategy::Before, first_middleware));
middlewares.push(Middleware::new(ProcessStrategy::After, second_middleware));

let alcazar = AppBuilder::default()
.set_addr(SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
8080,
))
.set_router(router)
.set_middlewares(middlewares)
.start()
.unwrap();

let mut stream = TcpStream::connect(alcazar.local_addr()).unwrap();

stream.write_all(b"GET / HTTP/1.1\r\n\r\n").unwrap();
stream.flush().unwrap();

let mut reader = BufReader::new(stream);
let mut buffer = String::new();

match reader.read_line(&mut buffer) {
Ok(_n) => {
if buffer.starts_with("HTTP/1.1 200 OK\r\n") {
println!("Hello, world!");
}
}
Err(_) => println!("Goodbye, world!"),
}

park_timeout(Duration::from_secs(1));
}
24 changes: 21 additions & 3 deletions src/alcazar.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::error::Result;
use crate::request::HttpRequest;
use crate::router::Router;
use crate::{error::Result, middleware::Middleware, middleware::ProcessStrategy};
use bastion_executor::run::run;
use lightproc::prelude::ProcStack;
use std::io::Write;
Expand All @@ -10,13 +10,15 @@ use tracing::info;
pub struct AppBuilder {
addr: SocketAddr,
router: Router,
middlewares: Vec<Middleware>,
}

impl Default for AppBuilder {
fn default() -> Self {
Self {
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0),
router: Router::default(),
middlewares: Vec::new(),
}
}
}
Expand All @@ -32,10 +34,16 @@ impl AppBuilder {
self
}

pub fn set_middlewares(&mut self, middlewares: Vec<Middleware>) -> &mut Self {
self.middlewares = middlewares;
self
}

pub fn start(&self) -> Result<App> {
let listener = TcpListener::bind(self.addr)?;
let local_addr = listener.local_addr()?;
let router = self.router.clone();
let middlewares = self.middlewares.clone();

info!("listening to {}", local_addr);
std::thread::spawn(move || -> Result<()> {
Expand All @@ -44,9 +52,19 @@ impl AppBuilder {
Ok((mut stream, _addr)) => {
let request = HttpRequest::parse_stream(&stream)?;
let endpoint = router.get_endpoint(request.method(), request.path())?;
// TODO: Call the endpoint's handler and write the response back
for middleware in &middlewares {
run(
async { middleware.process(ProcessStrategy::Before).await },
ProcStack::default(),
)?;
}
let handler = run(async { endpoint.handler().await }, ProcStack::default());

for middleware in &middlewares {
run(
async { middleware.process(ProcessStrategy::After).await },
ProcStack::default(),
)?;
}
stream.write_all(handler.into_bytes_response().as_slice())?;
stream.flush()?;
}
Expand Down
11 changes: 10 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ use std::io::Error as IOError;
use std::result;
use thiserror::Error;

// Alias for easier error handling and less boilerplate.
// Aliases for easier error handling and less boilerplate.
pub type Result<T> = result::Result<T, AlcazarError>;
pub type MiddlewareResult<T> = result::Result<T, MiddlewareError>;

#[derive(Error, Debug)]
pub enum AlcazarError {
Expand All @@ -16,6 +17,8 @@ pub enum AlcazarError {
ParseError(#[from] ParseError),
#[error(transparent)]
RoutingError(#[from] RoutingError),
#[error(transparent)]
MiddlewareError(#[from] MiddlewareError),
}

#[derive(Error, Debug, Clone)]
Expand Down Expand Up @@ -45,3 +48,9 @@ pub enum RoutingError {
#[error("can't compile {0} regex for the given path.")]
RegexCompileError(String),
}

#[derive(Error, Debug, Clone)]
pub enum MiddlewareError {
#[error("bad process startegy")]
BadProcessStrategy,
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod alcazar;
pub mod error;
pub mod middleware;
pub mod request;
pub mod router;
pub mod routing;
Expand Down
48 changes: 48 additions & 0 deletions src/middleware.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use crate::error::MiddlewareResult;
use futures::future::{FutureExt, FutureObj, Shared};
use std::future::Future;

#[derive(Clone)]
pub enum ProcessStrategy {
Before,
After,
}

#[derive(Clone)]
pub struct Middleware {
process_strategy: ProcessStrategy,
process: Shared<FutureObj<'static, MiddlewareResult<()>>>,
}

impl Middleware {
pub fn new<C, F>(process_strategy: ProcessStrategy, process: C) -> Self
where
C: Fn() -> F + Send + 'static,
F: Future<Output = MiddlewareResult<()>> + Send + 'static,
{
let process = FutureObj::new(Box::new(process())).shared();
Middleware {
process_strategy,
process,
}
}

pub async fn process(&self, process_strategy: ProcessStrategy) -> MiddlewareResult<()> {
match process_strategy {
ProcessStrategy::Before => match self.process_strategy {
ProcessStrategy::Before => {
self.process.clone().await?;
Ok(())
}
ProcessStrategy::After => Ok(()),
},
ProcessStrategy::After => match self.process_strategy {
ProcessStrategy::Before => Ok(()),
ProcessStrategy::After => {
self.process.clone().await?;
Ok(())
}
},
}
}
}