Skip to content

Consider exposing the HTTP status code for the first server response within the handshake #499

@str4d

Description

@str4d

I'm encountering a problem where I can connect to a server fine with tokio-tungstenite and maintain that connection, but at some point the stream gets reset (presumably due to load or the server being restarted) and my client disconnects. When my reconnection logic runs, I am generally seeing the error WebSocket protocol error: HTTP version must be 1.1 or higher:

tungstenite-rs/src/error.rs

Lines 176 to 178 in 255aaa2

/// Wrong HTTP version used (the WebSocket protocol requires version 1.1 or higher).
#[error("HTTP version must be 1.1 or higher")]
WrongHttpVersion,

AFAICT this error is raised in two places during the client-side handshake:

  • A check on client requests (which I'm confident are using HTTP 1.1 because I'm calling tokio_tungstenite::connect_async with a string URL, so the client HTTP version should always be the default of 1.1):
    if request.version() < http::Version::HTTP_11 {
    return Err(Error::Protocol(ProtocolError::WrongHttpVersion));
    }
  • A check on server responses:
    impl<'h, 'b: 'h> FromHttparse<httparse::Response<'h, 'b>> for Response {
    fn from_httparse(raw: httparse::Response<'h, 'b>) -> Result<Self> {
    if raw.version.expect("Bug: no HTTP version") < /*1.*/1 {
    return Err(Error::Protocol(ProtocolError::WrongHttpVersion));
    }
    let headers = HeaderMap::from_httparse(raw.headers)?;
    let mut response = Response::new(None);
    *response.status_mut() = StatusCode::from_u16(raw.code.expect("Bug: no HTTP status code"))?;
    *response.headers_mut() = headers;
    // TODO: httparse only supports HTTP 0.9/1.0/1.1 but not HTTP 2.0
    // so the only valid value we could get in the response would be 1.1.
    *response.version_mut() = http::Version::HTTP_11;
    Ok(response)
    }
    }

Looking at the server response, I noticed that the HTTP version check occurs before the status code is inspected. HTTP 1.0 does have status codes, and my current hypothesis is that the server (or maybe the reverse proxy in front of it) is returning a 500 error like 503 while the underlying server is down, and doing so with HTTP 1.0 instead of HTTP 1.1 (maybe because the HTTP server generating the error is written for a trivial baseline). But because tungstenite is checking the HTTP version first and rejecting on that, the HTTP status code is completely dropped.

Given that the HTTP version of an HTTP 1.* server is "negotiated" in its first reply, it would be very helpful if tungstenite returned more details in this situation. I see two possible resolutions:

  • It could return the server's status code inside ProtocolError::WrongHttpVersion.
  • It could perform the HTTP version check in two parts:
    • Check for HTTP 1.* (so that status codes exist).
    • Check for a WebSocket-compatible status code appropriate to the handshake phase (i.e. 101 Switching Protocols for the first response message), and return a ProtocolError if the wrong status code is observed (e.g. 200, or 503).
    • Check for HTTP 1.1 (so that Upgrade is supported).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions