🏗️ Elegant & Highly Performant Async Gemini Server Framework for the Modern Age
async framework gemini-protocol protocol gemini rust
at main 1161 lines 31 kB view raw
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}