🏗️ 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): Reduce allocations

Fuwn d883413c ec1bd9d7

+152 -87
+152 -87
src/router.rs
··· 340 340 loop { 341 341 match listener.accept().await { 342 342 Ok((stream, _)) => { 343 - let mut self_clone = self.clone(); 344 - let acceptor = self_clone.ssl_acceptor.clone(); 343 + let routes = self.routes.clone(); 344 + let error_handler = self.error_handler.clone(); 345 + let headers = self.headers.clone(); 346 + let footers = self.footers.clone(); 347 + let async_modules = self.async_modules.clone(); 348 + let modules = self.modules.clone(); 349 + let pre_route_callback = self.pre_route_callback.clone(); 350 + let post_route_callback = self.post_route_callback.clone(); 351 + let character_set = self.character_set.clone(); 352 + let languages = self.languages.clone(); 353 + let options = self.options.clone(); 354 + let acceptor = self.ssl_acceptor.clone(); 345 355 #[cfg(feature = "tokio")] 346 356 let spawner = tokio::spawn; 347 357 #[cfg(feature = "async-std")] ··· 368 378 println!("stream accept error: {e:?}"); 369 379 } 370 380 371 - if let Err(e) = self_clone.handle(&mut stream).await { 381 + let router_instance = Self { 382 + routes, 383 + error_handler, 384 + private_key_file_name: String::new(), 385 + private_key_content: None, 386 + certificate_file_name: String::new(), 387 + certificate_content: None, 388 + headers, 389 + footers, 390 + ssl_acceptor: acceptor, 391 + #[cfg(feature = "logger")] 392 + default_logger: false, 393 + pre_route_callback, 394 + post_route_callback, 395 + character_set, 396 + languages, 397 + port: 0, 398 + async_modules, 399 + modules, 400 + options, 401 + listener_address: String::new(), 402 + }; 403 + 404 + if let Err(e) = router_instance.handle(&mut stream).await { 372 405 error!("handle error: {e}"); 373 406 } 374 407 } ··· 385 418 386 419 #[allow( 387 420 clippy::too_many_lines, 388 - clippy::needless_pass_by_ref_mut, 389 421 clippy::significant_drop_in_scrutinee, 390 422 clippy::cognitive_complexity 391 423 )] 392 - async fn handle( 393 - &mut self, 394 - stream: &mut Stream, 395 - ) -> Result<(), Box<dyn Error>> { 424 + async fn handle(&self, stream: &mut Stream) -> Result<(), Box<dyn Error>> { 396 425 let mut buffer = [0u8; 1024]; 397 426 let mut url = Url::parse("gemini://fuwn.me/")?; 398 427 let mut footer = String::new(); ··· 401 430 while let Ok(size) = stream.read(&mut buffer).await { 402 431 let request = or_error!( 403 432 stream, 404 - String::from_utf8(buffer[0..size].to_vec()), 433 + std::str::from_utf8(&buffer[0..size]).map(ToString::to_string), 405 434 "59 The server (Windmark) received a bad request: {}" 406 435 ); 436 + let request_trimmed = request 437 + .find("\r\n") 438 + .map_or(&request[..], |pos| &request[..pos]); 407 439 408 440 url = or_error!( 409 441 stream, 410 - Url::parse(&request.replace("\r\n", "")), 442 + Url::parse(request_trimmed), 411 443 "59 The server (Windmark) received a bad request: {}" 412 444 ); 413 445 ··· 438 470 && path.ends_with('/') 439 471 && path != "/" 440 472 { 441 - path = path.trim_end_matches('/').to_string(); 442 - route = self.routes.at(&path); 473 + let trimmed = path.trim_end_matches('/'); 474 + 475 + if trimmed != path { 476 + path = trimmed.to_string(); 477 + route = self.routes.at(&path); 478 + } 443 479 } else if self 444 480 .options 445 481 .contains(&RouterOption::AddMissingTrailingSlash) 446 482 && !path.ends_with('/') 447 483 { 448 - let path_with_slash = format!("{path}/"); 484 + let mut path_with_slash = String::with_capacity(path.len() + 1); 485 + 486 + path_with_slash.push_str(&path); 487 + path_with_slash.push('/'); 449 488 450 489 if self.routes.at(&path_with_slash).is_ok() { 451 490 path = path_with_slash; ··· 455 494 } 456 495 457 496 let peer_certificate = stream.ssl().peer_certificate(); 497 + let url_clone = url.clone(); 458 498 let hook_context = HookContext::new( 459 499 stream.get_ref().peer_addr(), 460 - url.clone(), 461 - route 462 - .as_ref() 463 - .map_or(None, |route| Some(route.params.clone())), 500 + url_clone.clone(), 501 + route.as_ref().ok().map(|route| route.params.clone()), 464 502 peer_certificate.clone(), 465 503 ); 504 + let hook_context_clone = hook_context.clone(); 466 505 467 506 for module in &mut *self.async_modules.lock().await { 468 - module.on_pre_route(hook_context.clone()).await; 507 + module.on_pre_route(hook_context_clone.clone()).await; 469 508 } 470 509 510 + let hook_context_clone = hook_context.clone(); 511 + 471 512 if let Ok(mut modules) = self.modules.lock() { 472 513 for module in &mut *modules { 473 - module.on_pre_route(hook_context.clone()); 514 + module.on_pre_route(hook_context_clone.clone()); 474 515 } 475 516 } 476 517 ··· 479 520 } 480 521 481 522 let mut content = if let Ok(ref route) = route { 482 - let footers_length = (*self.footers.lock().unwrap()).len(); 483 523 let route_context = RouteContext::new( 484 524 stream.get_ref().peer_addr(), 485 - url.clone(), 525 + url_clone, 486 526 &route.params, 487 527 peer_certificate, 488 528 ); 489 529 490 - if let Ok(mut headers) = self.headers.lock() { 530 + { 531 + let mut headers = self.headers.lock().unwrap(); 532 + 491 533 for partial_header in &mut *headers { 492 534 writeln!( 493 535 &mut header, ··· 498 540 } 499 541 } 500 542 501 - for (i, partial_footer) in { 502 - #[allow(clippy::needless_borrow, clippy::explicit_auto_deref)] 503 - (&mut *self.footers.lock().unwrap()).iter_mut().enumerate() 504 - } { 505 - let _ = write!( 506 - &mut footer, 507 - "{}{}", 508 - partial_footer.call(route_context.clone()), 509 - if footers_length > 1 && i != footers_length - 1 { 510 - "\n" 511 - } else { 512 - "" 513 - }, 514 - ); 543 + { 544 + let mut footers = self.footers.lock().unwrap(); 545 + let length = footers.len(); 546 + 547 + for (i, partial_footer) in footers.iter_mut().enumerate() { 548 + let _ = write!( 549 + &mut footer, 550 + "{}{}", 551 + partial_footer.call(route_context.clone()), 552 + if length > 1 && i != length - 1 { 553 + "\n" 554 + } else { 555 + "" 556 + }, 557 + ); 558 + } 515 559 } 516 560 517 561 let mut lock = (*route.value).lock().await; ··· 524 568 .await 525 569 .call(ErrorContext::new( 526 570 stream.get_ref().peer_addr(), 527 - url.clone(), 571 + url_clone, 528 572 peer_certificate, 529 573 )) 530 574 .await 531 575 }; 576 + 577 + let hook_context_clone = hook_context.clone(); 532 578 533 579 for module in &mut *self.async_modules.lock().await { 534 - module.on_post_route(hook_context.clone()).await; 580 + module.on_post_route(hook_context_clone.clone()).await; 535 581 } 582 + 583 + let hook_context_clone = hook_context.clone(); 536 584 537 585 if let Ok(mut modules) = self.modules.lock() { 538 586 for module in &mut *modules { 539 - module.on_post_route(hook_context.clone()); 587 + module.on_post_route(hook_context_clone.clone()); 540 588 } 541 589 } 542 590 543 591 if let Ok(mut callback) = self.post_route_callback.lock() { 544 - callback.call(hook_context.clone(), &mut content); 592 + callback.call(hook_context, &mut content); 545 593 } 546 594 547 - stream 548 - .write_all( 595 + let status_code = 596 + if content.status == 21 || content.status == 22 || content.status == 23 { 597 + 20 598 + } else { 599 + content.status 600 + }; 601 + let status_line = match content.status { 602 + 20 => { 603 + let mime = content.mime.as_deref().unwrap_or("text/gemini"); 604 + let charset = content 605 + .character_set 606 + .as_deref() 607 + .unwrap_or(&self.character_set); 608 + let lang = content 609 + .languages 610 + .as_ref() 611 + .map_or_else(|| self.languages.join(","), |l| l.join(",")); 612 + 613 + format!("{status_code} {mime}; charset={charset}; lang={lang}") 614 + } 615 + 21 => { 549 616 format!( 550 - "{}{}\r\n{}", 551 - if content.status == 21 552 - || content.status == 22 553 - || content.status == 23 554 - { 555 - 20 556 - } else { 557 - content.status 558 - }, 559 - match content.status { 560 - 20 => 561 - format!( 562 - " {}; charset={}; lang={}", 563 - content.mime.unwrap_or_else(|| "text/gemini".to_string()), 564 - content 565 - .character_set 566 - .unwrap_or_else(|| self.character_set.clone()), 567 - content 568 - .languages 569 - .unwrap_or_else(|| self.languages.clone()) 570 - .join(","), 571 - ), 572 - 21 => content.mime.unwrap_or_default(), 573 - #[cfg(feature = "auto-deduce-mime")] 574 - 22 => format!(" {}", content.mime.unwrap_or_default()), 575 - _ => format!(" {}", content.content), 576 - }, 577 - match content.status { 578 - 20 => format!("{header}{}\n{footer}", content.content), 579 - 21 | 22 => content.content, 580 - _ => String::new(), 581 - } 617 + "{} {}", 618 + status_code, 619 + content.mime.as_deref().unwrap_or_default() 620 + ) 621 + } 622 + #[cfg(feature = "auto-deduce-mime")] 623 + 22 => { 624 + format!( 625 + "{} {}", 626 + status_code, 627 + content.mime.as_deref().unwrap_or_default() 582 628 ) 583 - .as_bytes(), 584 - ) 585 - .await?; 629 + } 630 + _ => { 631 + format!("{} {}", status_code, content.content) 632 + } 633 + }; 634 + let body = match content.status { 635 + 20 => { 636 + let mut body = String::with_capacity( 637 + header.len() + content.content.len() + footer.len() + 1, 638 + ); 639 + 640 + body.push_str(&header); 641 + body.push_str(&content.content); 642 + body.push('\n'); 643 + body.push_str(&footer); 644 + 645 + body 646 + } 647 + 21 | 22 => content.content, 648 + _ => String::new(), 649 + }; 650 + let mut response = 651 + String::with_capacity(status_line.len() + body.len() + 2); 586 652 653 + response.push_str(&status_line); 654 + response.push_str("\r\n"); 655 + response.push_str(&body); 656 + stream.write_all(response.as_bytes()).await?; 587 657 #[cfg(feature = "tokio")] 588 658 stream.shutdown().await?; 589 659 #[cfg(feature = "async-std")] ··· 595 665 fn create_acceptor(&mut self) -> Result<(), Box<dyn Error>> { 596 666 let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; 597 667 598 - if self.certificate_content.is_some() { 668 + if let Some(ref cert_content) = self.certificate_content { 599 669 builder.set_certificate( 600 - openssl::x509::X509::from_pem( 601 - self.certificate_content.clone().unwrap().as_bytes(), 602 - )? 603 - .as_ref(), 670 + openssl::x509::X509::from_pem(cert_content.as_bytes())?.as_ref(), 604 671 )?; 605 672 } else { 606 673 builder.set_certificate_file( ··· 609 676 )?; 610 677 } 611 678 612 - if self.private_key_content.is_some() { 679 + if let Some(ref key_content) = self.private_key_content { 613 680 builder.set_private_key( 614 - openssl::pkey::PKey::private_key_from_pem( 615 - self.private_key_content.clone().unwrap().as_bytes(), 616 - )? 617 - .as_ref(), 681 + openssl::pkey::PKey::private_key_from_pem(key_content.as_bytes())? 682 + .as_ref(), 618 683 )?; 619 684 } else { 620 685 builder.set_private_key_file(