seanmonstar/reqwest · error · reqwest::Error

error upgrading connection

Error message

error upgrading connection

What it means

The `Kind::Upgrade` error from `error::upgrade(...)` (error.rs:401-403). It is returned by `Response::upgrade()` (upgrade.rs:69-74) when the underlying `hyper::upgrade::on(...)` future resolves to an error — i.e. the connection could not be switched to a tunneled protocol.

Source

Thrown at src/error.rs:402

    .with_url(url)
}

pub(crate) fn url_bad_scheme(url: Url) -> Error {
    Error::new(Kind::Builder, Some(BadScheme)).with_url(url)
}

pub(crate) fn url_invalid_uri(url: Url) -> Error {
    Error::new(Kind::Builder, Some("Parsed Url is not a valid Uri")).with_url(url)
}

if_wasm! {
    pub(crate) fn wasm(js_val: wasm_bindgen::JsValue) -> BoxError {
        format!("{js_val:?}").into()
    }
}

pub(crate) fn upgrade<E: Into<BoxError>>(e: E) -> Error {
    Error::new(Kind::Upgrade, Some(e))
}

// io::Error helpers

#[allow(unused)]
pub(crate) fn decode_io(e: io::Error) -> Error {
    if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(false) {
        *e.into_inner()
            .expect("io::Error::get_ref was Some(_)")
            .downcast::<Error>()
            .expect("StdError::is() was true")
    } else {
        decode(e)
    }
}

// internal Error "sources"

View on GitHub (pinned to 17e9bcb51c)

Solutions

  1. Only call `.upgrade()` after confirming `resp.status() == 101` and that the server agreed to the protocol in its `Upgrade` header.
  2. Use a dedicated WebSocket crate (`tungstenite`) on top of reqwest rather than hand-rolling upgrades.
  3. Inspect `e.source()` for the hyper upgrade error to see why the oneshot channel never received the upgraded IO.

Example fix

// before
let upgraded = resp.upgrade().await?; // fails: not actually a 101

// after
if resp.status() != 101 {
    anyhow::bail!("expected 101 Switching Protocols, got {}", resp.status());
}
if !resp.headers().get("upgrade").map(|v| v == "websocket").unwrap_or(false) {
    anyhow::bail!("server did not agree to upgrade");
}
let upgraded = resp.upgrade().await?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_upgradable(resp: &reqwest::Response, proto: &str) -> bool {
    resp.status() == 101
        && resp.headers().get("upgrade")
            .map(|v| v.to_str().map(|s| s.eq_ignore_ascii_case(proto)).unwrap_or(false))
            .unwrap_or(false)
}
// guard before .upgrade()
if !is_upgradable(&resp, "websocket") { return Err(anyhow!("not a 101 upgrade")); }

Type guard

fn is_upgrade_error(e: &reqwest::Error) -> bool { e.is_upgrade() }

Try / catch

let upgraded = match resp.upgrade().await {
    Ok(u) => u,
    Err(e) if e.is_upgrade() => return Err(anyhow!("upgrade failed: {}",
        e.source().map(|s| s.to_string()).unwrap_or_default())),
    Err(e) => return Err(e.into()),
};

Prevention

When it happens

Trigger: Calling `resp.upgrade().await` on a response that was not actually a 101 Switching Protocols (or 200 with agreed upgrade), or where the peer never sent the expected upgrade bytes. Common for WebSocket-style handshakes where the server rejected the upgrade.

Common situations: Trying to `.upgrade()` a normal 200 response; server replied 426/400 to a WebSocket handshake; missing `Connection: Upgrade` / `Upgrade:` headers; proxy stripping upgrade headers.

Related errors


AI-assisted analysis of seanmonstar/reqwest@17e9bcb51c (2026-08-06). Data as JSON: /data/errors/effd94a874f157ac.json. Report an issue: GitHub.