'Rust Tcpstream: reading http request sometimes lose the body

I am learning how to write a http server using tcpstream with rust.

I use this function to read stream buffer.

fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 1024];

    stream.read(&mut buffer).unwrap();

    println!("Request: {}", String::from_utf8_lossy(&buffer[..]));

    let response = "HTTP/1.1 200 OK\r\n\r\n";

    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

But I found that, sometimes, the buffer lost the body.

It should be:

Request: POST /echo HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: python-requests/2.26.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

id=1&name=Foo

But when error,

Request: POST /echo HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: python-requests/2.26.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

It only lose the body part.

I use python3 requests to send requests.

What caused the error?



Solution 1:[1]

I'm writing my own rust reverse proxy as a means to learn the language. I came across this problem and my solution is below.

The fundemental problem is read_to_end and read_to_string expect an EOF char which doesn't get sent in a HTTP request body. So as far as I can tell you need to write your own byte buffer and then exit once there's no data left.

I'm sure this is not optimized but will get you past this problem. Excuse my use of unwrap. Obviously if this is a production system make sure you handle those more gracefully.

 #[tokio::main]
 async fn main() {
     let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap(); 

     let upstream_server = env::var("UPSTREAM").unwrap();  

     loop {
         println!("initialize stream");
         let (mut stream, _) = listener.accept().await.unwrap(); 

         println!("stream readable?");
         stream.readable().await.unwrap();

         println!("read all from stream");
         let buf = read_all(&stream).await.unwrap();

         // connect to upstream
         println!("connect to upstream");
         let mut upstream_stream = TcpStream::connect(&upstream_server).await.unwrap();

         println!("upstream writable?");
         upstream_stream.writable().await.unwrap();

         // write request body from client to upstream
         println!("write stream request body to upstream");
         upstream_stream.write_all(&buf).await.unwrap();

         println!("upstream readable?");
         upstream_stream.readable().await.unwrap();

         // read upstream response
         println!("read all from upstream");
         let upstream_buf = read_all(&upstream_stream).await.unwrap(); 

         println!("shutdown upstream connection");
         upstream_stream.shutdown().await.unwrap();

         println!("upstream writable?");
         stream.writable().await.unwrap();

         println!("write stream buf upstream response");
         stream.write_all(&upstream_buf.as_bytes()).await.unwrap(); 

         println!("shutdown stream");
         stream.shutdown().await.unwrap();
     }
 }

 async fn read_all(stream: &TcpStream) -> Result<Vec<u8>, std::io::Error> {
     let mut buf: Vec<u8> = Vec::new();

     loop {
         // Creating the buffer **after** the `await` prevents it from
         // being stored in the async task.
         let mut tmp_buf = [0; 4096]; => [u8; 4096]

         // Try to read data, this may still fail with `WouldBlock`
         // if the readiness event is a false positive.
         match stream.try_read(&mut tmp_buf) {
             Ok(0) => break,
             Ok(_) => {
                 buf.extend_from_slice(&tmp_buf)
             }
             Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { 
                 break;
             }
             Err(e) => { => Error
                 return Err(e.into());
             }
         }

     }

     return Ok(std::str::from_utf8(&buf).unwrap().trim_matches(char::from(0)).as_bytes().to_vec())

 }



Solution 2:[2]

This a simple way to get body content. This code reads line by line of header until get a empty file, the Rust TCPStream is used here! After read the header, there is a Content-Length in reader, so i convert the char to integer and use the size to create a vector. So is filled by read_exact function.

let mut reader = BufReader::new(stream.try_clone().unwrap());
let mut name = String::new();
loop {
let r = reader.read_line(&mut name).unwrap();
    if r < 3 { //detect empty line
        break;
    }
}
let mut size = 0;
let linesplit = name.split("\n");
for l in linesplit {
    if l.starts_with("Content-Length") {
            let sizeplit = l.split(":");
            for s in sizeplit {
                if !(s.starts_with("Content-Length")) {
                    size = s.trim().parse::<usize>().unwrap(); //Get Content-Length
            }
        }
    }
}
let mut buffer = vec![0; size]; //New Vector with size of Content   
reader.read_exact(&mut buffer).unwrap(); //Get the Body Content.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jordan Shaw
Solution 2