🏗️ Elegant & Highly Performant Async Gemini Server Framework for the Modern Age
async
framework
gemini-protocol
protocol
gemini
rust
1#![allow(clippy::significant_drop_tightening)]
2
3use std::{
4 collections::HashSet,
5 error::Error,
6 fmt::Write,
7 future::IntoFuture,
8 sync::{Arc, Mutex},
9 time,
10};
11
12#[cfg(feature = "async-std")]
13use async_std::{
14 io::{ReadExt, WriteExt},
15 sync::Mutex as AsyncMutex,
16};
17use openssl::ssl::{self, SslAcceptor, SslMethod};
18#[cfg(feature = "tokio")]
19use tokio::{
20 io::{AsyncReadExt, AsyncWriteExt},
21 sync::Mutex as AsyncMutex,
22};
23use url::Url;
24
25use crate::{
26 context::{ErrorContext, HookContext, RouteContext},
27 handler::{
28 ErrorResponse,
29 Partial,
30 PostRouteHook,
31 PreRouteHook,
32 RouteResponse,
33 },
34 module::{AsyncModule, Module},
35 response::Response,
36 router_option::RouterOption,
37};
38
39macro_rules! block {
40 ($body:expr) => {
41 #[cfg(feature = "tokio")]
42 ::tokio::task::block_in_place(|| {
43 ::tokio::runtime::Handle::current().block_on(async { $body });
44 });
45 #[cfg(feature = "async-std")]
46 ::async_std::task::block_on(async { $body });
47 };
48}
49
50macro_rules! or_error {
51 ($stream:ident, $operation:expr, $error_format:literal) => {
52 match $operation {
53 Ok(u) => u,
54 Err(e) => {
55 $stream
56 .write_all(format!($error_format, e).as_bytes())
57 .await?;
58
59 // $stream.shutdown().await?;
60
61 return Ok(());
62 }
63 }
64 };
65}
66
67#[cfg(feature = "tokio")]
68type Stream = tokio_openssl::SslStream<tokio::net::TcpStream>;
69#[cfg(feature = "async-std")]
70type Stream = async_std_openssl::SslStream<async_std::net::TcpStream>;
71
72/// A router which takes care of all tasks a Windmark server should handle:
73/// response generation, panics, logging, and more.
74#[derive(Clone)]
75pub struct Router {
76 routes: matchit::Router<Arc<AsyncMutex<Box<dyn RouteResponse>>>>,
77 error_handler: Arc<AsyncMutex<Box<dyn ErrorResponse>>>,
78 private_key_file_name: String,
79 private_key_content: Option<String>,
80 certificate_file_name: String,
81 certificate_content: Option<String>,
82 headers: Arc<Mutex<Vec<Box<dyn Partial>>>>,
83 footers: Arc<Mutex<Vec<Box<dyn Partial>>>>,
84 ssl_acceptor: Arc<SslAcceptor>,
85 #[cfg(feature = "logger")]
86 default_logger: bool,
87 #[cfg(feature = "logger")]
88 log_filter: String,
89 pre_route_callback: Arc<Mutex<Box<dyn PreRouteHook>>>,
90 post_route_callback: Arc<Mutex<Box<dyn PostRouteHook>>>,
91 character_set: String,
92 languages: Vec<String>,
93 port: i32,
94 async_modules: Arc<AsyncMutex<Vec<Box<dyn AsyncModule + Send>>>>,
95 modules: Arc<Mutex<Vec<Box<dyn Module + Send>>>>,
96 options: HashSet<RouterOption>,
97 listener_address: String,
98}
99
100struct RequestHandler {
101 routes: matchit::Router<Arc<AsyncMutex<Box<dyn RouteResponse>>>>,
102 error_handler: Arc<AsyncMutex<Box<dyn ErrorResponse>>>,
103 headers: Arc<Mutex<Vec<Box<dyn Partial>>>>,
104 footers: Arc<Mutex<Vec<Box<dyn Partial>>>>,
105 pre_route_callback: Arc<Mutex<Box<dyn PreRouteHook>>>,
106 post_route_callback: Arc<Mutex<Box<dyn PostRouteHook>>>,
107 character_set: String,
108 languages: Vec<String>,
109 async_modules: Arc<AsyncMutex<Vec<Box<dyn AsyncModule + Send>>>>,
110 modules: Arc<Mutex<Vec<Box<dyn Module + Send>>>>,
111 options: HashSet<RouterOption>,
112}
113
114impl RequestHandler {
115 #[allow(
116 clippy::too_many_lines,
117 clippy::significant_drop_in_scrutinee,
118 clippy::cognitive_complexity
119 )]
120 async fn handle(&self, stream: &mut Stream) -> Result<(), Box<dyn Error>> {
121 let mut buffer = [0u8; 1024];
122 let mut url = Url::parse("gemini://fuwn.me/")?;
123 let mut footer = String::new();
124 let mut header = String::new();
125
126 while let Ok(size) = stream.read(&mut buffer).await {
127 let request = or_error!(
128 stream,
129 std::str::from_utf8(&buffer[0..size]).map(ToString::to_string),
130 "59 The server (Windmark) received a bad request: {}"
131 );
132 let request_trimmed = request
133 .find("\r\n")
134 .map_or(&request[..], |pos| &request[..pos]);
135
136 url = or_error!(
137 stream,
138 Url::parse(request_trimmed),
139 "59 The server (Windmark) received a bad request: {}"
140 );
141
142 if request.contains("\r\n") {
143 break;
144 }
145 }
146
147 if url.path().is_empty() {
148 url.set_path("/");
149 }
150
151 let mut path = url.path().to_string();
152
153 if self
154 .options
155 .contains(&RouterOption::AllowCaseInsensitiveLookup)
156 {
157 path = path.to_lowercase();
158 }
159
160 let mut route = self.routes.at(&path);
161
162 if route.is_err() {
163 if self
164 .options
165 .contains(&RouterOption::RemoveExtraTrailingSlash)
166 && path.ends_with('/')
167 && path != "/"
168 {
169 let trimmed = path.trim_end_matches('/');
170
171 if trimmed != path {
172 path = trimmed.to_string();
173 route = self.routes.at(&path);
174 }
175 } else if self
176 .options
177 .contains(&RouterOption::AddMissingTrailingSlash)
178 && !path.ends_with('/')
179 {
180 let mut path_with_slash = String::with_capacity(path.len() + 1);
181
182 path_with_slash.push_str(&path);
183 path_with_slash.push('/');
184
185 if self.routes.at(&path_with_slash).is_ok() {
186 path = path_with_slash;
187 route = self.routes.at(&path);
188 }
189 }
190 }
191
192 let peer_certificate = stream.ssl().peer_certificate();
193 let url_clone = url.clone();
194 let hook_context = HookContext::new(
195 stream.get_ref().peer_addr(),
196 url_clone.clone(),
197 route.as_ref().ok().map(|route| route.params.clone()),
198 peer_certificate.clone(),
199 );
200 let hook_context_clone = hook_context.clone();
201
202 for module in &mut *self.async_modules.lock().await {
203 module.on_pre_route(hook_context_clone.clone()).await;
204 }
205
206 let hook_context_clone = hook_context.clone();
207
208 if let Ok(mut modules) = self.modules.lock() {
209 for module in &mut *modules {
210 module.on_pre_route(hook_context_clone.clone());
211 }
212 }
213
214 if let Ok(mut callback) = self.pre_route_callback.lock() {
215 callback.call(hook_context.clone());
216 }
217
218 let mut content = if let Ok(ref route) = route {
219 let route_context = RouteContext::new(
220 stream.get_ref().peer_addr(),
221 url_clone,
222 &route.params,
223 peer_certificate,
224 );
225
226 {
227 let mut headers = self.headers.lock().expect("headers lock poisoned");
228
229 for partial_header in &mut *headers {
230 writeln!(
231 &mut header,
232 "{}",
233 partial_header.call(route_context.clone()),
234 )
235 .expect("failed to write header");
236 }
237 }
238
239 {
240 let mut footers = self.footers.lock().expect("footers lock poisoned");
241 let length = footers.len();
242
243 for (i, partial_footer) in footers.iter_mut().enumerate() {
244 let _ = write!(
245 &mut footer,
246 "{}{}",
247 partial_footer.call(route_context.clone()),
248 if length > 1 && i != length - 1 {
249 "\n"
250 } else {
251 ""
252 },
253 );
254 }
255 }
256
257 let mut lock = (*route.value).lock().await;
258 let handler = lock.call(route_context);
259
260 handler.await
261 } else {
262 (*self.error_handler)
263 .lock()
264 .await
265 .call(ErrorContext::new(
266 stream.get_ref().peer_addr(),
267 url_clone,
268 peer_certificate,
269 ))
270 .await
271 };
272
273 let hook_context_clone = hook_context.clone();
274
275 for module in &mut *self.async_modules.lock().await {
276 module.on_post_route(hook_context_clone.clone()).await;
277 }
278
279 let hook_context_clone = hook_context.clone();
280
281 if let Ok(mut modules) = self.modules.lock() {
282 for module in &mut *modules {
283 module.on_post_route(hook_context_clone.clone());
284 }
285 }
286
287 if let Ok(mut callback) = self.post_route_callback.lock() {
288 callback.call(hook_context, &mut content);
289 }
290
291 let status_code =
292 if content.status == 21 || content.status == 22 || content.status == 23 {
293 20
294 } else {
295 content.status
296 };
297 let status_line = match content.status {
298 20 => {
299 let mime = content.mime.as_deref().unwrap_or("text/gemini");
300 let charset = content
301 .character_set
302 .as_deref()
303 .unwrap_or(&self.character_set);
304 let lang = content
305 .languages
306 .as_ref()
307 .map_or_else(|| self.languages.join(","), |l| l.join(","));
308
309 format!("{status_code} {mime}; charset={charset}; lang={lang}")
310 }
311 21 => {
312 format!(
313 "{} {}",
314 status_code,
315 content.mime.as_deref().unwrap_or_default()
316 )
317 }
318 #[cfg(feature = "auto-deduce-mime")]
319 22 => {
320 format!(
321 "{} {}",
322 status_code,
323 content.mime.as_deref().unwrap_or_default()
324 )
325 }
326 _ => {
327 format!("{} {}", status_code, content.content)
328 }
329 };
330 let body = match content.status {
331 20 => {
332 let mut body = String::with_capacity(
333 header.len() + content.content.len() + footer.len() + 1,
334 );
335
336 body.push_str(&header);
337 body.push_str(&content.content);
338 body.push('\n');
339 body.push_str(&footer);
340
341 body
342 }
343 21 | 22 => content.content,
344 _ => String::new(),
345 };
346 let mut response =
347 String::with_capacity(status_line.len() + body.len() + 2);
348
349 response.push_str(&status_line);
350 response.push_str("\r\n");
351 response.push_str(&body);
352 stream.write_all(response.as_bytes()).await?;
353 #[cfg(feature = "tokio")]
354 stream.shutdown().await?;
355 #[cfg(feature = "async-std")]
356 stream.get_mut().shutdown(std::net::Shutdown::Both)?;
357
358 Ok(())
359 }
360}
361
362impl Router {
363 /// Create a new `Router`
364 ///
365 /// # Examples
366 ///
367 /// ```rust
368 /// windmark::router::Router::new();
369 /// ```
370 ///
371 /// # Panics
372 ///
373 /// if a default `SslAcceptor` could not be built.
374 #[must_use]
375 pub fn new() -> Self { Self::default() }
376
377 /// Set the filename of the private key file.
378 ///
379 /// # Examples
380 ///
381 /// ```rust
382 /// windmark::router::Router::new().set_private_key_file("windmark_private.pem");
383 /// ```
384 pub fn set_private_key_file(
385 &mut self,
386 private_key_file_name: impl Into<String> + AsRef<str>,
387 ) -> &mut Self {
388 self.private_key_file_name = private_key_file_name.into();
389
390 self
391 }
392
393 /// Set the content of the private key
394 ///
395 /// # Examples
396 ///
397 /// ```rust
398 /// windmark::router::Router::new().set_private_key("...");
399 /// ```
400 pub fn set_private_key(
401 &mut self,
402 private_key_content: impl Into<String> + AsRef<str>,
403 ) -> &mut Self {
404 self.private_key_content = Some(private_key_content.into());
405
406 self
407 }
408
409 /// Set the filename of the certificate chain file.
410 ///
411 /// # Examples
412 ///
413 /// ```rust
414 /// windmark::router::Router::new().set_certificate_file("windmark_public.pem");
415 /// ```
416 pub fn set_certificate_file(
417 &mut self,
418 certificate_name: impl Into<String> + AsRef<str>,
419 ) -> &mut Self {
420 self.certificate_file_name = certificate_name.into();
421
422 self
423 }
424
425 /// Set the content of the certificate chain file.
426 ///
427 /// # Examples
428 ///
429 /// ```rust
430 /// windmark::router::Router::new().set_certificate("...");
431 /// ```
432 pub fn set_certificate(
433 &mut self,
434 certificate_content: impl Into<String> + AsRef<str>,
435 ) -> &mut Self {
436 self.certificate_content = Some(certificate_content.into());
437
438 self
439 }
440
441 /// Map routes to URL paths
442 ///
443 /// Supports both synchronous and asynchronous handlers
444 ///
445 /// # Examples
446 ///
447 /// ```rust
448 /// use windmark::response::Response;
449 ///
450 /// windmark::router::Router::new()
451 /// .mount("/", |_| {
452 /// async { Response::success("This is the index page!") }
453 /// })
454 /// .mount("/about", |_| async { Response::success("About that...") });
455 /// ```
456 ///
457 /// # Panics
458 ///
459 /// May panic if the route cannot be mounted.
460 pub fn mount<R>(
461 &mut self,
462 route: impl Into<String> + AsRef<str>,
463 mut handler: impl FnMut(RouteContext) -> R + Send + Sync + 'static,
464 ) -> &mut Self
465 where
466 R: IntoFuture<Output = Response> + Send + 'static,
467 <R as IntoFuture>::IntoFuture: Send,
468 {
469 self
470 .routes
471 .insert(
472 route.into(),
473 Arc::new(AsyncMutex::new(Box::new(move |context: RouteContext| {
474 handler(context).into_future()
475 }))),
476 )
477 .expect("failed to mount route");
478
479 self
480 }
481
482 /// Create an error handler which will be displayed on any error.
483 ///
484 /// # Examples
485 ///
486 /// ```rust
487 /// windmark::router::Router::new().set_error_handler(|_| {
488 /// windmark::response::Response::success("You have encountered an error!")
489 /// });
490 /// ```
491 pub fn set_error_handler<R>(
492 &mut self,
493 mut handler: impl FnMut(ErrorContext) -> R + Send + Sync + 'static,
494 ) -> &mut Self
495 where
496 R: IntoFuture<Output = Response> + Send + 'static,
497 <R as IntoFuture>::IntoFuture: Send,
498 {
499 self.error_handler = Arc::new(AsyncMutex::new(Box::new(move |context| {
500 handler(context).into_future()
501 })));
502
503 self
504 }
505
506 /// Add a header for the `Router` which should be displayed on every route.
507 ///
508 /// # Panics
509 ///
510 /// May panic if the header cannot be added.
511 ///
512 /// # Examples
513 ///
514 /// ```rust
515 /// windmark::router::Router::new().add_header(
516 /// |context: windmark::context::RouteContext| {
517 /// format!("This is displayed at the top of {}!", context.url.path())
518 /// },
519 /// );
520 /// ```
521 pub fn add_header(&mut self, handler: impl Partial + 'static) -> &mut Self {
522 (*self.headers.lock().expect("headers lock poisoned"))
523 .push(Box::new(handler));
524
525 self
526 }
527
528 /// Add a footer for the `Router` which should be displayed on every route.
529 ///
530 /// # Panics
531 ///
532 /// May panic if the header cannot be added.
533 ///
534 /// # Examples
535 ///
536 /// ```rust
537 /// windmark::router::Router::new().add_footer(
538 /// |context: windmark::context::RouteContext| {
539 /// format!("This is displayed at the bottom of {}!", context.url.path())
540 /// },
541 /// );
542 /// ```
543 pub fn add_footer(&mut self, handler: impl Partial + 'static) -> &mut Self {
544 (*self.footers.lock().expect("footers lock poisoned"))
545 .push(Box::new(handler));
546
547 self
548 }
549
550 /// Run the `Router` and wait for requests
551 ///
552 /// # Examples
553 ///
554 /// ```rust
555 /// windmark::router::Router::new().run();
556 /// ```
557 ///
558 /// # Panics
559 ///
560 /// if the client could not be accepted.
561 ///
562 /// # Errors
563 ///
564 /// if the `TcpListener` could not be bound.
565 pub async fn run(&mut self) -> Result<(), Box<dyn Error>> {
566 self.create_acceptor()?;
567
568 #[cfg(feature = "logger")]
569 if self.default_logger {
570 pretty_env_logger::formatted_builder()
571 .parse_filters(&self.log_filter)
572 .init();
573 }
574
575 #[cfg(feature = "tokio")]
576 let listener = tokio::net::TcpListener::bind(format!(
577 "{}:{}",
578 self.listener_address, self.port
579 ))
580 .await?;
581 #[cfg(feature = "async-std")]
582 let listener = async_std::net::TcpListener::bind(format!(
583 "{}:{}",
584 self.listener_address, self.port
585 ))
586 .await?;
587
588 #[cfg(feature = "logger")]
589 info!("windmark is listening for connections");
590
591 let handler = Arc::new(RequestHandler {
592 routes: self.routes.clone(),
593 error_handler: self.error_handler.clone(),
594 headers: self.headers.clone(),
595 footers: self.footers.clone(),
596 pre_route_callback: self.pre_route_callback.clone(),
597 post_route_callback: self.post_route_callback.clone(),
598 character_set: self.character_set.clone(),
599 languages: self.languages.clone(),
600 async_modules: self.async_modules.clone(),
601 modules: self.modules.clone(),
602 options: self.options.clone(),
603 });
604
605 loop {
606 match listener.accept().await {
607 Ok((stream, _)) => {
608 let handler = Arc::clone(&handler);
609 let acceptor = self.ssl_acceptor.clone();
610 #[cfg(feature = "tokio")]
611 let spawner = tokio::spawn;
612 #[cfg(feature = "async-std")]
613 let spawner = async_std::task::spawn;
614
615 spawner(async move {
616 let ssl = match ssl::Ssl::new(acceptor.context()) {
617 Ok(ssl) => ssl,
618 Err(e) => {
619 error!("ssl context error: {e:?}");
620
621 return;
622 }
623 };
624
625 #[cfg(feature = "tokio")]
626 let quick_stream = tokio_openssl::SslStream::new(ssl, stream);
627 #[cfg(feature = "async-std")]
628 let quick_stream = async_std_openssl::SslStream::new(ssl, stream);
629
630 match quick_stream {
631 Ok(mut stream) => {
632 if let Err(e) = std::pin::Pin::new(&mut stream).accept().await {
633 warn!("stream accept error: {e:?}");
634 }
635
636 if let Err(e) = handler.handle(&mut stream).await {
637 error!("handle error: {e}");
638 }
639 }
640 Err(e) => error!("ssl stream error: {e:?}"),
641 }
642 });
643 }
644 Err(e) => error!("tcp stream error: {e:?}"),
645 }
646 }
647 }
648
649 fn create_acceptor(&mut self) -> Result<(), Box<dyn Error>> {
650 let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls())?;
651
652 if let Some(ref cert_content) = self.certificate_content {
653 builder.set_certificate(
654 openssl::x509::X509::from_pem(cert_content.as_bytes())?.as_ref(),
655 )?;
656 } else {
657 builder.set_certificate_file(
658 &self.certificate_file_name,
659 ssl::SslFiletype::PEM,
660 )?;
661 }
662
663 if let Some(ref key_content) = self.private_key_content {
664 builder.set_private_key(
665 openssl::pkey::PKey::private_key_from_pem(key_content.as_bytes())?
666 .as_ref(),
667 )?;
668 } else {
669 builder.set_private_key_file(
670 &self.private_key_file_name,
671 ssl::SslFiletype::PEM,
672 )?;
673 }
674
675 builder.check_private_key()?;
676 builder.set_verify_callback(ssl::SslVerifyMode::PEER, |_, _| true);
677 builder.set_session_id_context(
678 time::SystemTime::now()
679 .duration_since(time::UNIX_EPOCH)?
680 .as_secs()
681 .to_string()
682 .as_bytes(),
683 )?;
684
685 self.ssl_acceptor = Arc::new(builder.build());
686
687 Ok(())
688 }
689
690 /// Use a self-made `SslAcceptor`
691 ///
692 /// # Examples
693 ///
694 /// ```rust
695 /// use openssl::ssl;
696 ///
697 /// windmark::router::Router::new().set_ssl_acceptor({
698 /// let mut builder =
699 /// ssl::SslAcceptor::mozilla_intermediate(ssl::SslMethod::tls()).unwrap();
700 ///
701 /// builder
702 /// .set_private_key_file("windmark_private.pem", ssl::SslFiletype::PEM)
703 /// .unwrap();
704 /// builder
705 /// .set_certificate_file("windmark_public.pem", ssl::SslFiletype::PEM)
706 /// .unwrap();
707 /// builder.check_private_key().unwrap();
708 ///
709 /// builder.build()
710 /// });
711 /// ```
712 pub fn set_ssl_acceptor(&mut self, ssl_acceptor: SslAcceptor) -> &mut Self {
713 self.ssl_acceptor = Arc::new(ssl_acceptor);
714
715 self
716 }
717
718 /// Enabled the default logger (the
719 /// [`pretty_env_logger`](https://crates.io/crates/pretty_env_logger) and
720 /// [`log`](https://crates.io/crates/log) crates).
721 #[cfg(feature = "logger")]
722 pub fn enable_default_logger(&mut self, enable: bool) -> &mut Self {
723 self.default_logger = enable;
724 self.log_filter = "windmark=trace".to_string();
725
726 self
727 }
728
729 /// Set the default logger's log level.
730 ///
731 /// If you enable Windmark's default logger with `enable_default_logger`,
732 /// Windmark will only log, logs from itself. By setting a log level with
733 /// this method, you are overriding the default log level, so you must choose
734 /// to enable logs from Windmark with the `log_windmark` parameter.
735 ///
736 /// Log level "language" is detailed
737 /// [here](https://docs.rs/env_logger/0.9.0/env_logger/#enabling-logging).
738 ///
739 /// # Examples
740 ///
741 /// ```rust
742 /// windmark::router::Router::new()
743 /// .enable_default_logger(true)
744 /// .set_log_level("your_crate_name=trace", true);
745 /// // If you would only like to log, logs from your crate:
746 /// // .set_log_level("your_crate_name=trace", false);
747 /// ```
748 #[cfg(feature = "logger")]
749 pub fn set_log_level(
750 &mut self,
751 log_level: impl Into<String> + AsRef<str>,
752 log_windmark: bool,
753 ) -> &mut Self {
754 self.log_filter = format!(
755 "{}{}",
756 if log_windmark { "windmark," } else { "" },
757 log_level.into()
758 );
759
760 self
761 }
762
763 /// Set a callback to run before a client response is delivered
764 ///
765 /// # Examples
766 ///
767 /// ```rust
768 /// use log::info;
769 ///
770 /// windmark::router::Router::new().set_pre_route_callback(
771 /// |context: windmark::context::HookContext| {
772 /// info!(
773 /// "accepted connection from {}",
774 /// context.peer_address.unwrap().ip(),
775 /// )
776 /// },
777 /// );
778 /// ```
779 pub fn set_pre_route_callback(
780 &mut self,
781 callback: impl PreRouteHook + 'static,
782 ) -> &mut Self {
783 self.pre_route_callback = Arc::new(Mutex::new(Box::new(callback)));
784
785 self
786 }
787
788 /// Set a callback to run after a client response is delivered
789 ///
790 /// # Examples
791 ///
792 /// ```rust
793 /// use log::info;
794 ///
795 /// windmark::router::Router::new().set_post_route_callback(
796 /// |context: windmark::context::HookContext,
797 /// _content: &mut windmark::response::Response| {
798 /// info!(
799 /// "closed connection from {}",
800 /// context.peer_address.unwrap().ip(),
801 /// )
802 /// },
803 /// );
804 /// ```
805 pub fn set_post_route_callback(
806 &mut self,
807 callback: impl PostRouteHook + 'static,
808 ) -> &mut Self {
809 self.post_route_callback = Arc::new(Mutex::new(Box::new(callback)));
810
811 self
812 }
813
814 /// Attach a stateless module to a `Router`.
815 ///
816 /// A module is an extension or middleware to a `Router`. Modules get full
817 /// access to the `Router`, but can be extended by a third party.
818 ///
819 /// # Examples
820 ///
821 /// ## Integrated Module
822 ///
823 /// ```rust
824 /// use windmark::response::Response;
825 ///
826 /// windmark::router::Router::new().attach_stateless(|r| {
827 /// r.mount(
828 /// "/module",
829 /// Box::new(|_| Response::success("This is a module!")),
830 /// );
831 /// r.set_error_handler(Box::new(|_| {
832 /// Response::not_found(
833 /// "This error handler has been implemented by a module!",
834 /// )
835 /// }));
836 /// });
837 /// ```
838 ///
839 /// ## External Module
840 ///
841 /// ```rust
842 /// use windmark::response::Response;
843 ///
844 /// mod windmark_example {
845 /// pub fn module(router: &mut windmark::router::Router) {
846 /// router.mount(
847 /// "/module",
848 /// Box::new(|_| {
849 /// windmark::response::Response::success("This is a module!")
850 /// }),
851 /// );
852 /// }
853 /// }
854 ///
855 /// windmark::router::Router::new().attach_stateless(windmark_example::module);
856 /// ```
857 pub fn attach_stateless<F>(&mut self, mut module: F) -> &mut Self
858 where F: FnMut(&mut Self) {
859 module(self);
860
861 self
862 }
863
864 /// Attach a stateful module to a `Router`; with async support
865 ///
866 /// Like a stateless module is an extension or middleware to a `Router`.
867 /// Modules get full access to the `Router` and can be extended by a third
868 /// party, but also, can create hooks will be executed through various parts
869 /// of a routes' lifecycle. Stateful modules also have state, so variables can
870 /// be stored for further access.
871 ///
872 /// # Panics
873 ///
874 /// May panic if the stateful module cannot be attached.
875 ///
876 /// # Examples
877 ///
878 /// ```rust
879 /// use log::info;
880 /// use windmark::{context::HookContext, router::Router};
881 ///
882 /// #[derive(Default)]
883 /// struct Clicker {
884 /// clicks: isize,
885 /// }
886 ///
887 /// #[async_trait::async_trait]
888 /// impl windmark::module::AsyncModule for Clicker {
889 /// async fn on_attach(&mut self, _: &mut Router) {
890 /// info!("clicker has been attached!");
891 /// }
892 ///
893 /// async fn on_pre_route(&mut self, context: HookContext) {
894 /// self.clicks += 1;
895 ///
896 /// info!(
897 /// "clicker has been called pre-route on {} with {} clicks!",
898 /// context.url.path(),
899 /// self.clicks
900 /// );
901 /// }
902 ///
903 /// async fn on_post_route(&mut self, context: HookContext) {
904 /// info!(
905 /// "clicker has been called post-route on {} with {} clicks!",
906 /// context.url.path(),
907 /// self.clicks
908 /// );
909 /// }
910 /// }
911 ///
912 /// // Router::new().attach_async(Clicker::default());
913 /// ```
914 pub fn attach_async(
915 &mut self,
916 mut module: impl AsyncModule + 'static,
917 ) -> &mut Self {
918 block!({
919 module.on_attach(self).await;
920
921 (*self.async_modules.lock().await).push(Box::new(module));
922 });
923
924 self
925 }
926
927 /// Attach a stateful module to a `Router`.
928 ///
929 /// Like a stateless module is an extension or middleware to a `Router`.
930 /// Modules get full access to the `Router` and can be extended by a third
931 /// party, but also, can create hooks will be executed through various parts
932 /// of a routes' lifecycle. Stateful modules also have state, so variables can
933 /// be stored for further access.
934 ///
935 /// # Panics
936 ///
937 /// May panic if the stateful module cannot be attached.
938 ///
939 /// # Examples
940 ///
941 /// ```rust
942 /// use log::info;
943 /// use windmark::{context::HookContext, response::Response, router::Router};
944 ///
945 /// #[derive(Default)]
946 /// struct Clicker {
947 /// clicks: isize,
948 /// }
949 ///
950 /// impl windmark::module::Module for Clicker {
951 /// fn on_attach(&mut self, _: &mut Router) {
952 /// info!("clicker has been attached!");
953 /// }
954 ///
955 /// fn on_pre_route(&mut self, context: HookContext) {
956 /// self.clicks += 1;
957 ///
958 /// info!(
959 /// "clicker has been called pre-route on {} with {} clicks!",
960 /// context.url.path(),
961 /// self.clicks
962 /// );
963 /// }
964 ///
965 /// fn on_post_route(&mut self, context: HookContext) {
966 /// info!(
967 /// "clicker has been called post-route on {} with {} clicks!",
968 /// context.url.path(),
969 /// self.clicks
970 /// );
971 /// }
972 /// }
973 ///
974 /// Router::new().attach(Clicker::default());
975 /// ```
976 pub fn attach(
977 &mut self,
978 mut module: impl Module + 'static + Send,
979 ) -> &mut Self {
980 module.on_attach(self);
981
982 (*self.modules.lock().expect("modules lock poisoned"))
983 .push(Box::new(module));
984
985 self
986 }
987
988 /// Specify a custom character set.
989 ///
990 /// Will be over-ridden if a character set is specified in a [`Response`].
991 ///
992 /// Defaults to `"utf-8"`.
993 ///
994 /// # Examples
995 ///
996 /// ```rust
997 /// windmark::router::Router::new().set_character_set("utf-8");
998 /// ```
999 pub fn set_character_set(
1000 &mut self,
1001 character_set: impl Into<String> + AsRef<str>,
1002 ) -> &mut Self {
1003 self.character_set = character_set.into();
1004
1005 self
1006 }
1007
1008 /// Specify a custom language.
1009 ///
1010 /// Will be over-ridden if a language is specified in a [`Response`].
1011 ///
1012 /// Defaults to `"en"`.
1013 ///
1014 /// # Examples
1015 ///
1016 /// ```rust
1017 /// windmark::router::Router::new().set_languages(["en"]);
1018 /// ```
1019 pub fn set_languages<S>(&mut self, language: impl AsRef<[S]>) -> &mut Self
1020 where S: Into<String> + AsRef<str> {
1021 self.languages = language
1022 .as_ref()
1023 .iter()
1024 .map(|s| s.as_ref().to_string())
1025 .collect::<Vec<String>>();
1026
1027 self
1028 }
1029
1030 /// Specify a custom port.
1031 ///
1032 /// Defaults to `1965`.
1033 ///
1034 /// # Examples
1035 ///
1036 /// ```rust
1037 /// windmark::router::Router::new().set_port(1965);
1038 /// ```
1039 pub const fn set_port(&mut self, port: i32) -> &mut Self {
1040 self.port = port;
1041
1042 self
1043 }
1044
1045 /// Add optional features to the router
1046 ///
1047 /// # Examples
1048 ///
1049 /// ```rust
1050 /// use windmark::router_option::RouterOption;
1051 ///
1052 /// windmark::router::Router::new()
1053 /// .add_options(&[RouterOption::RemoveExtraTrailingSlash]);
1054 /// ```
1055 pub fn add_options(&mut self, options: &[RouterOption]) -> &mut Self {
1056 for option in options {
1057 self.options.insert(*option);
1058 }
1059
1060 self
1061 }
1062
1063 /// Toggle optional features for the router
1064 ///
1065 /// # Examples
1066 ///
1067 /// ```rust
1068 /// use windmark::router_option::RouterOption;
1069 ///
1070 /// windmark::router::Router::new()
1071 /// .toggle_options(&[RouterOption::RemoveExtraTrailingSlash]);
1072 /// ```
1073 pub fn toggle_options(&mut self, options: &[RouterOption]) -> &mut Self {
1074 for option in options {
1075 if self.options.contains(option) {
1076 self.options.remove(option);
1077 } else {
1078 self.options.insert(*option);
1079 }
1080 }
1081
1082 self
1083 }
1084
1085 /// Remove optional features from the router
1086 ///
1087 /// # Examples
1088 ///
1089 /// ```rust
1090 /// use windmark::router_option::RouterOption;
1091 ///
1092 /// windmark::router::Router::new()
1093 /// .remove_options(&[RouterOption::RemoveExtraTrailingSlash]);
1094 /// ```
1095 pub fn remove_options(&mut self, options: &[RouterOption]) -> &mut Self {
1096 for option in options {
1097 self.options.remove(option);
1098 }
1099
1100 self
1101 }
1102
1103 /// Specify a custom listener address.
1104 ///
1105 /// Defaults to `"0.0.0.0"`.
1106 ///
1107 /// # Examples
1108 ///
1109 /// ```rust
1110 /// windmark::router::Router::new().set_listener_address("[::]");
1111 /// ```
1112 pub fn set_listener_address(
1113 &mut self,
1114 address: impl Into<String> + AsRef<str>,
1115 ) -> &mut Self {
1116 self.listener_address = address.into();
1117
1118 self
1119 }
1120}
1121
1122impl Default for Router {
1123 fn default() -> Self {
1124 Self {
1125 routes: matchit::Router::new(),
1126 error_handler: Arc::new(AsyncMutex::new(Box::new(|_| {
1127 async {
1128 Response::not_found(
1129 "This capsule has not implemented an error handler...",
1130 )
1131 }
1132 }))),
1133 private_key_file_name: String::new(),
1134 certificate_file_name: String::new(),
1135 headers: Arc::new(Mutex::new(vec![])),
1136 footers: Arc::new(Mutex::new(vec![])),
1137 ssl_acceptor: Arc::new(
1138 SslAcceptor::mozilla_intermediate(SslMethod::tls())
1139 .expect("failed to create default SSL acceptor")
1140 .build(),
1141 ),
1142 #[cfg(feature = "logger")]
1143 default_logger: false,
1144 #[cfg(feature = "logger")]
1145 log_filter: String::new(),
1146 pre_route_callback: Arc::new(Mutex::new(Box::new(|_| {}))),
1147 post_route_callback: Arc::new(Mutex::new(Box::new(
1148 |_, _: &'_ mut Response| {},
1149 ))),
1150 character_set: "utf-8".to_string(),
1151 languages: vec!["en".to_string()],
1152 port: 1965,
1153 modules: Arc::new(Mutex::new(vec![])),
1154 async_modules: Arc::new(AsyncMutex::new(vec![])),
1155 options: HashSet::new(),
1156 private_key_content: None,
1157 certificate_content: None,
1158 listener_address: "0.0.0.0".to_string(),
1159 }
1160 }
1161}