aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
authorFuwn <[email protected]>2022-06-01 01:04:43 +0000
committerFuwn <[email protected]>2022-06-01 01:04:43 +0000
commitc36a6a2b07a5a13fae2d3b8d404c9c99c95bdfcf (patch)
tree02de9953f5828e635653aba7751a59c6248ced82 /src/lib.rs
parentfix(lib.rs): response content no leading whitespace (diff)
downloadwindmark-c36a6a2b07a5a13fae2d3b8d404c9c99c95bdfcf.tar.xz
windmark-c36a6a2b07a5a13fae2d3b8d404c9c99c95bdfcf.zip
refactor(router): move router to seperate file
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs752
1 files changed, 2 insertions, 750 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 19cb235..b2b31c0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -111,761 +111,13 @@ pub mod handler;
pub mod module;
pub mod response;
pub mod returnable;
+pub mod router;
pub mod utilities;
#[macro_use]
extern crate log;
-use std::{
- error::Error,
- sync::{Arc, Mutex},
-};
-
pub use module::Module;
-use openssl::ssl::{self, SslAcceptor, SslMethod};
pub use response::Response;
+pub use router::Router;
pub use tokio::main;
-use tokio::{
- io::{AsyncReadExt, AsyncWriteExt},
- stream::StreamExt,
-};
-use url::Url;
-
-use crate::{
- handler::{Callback, ErrorResponse, Partial, RouteResponse},
- response::to_value_set_status,
- returnable::{CallbackContext, ErrorContext, RouteContext},
-};
-
-macro_rules! or_error {
- ($stream:ident, $operation:expr, $error_format:literal) => {
- match $operation {
- Ok(u) => u,
- Err(e) => {
- $stream
- .write_all(format!($error_format, e).as_bytes())
- .await?;
-
- $stream.shutdown().await?;
-
- return Ok(());
- }
- }
- };
-}
-
-/// A router which takes care of all tasks a Windmark server should handle:
-/// response generation, panics, logging, and more.
-#[derive(Clone)]
-pub struct Router {
- routes: matchit::Router<Arc<Mutex<RouteResponse>>>,
- error_handler: Arc<Mutex<ErrorResponse>>,
- private_key_file_name: String,
- ca_file_name: String,
- headers: Arc<Mutex<Vec<Partial>>>,
- footers: Arc<Mutex<Vec<Partial>>>,
- ssl_acceptor: Arc<SslAcceptor>,
- #[cfg(feature = "logger")]
- default_logger: bool,
- pre_route_callback: Arc<Mutex<Callback>>,
- post_route_callback: Arc<Mutex<Callback>>,
- charset: String,
- language: String,
- port: i32,
- modules: Arc<Mutex<Vec<Box<dyn Module + Send>>>>,
- fix_path: bool,
-}
-impl Router {
- /// Create a new `Router`
- ///
- /// # Examples
- ///
- /// ```rust
- /// let _router = windmark::Router::new();
- /// ```
- ///
- /// # Panics
- ///
- /// if a default `SslAcceptor` could not be built.
- #[must_use]
- pub fn new() -> Self { Self::default() }
-
- /// Set the filename of the private key file.
- ///
- /// # Examples
- ///
- /// ```rust
- /// windmark::Router::new().set_private_key_file("windmark_private.pem");
- /// ```
- pub fn set_private_key_file(
- &mut self,
- private_key_file_name: &str,
- ) -> &mut Self {
- self.private_key_file_name = private_key_file_name.to_string();
-
- self
- }
-
- /// Set the filename of the certificate chain file.
- ///
- /// # Examples
- ///
- /// ```rust
- /// windmark::Router::new().set_certificate_file("windmark_public.pem");
- /// ```
- pub fn set_certificate_file(&mut self, certificate_name: &str) -> &mut Self {
- self.ca_file_name = certificate_name.to_string();
-
- self
- }
-
- /// Map routes to URL paths
- ///
- /// # Examples
- ///
- /// ```rust
- /// use windmark::Response;
- ///
- /// windmark::Router::new()
- /// .mount(
- /// "/",
- /// Box::new(|_| Response::Success("This is the index page!".into())),
- /// )
- /// .mount(
- /// "/test",
- /// Box::new(|_| Response::Success("This is a test page!".into())),
- /// );
- /// ```
- ///
- /// # Panics
- ///
- /// if the route cannot be mounted.
- pub fn mount(&mut self, route: &str, handler: RouteResponse) -> &mut Self {
- self
- .routes
- .insert(route, Arc::new(Mutex::new(handler)))
- .unwrap();
-
- self
- }
-
- /// Create an error handler which will be displayed on any error.
- ///
- /// # Examples
- ///
- /// ```rust
- /// windmark::Router::new().set_error_handler(Box::new(|_| {
- /// windmark::Response::Success("You have encountered an error!".into())
- /// }));
- /// ```
- pub fn set_error_handler(&mut self, handler: ErrorResponse) -> &mut Self {
- self.error_handler = Arc::new(Mutex::new(handler));
-
- self
- }
-
- /// Add a header for the `Router` which should be displayed on every route.
- ///
- /// # Panics
- ///
- /// May panic if the header cannot be added.
- ///
- /// # Examples
- ///
- /// ```rust
- /// windmark::Router::new().add_header(Box::new(|context| {
- /// format!("This is displayed at the top of {}!", context.url.path())
- /// }));
- /// ```
- pub fn add_header(&mut self, handler: Partial) -> &mut Self {
- (*self.headers.lock().unwrap()).push(handler);
-
- self
- }
-
- /// Add a footer for the `Router` which should be displayed on every route.
- ///
- /// # Panics
- ///
- /// May panic if the header cannot be added.
- ///
- /// # Examples
- ///
- /// ```rust
- /// windmark::Router::new().add_footer(Box::new(|context| {
- /// format!("This is displayed at the bottom of {}!", context.url.path())
- /// }));
- /// ```
- pub fn add_footer(&mut self, handler: Partial) -> &mut Self {
- (*self.footers.lock().unwrap()).push(handler);
-
- self
- }
-
- /// Run the `Router` and wait for requests
- ///
- /// # Examples
- ///
- /// ```rust
- /// windmark::Router::new().run();
- /// ```
- ///
- /// # Panics
- ///
- /// if the client could not be accepted.
- ///
- /// # Errors
- ///
- /// if the `TcpListener` could not be bound.
- pub async fn run(&mut self) -> Result<(), Box<dyn Error>> {
- self.create_acceptor()?;
-
- #[cfg(feature = "logger")]
- if self.default_logger {
- pretty_env_logger::init();
- }
-
- let acceptor = self.ssl_acceptor.clone();
- let mut listener =
- tokio::net::TcpListener::bind(format!("0.0.0.0:{}", self.port)).await?;
-
- #[cfg(feature = "logger")]
- info!("windmark is listening for connections");
-
- while let Some(stream) = listener.incoming().next().await {
- match stream {
- Ok(stream) => {
- let acceptor = acceptor.clone();
- let mut self_clone = self.clone();
-
- tokio::spawn(async move {
- match tokio_openssl::accept(&acceptor, stream).await {
- Ok(mut stream) => {
- if let Err(e) = self_clone.handle(&mut stream).await {
- error!("handle error: {}", e);
- }
- }
- Err(e) => error!("ssl error: {:?}", e),
- }
- });
- }
- Err(e) => error!("tcp error: {:?}", e),
- }
- }
-
- Ok(())
- }
-
- #[allow(clippy::too_many_lines)]
- async fn handle(
- &mut self,
- stream: &mut tokio_openssl::SslStream<tokio::net::TcpStream>,
- ) -> Result<(), Box<dyn std::error::Error>> {
- let mut buffer = [0u8; 1024];
- let mut url = Url::parse("gemini://fuwn.me/")?;
- let mut response_status = 0;
- #[cfg(not(feature = "auto-deduce-mime"))]
- let mut response_mime_type = "".to_string();
- let mut footer = String::new();
- let mut header = String::new();
-
- while let Ok(size) = stream.read(&mut buffer).await {
- let request = or_error!(
- stream,
- String::from_utf8(buffer[0..size].to_vec()),
- "59 The server (Windmark) received a bad request: {}"
- );
- url = or_error!(
- stream,
- url::Url::parse(&request.replace("\r\n", "")),
- "59 The server (Windmark) received a bad request: {}"
- );
-
- if request.contains("\r\n") {
- break;
- }
- }
-
- let fixed_path = if self.fix_path {
- self
- .routes
- .fix_path(if url.path().is_empty() {
- "/"
- } else {
- url.path()
- })
- .unwrap_or_else(|| url.path().to_string())
- } else {
- url.path().to_string()
- };
- let route = &mut self.routes.at(&fixed_path);
-
- for module in &mut *self.modules.lock().unwrap() {
- module.on_pre_route(CallbackContext::new(stream.get_ref(), &url, {
- if let Ok(route) = &route {
- Some(&route.params)
- } else {
- None
- }
- }));
- }
-
- (*self.pre_route_callback).lock().unwrap().call_mut((
- stream.get_ref(),
- &url,
- {
- if let Ok(route) = &route {
- Some(&route.params)
- } else {
- None
- }
- },
- ));
-
- let content = if let Ok(ref route) = route {
- let footers_length = (*self.footers.lock().unwrap()).len();
-
- for partial_header in &mut *self.headers.lock().unwrap() {
- header.push_str(&format!(
- "{}\n",
- partial_header(RouteContext::new(
- stream.get_ref(),
- &url,
- &route.params,
- )),
- ));
- }
- for (i, partial_footer) in
- (&mut *self.footers.lock().unwrap()).iter_mut().enumerate()
- {
- footer.push_str(&format!(
- "{}{}",
- partial_footer(RouteContext::new(
- stream.get_ref(),
- &url,
- &route.params,
- )),
- if footers_length > 1 && i != footers_length - 1 {
- "\n"
- } else {
- ""
- },
- ));
- }
- to_value_set_status(
- (*route.value).lock().unwrap().call_mut((RouteContext::new(
- stream.get_ref(),
- &url,
- &route.params,
- ),)),
- &mut response_status,
- #[cfg(not(feature = "auto-deduce-mime"))]
- &mut response_mime_type,
- )
- } else {
- to_value_set_status(
- (*self.error_handler)
- .lock()
- .unwrap()
- .call_mut((ErrorContext::new(stream.get_ref(), &url),)),
- &mut response_status,
- #[cfg(not(feature = "auto-deduce-mime"))]
- &mut response_mime_type,
- )
- };
-
- stream
- .write_all(
- format!(
- "{}{}\r\n{}",
- if response_status == 21 {
- 20
- } else {
- response_status
- },
- match response_status {
- 20 =>
- format!(
- " text/gemini; charset={}; lang={}",
- self.charset, self.language
- ),
- #[cfg(feature = "auto-deduce-mime")]
- 21 => format!(" {}", tree_magic::from_u8(&*content.as_bytes())),
- #[cfg(not(feature = "auto-deduce-mime"))]
- 21 => response_mime_type,
- _ => format!(" {}", content),
- },
- match response_status {
- 20 => format!("{}{}\n{}", header, content, footer),
- 21 => content.to_string(),
- _ => "".to_string(),
- }
- )
- .as_bytes(),
- )
- .await?;
-
- for module in &mut *self.modules.lock().unwrap() {
- module.on_post_route(CallbackContext::new(stream.get_ref(), &url, {
- if let Ok(route) = &route {
- Some(&route.params)
- } else {
- None
- }
- }));
- }
-
- (*self.post_route_callback).lock().unwrap().call_mut((
- stream.get_ref(),
- &url,
- {
- if let Ok(route) = &route {
- Some(&route.params)
- } else {
- None
- }
- },
- ));
-
- stream.shutdown().await?;
-
- Ok(())
- }
-
- fn create_acceptor(&mut self) -> Result<(), Box<dyn Error>> {
- let mut builder = SslAcceptor::mozilla_intermediate(ssl::SslMethod::tls())?;
-
- builder.set_private_key_file(
- &self.private_key_file_name,
- ssl::SslFiletype::PEM,
- )?;
- builder.set_certificate_file(
- &self.ca_file_name,
- openssl::ssl::SslFiletype::PEM,
- )?;
- builder.check_private_key()?;
-
- self.ssl_acceptor = Arc::new(builder.build());
-
- Ok(())
- }
-
- /// Use a self-made `SslAcceptor`
- ///
- /// # Examples
- ///
- /// ```rust
- /// use openssl::ssl;
- ///
- /// windmark::Router::new().set_ssl_acceptor({
- /// let mut builder =
- /// ssl::SslAcceptor::mozilla_intermediate(ssl::SslMethod::tls()).unwrap();
- ///
- /// builder
- /// .set_private_key_file("windmark_private.pem", ssl::SslFiletype::PEM)
- /// .unwrap();
- /// builder
- /// .set_certificate_file(
- /// "windmark_public.pem",
- /// openssl::ssl::SslFiletype::PEM,
- /// )
- /// .unwrap();
- /// builder.check_private_key().unwrap();
- ///
- /// builder.build()
- /// });
- /// ```
- pub fn set_ssl_acceptor(&mut self, ssl_acceptor: SslAcceptor) -> &mut Self {
- self.ssl_acceptor = Arc::new(ssl_acceptor);
-
- self
- }
-
- /// Enabled the default logger (the
- /// [`pretty_env_logger`](https://crates.io/crates/pretty_env_logger) and
- /// [`log`](https://crates.io/crates/log) crates).
- #[cfg(feature = "logger")]
- pub fn enable_default_logger(&mut self, enable: bool) -> &mut Self {
- self.default_logger = enable;
- std::env::set_var("RUST_LOG", "windmark=trace");
-
- self
- }
-
- /// Set the default logger's log level.
- ///
- /// If you enable Windmark's default logger with `enable_default_logger`,
- /// Windmark will only log, logs from itself. By setting a log level with
- /// this method, you are overriding the default log level, so you must choose
- /// to enable logs from Windmark with the `log_windmark` parameter.
- ///
- /// Log level "language" is detailed
- /// [here](https://docs.rs/env_logger/0.9.0/env_logger/#enabling-logging).
- ///
- /// # Examples
- ///
- /// ```rust
- /// windmark::Router::new()
- /// .enable_default_logger(true)
- /// .set_log_level("your_crate_name=trace", true);
- /// // If you would only like to log, logs from your crate:
- /// // .set_log_level("your_crate_name=trace", false);
- /// ```
- #[cfg(feature = "logger")]
- pub fn set_log_level(
- &mut self,
- log_level: &str,
- log_windmark: bool,
- ) -> &mut Self {
- std::env::set_var(
- "RUST_LOG",
- format!(
- "{}{}",
- if log_windmark { "windmark," } else { "" },
- log_level
- ),
- );
-
- self
- }
-
- /// Set a callback to run before a client response is delivered
- ///
- /// # Examples
- ///
- /// ```rust
- /// use log::info;
- ///
- /// windmark::Router::new().set_pre_route_callback(Box::new(
- /// |stream, _url, _| {
- /// info!(
- /// "accepted connection from {}",
- /// stream.peer_addr().unwrap().ip(),
- /// )
- /// },
- /// ));
- /// ```
- pub fn set_pre_route_callback(&mut self, callback: Callback) -> &mut Self {
- self.pre_route_callback = Arc::new(Mutex::new(callback));
-
- self
- }
-
- /// Set a callback to run after a client response is delivered
- ///
- /// # Examples
- ///
- /// ```rust
- /// use log::info;
- ///
- /// windmark::Router::new().set_post_route_callback(Box::new(
- /// |stream, _url, _| {
- /// info!(
- /// "closed connection from {}",
- /// stream.peer_addr().unwrap().ip(),
- /// )
- /// },
- /// ));
- /// ```
- pub fn set_post_route_callback(&mut self, callback: Callback) -> &mut Self {
- self.post_route_callback = Arc::new(Mutex::new(callback));
-
- self
- }
-
- /// Attach a stateless module to a `Router`.
- ///
- /// A module is an extension or middleware to a `Router`. Modules get full
- /// access to the `Router`, but can be extended by a third party.
- ///
- /// # Examples
- ///
- /// ## Integrated Module
- ///
- /// ```rust
- /// use windmark::Response;
- ///
- /// windmark::Router::new().attach_stateless(|r| {
- /// r.mount(
- /// "/module",
- /// Box::new(|_| Response::Success("This is a module!".into())),
- /// );
- /// r.set_error_handler(Box::new(|_| {
- /// Response::NotFound(
- /// "This error handler has been implemented by a module!".into(),
- /// )
- /// }));
- /// });
- /// ```
- ///
- /// ## External Module
- ///
- /// ```rust
- /// use windmark::Response;
- ///
- /// mod windmark_example {
- /// pub fn module(router: &mut windmark::Router) {
- /// router.mount(
- /// "/module",
- /// Box::new(|_| windmark::Response::Success("This is a module!".into())),
- /// );
- /// }
- /// }
- ///
- /// windmark::Router::new().attach_stateless(windmark_example::module);
- /// ```
- pub fn attach_stateless<F>(&mut self, mut module: F) -> &mut Self
- where F: FnMut(&mut Self) {
- module(self);
-
- self
- }
-
- /// Attach a stateful module to a `Router`.
- ///
- /// Like a stateless module is an extension or middleware to a `Router`.
- /// Modules get full access to the `Router` and can be extended by a third
- /// party, but also, can create hooks will be executed through various parts
- /// of a routes' lifecycle. Stateful modules also have state, so variables can
- /// be stored for further access.
- ///
- /// # Panics
- ///
- /// May panic if the stateful module cannot be attached.
- ///
- /// # Examples
- ///
- /// ```rust
- /// use log::info;
- /// use windmark::{returnable::CallbackContext, Response, Router};
- ///
- /// #[derive(Default)]
- /// struct Clicker {
- /// clicks: isize,
- /// }
- /// impl windmark::Module for Clicker {
- /// fn on_attach(&mut self, _: &mut Router) {
- /// info!("clicker has been attached!");
- /// }
- ///
- /// fn on_pre_route(&mut self, context: CallbackContext<'_>) {
- /// self.clicks += 1;
- ///
- /// info!(
- /// "clicker has been called pre-route on {} with {} clicks!",
- /// context.url.path(),
- /// self.clicks
- /// );
- /// }
- ///
- /// fn on_post_route(&mut self, context: CallbackContext<'_>) {
- /// info!(
- /// "clicker has been called post-route on {} with {} clicks!",
- /// context.url.path(),
- /// self.clicks
- /// );
- /// }
- /// }
- ///
- /// Router::new().attach(Clicker::default());
- /// ```
- pub fn attach(
- &mut self,
- mut module: impl Module + 'static + Send,
- ) -> &mut Self {
- module.on_attach(self);
-
- (*self.modules.lock().unwrap()).push(Box::new(module));
-
- self
- }
-
- /// Specify a custom character set.
- ///
- /// Defaults to `"utf-8"`.
- ///
- /// # Examples
- ///
- /// ```rust
- /// windmark::Router::new().set_charset("utf-8");
- /// ```
- pub fn set_charset(&mut self, charset: &str) -> &mut Self {
- self.charset = charset.to_string();
-
- self
- }
-
- /// Specify a custom language.
- ///
- /// Defaults to `"en"`.
- ///
- /// # Examples
- ///
- /// ```rust
- /// windmark::Router::new().set_language("en");
- /// ```
- pub fn set_language(&mut self, language: &str) -> &mut Self {
- self.language = language.to_string();
-
- self
- }
-
- /// Specify a custom port.
- ///
- /// Defaults to `1965`.
- ///
- /// # Examples
- ///
- /// ```rust
- /// windmark::Router::new().set_port(1965);
- /// ```
- pub fn set_port(&mut self, port: i32) -> &mut Self {
- self.port = port;
-
- self
- }
-
- /// Performs a case-insensitive lookup of routes, using the case corrected
- /// path if successful. Missing/ extra trailing slashes are also corrected.
- ///
- /// # Examples
- ///
- /// ```rust
- /// windmark::Router::new().set_fix_path(true);
- /// ```
- pub fn set_fix_path(&mut self, fix_path: bool) -> &mut Self {
- self.fix_path = fix_path;
-
- self
- }
-}
-impl Default for Router {
- fn default() -> Self {
- Self {
- routes: matchit::Router::new(),
- error_handler: Arc::new(Mutex::new(Box::new(|_| {
- Response::NotFound(
- "This capsule has not implemented an error handler...".to_string(),
- )
- }))),
- private_key_file_name: "".to_string(),
- ca_file_name: "".to_string(),
- headers: Arc::new(Mutex::new(vec![])),
- footers: Arc::new(Mutex::new(vec![])),
- ssl_acceptor: Arc::new(
- SslAcceptor::mozilla_intermediate(SslMethod::tls())
- .unwrap()
- .build(),
- ),
- #[cfg(feature = "logger")]
- default_logger: false,
- pre_route_callback: Arc::new(Mutex::new(Box::new(|_, _, _| {}))),
- post_route_callback: Arc::new(Mutex::new(Box::new(|_, _, _| {}))),
- charset: "utf-8".to_string(),
- language: "en".to_string(),
- port: 1965,
- modules: Arc::new(Mutex::new(vec![])),
- fix_path: false,
- }
- }
-}