Repository: sparckles/Robyn (language: python)

Issue:
A file serve Download bug.
### Bug Description

response a **.wav** file . The downloaded file is corrupted.   source size is 4.45 MB (4,671,364 bytes). but download file  has become 7.84 MB (8,229,002 bytes).
#### test code 
```python
import os
import pathlib

from robyn import Robyn, serve_file

app = Robyn(__name__)

current_file_path = pathlib.Path(__file__).parent.resolve()


@app.get("/")
def hello_world():
    file_path = os.path.join(current_file_path, "source.wav")  #a audio file
    return serve_file(file_path)


if __name__ == "__main__":
    app.start()

```

### Steps to Reproduce

_No response_

### Your operating system

Windows

### Your Python version (`python --version`)

3.11

### Your Robyn version

latest

### Additional Info

the console output **Robyn version: 0.56.0**.  I just use  **pip** install this project


Discussion / hints:
Hey @asamaayako 👋
Thanks for reporting this. I will investigate tonight 😊

Fix patch:
diff --git a/docs_src/src/pages/documentation/api_reference/file-uploads.mdx b/docs_src/src/pages/documentation/api_reference/file-uploads.mdx
index 9abbcf2c1..22467d187 100644
--- a/docs_src/src/pages/documentation/api_reference/file-uploads.mdx
+++ b/docs_src/src/pages/documentation/api_reference/file-uploads.mdx
@@ -189,7 +189,7 @@ Batman scaled his application across multiple cores for better performance. He u
 
     @app.get("/")
     async def h(request):
-        return serve_file("./index.html")
+        return serve_file("./index.html", file_name="index.html") # file_name is optional
 
     app.start(port=8080)
 
@@ -203,7 +203,7 @@ Batman scaled his application across multiple cores for better performance. He u
 
     @app.get("/")
     async def h(request: Request):
-        return serve_file("./index.html")
+        return serve_file("./index.html", file_name="index.html") # file_name is optional
 
     app.start(port=8080)
 
diff --git a/robyn/responses.py b/robyn/responses.py
index c19279907..10f1b0300 100644
--- a/robyn/responses.py
+++ b/robyn/responses.py
@@ -1,4 +1,6 @@
 from typing import Optional
+import os
+import mimetypes
 
 from robyn.robyn import Response, Headers
 
@@ -39,11 +41,21 @@ def serve_html(file_path: str) -> FileResponse:
     return FileResponse(file_path, headers=Headers({"Content-Type": "text/html"}))
 
 
-def serve_file(file_path: str) -> FileResponse:
+def serve_file(file_path: str, file_name: Optional[str] = None) -> FileResponse:
     """
     This function will help in serving a file
 
     :param file_path str: file path to serve as a response
+    :param file_name [str | None]: file name to serve as a response, defaults to None
     """
+    file_name = file_name or os.path.basename(file_path)
 
-    return FileResponse(file_path, headers=Headers({"Content-Disposition": "attachment"}))
+    mime_type = mimetypes.guess_type(file_name)[0]
+
+    headers = Headers({"Content-Type": mime_type})
+    headers.append("Content-Disposition", f"attachment; filename={file_name}")
+
+    return FileResponse(
+        file_path,
+        headers=headers,
+    )
diff --git a/src/io_helpers/mod.rs b/src/io_helpers/mod.rs
index 914e21b46..7c7067278 100644
--- a/src/io_helpers/mod.rs
+++ b/src/io_helpers/mod.rs
@@ -25,9 +25,9 @@ pub fn apply_hashmap_headers(response: &mut HttpResponseBuilder, headers: &Heade
 /// * `file_path` - The file path that we want the function to read
 ///
 // ideally this should be async
-pub fn read_file(file_path: &str) -> Result<String> {
+pub fn read_file(file_path: &str) -> Result<Vec<u8>> {
     let mut file = File::open(file_path)?;
     let mut buf = vec![];
     file.read_to_end(&mut buf)?;
-    Ok(String::from_utf8_lossy(&buf).to_string())
+    Ok(buf)
 }
diff --git a/src/types/response.rs b/src/types/response.rs
index 227d4147f..e294279df 100644
--- a/src/types/response.rs
+++ b/src/types/response.rs
@@ -148,12 +148,15 @@ impl PyResponse {
 
     #[setter]
     pub fn set_file_path(&mut self, py: Python, file_path: &str) -> PyResult<()> {
-        // we should be handling based on headers but works for now
         self.response_type = "static_file".to_string();
         self.file_path = Some(file_path.to_string());
-        self.description = read_file(file_path)
-            .map_err(|e| PyErr::new::<PyIOError, _>(e.to_string()))?
-            .into_py(py);
-        Ok(())
+
+        match read_file(file_path) {
+            Ok(content) => {
+                self.description = PyBytes::new(py, &content).into();
+                Ok(())
+            }
+            Err(e) => Err(PyIOError::new_err(format!("Failed to read file: {}", e))),
+        }
     }
 }


Test patch:
diff --git a/integration_tests/test_file_download.py b/integration_tests/test_file_download.py
index 18047838e..67d2197f4 100644
--- a/integration_tests/test_file_download.py
+++ b/integration_tests/test_file_download.py
@@ -6,6 +6,6 @@
 @pytest.mark.parametrize("function_type", ["sync", "async"])
 def test_file_download(function_type: str, session):
     r = get(f"/{function_type}/file/download")
-    assert r.headers.get("Content-Disposition") == "attachment"
+    assert r.headers.get("Content-Disposition") == "attachment; filename=test.txt"
 
     assert r.text == "This is a test file for the downloading purpose"