hyperium/hyper · error · hyper::Error

received unexpected message from connection

Error message

received unexpected message from connection

What it means

Thrown via Error::new_unexpected_message() (src/error.rs:382, Kind::UnexpectedMessage), HTTP/1 only. It means the connection delivered a full new message (request or response) at a point in the state machine where none was expected — e.g. the server already moved on or the client got a second response head while not pipelining. Produced at proto/h1/conn.rs:465 and :488, and proto/h1/dispatch.rs:712.

Source

Thrown at src/error.rs:382

    #[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
    pub(super) fn new_incomplete() -> Error {
        Error::new(Kind::IncompleteMessage)
    }

    #[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
    pub(super) fn new_too_large() -> Error {
        Error::new(Kind::Parse(Parse::TooLarge))
    }

    #[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
    pub(super) fn new_version_h2() -> Error {
        Error::new(Kind::Parse(Parse::VersionH2))
    }

    #[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
    pub(super) fn new_unexpected_message() -> Error {
        Error::new(Kind::UnexpectedMessage)
    }

    #[cfg(all(
        any(feature = "client", feature = "server"),
        any(feature = "http1", feature = "http2")
    ))]
    pub(super) fn new_io(cause: std::io::Error) -> Error {
        Error::new(Kind::Io).with(cause)
    }

    #[cfg(any(
        all(feature = "http1", any(feature = "client", feature = "server")),
        all(feature = "http2", feature = "client")
    ))]
    pub(super) fn new_closed() -> Error {
        Error::new(Kind::ChannelClosed)
    }

View on GitHub (pinned to 084473f728)

Solutions

  1. Capture the peer's exact bytes with a packet capture (tcpdump/wireshark) to confirm it is sending data outside the expected HTTP/1 framing.
  2. If you control the peer, fix its request/response sequencing; otherwise treat the connection as corrupt and close it.
  3. Disable HTTP/1 pipelining assumptions and reuse the connection conservatively (open a fresh connection after such an error).

Example fix

// before: any connection error terminates the whole client
let resp = sender.send_request(req).await?;

// after: drop the corrupt connection and start a new one
match sender.send_request(req).await {
    Err(e) => {
        if !e.is_incomplete_message() {
            // unexpected_message etc. => connection is unusable, reconnect
        }
        return Err(e);
    }
    Ok(r) => Ok(r),
}
Defensive patterns

Strategy: try-catch

Type guard

// UnexpectedMessage isn't exposed as is_*(); match the description/source only
// for logging — do NOT depend on the string. Treat unknown connection errors
// as fatal for that connection.
fn is_connection_corrupt(_e: &hyper::Error) -> bool { /* not stable to detect */ false }

Try / catch

if let Err(e) = sender.send_request(req).await {
    // unexpected_message means the connection framing is broken
    drop_connection_and_reconnect();
    return Err(e);
}

Prevention

When it happens

Trigger: A client receives a response head while hyper is not waiting for one (conn.rs:465/488); the HTTP/1 dispatch loop gets a message when it expected a body continuation or idle (dispatch.rs:712). Often tied to an ill-behaved peer sending extra data, or a keep-alive reuse race after an error.

Common situations: A server that pipelines responses incorrectly; a buggy proxy injecting an extra response; a peer that sends a second request head inside what hyper thought was a body; protocol confusion after a 101 Upgrade or an informational response.

Related errors


AI-assisted analysis of hyperium/hyper@084473f728 (2026-08-06). Data as JSON: /data/errors/ad40d9e71689a1cc.json. Report an issue: GitHub.