seanmonstar/reqwest · error · reqwest::Error

error sending request

Error message

error sending request

What it means

Surfaced by the HTTP/3 client when sending a request over an already-pooled QUIC/H3 stream fails. It is constructed at h3_client/mod.rs:74 by wrapping the error from `pooled.send_request(req).await` in `Kind::Request`. Unlike the generic request error, this one is specific to the `http3` feature path and means the h3 layer (quinn/h3 crate) rejected or aborted the request stream.

Source

Thrown at src/async_impl/h3_client/mod.rs:74

        trace!("connecting to {key:?}...");
        let (driver, tx) = self.connector.connect(dest).await?;
        trace!("saving new pooled connection to {key:?}");
        Ok(self.pool.new_connection(lock, driver, tx))
    }

    async fn send_request(
        mut self,
        key: Key,
        req: Request<Body>,
    ) -> Result<Response<ResponseBody>, Error> {
        let mut pooled = match self.get_pooled_client(key).await {
            Ok(client) => client,
            Err(e) => return Err(error::request(e)),
        };
        pooled
            .send_request(req)
            .await
            .map_err(|e| Error::new(Kind::Request, Some(e)))
    }

    pub fn request(&self, mut req: Request<Body>) -> H3ResponseFuture {
        let pool_key = match pool::extract_domain(req.uri_mut()) {
            Ok(s) => s,
            Err(e) => {
                return H3ResponseFuture {
                    inner: SyncWrapper::new(Box::pin(future::ready(Err(e)))),
                }
            }
        };
        H3ResponseFuture {
            inner: SyncWrapper::new(Box::pin(self.clone().send_request(pool_key, req))),
        }
    }
}

impl Service<Request<Body>> for H3Client {

View on GitHub (pinned to 17e9bcb51c)

Solutions

  1. Inspect the wrapped source via `e.source()` to get the real h3/quinn error code (e.g. H3_EXCESSIVE_LOAD, connection error) and act on it.
  2. Retry with backoff — HTTP/3 stream errors are frequently transient; ensure `Client` retry policy or an outer retry loop handles it.
  3. If reproducible, fall back to HTTP/2 by building a second Client without `http3_*` and disabling the `http3` code path for that host.
  4. Verify the server actually advertises `h3` via Alt-Svc / ALPN and that UDP/443 is reachable; HTTP/3 is often silently blocked by networks.

Example fix

// before
let resp = client.get(url).send().await?; // opaque 'error sending request'

// after
match client.get(url).send().await {
    Ok(r) => { /* ... */ }
    Err(e) if e.is_request() => {
        if let Some(src) = e.source() {
            log::warn!("h3 send failed: {src}");
        }
        // retry or fall back to an h2-only client
    }
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: retry

Validate before calling

// No pre-check possible; verify the endpoint speaks h3 first.
// Use http3_prior_knowledge only on hosts you control.

Type guard

fn is_h3_send_error(e: &reqwest::Error) -> bool {
    e.is_request()
}

Try / catch

match client.get(&url).send().await {
    Ok(r) => Ok(r),
    Err(e) if e.is_request() => {
        if let Some(src) = e.source() { log::warn!("h3 send: {src}"); }
        retry_with_backoff().await
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Client built with `.http3_prior_knowledge()` or `.http3_only()` (feature `http3` enabled) and the outbound `send_request` on the QUIC stream errors: stream reset by peer, H3 protocol error (e.g. `H3_EXCESSIVE_LOAD`), GOAWAY from server, max stream count reached, or the pooled connection died mid-send.

Common situations: Talking to an HTTP/3 endpoint that rate-limits or rejects excessive streams; server sends GOAWAY and closes the connection; QUIC MTU/UDP blocked partway so h3 stream stalls and aborts; mismatched ALPN so the 'h3' connection isn't really HTTP/3.

Related errors


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