Home > Blockchain >  Rust - downloading files via handmade tcp/http server
Rust - downloading files via handmade tcp/http server

Time:07-16

I'm trying to implement simple tcp/http server on Rust. Main feature is possibility to download files from server via url. As exapmple: localhost:port/root_storage/someFile.fileExt

Main function:

fn main() {
    let tcp_listener = TcpListener::bind("127.0.0.1:7000").unwrap();
    println!("Server starded!");
    for stream in tcp_listener.incoming()
    {
        let stream = stream.unwrap();
        handle_connection(stream);
    }
}

I'm receiving request with tcpStream, parsing to get incoming url etc. Then I'm tryiting to send some file back to browser and download him on client side.

 if  http_request[0].contains("storage/10mb.pdf")  {
    status_line = "HTTP/1.1 200 OK";

    let buf_content = fs::read("storage/10mb.pdf").unwrap();
    let contents = unsafe {String::from_utf8_unchecked(buf_content)};
    length = contents.len();

    response = format!("{status_line}\r\n
    Content-Disposition: attachment; filename=\"10mb.pdf\"\r\n
    Content-Type: application/pdf\r\n
    Content-Length: {length}\r\n\r\n
    {contents}");
}

It works, but only with pdf format.

This code won't occure file downloading, just showing content of file in browser:

    else if http_request[0].contains("storage/10mb.txt")  {
    status_line = "HTTP/1.1 200 OK";

    let buf_content = fs::read("storage/10mb.txt").unwrap();
    let contents =  unsafe {String::from_utf8_unchecked(buf_content)};
    length = contents.len();

    response = format!("{status_line}\r\n
    Content-Disposition: attachment; filename=\"10mb.txt\"\r\n
    Content-Type: application/octet-stream\r\n
    Content-Length: {length}\r\n\r\n
    {contents}");

stream.write_all(response.as_bytes()).unwrap();
stream.flush().unwrap();

My assumption it's about in contents encoding. I don't know why - Content-Disposition and other headers doesn't work at all. If I remove Content-Disposition from pdf case. It will download file anyway.

What's wrong with my implementation? May be I encode contents in wrong way or something else?

UPD: Have this, but also no effect

if  http_request[0].contains("storage/10mb.pdf")  {
    status_line = "HTTP/1.1 200 OK";

    let buf_content = fs::read("storage/10mb.pdf").unwrap();
    length = buf_content.len();

    response = format!("{status_line}\r\n
    Content-Disposition: attachment; filename=\"10mb.pdf\"\r\n
    Content-Type: application/pdf\r\n
    Content-Length: {length}\r\n\r\n");
    stream.write_all(response.as_bytes()).unwrap();
    stream.write_all(&buf_content).unwrap();
    stream.flush().unwrap();
}
else if http_request[0].contains("storage/10mb.txt")  {
    status_line = "HTTP/1.1 200 OK";

    let buf_content = fs::read("storage/10mb.txt").unwrap();
    length = buf_content.len();

    response = format!("{status_line}\r\n
    Content-Disposition: attachment; filename=\"10mb.txt\"\r\n
    Content-Type: text/plain\r\n
    Content-Length: {length}\r\n\r\n");
    stream.write_all(response.as_bytes()).unwrap();
    stream.write_all(&buf_content).unwrap();
    stream.flush().unwrap();
}

CodePudding user response:

I think there is a problem with the multiline text literal.

Every line contains an implicit \n after your explicit \r\n and the next line starts with many spaces before the option name.

When the browser receives this reply header, it does not understand it and probably tries to display everything...

If you end the lines with \ (and nothing behind, not even a space), then only your explicit \r\n will end the lines and the leading spaces will be discarded.

    response = format!("{status_line}\r\n\
    Content-Disposition: attachment; filename=\"10mb.txt\"\r\n\
    Content-Type: text/plain\r\n\
    Content-Length: {length}\r\n\r\n");
  • Related