Skip to main content

base_http_server/
lib.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4//! Application handlers and supervised HTTP/1 connections over Base scheduler
5//! capabilities. All connection futures are owned and joined by the server.
6#![doc = include_str!("../readme.md")]
7#![forbid(unsafe_code)]
8
9use base_endpoint::stream::{ReadableStream, WritableStream};
10use base_http::{
11    InterimHandler, InterimResponses, Request, Response, Scheme, Status, body::ReadableBody,
12};
13use base_http1::{RequestReader, ResponseWriter};
14use base_scheduler::{
15    Cancellation, Clock, OperationError, SocketAccept, SocketClose, SocketSetOption,
16    SocketStreamReading, SocketStreamWriting, TcpNoDelay,
17};
18use futures_util::{
19    future::{Either, select},
20    stream::{FuturesUnordered, StreamExt},
21};
22use std::{
23    fmt,
24    future::{Future, poll_fn},
25    io,
26    pin::pin,
27    task::Poll,
28    time::Duration,
29};
30
31/// Generic bodies and hooks keep handlers independent of the HTTP wire version
32/// and scheduler. A returned body may retain the request body, including borrows.
33/// An error responds with a bodiless status and closes the connection, which then reports it as [`Error::Handler`]: 400 when a request read failed, such as a malformed or truncated body, even if the error has another cause, and otherwise 500. Return an ordinary response for other client errors. A final response which HTTP/1 cannot send, such as an informational status, conflicting framing, a `Content-Length` field its body contradicts or an unsolicited upgrade, likewise receives a bodiless 500 and closes the connection, which reports it as [`Error::Response`].
34pub trait Handler: Sync {
35    type Error: std::error::Error + Send + Sync + 'static;
36    type Body<B: ReadableBody>: ReadableBody;
37
38    fn call<B: ReadableBody, I: InterimHandler>(
39        &self,
40        request: Request<B>,
41        interim: &mut InterimResponses<I>,
42        cancellation: &Cancellation,
43    ) -> impl Future<Output = Result<Response<Self::Body<B>>, Self::Error>> + Send;
44}
45
46#[derive(Clone, Debug)]
47pub struct Options {
48    pub http1: base_http1::Options,
49    /// HTTP engine selection, shared by plain and encrypted establishment.
50    pub protocols: base_http_protocol::HttpProtocols,
51    pub max_connections: usize,
52    /// Disable Nagle's algorithm on each accepted connection, so small writes
53    /// such as streamed frames are not held back waiting for acknowledgement.
54    /// This is a best-effort latency hint: a socket that rejects it is still
55    /// served.
56    pub tcp_nodelay: bool,
57}
58impl Default for Options {
59    fn default() -> Self {
60        Self {
61            http1: base_http1::Options::default(),
62            protocols: base_http_protocol::HttpProtocols::default(),
63            max_connections: 256,
64            tcp_nodelay: true,
65        }
66    }
67}
68
69/// A server failure. Protocol and negotiation errors are transparent; other wrapping variants describe this layer and report their cause as the source, so a cause chain never repeats a message.
70#[derive(Debug)]
71pub enum Error {
72    Protocol(base_http1::Error),
73    Handler(Box<dyn std::error::Error + Send + Sync>),
74    /// A handler returned a final response which the protocol writer refused before writing any of it, such as an informational status or conflicting framing.
75    Response(base_http1::Error),
76    Io(std::io::Error),
77    Cancelled,
78    Negotiation(base_http_protocol::NegotiationError),
79}
80impl fmt::Display for Error {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        match self {
83            Self::Protocol(e) => e.fmt(f),
84            Self::Handler(_) => f.write_str("request handler failed"),
85            Self::Response(_) => f.write_str("request handler returned an invalid response"),
86            Self::Io(_) => f.write_str("HTTP server I/O failed"),
87            Self::Cancelled => f.write_str("HTTP server cancelled"),
88            Self::Negotiation(error) => error.fmt(f),
89        }
90    }
91}
92impl std::error::Error for Error {
93    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
94        match self {
95            Self::Protocol(e) => e.source(),
96            Self::Handler(e) => Some(e.as_ref()),
97            Self::Response(e) => Some(e),
98            Self::Io(e) => Some(e),
99            Self::Cancelled => None,
100            Self::Negotiation(error) => error.source(),
101        }
102    }
103}
104impl From<base_http1::Error> for Error {
105    fn from(e: base_http1::Error) -> Self {
106        match e {
107            base_http1::Error::Cancelled => Self::Cancelled,
108            e => Self::Protocol(e),
109        }
110    }
111}
112impl From<std::io::Error> for Error {
113    fn from(e: std::io::Error) -> Self {
114        Self::Io(e)
115    }
116}
117impl From<base_scheduler::OperationError> for Error {
118    fn from(error: base_scheduler::OperationError) -> Self {
119        base_http1::Error::from(error).into()
120    }
121}
122
123async fn cancellable<F: Future>(
124    future: F,
125    cancellation: &Cancellation,
126) -> Result<F::Output, Error> {
127    cancellation.check().map_err(|_| Error::Cancelled)?;
128    if cancellation.is_never() {
129        return Ok(future.await);
130    }
131    let mut future = pin!(future);
132    let mut cancelled = pin!(cancellation.cancelled());
133    poll_fn(|cx| {
134        if cancelled.as_mut().poll(cx).is_ready() {
135            return Poll::Ready(Err(Error::Cancelled));
136        }
137        future.as_mut().poll(cx).map(Ok)
138    })
139    .await
140}
141
142/// Own one accepted socket through request admission, handler execution, output,
143/// uncancellable lane retirement, and finally exclusive socket closure. Await
144/// completion after cooperative cancellation; dropping this future is not a join.
145pub async fn serve_connection<S, H>(
146    scheduler: &S,
147    socket: S::Socket,
148    handler: &H,
149    options: &base_http1::Options,
150    cancellation: &Cancellation,
151) -> Result<u64, Error>
152where
153    S: SocketStreamReading + SocketStreamWriting + SocketClose,
154    S::Socket: 'static,
155    H: Handler,
156{
157    let outcome = connection_outcome(scheduler, socket, handler, options, cancellation).await;
158    outcome.result.map(|()| outcome.requests)
159}
160
161struct Outcome {
162    requests: u64,
163    result: Result<(), Error>,
164}
165
166async fn connection_outcome<S, H>(
167    scheduler: &S,
168    socket: S::Socket,
169    handler: &H,
170    options: &base_http1::Options,
171    cancellation: &Cancellation,
172) -> Outcome
173where
174    S: SocketStreamReading + SocketStreamWriting + SocketClose,
175    S::Socket: 'static,
176    H: Handler,
177{
178    let mut requests = 0;
179    // The outer owner closes the socket even when capability construction fails.
180    let result = async {
181        options.validate()?;
182        let mut reader = scheduler.socket_stream_reader(&socket)?;
183        let writer = match scheduler.socket_stream_writer(&socket) {
184            Ok(writer) => writer,
185            Err(error) => {
186                // Report the construction failure in preference to a retirement failure.
187                let _ = ReadableStream::close(&mut reader).await;
188                return Err(Error::Io(error));
189            }
190        };
191        // The plaintext engine is HTTP/1, whose codecs fail to construct only when `options.validate` above fails.
192        let (input, output) = base_http_protocol::HttpProtocols::default()
193            .plaintext()
194            .server(reader, writer, options.clone())
195            .expect("validated HTTP/1 options construct server codecs");
196        serve_codecs(
197            input,
198            output,
199            handler,
200            cancellation,
201            &mut requests,
202            &Scheme::HTTP,
203        )
204        .await
205    }
206    .await;
207    let closed = scheduler.socket_close(socket).await;
208    Outcome {
209        requests,
210        result: result.and_then(|()| Ok(closed?)),
211    }
212}
213
214/// Serve exchanges over established codecs, then retire both directions.
215async fn serve_codecs<R, W, H>(
216    mut input: RequestReader<R>,
217    mut output: ResponseWriter<W>,
218    handler: &H,
219    cancellation: &Cancellation,
220    count: &mut u64,
221    scheme: &Scheme,
222) -> Result<(), Error>
223where
224    R: ReadableStream,
225    W: WritableStream,
226    H: Handler,
227{
228    let result = exchanges(
229        &mut input,
230        &mut output,
231        handler,
232        cancellation,
233        count,
234        scheme,
235    )
236    .await;
237    // Both cleanups run regardless of the exchange or the other cleanup result.
238    let input_closed = input.close().await;
239    let output_closed = output.close().await;
240    result?;
241    input_closed?;
242    output_closed?;
243    Ok(())
244}
245
246async fn exchanges<R, W, H>(
247    input: &mut RequestReader<R>,
248    output: &mut ResponseWriter<W>,
249    handler: &H,
250    cancellation: &Cancellation,
251    count: &mut u64,
252    scheme: &Scheme,
253) -> Result<(), Error>
254where
255    R: ReadableStream,
256    W: WritableStream,
257    H: Handler,
258{
259    loop {
260        let (mut request, exchange) = match input.read_request(cancellation).await {
261            Ok(Some(pair)) => pair,
262            Ok(None) => return Ok(()),
263            Err(error) => {
264                if !matches!(error, base_http1::Error::Cancelled) {
265                    let status = if matches!(error, base_http1::Error::Limit(_)) {
266                        Status::new(431).unwrap()
267                    } else {
268                        Status::BAD_REQUEST
269                    };
270                    // A bad request cannot be reused even if rejection succeeds.
271                    let _ = output.reject(status, cancellation).await;
272                }
273                return Err(error.into());
274            }
275        };
276        request.scheme.get_or_insert_with(|| scheme.clone());
277        let result = {
278            let mut interim = InterimResponses::new(output.interim(exchange.version()));
279            let result = {
280                let called = pin!(handler.call(request, &mut interim, cancellation));
281                cancellable(called, cancellation).await?
282            };
283            interim.finish();
284            result
285        };
286        // The response can borrow the input lane, which a failure inspects, so only a successful result leaves this block.
287        let mut response = 'handled: {
288            let error = match result.map_err(|error| Error::Handler(Box::new(error))) {
289                Ok(response) => break 'handled response,
290                Err(error) => error,
291            };
292            // Cancellation can arrive between polling the outer signal and polling a handler which propagates a cancelled body operation.
293            if cancellation.is_cancelled() {
294                return Err(Error::Cancelled);
295            }
296            // No final head has been written, so the peer can still learn of the failure: failed request input means the request was bad, and anything else is a server error. The input position may be unknown and unread input may remain, so the connection always closes afterwards; output already out of sync refuses the response and just closes.
297            let status = if input.is_poisoned() {
298                Status::BAD_REQUEST
299            } else {
300                Status::INTERNAL_SERVER_ERROR
301            };
302            let _ = output.reject(status, cancellation).await;
303            return Err(error);
304        };
305        let written = output
306            .write_response(&exchange, &mut response, cancellation)
307            .await;
308        drop(response);
309        let persistent = match written {
310            Ok(persistent) => persistent,
311            // Usable output means the writer refused the response before writing any of it, so the peer can still learn of the failure; the request may be unfinished, so the connection closes afterwards.
312            Err(error) if output.is_usable() && !matches!(error, base_http1::Error::Cancelled) => {
313                let _ = output
314                    .reject(Status::INTERNAL_SERVER_ERROR, cancellation)
315                    .await;
316                return Err(Error::Response(error));
317            }
318            Err(error) => return Err(error.into()),
319        };
320        *count += 1;
321        if !persistent || !input.is_reusable() {
322            return Ok(());
323        }
324    }
325}
326
327#[derive(Default, Debug)]
328pub struct Statistics {
329    pub connections: u64,
330    pub requests: u64,
331    pub failed_connections: u64,
332    /// Transient accept failures which the listener survived, such as a peer
333    /// aborting before acceptance or descriptor exhaustion.
334    pub accept_errors: u64,
335}
336
337/// How long acceptance pauses after a resource-exhaustion failure. The pending
338/// peer stays queued in the backlog, while existing connections progress and
339/// may release descriptors or memory meanwhile.
340const ACCEPT_BACKOFF: Duration = Duration::from_millis(100);
341
342/// Consecutive immediate retries before acceptance backs off anyway. A
343/// failure which completes without suspending would otherwise spin within one
344/// poll, starving connections and cancellation, if it persists rather than
345/// belonging to one peer.
346const ACCEPT_RETRIES: u32 = 16;
347
348/// How a listener responds to a failed accept.
349#[derive(Debug, PartialEq, Eq)]
350enum AcceptFailure {
351    /// The failure belongs to one pending peer or interrupted the call; the
352    /// next accept can succeed immediately.
353    Retry,
354    /// The process or system lacks descriptors, buffers or memory; wait before
355    /// accepting again so the loop does not spin.
356    Backoff,
357    /// The listener itself or the scheduler failed; stop serving.
358    Fatal,
359}
360
361fn accept_failure(error: &io::Error) -> AcceptFailure {
362    use io::ErrorKind::*;
363    match error.kind() {
364        ConnectionAborted | ConnectionReset | ConnectionRefused | Interrupted | WouldBlock
365        | NetworkDown | NetworkUnreachable | HostUnreachable => return AcceptFailure::Retry,
366        OutOfMemory => return AcceptFailure::Backoff,
367        _ => {}
368    }
369    match error.raw_os_error() {
370        Some(code) if os::RETRY.contains(&code) => AcceptFailure::Retry,
371        Some(code) if os::BACKOFF.contains(&code) => AcceptFailure::Backoff,
372        _ => AcceptFailure::Fatal,
373    }
374}
375
376/// Error codes which `io::ErrorKind` leaves uncategorized.
377#[cfg(unix)]
378mod os {
379    /// Linux reports pending network errors of the new connection from
380    /// `accept`, and documents that they should be retried, and reports a
381    /// firewall rejection of the pending peer as `EPERM`. `EACCES`, which
382    /// shares its `io::ErrorKind`, is left fatal: a security module denying
383    /// every accept would otherwise loop silently.
384    #[cfg(any(target_os = "linux", target_os = "android"))]
385    pub const RETRY: &[i32] = &[
386        libc::EPERM,
387        libc::EPROTO,
388        libc::ENOPROTOOPT,
389        libc::EHOSTDOWN,
390        libc::ENONET,
391    ];
392    #[cfg(not(any(target_os = "linux", target_os = "android")))]
393    pub const RETRY: &[i32] = &[libc::EPROTO];
394
395    pub const BACKOFF: &[i32] = &[libc::EMFILE, libc::ENFILE, libc::ENOBUFS];
396}
397
398/// Winsock codes which `io::ErrorKind` leaves uncategorized.
399#[cfg(windows)]
400mod os {
401    /// `WSAEINTR`.
402    pub const RETRY: &[i32] = &[10004];
403    /// `WSAEMFILE` and `WSAENOBUFS`.
404    pub const BACKOFF: &[i32] = &[10024, 10055];
405}
406
407#[cfg(not(any(unix, windows)))]
408mod os {
409    pub const RETRY: &[i32] = &[];
410    pub const BACKOFF: &[i32] = &[];
411}
412
413/// Accept one peer, surviving transient failures. Cancellation and scheduler
414/// closure arrive as errors from the accept or the backoff wait; a failed
415/// backoff wait also ends acceptance, since retrying without one could spin.
416async fn accept<S: SocketAccept + Clock>(
417    scheduler: &S,
418    listener: &S::Socket,
419    accept_errors: &mut u64,
420    cancellation: &Cancellation,
421) -> Result<S::Socket, OperationError> {
422    let mut retries = 0;
423    loop {
424        let error = match scheduler.socket_accept(listener, cancellation).await {
425            Ok((socket, _)) => return Ok(socket),
426            Err(OperationError::Io(error)) => error,
427            Err(error) => return Err(error),
428        };
429        let backoff = match accept_failure(&error) {
430            AcceptFailure::Retry => {
431                retries += 1;
432                retries > ACCEPT_RETRIES
433            }
434            AcceptFailure::Backoff => true,
435            AcceptFailure::Fatal => return Err(error.into()),
436        };
437        *accept_errors += 1;
438        if backoff {
439            retries = 0;
440            scheduler.sleep(ACCEPT_BACKOFF, cancellation).await?;
441        }
442    }
443}
444
445/// Serve an already-bound listener with bounded concurrent connection futures.
446/// No hidden tasks or detached jobs are created. Cancellation stops acceptance,
447/// closes the owned listener, then cancels active exchanges and joins their
448/// retirement. Individual peer/handler failures are counted without stopping
449/// other peers.
450///
451/// Transient accept failures do not stop the listener: those belonging to one
452/// pending peer, such as `ECONNABORTED`, are retried immediately, and resource
453/// exhaustion, such as `EMFILE`, pauses acceptance briefly on the scheduler's
454/// clock while existing connections continue. Repeated per-peer failures also
455/// pause after a few immediate retries, so a persistent one cannot spin. Both
456/// are counted in [`Statistics::accept_errors`]. Any other accept failure,
457/// including scheduler closure or a failed backoff wait, is returned after the
458/// same shutdown sequence, in preference to a listener close failure.
459pub async fn serve<S, H>(
460    scheduler: &S,
461    listener: S::Socket,
462    handler: &H,
463    options: &Options,
464    cancellation: &Cancellation,
465) -> Result<Statistics, Error>
466where
467    S: SocketAccept
468        + Clock
469        + SocketSetOption<TcpNoDelay>
470        + SocketStreamReading
471        + SocketStreamWriting
472        + SocketClose,
473    S::Socket: 'static,
474    H: Handler,
475{
476    serve_with(
477        scheduler,
478        listener,
479        options,
480        cancellation,
481        |socket, cancellation| async move {
482            connection_outcome(scheduler, socket, handler, &options.http1, &cancellation).await
483        },
484    )
485    .await
486}
487
488async fn serve_with<S, F, Fut>(
489    scheduler: &S,
490    listener: S::Socket,
491    options: &Options,
492    cancellation: &Cancellation,
493    connection: F,
494) -> Result<Statistics, Error>
495where
496    S: SocketAccept + Clock + SocketSetOption<TcpNoDelay> + SocketClose,
497    F: Fn(S::Socket, Cancellation) -> Fut,
498    Fut: Future<Output = Outcome>,
499{
500    let connections_cancel = cancellation.child();
501    let mut connections = FuturesUnordered::new();
502    let mut statistics = Statistics::default();
503    // Counted apart from `statistics`, which completions update while an accept is pending.
504    let mut accept_errors = 0;
505    let result = async {
506        options.http1.validate()?;
507        if options.max_connections == 0 {
508            return Err(Error::Protocol(base_http1::Error::Invalid(
509                "zero connection limit",
510            )));
511        }
512        loop {
513            if connections.len() == options.max_connections {
514                record(
515                    &mut statistics,
516                    cancellable(connections.next(), cancellation)
517                        .await?
518                        .unwrap(),
519                );
520                continue;
521            }
522            // Keep this accept alive while existing connections complete. A
523            // backend may already own an accepted socket between polls; dropping
524            // and recreating acceptance can silently discard that new peer.
525            let accepted = {
526                let mut accept = pin!(accept(
527                    scheduler,
528                    &listener,
529                    &mut accept_errors,
530                    cancellation
531                ));
532                loop {
533                    let completed = pin!(async {
534                        if connections.is_empty() {
535                            std::future::pending().await
536                        } else {
537                            connections.next().await.unwrap()
538                        }
539                    });
540                    match select(accept.as_mut(), completed).await {
541                        Either::Left((result, _)) => break result,
542                        Either::Right((result, _)) => record(&mut statistics, result),
543                    }
544                }
545            };
546            let socket = accepted.map_err(base_http1::Error::from)?;
547            statistics.connections += 1;
548            let cancellation = connections_cancel.clone();
549            connections.push(async {
550                if options.tcp_nodelay {
551                    // A latency hint only: the connection is served without it.
552                    let _ = scheduler
553                        .socket_set_option(&socket, TcpNoDelay, true, &cancellation)
554                        .await;
555                }
556                connection(socket, cancellation).await
557            });
558        }
559    }
560    .await;
561    statistics.accept_errors = accept_errors;
562    // Close the listener first, so new peers are refused rather than left
563    // queued in a backlog which nothing accepts while connections retire.
564    let closed = scheduler.socket_close(listener).await;
565    connections_cancel.cancel();
566    while let Some(result) = connections.next().await {
567        record(&mut statistics, result);
568    }
569    // The failure which ended serving explains a consequent close failure.
570    match result {
571        Ok(()) | Err(Error::Cancelled) => {
572            closed?;
573            Ok(statistics)
574        }
575        Err(error) => Err(error),
576    }
577}
578
579fn record(statistics: &mut Statistics, outcome: Outcome) {
580    statistics.requests += outcome.requests;
581    match outcome.result {
582        Ok(()) => {}
583        Err(Error::Cancelled) => {}
584        Err(_) => statistics.failed_connections += 1,
585    }
586}
587
588/// Serve HTTP/1 over an already authenticated TLS connection. ALPN is
589/// interpreted by HTTP before any request is admitted.
590#[cfg(feature = "rustls")]
591pub async fn serve_tls_connection<S, H>(
592    scheduler: &S,
593    connection: base_endpoint::tls::rustls::RustlsConnection<S::Socket>,
594    handler: &H,
595    options: &Options,
596    cancellation: &Cancellation,
597) -> Result<u64, Error>
598where
599    S: SocketStreamReading + SocketStreamWriting + SocketClose,
600    S::Socket: 'static,
601    H: Handler,
602{
603    let outcome =
604        tls_connection_outcome(scheduler, connection, handler, options, cancellation).await;
605    outcome.result.map(|()| outcome.requests)
606}
607
608#[cfg(feature = "rustls")]
609async fn tls_connection_outcome<S, H>(
610    scheduler: &S,
611    connection: base_endpoint::tls::rustls::RustlsConnection<S::Socket>,
612    handler: &H,
613    options: &Options,
614    cancellation: &Cancellation,
615) -> Outcome
616where
617    S: SocketStreamReading + SocketStreamWriting + SocketClose,
618    S::Socket: 'static,
619    H: Handler,
620{
621    use base_endpoint::tls::TlsSession;
622    let selected = connection.negotiated_protocol().map(<[u8]>::to_vec);
623    let mut requests = 0;
624    let result = connection
625        .with_duplex(scheduler, cancellation, |reader, writer| async {
626            let protocol = options.protocols.negotiated(selected.as_deref())?;
627            let (input, output) = protocol.server(reader, writer, options.http1.clone())?;
628            serve_codecs(
629                input,
630                output,
631                handler,
632                cancellation,
633                &mut requests,
634                &Scheme::HTTPS,
635            )
636            .await
637        })
638        .await;
639    Outcome { requests, result }
640}
641
642/// Configure HTTP/1 ALPN and serve TLS on an owned TCP listener. Handshakes
643/// occupy bounded connection slots and progress concurrently with acceptance.
644#[cfg(feature = "rustls")]
645pub async fn serve_tls<S, H>(
646    scheduler: &S,
647    listener: S::Socket,
648    config: std::sync::Arc<rustls::ServerConfig>,
649    handler: &H,
650    options: &Options,
651    cancellation: &Cancellation,
652) -> Result<Statistics, Error>
653where
654    S: SocketAccept
655        + Clock
656        + SocketSetOption<TcpNoDelay>
657        + SocketStreamReading
658        + SocketStreamWriting
659        + SocketClose,
660    S::Socket: 'static,
661    H: Handler,
662{
663    use base_endpoint::tls::{TlsAcceptor, rustls::RustlsAcceptor};
664    let mut config = (*config).clone();
665    config.alpn_protocols = options.protocols.alpn_protocols();
666    let acceptor = RustlsAcceptor::new(std::sync::Arc::new(config));
667    serve_with(
668        scheduler,
669        listener,
670        options,
671        cancellation,
672        |socket, cancellation| {
673            let acceptor = &acceptor;
674            async move {
675                match acceptor.accept(scheduler, socket, &cancellation).await {
676                    Ok(connection) => {
677                        tls_connection_outcome(
678                            scheduler,
679                            connection,
680                            handler,
681                            options,
682                            &cancellation,
683                        )
684                        .await
685                    }
686                    Err(error) => Outcome {
687                        requests: 0,
688                        result: Err(error.into()),
689                    },
690                }
691            }
692        },
693    )
694    .await
695}
696
697impl From<base_http_protocol::NegotiationError> for Error {
698    fn from(error: base_http_protocol::NegotiationError) -> Self {
699        Self::Negotiation(error)
700    }
701}
702
703#[cfg(test)]
704mod tests {
705    use super::*;
706    use std::{error::Error as _, io};
707
708    #[test]
709    fn protocol_errors_are_transparent_and_io_errors_have_context() {
710        let error = Error::from(base_http1::Error::Io(io::Error::from(
711            io::ErrorKind::ConnectionReset,
712        )));
713        assert_eq!(error.to_string(), "HTTP/1 connection I/O failed");
714        assert_eq!(error.source().unwrap().to_string(), "connection reset");
715
716        let error = Error::from(io::Error::from(io::ErrorKind::ConnectionReset));
717        assert_eq!(error.to_string(), "HTTP server I/O failed");
718        assert_eq!(error.source().unwrap().to_string(), "connection reset");
719    }
720
721    #[test]
722    fn handler_errors_are_reported_as_the_source() {
723        let error = Error::Handler(Box::new(io::Error::other("database unavailable")));
724        assert_eq!(error.to_string(), "request handler failed");
725        assert_eq!(error.source().unwrap().to_string(), "database unavailable");
726    }
727
728    #[test]
729    fn invalid_responses_report_the_refusal_as_the_source() {
730        let error = Error::Response(base_http1::Error::Invalid("final response required"));
731        assert_eq!(
732            error.to_string(),
733            "request handler returned an invalid response"
734        );
735        assert_eq!(
736            error.source().unwrap().to_string(),
737            "invalid HTTP/1 message: final response required"
738        );
739    }
740
741    #[test]
742    fn scheduler_operation_errors_keep_cancellation_distinct() {
743        let error = Error::from(base_scheduler::OperationError::from(
744            base_scheduler::Cancelled,
745        ));
746        assert_eq!(format!("{error:?}"), "Cancelled");
747        assert_eq!(error.to_string(), "HTTP server cancelled");
748        assert!(error.source().is_none());
749
750        let error = Error::from(base_scheduler::OperationError::from(io::Error::from(
751            io::ErrorKind::ConnectionReset,
752        )));
753        let expected = Error::Protocol(base_http1::Error::Io(io::Error::from(
754            io::ErrorKind::ConnectionReset,
755        )));
756        assert_eq!(format!("{error:?}"), format!("{expected:?}"));
757        assert_eq!(error.source().unwrap().to_string(), "connection reset");
758    }
759
760    #[test]
761    fn accept_failures_are_classified_by_their_effect_on_the_listener() {
762        for kind in [
763            io::ErrorKind::ConnectionAborted,
764            io::ErrorKind::ConnectionReset,
765            io::ErrorKind::ConnectionRefused,
766            io::ErrorKind::Interrupted,
767            io::ErrorKind::WouldBlock,
768            io::ErrorKind::NetworkDown,
769            io::ErrorKind::NetworkUnreachable,
770            io::ErrorKind::HostUnreachable,
771        ] {
772            assert_eq!(
773                accept_failure(&kind.into()),
774                AcceptFailure::Retry,
775                "{kind:?}"
776            );
777        }
778        assert_eq!(
779            accept_failure(&io::ErrorKind::OutOfMemory.into()),
780            AcceptFailure::Backoff
781        );
782        for kind in [
783            io::ErrorKind::InvalidInput,
784            io::ErrorKind::BrokenPipe,
785            io::ErrorKind::PermissionDenied,
786            io::ErrorKind::Other,
787        ] {
788            assert_eq!(
789                accept_failure(&kind.into()),
790                AcceptFailure::Fatal,
791                "{kind:?}"
792            );
793        }
794        for &code in os::RETRY {
795            let error = io::Error::from_raw_os_error(code);
796            assert_eq!(accept_failure(&error), AcceptFailure::Retry, "{error}");
797        }
798        for &code in os::BACKOFF {
799            let error = io::Error::from_raw_os_error(code);
800            assert_eq!(accept_failure(&error), AcceptFailure::Backoff, "{error}");
801        }
802        #[cfg(unix)]
803        for code in [
804            libc::EBADF,
805            libc::EINVAL,
806            libc::ENOTSOCK,
807            libc::EOPNOTSUPP,
808            libc::EACCES,
809        ] {
810            let error = io::Error::from_raw_os_error(code);
811            assert_eq!(accept_failure(&error), AcceptFailure::Fatal, "{error}");
812        }
813    }
814
815    #[test]
816    fn negotiation_errors_are_transparent() {
817        let error = Error::from(base_http_protocol::NegotiationError::Missing);
818        assert_eq!(format!("{error:?}"), "Negotiation(Missing)");
819        assert_eq!(
820            error.to_string(),
821            "HTTP application protocol was not negotiated"
822        );
823        assert!(error.source().is_none());
824    }
825}