🏗️ Elegant & Highly Performant Async Gemini Server Framework for the Modern Age
async framework gemini-protocol protocol gemini rust
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(router): move router to seperate file

Fuwn c36a6a2b 889cd693

+773 -752
+2 -2
Makefile.toml
··· 54 54 command = "cargo" 55 55 args = ["doc", "--open", "--no-deps"] 56 56 57 - [tasks.run] 57 + [tasks.example] 58 58 command = "cargo" 59 - args = ["run", "--example", "windmark", "--features", "auto-deduce-mime,logger"] 59 + args = ["run", "--example", "${@}", "--all-features"]
+2 -750
src/lib.rs
··· 111 111 pub mod module; 112 112 pub mod response; 113 113 pub mod returnable; 114 + pub mod router; 114 115 pub mod utilities; 115 116 116 117 #[macro_use] 117 118 extern crate log; 118 - 119 - use std::{ 120 - error::Error, 121 - sync::{Arc, Mutex}, 122 - }; 123 119 124 120 pub use module::Module; 125 - use openssl::ssl::{self, SslAcceptor, SslMethod}; 126 121 pub use response::Response; 122 + pub use router::Router; 127 123 pub use tokio::main; 128 - use tokio::{ 129 - io::{AsyncReadExt, AsyncWriteExt}, 130 - stream::StreamExt, 131 - }; 132 - use url::Url; 133 - 134 - use crate::{ 135 - handler::{Callback, ErrorResponse, Partial, RouteResponse}, 136 - response::to_value_set_status, 137 - returnable::{CallbackContext, ErrorContext, RouteContext}, 138 - }; 139 - 140 - macro_rules! or_error { 141 - ($stream:ident, $operation:expr, $error_format:literal) => { 142 - match $operation { 143 - Ok(u) => u, 144 - Err(e) => { 145 - $stream 146 - .write_all(format!($error_format, e).as_bytes()) 147 - .await?; 148 - 149 - $stream.shutdown().await?; 150 - 151 - return Ok(()); 152 - } 153 - } 154 - }; 155 - } 156 - 157 - /// A router which takes care of all tasks a Windmark server should handle: 158 - /// response generation, panics, logging, and more. 159 - #[derive(Clone)] 160 - pub struct Router { 161 - routes: matchit::Router<Arc<Mutex<RouteResponse>>>, 162 - error_handler: Arc<Mutex<ErrorResponse>>, 163 - private_key_file_name: String, 164 - ca_file_name: String, 165 - headers: Arc<Mutex<Vec<Partial>>>, 166 - footers: Arc<Mutex<Vec<Partial>>>, 167 - ssl_acceptor: Arc<SslAcceptor>, 168 - #[cfg(feature = "logger")] 169 - default_logger: bool, 170 - pre_route_callback: Arc<Mutex<Callback>>, 171 - post_route_callback: Arc<Mutex<Callback>>, 172 - charset: String, 173 - language: String, 174 - port: i32, 175 - modules: Arc<Mutex<Vec<Box<dyn Module + Send>>>>, 176 - fix_path: bool, 177 - } 178 - impl Router { 179 - /// Create a new `Router` 180 - /// 181 - /// # Examples 182 - /// 183 - /// ```rust 184 - /// let _router = windmark::Router::new(); 185 - /// ``` 186 - /// 187 - /// # Panics 188 - /// 189 - /// if a default `SslAcceptor` could not be built. 190 - #[must_use] 191 - pub fn new() -> Self { Self::default() } 192 - 193 - /// Set the filename of the private key file. 194 - /// 195 - /// # Examples 196 - /// 197 - /// ```rust 198 - /// windmark::Router::new().set_private_key_file("windmark_private.pem"); 199 - /// ``` 200 - pub fn set_private_key_file( 201 - &mut self, 202 - private_key_file_name: &str, 203 - ) -> &mut Self { 204 - self.private_key_file_name = private_key_file_name.to_string(); 205 - 206 - self 207 - } 208 - 209 - /// Set the filename of the certificate chain file. 210 - /// 211 - /// # Examples 212 - /// 213 - /// ```rust 214 - /// windmark::Router::new().set_certificate_file("windmark_public.pem"); 215 - /// ``` 216 - pub fn set_certificate_file(&mut self, certificate_name: &str) -> &mut Self { 217 - self.ca_file_name = certificate_name.to_string(); 218 - 219 - self 220 - } 221 - 222 - /// Map routes to URL paths 223 - /// 224 - /// # Examples 225 - /// 226 - /// ```rust 227 - /// use windmark::Response; 228 - /// 229 - /// windmark::Router::new() 230 - /// .mount( 231 - /// "/", 232 - /// Box::new(|_| Response::Success("This is the index page!".into())), 233 - /// ) 234 - /// .mount( 235 - /// "/test", 236 - /// Box::new(|_| Response::Success("This is a test page!".into())), 237 - /// ); 238 - /// ``` 239 - /// 240 - /// # Panics 241 - /// 242 - /// if the route cannot be mounted. 243 - pub fn mount(&mut self, route: &str, handler: RouteResponse) -> &mut Self { 244 - self 245 - .routes 246 - .insert(route, Arc::new(Mutex::new(handler))) 247 - .unwrap(); 248 - 249 - self 250 - } 251 - 252 - /// Create an error handler which will be displayed on any error. 253 - /// 254 - /// # Examples 255 - /// 256 - /// ```rust 257 - /// windmark::Router::new().set_error_handler(Box::new(|_| { 258 - /// windmark::Response::Success("You have encountered an error!".into()) 259 - /// })); 260 - /// ``` 261 - pub fn set_error_handler(&mut self, handler: ErrorResponse) -> &mut Self { 262 - self.error_handler = Arc::new(Mutex::new(handler)); 263 - 264 - self 265 - } 266 - 267 - /// Add a header for the `Router` which should be displayed on every route. 268 - /// 269 - /// # Panics 270 - /// 271 - /// May panic if the header cannot be added. 272 - /// 273 - /// # Examples 274 - /// 275 - /// ```rust 276 - /// windmark::Router::new().add_header(Box::new(|context| { 277 - /// format!("This is displayed at the top of {}!", context.url.path()) 278 - /// })); 279 - /// ``` 280 - pub fn add_header(&mut self, handler: Partial) -> &mut Self { 281 - (*self.headers.lock().unwrap()).push(handler); 282 - 283 - self 284 - } 285 - 286 - /// Add a footer for the `Router` which should be displayed on every route. 287 - /// 288 - /// # Panics 289 - /// 290 - /// May panic if the header cannot be added. 291 - /// 292 - /// # Examples 293 - /// 294 - /// ```rust 295 - /// windmark::Router::new().add_footer(Box::new(|context| { 296 - /// format!("This is displayed at the bottom of {}!", context.url.path()) 297 - /// })); 298 - /// ``` 299 - pub fn add_footer(&mut self, handler: Partial) -> &mut Self { 300 - (*self.footers.lock().unwrap()).push(handler); 301 - 302 - self 303 - } 304 - 305 - /// Run the `Router` and wait for requests 306 - /// 307 - /// # Examples 308 - /// 309 - /// ```rust 310 - /// windmark::Router::new().run(); 311 - /// ``` 312 - /// 313 - /// # Panics 314 - /// 315 - /// if the client could not be accepted. 316 - /// 317 - /// # Errors 318 - /// 319 - /// if the `TcpListener` could not be bound. 320 - pub async fn run(&mut self) -> Result<(), Box<dyn Error>> { 321 - self.create_acceptor()?; 322 - 323 - #[cfg(feature = "logger")] 324 - if self.default_logger { 325 - pretty_env_logger::init(); 326 - } 327 - 328 - let acceptor = self.ssl_acceptor.clone(); 329 - let mut listener = 330 - tokio::net::TcpListener::bind(format!("0.0.0.0:{}", self.port)).await?; 331 - 332 - #[cfg(feature = "logger")] 333 - info!("windmark is listening for connections"); 334 - 335 - while let Some(stream) = listener.incoming().next().await { 336 - match stream { 337 - Ok(stream) => { 338 - let acceptor = acceptor.clone(); 339 - let mut self_clone = self.clone(); 340 - 341 - tokio::spawn(async move { 342 - match tokio_openssl::accept(&acceptor, stream).await { 343 - Ok(mut stream) => { 344 - if let Err(e) = self_clone.handle(&mut stream).await { 345 - error!("handle error: {}", e); 346 - } 347 - } 348 - Err(e) => error!("ssl error: {:?}", e), 349 - } 350 - }); 351 - } 352 - Err(e) => error!("tcp error: {:?}", e), 353 - } 354 - } 355 - 356 - Ok(()) 357 - } 358 - 359 - #[allow(clippy::too_many_lines)] 360 - async fn handle( 361 - &mut self, 362 - stream: &mut tokio_openssl::SslStream<tokio::net::TcpStream>, 363 - ) -> Result<(), Box<dyn std::error::Error>> { 364 - let mut buffer = [0u8; 1024]; 365 - let mut url = Url::parse("gemini://fuwn.me/")?; 366 - let mut response_status = 0; 367 - #[cfg(not(feature = "auto-deduce-mime"))] 368 - let mut response_mime_type = "".to_string(); 369 - let mut footer = String::new(); 370 - let mut header = String::new(); 371 - 372 - while let Ok(size) = stream.read(&mut buffer).await { 373 - let request = or_error!( 374 - stream, 375 - String::from_utf8(buffer[0..size].to_vec()), 376 - "59 The server (Windmark) received a bad request: {}" 377 - ); 378 - url = or_error!( 379 - stream, 380 - url::Url::parse(&request.replace("\r\n", "")), 381 - "59 The server (Windmark) received a bad request: {}" 382 - ); 383 - 384 - if request.contains("\r\n") { 385 - break; 386 - } 387 - } 388 - 389 - let fixed_path = if self.fix_path { 390 - self 391 - .routes 392 - .fix_path(if url.path().is_empty() { 393 - "/" 394 - } else { 395 - url.path() 396 - }) 397 - .unwrap_or_else(|| url.path().to_string()) 398 - } else { 399 - url.path().to_string() 400 - }; 401 - let route = &mut self.routes.at(&fixed_path); 402 - 403 - for module in &mut *self.modules.lock().unwrap() { 404 - module.on_pre_route(CallbackContext::new(stream.get_ref(), &url, { 405 - if let Ok(route) = &route { 406 - Some(&route.params) 407 - } else { 408 - None 409 - } 410 - })); 411 - } 412 - 413 - (*self.pre_route_callback).lock().unwrap().call_mut(( 414 - stream.get_ref(), 415 - &url, 416 - { 417 - if let Ok(route) = &route { 418 - Some(&route.params) 419 - } else { 420 - None 421 - } 422 - }, 423 - )); 424 - 425 - let content = if let Ok(ref route) = route { 426 - let footers_length = (*self.footers.lock().unwrap()).len(); 427 - 428 - for partial_header in &mut *self.headers.lock().unwrap() { 429 - header.push_str(&format!( 430 - "{}\n", 431 - partial_header(RouteContext::new( 432 - stream.get_ref(), 433 - &url, 434 - &route.params, 435 - )), 436 - )); 437 - } 438 - for (i, partial_footer) in 439 - (&mut *self.footers.lock().unwrap()).iter_mut().enumerate() 440 - { 441 - footer.push_str(&format!( 442 - "{}{}", 443 - partial_footer(RouteContext::new( 444 - stream.get_ref(), 445 - &url, 446 - &route.params, 447 - )), 448 - if footers_length > 1 && i != footers_length - 1 { 449 - "\n" 450 - } else { 451 - "" 452 - }, 453 - )); 454 - } 455 - to_value_set_status( 456 - (*route.value).lock().unwrap().call_mut((RouteContext::new( 457 - stream.get_ref(), 458 - &url, 459 - &route.params, 460 - ),)), 461 - &mut response_status, 462 - #[cfg(not(feature = "auto-deduce-mime"))] 463 - &mut response_mime_type, 464 - ) 465 - } else { 466 - to_value_set_status( 467 - (*self.error_handler) 468 - .lock() 469 - .unwrap() 470 - .call_mut((ErrorContext::new(stream.get_ref(), &url),)), 471 - &mut response_status, 472 - #[cfg(not(feature = "auto-deduce-mime"))] 473 - &mut response_mime_type, 474 - ) 475 - }; 476 - 477 - stream 478 - .write_all( 479 - format!( 480 - "{}{}\r\n{}", 481 - if response_status == 21 { 482 - 20 483 - } else { 484 - response_status 485 - }, 486 - match response_status { 487 - 20 => 488 - format!( 489 - " text/gemini; charset={}; lang={}", 490 - self.charset, self.language 491 - ), 492 - #[cfg(feature = "auto-deduce-mime")] 493 - 21 => format!(" {}", tree_magic::from_u8(&*content.as_bytes())), 494 - #[cfg(not(feature = "auto-deduce-mime"))] 495 - 21 => response_mime_type, 496 - _ => format!(" {}", content), 497 - }, 498 - match response_status { 499 - 20 => format!("{}{}\n{}", header, content, footer), 500 - 21 => content.to_string(), 501 - _ => "".to_string(), 502 - } 503 - ) 504 - .as_bytes(), 505 - ) 506 - .await?; 507 - 508 - for module in &mut *self.modules.lock().unwrap() { 509 - module.on_post_route(CallbackContext::new(stream.get_ref(), &url, { 510 - if let Ok(route) = &route { 511 - Some(&route.params) 512 - } else { 513 - None 514 - } 515 - })); 516 - } 517 - 518 - (*self.post_route_callback).lock().unwrap().call_mut(( 519 - stream.get_ref(), 520 - &url, 521 - { 522 - if let Ok(route) = &route { 523 - Some(&route.params) 524 - } else { 525 - None 526 - } 527 - }, 528 - )); 529 - 530 - stream.shutdown().await?; 531 - 532 - Ok(()) 533 - } 534 - 535 - fn create_acceptor(&mut self) -> Result<(), Box<dyn Error>> { 536 - let mut builder = SslAcceptor::mozilla_intermediate(ssl::SslMethod::tls())?; 537 - 538 - builder.set_private_key_file( 539 - &self.private_key_file_name, 540 - ssl::SslFiletype::PEM, 541 - )?; 542 - builder.set_certificate_file( 543 - &self.ca_file_name, 544 - openssl::ssl::SslFiletype::PEM, 545 - )?; 546 - builder.check_private_key()?; 547 - 548 - self.ssl_acceptor = Arc::new(builder.build()); 549 - 550 - Ok(()) 551 - } 552 - 553 - /// Use a self-made `SslAcceptor` 554 - /// 555 - /// # Examples 556 - /// 557 - /// ```rust 558 - /// use openssl::ssl; 559 - /// 560 - /// windmark::Router::new().set_ssl_acceptor({ 561 - /// let mut builder = 562 - /// ssl::SslAcceptor::mozilla_intermediate(ssl::SslMethod::tls()).unwrap(); 563 - /// 564 - /// builder 565 - /// .set_private_key_file("windmark_private.pem", ssl::SslFiletype::PEM) 566 - /// .unwrap(); 567 - /// builder 568 - /// .set_certificate_file( 569 - /// "windmark_public.pem", 570 - /// openssl::ssl::SslFiletype::PEM, 571 - /// ) 572 - /// .unwrap(); 573 - /// builder.check_private_key().unwrap(); 574 - /// 575 - /// builder.build() 576 - /// }); 577 - /// ``` 578 - pub fn set_ssl_acceptor(&mut self, ssl_acceptor: SslAcceptor) -> &mut Self { 579 - self.ssl_acceptor = Arc::new(ssl_acceptor); 580 - 581 - self 582 - } 583 - 584 - /// Enabled the default logger (the 585 - /// [`pretty_env_logger`](https://crates.io/crates/pretty_env_logger) and 586 - /// [`log`](https://crates.io/crates/log) crates). 587 - #[cfg(feature = "logger")] 588 - pub fn enable_default_logger(&mut self, enable: bool) -> &mut Self { 589 - self.default_logger = enable; 590 - std::env::set_var("RUST_LOG", "windmark=trace"); 591 - 592 - self 593 - } 594 - 595 - /// Set the default logger's log level. 596 - /// 597 - /// If you enable Windmark's default logger with `enable_default_logger`, 598 - /// Windmark will only log, logs from itself. By setting a log level with 599 - /// this method, you are overriding the default log level, so you must choose 600 - /// to enable logs from Windmark with the `log_windmark` parameter. 601 - /// 602 - /// Log level "language" is detailed 603 - /// [here](https://docs.rs/env_logger/0.9.0/env_logger/#enabling-logging). 604 - /// 605 - /// # Examples 606 - /// 607 - /// ```rust 608 - /// windmark::Router::new() 609 - /// .enable_default_logger(true) 610 - /// .set_log_level("your_crate_name=trace", true); 611 - /// // If you would only like to log, logs from your crate: 612 - /// // .set_log_level("your_crate_name=trace", false); 613 - /// ``` 614 - #[cfg(feature = "logger")] 615 - pub fn set_log_level( 616 - &mut self, 617 - log_level: &str, 618 - log_windmark: bool, 619 - ) -> &mut Self { 620 - std::env::set_var( 621 - "RUST_LOG", 622 - format!( 623 - "{}{}", 624 - if log_windmark { "windmark," } else { "" }, 625 - log_level 626 - ), 627 - ); 628 - 629 - self 630 - } 631 - 632 - /// Set a callback to run before a client response is delivered 633 - /// 634 - /// # Examples 635 - /// 636 - /// ```rust 637 - /// use log::info; 638 - /// 639 - /// windmark::Router::new().set_pre_route_callback(Box::new( 640 - /// |stream, _url, _| { 641 - /// info!( 642 - /// "accepted connection from {}", 643 - /// stream.peer_addr().unwrap().ip(), 644 - /// ) 645 - /// }, 646 - /// )); 647 - /// ``` 648 - pub fn set_pre_route_callback(&mut self, callback: Callback) -> &mut Self { 649 - self.pre_route_callback = Arc::new(Mutex::new(callback)); 650 - 651 - self 652 - } 653 - 654 - /// Set a callback to run after a client response is delivered 655 - /// 656 - /// # Examples 657 - /// 658 - /// ```rust 659 - /// use log::info; 660 - /// 661 - /// windmark::Router::new().set_post_route_callback(Box::new( 662 - /// |stream, _url, _| { 663 - /// info!( 664 - /// "closed connection from {}", 665 - /// stream.peer_addr().unwrap().ip(), 666 - /// ) 667 - /// }, 668 - /// )); 669 - /// ``` 670 - pub fn set_post_route_callback(&mut self, callback: Callback) -> &mut Self { 671 - self.post_route_callback = Arc::new(Mutex::new(callback)); 672 - 673 - self 674 - } 675 - 676 - /// Attach a stateless module to a `Router`. 677 - /// 678 - /// A module is an extension or middleware to a `Router`. Modules get full 679 - /// access to the `Router`, but can be extended by a third party. 680 - /// 681 - /// # Examples 682 - /// 683 - /// ## Integrated Module 684 - /// 685 - /// ```rust 686 - /// use windmark::Response; 687 - /// 688 - /// windmark::Router::new().attach_stateless(|r| { 689 - /// r.mount( 690 - /// "/module", 691 - /// Box::new(|_| Response::Success("This is a module!".into())), 692 - /// ); 693 - /// r.set_error_handler(Box::new(|_| { 694 - /// Response::NotFound( 695 - /// "This error handler has been implemented by a module!".into(), 696 - /// ) 697 - /// })); 698 - /// }); 699 - /// ``` 700 - /// 701 - /// ## External Module 702 - /// 703 - /// ```rust 704 - /// use windmark::Response; 705 - /// 706 - /// mod windmark_example { 707 - /// pub fn module(router: &mut windmark::Router) { 708 - /// router.mount( 709 - /// "/module", 710 - /// Box::new(|_| windmark::Response::Success("This is a module!".into())), 711 - /// ); 712 - /// } 713 - /// } 714 - /// 715 - /// windmark::Router::new().attach_stateless(windmark_example::module); 716 - /// ``` 717 - pub fn attach_stateless<F>(&mut self, mut module: F) -> &mut Self 718 - where F: FnMut(&mut Self) { 719 - module(self); 720 - 721 - self 722 - } 723 - 724 - /// Attach a stateful module to a `Router`. 725 - /// 726 - /// Like a stateless module is an extension or middleware to a `Router`. 727 - /// Modules get full access to the `Router` and can be extended by a third 728 - /// party, but also, can create hooks will be executed through various parts 729 - /// of a routes' lifecycle. Stateful modules also have state, so variables can 730 - /// be stored for further access. 731 - /// 732 - /// # Panics 733 - /// 734 - /// May panic if the stateful module cannot be attached. 735 - /// 736 - /// # Examples 737 - /// 738 - /// ```rust 739 - /// use log::info; 740 - /// use windmark::{returnable::CallbackContext, Response, Router}; 741 - /// 742 - /// #[derive(Default)] 743 - /// struct Clicker { 744 - /// clicks: isize, 745 - /// } 746 - /// impl windmark::Module for Clicker { 747 - /// fn on_attach(&mut self, _: &mut Router) { 748 - /// info!("clicker has been attached!"); 749 - /// } 750 - /// 751 - /// fn on_pre_route(&mut self, context: CallbackContext<'_>) { 752 - /// self.clicks += 1; 753 - /// 754 - /// info!( 755 - /// "clicker has been called pre-route on {} with {} clicks!", 756 - /// context.url.path(), 757 - /// self.clicks 758 - /// ); 759 - /// } 760 - /// 761 - /// fn on_post_route(&mut self, context: CallbackContext<'_>) { 762 - /// info!( 763 - /// "clicker has been called post-route on {} with {} clicks!", 764 - /// context.url.path(), 765 - /// self.clicks 766 - /// ); 767 - /// } 768 - /// } 769 - /// 770 - /// Router::new().attach(Clicker::default()); 771 - /// ``` 772 - pub fn attach( 773 - &mut self, 774 - mut module: impl Module + 'static + Send, 775 - ) -> &mut Self { 776 - module.on_attach(self); 777 - 778 - (*self.modules.lock().unwrap()).push(Box::new(module)); 779 - 780 - self 781 - } 782 - 783 - /// Specify a custom character set. 784 - /// 785 - /// Defaults to `"utf-8"`. 786 - /// 787 - /// # Examples 788 - /// 789 - /// ```rust 790 - /// windmark::Router::new().set_charset("utf-8"); 791 - /// ``` 792 - pub fn set_charset(&mut self, charset: &str) -> &mut Self { 793 - self.charset = charset.to_string(); 794 - 795 - self 796 - } 797 - 798 - /// Specify a custom language. 799 - /// 800 - /// Defaults to `"en"`. 801 - /// 802 - /// # Examples 803 - /// 804 - /// ```rust 805 - /// windmark::Router::new().set_language("en"); 806 - /// ``` 807 - pub fn set_language(&mut self, language: &str) -> &mut Self { 808 - self.language = language.to_string(); 809 - 810 - self 811 - } 812 - 813 - /// Specify a custom port. 814 - /// 815 - /// Defaults to `1965`. 816 - /// 817 - /// # Examples 818 - /// 819 - /// ```rust 820 - /// windmark::Router::new().set_port(1965); 821 - /// ``` 822 - pub fn set_port(&mut self, port: i32) -> &mut Self { 823 - self.port = port; 824 - 825 - self 826 - } 827 - 828 - /// Performs a case-insensitive lookup of routes, using the case corrected 829 - /// path if successful. Missing/ extra trailing slashes are also corrected. 830 - /// 831 - /// # Examples 832 - /// 833 - /// ```rust 834 - /// windmark::Router::new().set_fix_path(true); 835 - /// ``` 836 - pub fn set_fix_path(&mut self, fix_path: bool) -> &mut Self { 837 - self.fix_path = fix_path; 838 - 839 - self 840 - } 841 - } 842 - impl Default for Router { 843 - fn default() -> Self { 844 - Self { 845 - routes: matchit::Router::new(), 846 - error_handler: Arc::new(Mutex::new(Box::new(|_| { 847 - Response::NotFound( 848 - "This capsule has not implemented an error handler...".to_string(), 849 - ) 850 - }))), 851 - private_key_file_name: "".to_string(), 852 - ca_file_name: "".to_string(), 853 - headers: Arc::new(Mutex::new(vec![])), 854 - footers: Arc::new(Mutex::new(vec![])), 855 - ssl_acceptor: Arc::new( 856 - SslAcceptor::mozilla_intermediate(SslMethod::tls()) 857 - .unwrap() 858 - .build(), 859 - ), 860 - #[cfg(feature = "logger")] 861 - default_logger: false, 862 - pre_route_callback: Arc::new(Mutex::new(Box::new(|_, _, _| {}))), 863 - post_route_callback: Arc::new(Mutex::new(Box::new(|_, _, _| {}))), 864 - charset: "utf-8".to_string(), 865 - language: "en".to_string(), 866 - port: 1965, 867 - modules: Arc::new(Mutex::new(vec![])), 868 - fix_path: false, 869 - } 870 - } 871 - }
+769
src/router.rs
··· 1 + // This file is part of Windmark <https://github.com/gemrest/windmark>. 2 + // Copyright (C) 2022-2022 Fuwn <contact@fuwn.me> 3 + // 4 + // This program is free software: you can redistribute it and/or modify 5 + // it under the terms of the GNU General Public License as published by 6 + // the Free Software Foundation, version 3. 7 + // 8 + // This program is distributed in the hope that it will be useful, but 9 + // WITHOUT ANY WARRANTY; without even the implied warranty of 10 + // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 + // General Public License for more details. 12 + // 13 + // You should have received a copy of the GNU General Public License 14 + // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 + // 16 + // Copyright (C) 2022-2022 Fuwn <contact@fuwn.me> 17 + // SPDX-License-Identifier: GPL-3.0-only 18 + 19 + use std::{ 20 + error::Error, 21 + sync::{Arc, Mutex}, 22 + }; 23 + 24 + use openssl::ssl::{self, SslAcceptor, SslMethod}; 25 + use tokio::{ 26 + io::{AsyncReadExt, AsyncWriteExt}, 27 + stream::StreamExt, 28 + }; 29 + use url::Url; 30 + 31 + use crate::{ 32 + handler::{Callback, ErrorResponse, Partial, RouteResponse}, 33 + module::Module, 34 + response::{to_value_set_status, Response}, 35 + returnable::{CallbackContext, ErrorContext, RouteContext}, 36 + }; 37 + 38 + macro_rules! or_error { 39 + ($stream:ident, $operation:expr, $error_format:literal) => { 40 + match $operation { 41 + Ok(u) => u, 42 + Err(e) => { 43 + $stream 44 + .write_all(format!($error_format, e).as_bytes()) 45 + .await?; 46 + 47 + $stream.shutdown().await?; 48 + 49 + return Ok(()); 50 + } 51 + } 52 + }; 53 + } 54 + 55 + /// A router which takes care of all tasks a Windmark server should handle: 56 + /// response generation, panics, logging, and more. 57 + #[derive(Clone)] 58 + pub struct Router { 59 + routes: matchit::Router<Arc<Mutex<RouteResponse>>>, 60 + error_handler: Arc<Mutex<ErrorResponse>>, 61 + private_key_file_name: String, 62 + ca_file_name: String, 63 + headers: Arc<Mutex<Vec<Partial>>>, 64 + footers: Arc<Mutex<Vec<Partial>>>, 65 + ssl_acceptor: Arc<SslAcceptor>, 66 + #[cfg(feature = "logger")] 67 + default_logger: bool, 68 + pre_route_callback: Arc<Mutex<Callback>>, 69 + post_route_callback: Arc<Mutex<Callback>>, 70 + charset: String, 71 + language: String, 72 + port: i32, 73 + modules: Arc<Mutex<Vec<Box<dyn Module + Send>>>>, 74 + fix_path: bool, 75 + } 76 + impl Router { 77 + /// Create a new `Router` 78 + /// 79 + /// # Examples 80 + /// 81 + /// ```rust 82 + /// let _router = windmark::Router::new(); 83 + /// ``` 84 + /// 85 + /// # Panics 86 + /// 87 + /// if a default `SslAcceptor` could not be built. 88 + #[must_use] 89 + pub fn new() -> Self { Self::default() } 90 + 91 + /// Set the filename of the private key file. 92 + /// 93 + /// # Examples 94 + /// 95 + /// ```rust 96 + /// windmark::Router::new().set_private_key_file("windmark_private.pem"); 97 + /// ``` 98 + pub fn set_private_key_file( 99 + &mut self, 100 + private_key_file_name: &str, 101 + ) -> &mut Self { 102 + self.private_key_file_name = private_key_file_name.to_string(); 103 + 104 + self 105 + } 106 + 107 + /// Set the filename of the certificate chain file. 108 + /// 109 + /// # Examples 110 + /// 111 + /// ```rust 112 + /// windmark::Router::new().set_certificate_file("windmark_public.pem"); 113 + /// ``` 114 + pub fn set_certificate_file(&mut self, certificate_name: &str) -> &mut Self { 115 + self.ca_file_name = certificate_name.to_string(); 116 + 117 + self 118 + } 119 + 120 + /// Map routes to URL paths 121 + /// 122 + /// # Examples 123 + /// 124 + /// ```rust 125 + /// use windmark::Response; 126 + /// 127 + /// windmark::Router::new() 128 + /// .mount( 129 + /// "/", 130 + /// Box::new(|_| Response::Success("This is the index page!".into())), 131 + /// ) 132 + /// .mount( 133 + /// "/test", 134 + /// Box::new(|_| Response::Success("This is a test page!".into())), 135 + /// ); 136 + /// ``` 137 + /// 138 + /// # Panics 139 + /// 140 + /// if the route cannot be mounted. 141 + pub fn mount(&mut self, route: &str, handler: RouteResponse) -> &mut Self { 142 + self 143 + .routes 144 + .insert(route, Arc::new(Mutex::new(handler))) 145 + .unwrap(); 146 + 147 + self 148 + } 149 + 150 + /// Create an error handler which will be displayed on any error. 151 + /// 152 + /// # Examples 153 + /// 154 + /// ```rust 155 + /// windmark::Router::new().set_error_handler(Box::new(|_| { 156 + /// windmark::Response::Success("You have encountered an error!".into()) 157 + /// })); 158 + /// ``` 159 + pub fn set_error_handler(&mut self, handler: ErrorResponse) -> &mut Self { 160 + self.error_handler = Arc::new(Mutex::new(handler)); 161 + 162 + self 163 + } 164 + 165 + /// Add a header for the `Router` which should be displayed on every route. 166 + /// 167 + /// # Panics 168 + /// 169 + /// May panic if the header cannot be added. 170 + /// 171 + /// # Examples 172 + /// 173 + /// ```rust 174 + /// windmark::Router::new().add_header(Box::new(|context| { 175 + /// format!("This is displayed at the top of {}!", context.url.path()) 176 + /// })); 177 + /// ``` 178 + pub fn add_header(&mut self, handler: Partial) -> &mut Self { 179 + (*self.headers.lock().unwrap()).push(handler); 180 + 181 + self 182 + } 183 + 184 + /// Add a footer for the `Router` which should be displayed on every route. 185 + /// 186 + /// # Panics 187 + /// 188 + /// May panic if the header cannot be added. 189 + /// 190 + /// # Examples 191 + /// 192 + /// ```rust 193 + /// windmark::Router::new().add_footer(Box::new(|context| { 194 + /// format!("This is displayed at the bottom of {}!", context.url.path()) 195 + /// })); 196 + /// ``` 197 + pub fn add_footer(&mut self, handler: Partial) -> &mut Self { 198 + (*self.footers.lock().unwrap()).push(handler); 199 + 200 + self 201 + } 202 + 203 + /// Run the `Router` and wait for requests 204 + /// 205 + /// # Examples 206 + /// 207 + /// ```rust 208 + /// windmark::Router::new().run(); 209 + /// ``` 210 + /// 211 + /// # Panics 212 + /// 213 + /// if the client could not be accepted. 214 + /// 215 + /// # Errors 216 + /// 217 + /// if the `TcpListener` could not be bound. 218 + pub async fn run(&mut self) -> Result<(), Box<dyn Error>> { 219 + self.create_acceptor()?; 220 + 221 + #[cfg(feature = "logger")] 222 + if self.default_logger { 223 + pretty_env_logger::init(); 224 + } 225 + 226 + let acceptor = self.ssl_acceptor.clone(); 227 + let mut listener = 228 + tokio::net::TcpListener::bind(format!("0.0.0.0:{}", self.port)).await?; 229 + 230 + #[cfg(feature = "logger")] 231 + info!("windmark is listening for connections"); 232 + 233 + while let Some(stream) = listener.incoming().next().await { 234 + match stream { 235 + Ok(stream) => { 236 + let acceptor = acceptor.clone(); 237 + let mut self_clone = self.clone(); 238 + 239 + tokio::spawn(async move { 240 + match tokio_openssl::accept(&acceptor, stream).await { 241 + Ok(mut stream) => { 242 + if let Err(e) = self_clone.handle(&mut stream).await { 243 + error!("handle error: {}", e); 244 + } 245 + } 246 + Err(e) => error!("ssl error: {:?}", e), 247 + } 248 + }); 249 + } 250 + Err(e) => error!("tcp error: {:?}", e), 251 + } 252 + } 253 + 254 + Ok(()) 255 + } 256 + 257 + #[allow(clippy::too_many_lines)] 258 + async fn handle( 259 + &mut self, 260 + stream: &mut tokio_openssl::SslStream<tokio::net::TcpStream>, 261 + ) -> Result<(), Box<dyn std::error::Error>> { 262 + let mut buffer = [0u8; 1024]; 263 + let mut url = Url::parse("gemini://fuwn.me/")?; 264 + let mut response_status = 0; 265 + #[cfg(not(feature = "auto-deduce-mime"))] 266 + let mut response_mime_type = "".to_string(); 267 + let mut footer = String::new(); 268 + let mut header = String::new(); 269 + 270 + while let Ok(size) = stream.read(&mut buffer).await { 271 + let request = or_error!( 272 + stream, 273 + String::from_utf8(buffer[0..size].to_vec()), 274 + "59 The server (Windmark) received a bad request: {}" 275 + ); 276 + url = or_error!( 277 + stream, 278 + url::Url::parse(&request.replace("\r\n", "")), 279 + "59 The server (Windmark) received a bad request: {}" 280 + ); 281 + 282 + if request.contains("\r\n") { 283 + break; 284 + } 285 + } 286 + 287 + let fixed_path = if self.fix_path { 288 + self 289 + .routes 290 + .fix_path(if url.path().is_empty() { 291 + "/" 292 + } else { 293 + url.path() 294 + }) 295 + .unwrap_or_else(|| url.path().to_string()) 296 + } else { 297 + url.path().to_string() 298 + }; 299 + let route = &mut self.routes.at(&fixed_path); 300 + 301 + for module in &mut *self.modules.lock().unwrap() { 302 + module.on_pre_route(CallbackContext::new(stream.get_ref(), &url, { 303 + if let Ok(route) = &route { 304 + Some(&route.params) 305 + } else { 306 + None 307 + } 308 + })); 309 + } 310 + 311 + (*self.pre_route_callback).lock().unwrap().call_mut(( 312 + stream.get_ref(), 313 + &url, 314 + { 315 + if let Ok(route) = &route { 316 + Some(&route.params) 317 + } else { 318 + None 319 + } 320 + }, 321 + )); 322 + 323 + let content = if let Ok(ref route) = route { 324 + let footers_length = (*self.footers.lock().unwrap()).len(); 325 + 326 + for partial_header in &mut *self.headers.lock().unwrap() { 327 + header.push_str(&format!( 328 + "{}\n", 329 + partial_header(RouteContext::new( 330 + stream.get_ref(), 331 + &url, 332 + &route.params, 333 + )), 334 + )); 335 + } 336 + for (i, partial_footer) in 337 + (&mut *self.footers.lock().unwrap()).iter_mut().enumerate() 338 + { 339 + footer.push_str(&format!( 340 + "{}{}", 341 + partial_footer(RouteContext::new( 342 + stream.get_ref(), 343 + &url, 344 + &route.params, 345 + )), 346 + if footers_length > 1 && i != footers_length - 1 { 347 + "\n" 348 + } else { 349 + "" 350 + }, 351 + )); 352 + } 353 + to_value_set_status( 354 + (*route.value).lock().unwrap().call_mut((RouteContext::new( 355 + stream.get_ref(), 356 + &url, 357 + &route.params, 358 + ),)), 359 + &mut response_status, 360 + #[cfg(not(feature = "auto-deduce-mime"))] 361 + &mut response_mime_type, 362 + ) 363 + } else { 364 + to_value_set_status( 365 + (*self.error_handler) 366 + .lock() 367 + .unwrap() 368 + .call_mut((ErrorContext::new(stream.get_ref(), &url),)), 369 + &mut response_status, 370 + #[cfg(not(feature = "auto-deduce-mime"))] 371 + &mut response_mime_type, 372 + ) 373 + }; 374 + 375 + stream 376 + .write_all( 377 + format!( 378 + "{}{}\r\n{}", 379 + if response_status == 21 { 380 + 20 381 + } else { 382 + response_status 383 + }, 384 + match response_status { 385 + 20 => 386 + format!( 387 + " text/gemini; charset={}; lang={}", 388 + self.charset, self.language 389 + ), 390 + #[cfg(feature = "auto-deduce-mime")] 391 + 21 => format!(" {}", tree_magic::from_u8(&*content.as_bytes())), 392 + #[cfg(not(feature = "auto-deduce-mime"))] 393 + 21 => response_mime_type, 394 + _ => format!(" {}", content), 395 + }, 396 + match response_status { 397 + 20 => format!("{}{}\n{}", header, content, footer), 398 + 21 => content.to_string(), 399 + _ => "".to_string(), 400 + } 401 + ) 402 + .as_bytes(), 403 + ) 404 + .await?; 405 + 406 + for module in &mut *self.modules.lock().unwrap() { 407 + module.on_post_route(CallbackContext::new(stream.get_ref(), &url, { 408 + if let Ok(route) = &route { 409 + Some(&route.params) 410 + } else { 411 + None 412 + } 413 + })); 414 + } 415 + 416 + (*self.post_route_callback).lock().unwrap().call_mut(( 417 + stream.get_ref(), 418 + &url, 419 + { 420 + if let Ok(route) = &route { 421 + Some(&route.params) 422 + } else { 423 + None 424 + } 425 + }, 426 + )); 427 + 428 + stream.shutdown().await?; 429 + 430 + Ok(()) 431 + } 432 + 433 + fn create_acceptor(&mut self) -> Result<(), Box<dyn Error>> { 434 + let mut builder = SslAcceptor::mozilla_intermediate(ssl::SslMethod::tls())?; 435 + 436 + builder.set_private_key_file( 437 + &self.private_key_file_name, 438 + ssl::SslFiletype::PEM, 439 + )?; 440 + builder.set_certificate_file( 441 + &self.ca_file_name, 442 + openssl::ssl::SslFiletype::PEM, 443 + )?; 444 + builder.check_private_key()?; 445 + 446 + self.ssl_acceptor = Arc::new(builder.build()); 447 + 448 + Ok(()) 449 + } 450 + 451 + /// Use a self-made `SslAcceptor` 452 + /// 453 + /// # Examples 454 + /// 455 + /// ```rust 456 + /// use openssl::ssl; 457 + /// 458 + /// windmark::Router::new().set_ssl_acceptor({ 459 + /// let mut builder = 460 + /// ssl::SslAcceptor::mozilla_intermediate(ssl::SslMethod::tls()).unwrap(); 461 + /// 462 + /// builder 463 + /// .set_private_key_file("windmark_private.pem", ssl::SslFiletype::PEM) 464 + /// .unwrap(); 465 + /// builder 466 + /// .set_certificate_file( 467 + /// "windmark_public.pem", 468 + /// openssl::ssl::SslFiletype::PEM, 469 + /// ) 470 + /// .unwrap(); 471 + /// builder.check_private_key().unwrap(); 472 + /// 473 + /// builder.build() 474 + /// }); 475 + /// ``` 476 + pub fn set_ssl_acceptor(&mut self, ssl_acceptor: SslAcceptor) -> &mut Self { 477 + self.ssl_acceptor = Arc::new(ssl_acceptor); 478 + 479 + self 480 + } 481 + 482 + /// Enabled the default logger (the 483 + /// [`pretty_env_logger`](https://crates.io/crates/pretty_env_logger) and 484 + /// [`log`](https://crates.io/crates/log) crates). 485 + #[cfg(feature = "logger")] 486 + pub fn enable_default_logger(&mut self, enable: bool) -> &mut Self { 487 + self.default_logger = enable; 488 + std::env::set_var("RUST_LOG", "windmark=trace"); 489 + 490 + self 491 + } 492 + 493 + /// Set the default logger's log level. 494 + /// 495 + /// If you enable Windmark's default logger with `enable_default_logger`, 496 + /// Windmark will only log, logs from itself. By setting a log level with 497 + /// this method, you are overriding the default log level, so you must choose 498 + /// to enable logs from Windmark with the `log_windmark` parameter. 499 + /// 500 + /// Log level "language" is detailed 501 + /// [here](https://docs.rs/env_logger/0.9.0/env_logger/#enabling-logging). 502 + /// 503 + /// # Examples 504 + /// 505 + /// ```rust 506 + /// windmark::Router::new() 507 + /// .enable_default_logger(true) 508 + /// .set_log_level("your_crate_name=trace", true); 509 + /// // If you would only like to log, logs from your crate: 510 + /// // .set_log_level("your_crate_name=trace", false); 511 + /// ``` 512 + #[cfg(feature = "logger")] 513 + pub fn set_log_level( 514 + &mut self, 515 + log_level: &str, 516 + log_windmark: bool, 517 + ) -> &mut Self { 518 + std::env::set_var( 519 + "RUST_LOG", 520 + format!( 521 + "{}{}", 522 + if log_windmark { "windmark," } else { "" }, 523 + log_level 524 + ), 525 + ); 526 + 527 + self 528 + } 529 + 530 + /// Set a callback to run before a client response is delivered 531 + /// 532 + /// # Examples 533 + /// 534 + /// ```rust 535 + /// use log::info; 536 + /// 537 + /// windmark::Router::new().set_pre_route_callback(Box::new( 538 + /// |stream, _url, _| { 539 + /// info!( 540 + /// "accepted connection from {}", 541 + /// stream.peer_addr().unwrap().ip(), 542 + /// ) 543 + /// }, 544 + /// )); 545 + /// ``` 546 + pub fn set_pre_route_callback(&mut self, callback: Callback) -> &mut Self { 547 + self.pre_route_callback = Arc::new(Mutex::new(callback)); 548 + 549 + self 550 + } 551 + 552 + /// Set a callback to run after a client response is delivered 553 + /// 554 + /// # Examples 555 + /// 556 + /// ```rust 557 + /// use log::info; 558 + /// 559 + /// windmark::Router::new().set_post_route_callback(Box::new( 560 + /// |stream, _url, _| { 561 + /// info!( 562 + /// "closed connection from {}", 563 + /// stream.peer_addr().unwrap().ip(), 564 + /// ) 565 + /// }, 566 + /// )); 567 + /// ``` 568 + pub fn set_post_route_callback(&mut self, callback: Callback) -> &mut Self { 569 + self.post_route_callback = Arc::new(Mutex::new(callback)); 570 + 571 + self 572 + } 573 + 574 + /// Attach a stateless module to a `Router`. 575 + /// 576 + /// A module is an extension or middleware to a `Router`. Modules get full 577 + /// access to the `Router`, but can be extended by a third party. 578 + /// 579 + /// # Examples 580 + /// 581 + /// ## Integrated Module 582 + /// 583 + /// ```rust 584 + /// use windmark::Response; 585 + /// 586 + /// windmark::Router::new().attach_stateless(|r| { 587 + /// r.mount( 588 + /// "/module", 589 + /// Box::new(|_| Response::Success("This is a module!".into())), 590 + /// ); 591 + /// r.set_error_handler(Box::new(|_| { 592 + /// Response::NotFound( 593 + /// "This error handler has been implemented by a module!".into(), 594 + /// ) 595 + /// })); 596 + /// }); 597 + /// ``` 598 + /// 599 + /// ## External Module 600 + /// 601 + /// ```rust 602 + /// use windmark::Response; 603 + /// 604 + /// mod windmark_example { 605 + /// pub fn module(router: &mut windmark::Router) { 606 + /// router.mount( 607 + /// "/module", 608 + /// Box::new(|_| windmark::Response::Success("This is a module!".into())), 609 + /// ); 610 + /// } 611 + /// } 612 + /// 613 + /// windmark::Router::new().attach_stateless(windmark_example::module); 614 + /// ``` 615 + pub fn attach_stateless<F>(&mut self, mut module: F) -> &mut Self 616 + where F: FnMut(&mut Self) { 617 + module(self); 618 + 619 + self 620 + } 621 + 622 + /// Attach a stateful module to a `Router`. 623 + /// 624 + /// Like a stateless module is an extension or middleware to a `Router`. 625 + /// Modules get full access to the `Router` and can be extended by a third 626 + /// party, but also, can create hooks will be executed through various parts 627 + /// of a routes' lifecycle. Stateful modules also have state, so variables can 628 + /// be stored for further access. 629 + /// 630 + /// # Panics 631 + /// 632 + /// May panic if the stateful module cannot be attached. 633 + /// 634 + /// # Examples 635 + /// 636 + /// ```rust 637 + /// use log::info; 638 + /// use windmark::{returnable::CallbackContext, Response, Router}; 639 + /// 640 + /// #[derive(Default)] 641 + /// struct Clicker { 642 + /// clicks: isize, 643 + /// } 644 + /// impl windmark::Module for Clicker { 645 + /// fn on_attach(&mut self, _: &mut Router) { 646 + /// info!("clicker has been attached!"); 647 + /// } 648 + /// 649 + /// fn on_pre_route(&mut self, context: CallbackContext<'_>) { 650 + /// self.clicks += 1; 651 + /// 652 + /// info!( 653 + /// "clicker has been called pre-route on {} with {} clicks!", 654 + /// context.url.path(), 655 + /// self.clicks 656 + /// ); 657 + /// } 658 + /// 659 + /// fn on_post_route(&mut self, context: CallbackContext<'_>) { 660 + /// info!( 661 + /// "clicker has been called post-route on {} with {} clicks!", 662 + /// context.url.path(), 663 + /// self.clicks 664 + /// ); 665 + /// } 666 + /// } 667 + /// 668 + /// Router::new().attach(Clicker::default()); 669 + /// ``` 670 + pub fn attach( 671 + &mut self, 672 + mut module: impl Module + 'static + Send, 673 + ) -> &mut Self { 674 + module.on_attach(self); 675 + 676 + (*self.modules.lock().unwrap()).push(Box::new(module)); 677 + 678 + self 679 + } 680 + 681 + /// Specify a custom character set. 682 + /// 683 + /// Defaults to `"utf-8"`. 684 + /// 685 + /// # Examples 686 + /// 687 + /// ```rust 688 + /// windmark::Router::new().set_charset("utf-8"); 689 + /// ``` 690 + pub fn set_charset(&mut self, charset: &str) -> &mut Self { 691 + self.charset = charset.to_string(); 692 + 693 + self 694 + } 695 + 696 + /// Specify a custom language. 697 + /// 698 + /// Defaults to `"en"`. 699 + /// 700 + /// # Examples 701 + /// 702 + /// ```rust 703 + /// windmark::Router::new().set_language("en"); 704 + /// ``` 705 + pub fn set_language(&mut self, language: &str) -> &mut Self { 706 + self.language = language.to_string(); 707 + 708 + self 709 + } 710 + 711 + /// Specify a custom port. 712 + /// 713 + /// Defaults to `1965`. 714 + /// 715 + /// # Examples 716 + /// 717 + /// ```rust 718 + /// windmark::Router::new().set_port(1965); 719 + /// ``` 720 + pub fn set_port(&mut self, port: i32) -> &mut Self { 721 + self.port = port; 722 + 723 + self 724 + } 725 + 726 + /// Performs a case-insensitive lookup of routes, using the case corrected 727 + /// path if successful. Missing/ extra trailing slashes are also corrected. 728 + /// 729 + /// # Examples 730 + /// 731 + /// ```rust 732 + /// windmark::Router::new().set_fix_path(true); 733 + /// ``` 734 + pub fn set_fix_path(&mut self, fix_path: bool) -> &mut Self { 735 + self.fix_path = fix_path; 736 + 737 + self 738 + } 739 + } 740 + impl Default for Router { 741 + fn default() -> Self { 742 + Self { 743 + routes: matchit::Router::new(), 744 + error_handler: Arc::new(Mutex::new(Box::new(|_| { 745 + Response::NotFound( 746 + "This capsule has not implemented an error handler...".to_string(), 747 + ) 748 + }))), 749 + private_key_file_name: "".to_string(), 750 + ca_file_name: "".to_string(), 751 + headers: Arc::new(Mutex::new(vec![])), 752 + footers: Arc::new(Mutex::new(vec![])), 753 + ssl_acceptor: Arc::new( 754 + SslAcceptor::mozilla_intermediate(SslMethod::tls()) 755 + .unwrap() 756 + .build(), 757 + ), 758 + #[cfg(feature = "logger")] 759 + default_logger: false, 760 + pre_route_callback: Arc::new(Mutex::new(Box::new(|_, _, _| {}))), 761 + post_route_callback: Arc::new(Mutex::new(Box::new(|_, _, _| {}))), 762 + charset: "utf-8".to_string(), 763 + language: "en".to_string(), 764 + port: 1965, 765 + modules: Arc::new(Mutex::new(vec![])), 766 + fix_path: false, 767 + } 768 + } 769 + }