Giao thức tải lên tập tin sử dụng biểu mẫu HTML dựa trên cơ chế multipart/form-data. Dưới đây là cấu trúc yêu cầu mẫu:
POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="files"; filename="document.pdf"
Content-Type: application/pdf
[Binary data of document.pdf]
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="files"; filename="image.png"
Content-Type: image/png
[Binary data of image.png]
----WebKitFormBoundary7MA4YWxkTrZu0gW--
Yếu tố then chốt bao gồm: phương thức POST, header Content-Type chỉ định multipart, và dữ liệu tập tin được phân tách bằng boundary.
Triển khai phía giao diện
Biểu mẫu HTML với khả năng chọn nhiều tập tin:
<form #uploadForm (submit)="submitForm()">
<input type="file" name="files" multiple #fileInput>
<button type="submit">Gửi tập tin</button>
</form>
Logic xử lý trong TypeScript:
submitForm() {
const inputElement = document.querySelector('input[type="file"]') as HTMLInputElement;
const files = inputElement.files;
if (!files?.length) return;
const payload = new FormData();
Array.from(files).forEach(file => {
payload.append('files', file, file.name);
});
this.httpClient.post('/api/upload', payload)
.subscribe({
next: response => console.log('Hoàn tất:', response),
error: err => console.error('Lỗi hệ thống:', err)
});
}
Xử lý phía máy chủ bằng Rust
Thiết lập dự án trong Cargo.toml:
[dependencies]
actix-web = "4.3"
actix-multipart = "0.4"
tokio = { version = "1.32", features = ["fs", "macros"] }
futures-util = "0.3"
Triển khai API nhận tập tin:
use actix_multipart::Multipart;
use actix_web::{post, web, HttpResponse, Result};
use futures_util::StreamExt;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
#[post("/api/upload")]
async fn handle_upload(mut payload: Multipart) -> Result<HttpResponse> {
while let Some(field) = payload.next().await {
let mut part = field?;
let file_name = part
.content_disposition()
.get_filename()
.map(|s| s.to_string())
.unwrap_or_else(|| "untitled".to_string());
let mut output = File::create(format!("./uploads/{}", file_name)).await?;
while let Some(chunk) = part.next().await {
output.write_all(&chunk?).await?;
}
}
Ok(HttpResponse::Created().json(serde_json::json!({
"status": "success",
"message": "Tải lên thành công"
})))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
std::fs::create_dir_all("./uploads")?;
HttpServer::new(|| App::new().service(handle_upload))
.bind(("127.0.0.1", 8080))?
.run()
.await
}