1

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();
}
17
  • 1
    If it's just the matter of how the browser displays the content, then Content-Type: text/plain could help... As far as I understand, this could be related to some settings in the browser. Commented Jul 15, 2022 at 11:48
  • Not directly related but, does your server manage Connection: keep-alive? If not and you unconditionally close the connection after the http reply, then you should add Connection: close in the reply header. Commented Jul 15, 2022 at 11:51
  • I tried, no result( Commented Jul 15, 2022 at 11:52
  • @prog-fh not sure about keep-alive, but request sends with Connection: keep-alive. How can I check does server support keep-alive and should i close him every time? Commented Jul 15, 2022 at 11:54
  • You actually wrote the server, you probably know if it handles keep-alive or not. If you are not certain, then this certainly not the case. Commented Jul 15, 2022 at 11:56

1 Answer 1

1

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");
Sign up to request clarification or add additional context in comments.

3 Comments

Wow, It WORKS. Greate man. Don't know about implicit \n. But strange how it worked with PDF files?
Could you recommend the best way of sending response? I mean doing like current code or may be create 1 buffer and write to him resposnse.as_bytes() and then buf_content or any diffrent way?
@Dkwcs In my opinion, what you are doing here is correct: send the bytes which constitute the textual http-reply header (with .as_bytes()), then send the actual content as bytes without any transformation. About the pdf file that seemed to be working in the incorrect conditions, I think it's just some kind of chance. pdf contains a combination of text et binary elements. Maybe the browser/pdf-reader is smart enough to ignore the unexpected textual parts?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.