im using actix_web as backend framework
and for uploading files i have this code:
#[derive(Serialize, Deserialize, Clone)]
struct FileData {
ptr: u64,
len: usize,
cap: usize
}
impl FileData {
pub fn from_vec(vec: Vec<u8>) -> Self {
let mut me = ManuallyDrop::new(vec);
Self {
ptr: me.as_mut_ptr() as u64,
len: me.len(),
cap: me.capacity()
}
}
#[inline]
pub fn into_vec(self) -> Vec<u8> {
unsafe {
Vec::from_raw_parts(self.ptr as *mut u8, self.len, self.cap)
}
}
}
#[derive(Serialize, Deserialize)]
pub struct File {
name: String,
data: FileData
}
impl File {
#[inline]
pub fn new(name: String, data: FileData) -> Self {
Self { name, data }
}
#[inline]
pub fn name(&self) -> &String {
&self.name
}
#[inline]
pub fn into_data(self)-> FileData {
self.data
}
}
#[repr(transparent)]
pub struct Multipart<T> {
data: T
}
impl<T> Multipart<T> {
#[inline]
pub fn new(data: T) -> Self {
Self { data }
}
#[inline]
pub fn into_inner(self) -> T {
self.data
}
}
pub fn params_insert(
params: &mut Map<String, Value>,
field_name: &str,
field_name_formatted: &String,
element: Value,
) {
if params.contains_key(field_name_formatted) {
if let Value::Array(val) = params.get_mut(field_name_formatted).unwrap() {
val.push(element);
}
} else if field_name.ends_with("[]") {
params.insert(field_name_formatted.to_owned(), Value::Array(vec![element]));
} else {
params.insert(field_name_formatted.to_owned(), element);
}
}
impl<T> Multipart<T>
where
T: DeserializeOwned
{
pub async fn extract_multipart(
mut payload: actix_multipart::Multipart,
send_progress: SendProgress,
headers: HeaderMap
) -> Result<Self, MultipartError>
{
fn cleanup(filedatas: Vec<FileData>) {
for filedata in filedatas {
let _ = filedata.into_vec();
}
}
let mut this: Map<String, Value> = Map::new();
let mut filedatas: Vec<FileData> = Vec::new();
'mainWhile: while let Ok(Some(mut field)) = payload.try_next().await {
if let Some(filename) = field.content_disposition().get_filename() {
/* we got a file */
let field_name: String = field.name().into();
let field_name_formatted: String = field_name.replace("[]", "");
let filename: String = filename.replace(" ", "");
let mut data: Vec<u8>;
let mut bytes_read = 0;
let file_len: Option<usize>;
/* TODO: check for file extension */
/* see if file len is in headers to preallocate data and check for size earlier */
match headers.get(format!("x-file-len-{}", filename)) {
Some(__file_len) => {
/* TODO: handle errors on these unwraps (remove tmp files in ram) */
file_len = __file_len.to_str().unwrap().parse::<usize>().unwrap().into();
/* TODO: check for size earlier */
data = Vec::with_capacity(file_len.clone().unwrap());
},
None => {
file_len = None;
data = Vec::new();
}
}
while let Some(chunk) = field.next().await {
match chunk {
Ok(bytes) => {
let bytes: Vec<u8> = bytes.to_vec();
if file_len.is_none() {
data.reserve_exact(bytes.len());
}
bytes_read += bytes.len();
/* TODO: check for max file size */
for byte in bytes {
data.push(byte);
}
/* TODO: send progress info */
},
Err(e) => {
continue 'mainWhile;
}
}
}
if data.is_empty() {
continue 'mainWhile;
}
/* data is ready to be inserted */
let filedata = FileData::from_vec(data);
filedatas.push(filedata.clone());
let file = File::new(filename, filedata);
params_insert(&mut this, &field_name, &field_name_formatted, file.into());
} else {
/* we got a normal field */
}
}
match serde_json::from_value::<T>(Value::Object(this)) {
Ok(d) => Ok(Self::new(d)),
Err(e) => {
cleanup(filedatas);
return Err(e.into());
}
}
}
}
FromRequest is implemented
then in route i use it like this
#[derive(Deserialize)]
struct Data {
file: File
}
pub async fn route_handler(data: Multipart<Data>) -> HttpResponse {
let data = data.into_inner();
write_data_into_disk(data); /* this calls FileData::into_vec and drops it */
/* send response here */
}
it works but it leaks memory
not always though
any help would be appreciated