1pub mod config;
53pub mod connection;
54pub mod errors;
55pub mod handle;
56pub mod lifecycle;
57pub mod service;
58pub mod static_service;
59
60pub use crate::primitives::request::Request;
61pub use config::{try_from_serve_config, RuntimeConfig, RuntimeConfigBuilder};
62pub use errors::{ServerError, ShutdownResult};
63pub use handle::ServerHandle;
64pub use lifecycle::LifecycleState;
65pub use service::{
66 service_fn, service_fn_head, service_fn_with_policy, Service, ServiceError, ServiceFn,
67};
68pub use static_service::{StaticService, StaticServiceBuilder};
69
70use std::sync::Arc;
71
72use hyper_util::rt::TokioIo;
73use tokio::net::TcpListener;
74use tokio::sync::broadcast;
75
76use crate::config::ServeConfig;
77use crate::server::lifecycle::Lifecycle;
78
79pub struct Server {
117 config: RuntimeConfig,
118 builtin_static_service: Option<StaticService>,
119 lifecycle: Arc<Lifecycle>,
120 listener_source: Option<ListenerSource>,
121}
122
123#[derive(Debug)]
128pub struct RuntimeState {
129 pub(crate) file_stream_semaphore: Arc<tokio::sync::Semaphore>,
130}
131
132impl RuntimeState {
133 pub(crate) fn new(config: &RuntimeConfig) -> Self {
134 Self {
135 file_stream_semaphore: Arc::new(tokio::sync::Semaphore::new(config.max_file_streams)),
136 }
137 }
138
139 #[doc(hidden)]
143 pub fn new_for_testing(max_file_streams: usize) -> Self {
144 Self {
145 file_stream_semaphore: Arc::new(tokio::sync::Semaphore::new(max_file_streams)),
146 }
147 }
148
149 pub fn file_stream_semaphore(&self) -> &Arc<tokio::sync::Semaphore> {
151 &self.file_stream_semaphore
152 }
153}
154
155#[derive(Debug)]
157enum ListenerSource {
158 Bind(std::net::SocketAddr),
160 Listener(TcpListener),
162}
163
164impl Server {
165 pub fn builder() -> ServerBuilder {
167 ServerBuilder {
168 runtime_config: None,
169 serve_config: None,
170 listener_source: None,
171 }
172 }
173}
174
175#[derive(Debug)]
192#[must_use]
193pub struct ServerBuilder {
194 runtime_config: Option<RuntimeConfig>,
195 serve_config: Option<Arc<ServeConfig>>,
196 listener_source: Option<ListenerSource>,
197}
198
199impl ServerBuilder {
200 pub fn runtime(mut self, config: RuntimeConfig) -> Self {
202 self.runtime_config = Some(config);
203 self
204 }
205
206 pub fn serve_config(mut self, config: Arc<ServeConfig>) -> Self {
211 self.serve_config = Some(config);
212 self
213 }
214
215 pub fn bind(mut self, addr: std::net::SocketAddr) -> Self {
220 self.listener_source = Some(ListenerSource::Bind(addr));
221 self
222 }
223
224 pub fn from_listener(mut self, listener: TcpListener) -> Self {
240 self.listener_source = Some(ListenerSource::Listener(listener));
241 self
242 }
243
244 pub fn build(self) -> Result<Server, ServerError> {
251 let serve_config = self.serve_config;
252 let config = match self.runtime_config {
253 Some(c) => c,
254 None => match &serve_config {
255 Some(sc) => config::try_from_serve_config(sc)?,
256 None => {
257 return Err(ServerError::Config(
258 "runtime configuration or serve configuration required".into(),
259 ))
260 }
261 },
262 };
263 let builtin_static_service = serve_config
264 .map(StaticService::from_serve_config)
265 .transpose()
266 .map_err(|e| ServerError::Config(e.to_string()))?;
267 Ok(Server {
268 config,
269 builtin_static_service,
270 lifecycle: Arc::new(Lifecycle::new()),
271 listener_source: self.listener_source,
272 })
273 }
274
275 pub fn static_service(self, root: impl AsRef<std::path::Path>) -> Result<Server, ServerError> {
279 let serve_config = Arc::new(ServeConfig {
280 root: root.as_ref().to_path_buf(),
281 ..ServeConfig::default()
282 });
283 let config = match self.runtime_config {
284 Some(c) => c,
285 None => config::try_from_serve_config(&serve_config)?,
286 };
287 let builtin_static_service = StaticService::from_serve_config(serve_config)
288 .map_err(|e| ServerError::Config(e.to_string()))?;
289 Ok(Server {
290 config,
291 builtin_static_service: Some(builtin_static_service),
292 lifecycle: Arc::new(Lifecycle::new()),
293 listener_source: self.listener_source,
294 })
295 }
296}
297
298impl Server {
299 pub async fn start(self) -> Result<ServerHandle, ServerError> {
305 let Server {
306 config,
307 builtin_static_service,
308 lifecycle,
309 listener_source,
310 } = self;
311 let service = builtin_static_service.ok_or_else(|| {
312 ServerError::Config("serve configuration required for static service".into())
313 })?;
314
315 Server {
316 config,
317 builtin_static_service: None,
318 lifecycle,
319 listener_source,
320 }
321 .start_with_service(service)
322 .await
323 }
324
325 pub async fn start_with_service<S: Service>(
331 self,
332 service: S,
333 ) -> Result<ServerHandle, ServerError> {
334 let Server {
335 config: runtime_config,
336 builtin_static_service: _,
337 lifecycle,
338 listener_source,
339 } = self;
340 lifecycle.start()?;
341
342 let listener = match listener_source {
343 Some(ListenerSource::Listener(l)) => l,
344 Some(ListenerSource::Bind(addr)) => {
345 TcpListener::bind(addr).await.map_err(ServerError::Bind)?
346 }
347 None => TcpListener::bind(runtime_config.bind)
348 .await
349 .map_err(ServerError::Bind)?,
350 };
351
352 let local_addr = listener.local_addr().map_err(ServerError::Bind)?;
353
354 let config = Arc::new(runtime_config);
355 let connection_semaphore = Arc::new(tokio::sync::Semaphore::new(config.max_connections));
356 let runtime_state = Arc::new(RuntimeState::new(&config));
357
358 let (shutdown_tx, shutdown_rx) = broadcast::channel::<()>(1);
359 let shutdown_tx_clone = shutdown_tx.clone();
360 let lifecycle = lifecycle.clone();
361
362 let join = tokio::spawn({
363 let lifecycle = lifecycle.clone();
364 async move {
365 accept_loop_generic(
366 listener,
367 local_addr,
368 config,
369 runtime_state,
370 connection_semaphore,
371 service,
372 shutdown_rx,
373 lifecycle,
374 )
375 .await
376 }
377 });
378
379 Ok(ServerHandle::new(
380 local_addr,
381 shutdown_tx_clone,
382 join,
383 lifecycle,
384 ))
385 }
386}
387
388#[allow(clippy::too_many_arguments)]
391async fn accept_loop_generic<S: Service>(
392 listener: TcpListener,
393 local_addr: std::net::SocketAddr,
394 config: Arc<RuntimeConfig>,
395 runtime_state: Arc<RuntimeState>,
396 connection_semaphore: Arc<tokio::sync::Semaphore>,
397 service: S,
398 mut shutdown_rx: broadcast::Receiver<()>,
399 lifecycle: Arc<Lifecycle>,
400) -> ShutdownResult {
401 let service = Arc::new(service);
402
403 if lifecycle.mark_running().is_err() {
405 let _ = lifecycle.mark_failed();
406 return ShutdownResult::Clean;
407 }
408
409 crate::ops::Logger::global().emit(crate::ops::Event::new(
410 crate::ops::Severity::Info,
411 crate::ops::EventKind::ListenerReady,
412 "accept loop started",
413 ));
414
415 let correlation = crate::ops::CorrelationId::new();
416 let counters = crate::ops::global_counters();
417
418 let mut tasks = tokio::task::JoinSet::new();
420 let mut backoff_idx: usize = 0;
421 let mut error_repeat_count: usize = 0;
422 let mut last_error_kind: Option<String> = None;
423
424 loop {
425 tokio::select! {
426 result = listener.accept() => {
427 match result {
428 Ok((stream, peer_addr)) => {
429 let _ = stream.set_nodelay(true);
430 backoff_idx = 0;
431 error_repeat_count = 0;
432 last_error_kind = None;
433 let conn_id = correlation.next();
434 counters.connections_accepted.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
435 counters.active_connections.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
436
437 crate::ops::Logger::global().emit(
438 crate::ops::Event::new(
439 crate::ops::Severity::Debug,
440 crate::ops::EventKind::ConnectionAccepted,
441 "connection accepted",
442 )
443 .connection_id(conn_id),
444 );
445
446 let permit = match connection_semaphore.clone().try_acquire_owned() {
447 Ok(p) => p,
448 Err(_) => {
449 counters.connections_rejected.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
450 counters.active_connections.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
451 crate::ops::Logger::global().emit(
452 crate::ops::Event::new(
453 crate::ops::Severity::Debug,
454 crate::ops::EventKind::ConnectionRejected,
455 "connection rejected: admission limit",
456 )
457 .connection_id(conn_id),
458 );
459 drop(stream);
460 continue;
461 }
462 };
463
464 let mut shutdown_rx = shutdown_rx.resubscribe();
465 let runtime_state = runtime_state.clone();
466 let config = config.clone();
467 let service = service.clone();
468 let remote_addr = peer_addr;
469 let local_addr_pre_tls = stream.local_addr().unwrap_or(local_addr);
470
471 tasks.spawn(async move {
472 let _permit = permit;
473 let _active_connection = ActiveConnectionGuard;
474
475 #[cfg(feature = "tls")]
476 {
477 if let Some(tls_config) = &config.tls_config {
478 let tls_acceptor = tokio_rustls::TlsAcceptor::from(tls_config.clone());
479 match accept_tls(stream, &tls_acceptor, config.header_read_timeout, conn_id).await {
480 Some((tls_stream, tls_info)) => {
481 crate::ops::Logger::global().emit(
482 crate::ops::Event::new(
483 crate::ops::Severity::Debug,
484 crate::ops::EventKind::TlsHandshakeSuccess,
485 "TLS handshake completed",
486 )
487 .connection_id(conn_id),
488 );
489 let io = TokioIo::new(tls_stream);
490 connection::serve_connection_with_runtime_state(
491 io,
492 ArcService(service),
493 &config,
494 runtime_state.clone(),
495 &mut shutdown_rx,
496 conn_id,
497 local_addr_pre_tls,
498 remote_addr,
499 true,
500 Some(tls_info),
501 ).await;
502 return;
503 }
504 None => {
505 return;
506 }
507 }
508 }
509 }
510
511 let io = TokioIo::new(stream);
512 connection::serve_connection_with_runtime_state(
513 io,
514 ArcService(service),
515 &config,
516 runtime_state.clone(),
517 &mut shutdown_rx,
518 conn_id,
519 local_addr_pre_tls,
520 remote_addr,
521 false,
522 None,
523 ).await;
524 });
525 }
526 Err(e) => {
527 let fatal = classify_accept_error(&e, &mut shutdown_rx, &mut backoff_idx, &mut error_repeat_count, &mut last_error_kind).await;
528 if fatal {
529 break;
530 }
531 }
532 }
533 }
534 _ = shutdown_rx.recv() => {
535 break;
536 }
537 }
538 }
539
540 crate::ops::Logger::global().emit(crate::ops::Event::new(
541 crate::ops::Severity::Info,
542 crate::ops::EventKind::ShutdownRequested,
543 "shutdown requested",
544 ));
545
546 let _ = lifecycle.drain();
548
549 let drain_timeout = config.graceful_shutdown_timeout;
551 let deadline = tokio::time::Instant::now() + drain_timeout;
552 let mut timed_out = false;
553
554 loop {
555 let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
556 if remaining.is_zero() {
557 timed_out = true;
558 break;
559 }
560 match tokio::time::timeout(remaining, tasks.join_next()).await {
561 Ok(Some(result)) => {
562 if let Err(e) = result {
563 if e.is_panic() {
564 counters
565 .connection_panics
566 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
567 crate::ops::Logger::global().emit(crate::ops::Event::new(
568 crate::ops::Severity::Error,
569 crate::ops::EventKind::ConnectionPanic,
570 "connection task panicked during drain",
571 ));
572 }
573 }
574 }
575 Ok(None) => break,
576 Err(_) => {
577 timed_out = true;
578 break;
579 }
580 }
581 }
582
583 let mut abort_count = 0usize;
584
585 if timed_out {
586 crate::ops::Logger::global().emit(crate::ops::Event::new(
587 crate::ops::Severity::Warn,
588 crate::ops::EventKind::ForcedShutdownStarted,
589 "grace deadline exceeded, aborting remaining tasks",
590 ));
591 tasks.abort_all();
592 while let Some(result) = tasks.join_next().await {
593 abort_count += 1;
594 if let Err(e) = result {
595 if e.is_panic() {
596 counters
597 .connection_panics
598 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
599 crate::ops::Logger::global().emit(crate::ops::Event::new(
600 crate::ops::Severity::Error,
601 crate::ops::EventKind::ConnectionPanic,
602 "connection task panicked during forced shutdown",
603 ));
604 }
605 }
606 }
607 }
608
609 let _ = lifecycle.mark_stopped();
610
611 let result = if timed_out {
612 counters
613 .forced_shutdowns
614 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
615 ShutdownResult::Timeout
616 } else {
617 counters
618 .graceful_shutdowns
619 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
620 ShutdownResult::Clean
621 };
622
623 crate::ops::Logger::global().emit(crate::ops::Event::new(
624 crate::ops::Severity::Info,
625 crate::ops::EventKind::ShutdownComplete,
626 format!("shutdown complete: {:?} (aborted={})", result, abort_count),
627 ));
628
629 result
630}
631
632#[cfg(feature = "tls")]
638async fn accept_tls(
639 stream: tokio::net::TcpStream,
640 tls_acceptor: &tokio_rustls::TlsAcceptor,
641 timeout: std::time::Duration,
642 conn_id: u64,
643) -> Option<(
644 tokio_rustls::server::TlsStream<tokio::net::TcpStream>,
645 crate::primitives::connection_info::TlsInfo,
646)> {
647 match tokio::time::timeout(timeout, tls_acceptor.accept(stream)).await {
648 Ok(Ok(tls_stream)) => {
649 let tls_info = extract_tls_info(&tls_stream);
650 Some((tls_stream, tls_info))
651 }
652 Ok(Err(_)) => {
653 crate::ops::Logger::global().emit(
654 crate::ops::Event::new(
655 crate::ops::Severity::Warn,
656 crate::ops::EventKind::TlsHandshakeFailure,
657 "TLS handshake failed",
658 )
659 .connection_id(conn_id),
660 );
661 None
662 }
663 Err(_) => {
664 crate::ops::Logger::global().emit(
665 crate::ops::Event::new(
666 crate::ops::Severity::Warn,
667 crate::ops::EventKind::TlsHandshakeTimeout,
668 "TLS handshake timeout",
669 )
670 .connection_id(conn_id),
671 );
672 None
673 }
674 }
675}
676
677#[cfg(feature = "tls")]
679fn extract_tls_info(
680 tls_stream: &tokio_rustls::server::TlsStream<tokio::net::TcpStream>,
681) -> crate::primitives::connection_info::TlsInfo {
682 use crate::primitives::connection_info::TlsInfo;
683
684 let (_io, conn) = tls_stream.get_ref();
685 let protocol_version = conn.protocol_version().map(|v| format!("{v:?}"));
686 let server_name = conn.server_name().map(|n| n.to_owned());
687 TlsInfo {
688 protocol_version,
689 server_name,
690 }
691}
692
693#[allow(clippy::collapsible_match)]
703async fn classify_accept_error(
704 e: &std::io::Error,
705 shutdown_rx: &mut broadcast::Receiver<()>,
706 backoff_idx: &mut usize,
707 error_repeat_count: &mut usize,
708 last_error_kind: &mut Option<String>,
709) -> bool {
710 use crate::ops::{Event, EventKind, Logger, Severity};
711
712 let err_str = e.to_string();
713 let kind = e.kind();
714 let fd_exhausted = is_fd_exhaustion(e);
715
716 let (severity, event_kind, should_backoff, is_fatal) = match kind {
717 std::io::ErrorKind::Interrupted => (
718 Severity::Debug,
719 EventKind::ListenerTransientError,
720 true,
721 false,
722 ),
723 std::io::ErrorKind::ConnectionRefused
724 | std::io::ErrorKind::ConnectionReset
725 | std::io::ErrorKind::ConnectionAborted
726 | std::io::ErrorKind::BrokenPipe => (
727 Severity::Debug,
728 EventKind::ListenerTransientError,
729 true,
730 false,
731 ),
732 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut => (
733 Severity::Warn,
734 EventKind::ListenerTransientError,
735 true,
736 false,
737 ),
738 std::io::ErrorKind::OutOfMemory | std::io::ErrorKind::Other if fd_exhausted => {
739 (Severity::Error, EventKind::ResourceExhaustion, true, false)
740 }
741 std::io::ErrorKind::OutOfMemory | std::io::ErrorKind::Other => (
742 Severity::Error,
743 EventKind::ListenerPersistentError,
744 false,
745 true,
746 ),
747 _ if fd_exhausted => (Severity::Error, EventKind::ResourceExhaustion, true, false),
748 _ => (
749 Severity::Error,
750 EventKind::ListenerPersistentError,
751 false,
752 true,
753 ),
754 };
755
756 crate::ops::global_counters()
757 .listener_errors
758 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
759
760 let current_kind = format!("{}", event_kind);
762 let is_same_kind = last_error_kind.as_deref() == Some(¤t_kind);
763 if is_same_kind {
764 *error_repeat_count += 1;
765 } else {
766 *error_repeat_count = 1;
767 *last_error_kind = Some(current_kind);
768 }
769
770 let should_emit = *error_repeat_count == 1 || (*error_repeat_count).is_multiple_of(10);
772 if should_emit {
773 let message = if *error_repeat_count > 1 {
774 format!(
775 "accept error ({} consecutive): {}",
776 error_repeat_count, err_str
777 )
778 } else {
779 format!("accept error: {}", err_str)
780 };
781 Logger::global().emit(Event::new(severity, event_kind, message).field(
782 crate::ops::Field::Str("error_kind".into(), format!("{:?}", kind)),
783 ));
784 }
785
786 if should_backoff {
787 static BACKOFF_MS: [u64; 5] = [1, 2, 4, 8, 50];
788 let idx = (*backoff_idx).min(BACKOFF_MS.len() - 1);
789 *backoff_idx = backoff_idx.saturating_add(1);
790 let backoff = std::time::Duration::from_millis(BACKOFF_MS[idx]);
791 tokio::select! {
792 _ = tokio::time::sleep(backoff) => {}
793 _ = shutdown_rx.recv() => {}
794 }
795 }
796
797 is_fatal
798}
799
800fn is_fd_exhaustion(error: &std::io::Error) -> bool {
801 #[cfg(unix)]
802 if let Some(raw) = error.raw_os_error() {
803 return raw == rustix::io::Errno::MFILE.raw_os_error().abs()
804 || raw == rustix::io::Errno::NFILE.raw_os_error().abs();
805 }
806
807 if error.raw_os_error().is_some() {
808 return false;
809 }
810
811 let message = error.to_string().to_ascii_lowercase();
812 message.contains("too many open files")
813 || message.contains("emfile")
814 || message.contains("enfile")
815}
816
817struct ActiveConnectionGuard;
818
819impl Drop for ActiveConnectionGuard {
820 fn drop(&mut self) {
821 crate::ops::global_counters()
822 .active_connections
823 .fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
824 }
825}
826
827struct ArcService<S>(Arc<S>);
829
830impl<S: Service> Service for ArcService<S> {
831 fn request_body_policy(
832 &self,
833 head: &crate::primitives::request_head::RequestHead,
834 ) -> crate::primitives::request_body_policy::RequestBodyPolicy {
835 self.0.request_body_policy(head)
836 }
837
838 fn call(
839 &self,
840 request: crate::primitives::request::Request,
841 ) -> std::pin::Pin<
842 Box<
843 dyn std::future::Future<
844 Output = Result<crate::primitives::canonical::Response, ServiceError>,
845 > + Send
846 + '_,
847 >,
848 > {
849 self.0.call(request)
850 }
851}
852
853#[cfg(test)]
854mod tests {
855 use super::*;
856
857 #[cfg(unix)]
858 #[tokio::test]
859 async fn classify_accept_error_uses_os_error_for_fd_exhaustion() {
860 let error = std::io::Error::from_raw_os_error(libc::EMFILE);
861 let (tx, mut rx) = broadcast::channel(1);
862 let mut backoff = 0;
863 let mut repeats = 0;
864 let mut last = None;
865 assert!(
866 !classify_accept_error(&error, &mut rx, &mut backoff, &mut repeats, &mut last,).await
867 );
868 let _ = tx.send(());
869 }
870}