Using stream.peek() instead of stream.read() gives: Connection reset by peer

I have a basic HTTP server based on Tokio in which the programmer can define routes:

    // Define all your routes here.
    let routes: Arc<std::vec::Vec<Route>> = Arc::new(vec![
        route!("GET", "/basic-get", Routes::basic_get),
        route!("GET", "/start-websocket", Routes::start_websocket),
    ]);

Each route has a matching Route::function. All Route-functions have the same signature:

// Route functions

pub async fn basic_get(&self, req: Http, stream: &mut TcpStream, psql: Psql) -> Result<HttpResponse, String> {
    //...

    // Return GET response
    Ok(HttpResponse{
        status: 200,
        headers: vec![
            ("Content-Type".into(), "text/plain".into()),
        ],
        body: "Hello".into(),
    })
}

pub async fn start_websocket(&self, req: Http, stream: &mut TcpStream, psql: Psql) -> Result<HttpResponse, String> {
    // Upgrade HTTP connection to websocket
    let ws_stream = tokio_tungstenite::accept_async(stream).await.expect("Failed to accept");
    let (mut send, mut recv) = ws_stream.split();
    
     //...

    // Return GET response
    Ok(HttpResponse{
        status: 200,
        headers: vec![
            ("Content-Type".into(), "text/plain".into()),
        ],
        body: "Hello".into(),
    })
}

Some routes are used as REST-api and are meant to just simply return a HTTP-response. But there are also routes which are meant to create a websocket-connection from the HTTP-request and send data back to the client in a stream.

The send in let (mut send, mut recv) = ws_stream.split(); is responsible for sending data back to the client in a websocket route.

The reason the Route-functions have stream: &mut TcpStream as parameter is because I need the incoming stream to upgrade the HTTP-request to a websocket.

Incoming streams are processed like this:

// Process the incoming stream.
async fn process(stream: &mut TcpStream, psql: Psql, routes: &std::vec::Vec<Route>) -> Result<(), Box<dyn Error>> {
 
    // Buffer holding the stream in bytes.
    let mut buffer = [0; 1024];
    if let Err(e) = stream.peek(&mut buffer).await {
        eprintln!("Error: {}", e);
        
        let response = Routes::internal_server_error().await;
        stream.write_all(response.to_string().as_bytes()).await.unwrap();
        stream.flush().await.unwrap();
        return Ok(());
    }
   
    // Create a string from the contents of the buffer.
    let stream_string = String::from_utf8_lossy(&buffer[..]);

    // Turn the raw stream as string into a workable Http-object.
    let mut http = Http::from_str(&stream_string);
    
    // Check if there is a route matching the HTTP-method and path.
    let response = match RouteHandler::goto(&mut http, stream, psql, routes).await {
        Ok(r) => r,
        Err(e) => {
            Routes::internal_server_error().await
        }
    };

    // Write response to stream as answer.
    if stream.write_all(format!("{}\n", response).as_bytes()).await.is_err() {
        stream.flush().await.unwrap();
        return Ok(());
    }
    stream.flush().await.unwrap();

    Ok(())
}

The part with the comment // Write response to stream as answer. is just used to return a HTTP response.

I use stream.peek() because IF a websocket-route is called I need to be able to pass the whole stream to the websocket route function. Using stream.read() consumes the content of the stream making it unable to upgrade it to a websocket.

This code works, but the problem is that for basic HTTP requests I get the error:

curl: (56) Recv failure: Connection reset by peer

Both the websockets and REST-API work fine, also when using it in the web-app, but CURL and Postman give this error. stream.peek() is the cause of this.

How to sovle this?

I don't know for sure, but it seems that because you used peek, the receive buffer is not empty when the tcp socket is dropped, which cause the tcpip stack to treat it as abort (tcp segment with RST flag) instead of close (FIN flag). just my guess.

you can try to either:

a. call read on the tcp stream when it's decided not a websocket to drain the socket buffer, or:

b. call shutdown manually after the response is sent to client, to force the tcpip stack send a FIN packet.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.