I have a local RUST server I'm building, and I'm using a local rest client to send messages to it. It just has some test output to console at the moment, and my function looks something like this:
fn handle_connection(mut stream: TcpStream){
let mut buf_reader = BufReader::with_capacity(16384, &mut stream);
let mut request_lines = buf_reader.lines().peekable();
let first_request_line = request_lines.next().unwrap().unwrap();
let parts: Vec<&str> = first_request_line[..].split(" ").collect();
if (parts[0] == "POST" && parts[2] == "HTTP/1.1") {
let mut buffer_results = HashMap::new();
let mut i = 0;
while (request_lines.peek().is_some()){
let newline = request_lines.next().unwrap().unwrap();
let newline_copy = newline.clone();
if newline_copy == "" {
continue;
} else if newline_copy.contains(":"){
let newline_string = newline_copy.clone().to_string().to_owned();
let newline_str = string_to_static_str(newline_string);
let templine: Vec<&str> = (newline_str.split(":")).clone().collect();
let key = templine[0];
let value = templine[1..].join(":");
buffer_results.insert(key, value.clone());
println!("Continuing Stream {}:{}", key, value);
} else {
let newline_clone = newline_copy.clone().to_string().to_owned();
let i_i = i32_to_static_str(i.clone());
buffer_results.insert(i_i, newline_clone);
println!("Continuing Stream {}:{:#?}", i.to_string(), newline_copy.to_string());
}
i += 1;
}
...
And I do a REST using RESTED that looks like this:
However, the console output only contains headers, no content:
POST CALL:[
"POST",
"/",
"HTTP/1.1",
]
Continuing Stream Host: 127.0.0.1:7878
Continuing Stream User-Agent: [[User-Agent]]
Continuing Stream Accept: HTTP/1.1
Continuing Stream Accept-Language: [[Language]],en;q=0.5
Continuing Stream Accept-Encoding: gzip, deflate, br, zstd
Continuing Stream content-type: application/x-www-form-urlencoded
Continuing Stream Content-Length: 27
Continuing Stream Origin: moz-extension://[[HASH]]
Continuing Stream Connection: keep-alive
Continuing Stream Sec-Fetch-Dest: empty
Continuing Stream Sec-Fetch-Mode: cors
Continuing Stream Sec-Fetch-Site: same-origin
Continuing Stream Priority: u=0
I cant fetch the data if I switch to multi-part form as the request type:
Continuing Stream content-type: multipart/form-data
Continuing Stream 13:"------[[hash]]"
Continuing Stream Content-Disposition: form-data; name="my_silly_request"
Continuing Stream 15:"void_angel"
Continuing Stream 16:"------[[hash]]--"
But if I use JSON or URLencoded Form, it gives the incomplete version above.
I've tried switching from default BufReader::new() to ::with_capaicty() with a larger capacity to hopefully bring in more lines, no success. I've tried adding more variables at the end assigned with request_lines.next().unwrap().unwrap();
to hopefully catch some lines that weren't in the buffer yet when my function started with no success. I tried doing buffer.read() and just got some errors about nothing being there.
So I'm assuming its something different about the transmission of these two form types I'm missing, but I have no clue what. I feel I should know, but my brain & search bar just aren't giving me answers.
You're using .lines().next()
which waits for a line ending before returning:
#[stable(feature = "rust1", since = "1.0.0")]
impl<B: BufRead> Iterator for Lines<B> {
type Item = Result<String>;
fn next(&mut self) -> Option<Result<String>> {
let mut buf = String::new();
match self.buf.read_line(&mut buf) {
Ok(0) => None,
Ok(_n) => {
if buf.ends_with('\n') {
buf.pop();
if buf.ends_with('\r') {
buf.pop();
}
}
Some(Ok(buf))
}
Err(e) => Some(Err(e)),
}
}
}
#[stable(feature = "rust1", since = "1.0.0")]
fn read_line(&mut self, buf: &mut String) -> Result<usize> {
unsafe { append_to_string(buf, |b| read_until(self, b'\n', b)) }
}
If you set a read timeout on the Stream
, and wait until it times out, you'll see that the multipart form data has a line ending and the URL encoded form doesn't.
use std::{
io::{prelude::*},
net::{TcpListener, TcpStream}, time::Duration,
};
fn handle_connection(mut stream: &TcpStream) {
stream.set_read_timeout(Some(Duration::from_secs(1))).unwrap();
let mut buffer = String::new();
stream.read_to_string(&mut buffer).unwrap();
println!("Request: {buffer:#?}\n");
}
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
handle_connection(&stream.unwrap());
}
}
Request: "POST / HTTP/1.1\r\nHost: 127.0.0.1:7878\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0\r\nAccept: */*\r\nAccept-Language: en-GB,en;q=0.5\r\nAccept-Encoding: gzip, deflate, br, zstd\r\ncontent-type: application/x-www-form-urlencoded\r\nContent-Length: 27\r\nOrigin: moz-extension://739eb095-cac3-45a9-be66-a909f910c0b2\r\nConnection: keep-alive\r\nSec-Fetch-Dest: empty\r\nSec-Fetch-Mode: cors\r\nSec-Fetch-Site: same-origin\r\nPriority: u=0\r\n\r\nmy_silly_request=void_angel"
Request: "POST / HTTP/1.1\r\nHost: 127.0.0.1:7878\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0\r\nAccept: */*\r\nAccept-Language: en-GB,en;q=0.5\r\nAccept-Encoding: gzip, deflate, br, zstd\r\ncontent-type: multipart/form-data\r\nContent-Length: 187\r\nOrigin: moz-extension://739eb095-cac3-45a9-be66-a909f910c0b2\r\nConnection: keep-alive\r\nSec-Fetch-Dest: empty\r\nSec-Fetch-Mode: cors\r\nSec-Fetch-Site: same-origin\r\nPriority: u=0\r\n\r\n------geckoformboundary2de326eb804c2dc5c34c7fdd2d3d4e52\r\nContent-Disposition: form-data; name=\"my_silly_request\"\r\n\r\nvoid_angel\r\n------geckoformboundary2de326eb804c2dc5c34c7fdd2d3d4e52--\r\n"
If you refresh the RESTED Client page in the middle of the URL encoded request (while using your code), you might actually see the data come through.
Continuing Stream Host: 127.0.0.1:7878
Continuing Stream User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0
Continuing Stream Accept: */*
Continuing Stream Accept-Language: en-GB,en;q=0.5
Continuing Stream Accept-Encoding: gzip, deflate, br, zstd
Continuing Stream content-type: application/x-www-form-urlencoded
Continuing Stream Content-Length: 27
Continuing Stream Origin: moz-extension://739eb095-cac3-45a9-be66-a909f910c0b2
Continuing Stream Connection: keep-alive
Continuing Stream Sec-Fetch-Dest: empty
Continuing Stream Sec-Fetch-Mode: cors
Continuing Stream Sec-Fetch-Site: same-origin
Continuing Stream Priority: u=0
<refresh RESTED Client page here>
Continuing Stream 13:"my_silly_request=void_angel"
If you want to know when to finish reading a URL encoded request, using the Content-Length
header might be a good idea e.g.:
use std::{
io::{BufRead, BufReader, Read},
net::{TcpListener, TcpStream},
};
fn handle_connection(stream: &TcpStream) {
let mut buf_reader = BufReader::new(stream);
let request_lines: Vec<String> = buf_reader
.by_ref() // Used so lines() doesn't consume the reader
.lines()
.map(|result| result.unwrap())
.take_while(|line| !line.is_empty()) // Read until an empty line (e.g. before URL-encoded body)
.collect();
let mut content_type: Option<String> = None;
let mut content_length: Option<usize> = None;
// RESTED doesn't use correct capitalization for headers sometimes
for line in request_lines.iter().map(|s| s.to_lowercase()) {
if let Some(s) = line.strip_prefix("content-type:") {
content_type = Some(s.trim().to_string());
}
if let Some(s) = line.strip_prefix("content-length:") {
content_length = Some(s.trim().parse().unwrap_or(0));
}
}
if content_type.unwrap() == "application/x-www-form-urlencoded" && content_length.unwrap() > 0 {
let buf = &mut vec![0; content_length.unwrap()];
buf_reader.read_exact(buf).unwrap();
let content = String::from_utf8_lossy(buf);
println!("Received body: {content}");
}
}
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
handle_connection(&stream.unwrap());
}
}
Received body: my_silly_request=void_angel