Permit concurrency in uploads
This commit is contained in:
parent
070c4a4d4a
commit
2a69e0a4a3
10
src/error.rs
10
src/error.rs
|
@ -65,6 +65,8 @@ pub enum Error {
|
||||||
MissingMiddleware,
|
MissingMiddleware,
|
||||||
#[error("Impossible Error! Middleware exists, didn't fail, and didn't send value")]
|
#[error("Impossible Error! Middleware exists, didn't fail, and didn't send value")]
|
||||||
TxDropped,
|
TxDropped,
|
||||||
|
#[error("Panic in spawned future")]
|
||||||
|
Panic,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<MultipartError> for Error {
|
impl From<MultipartError> for Error {
|
||||||
|
@ -73,6 +75,12 @@ impl From<MultipartError> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<crate::spawn::Canceled> for Error {
|
||||||
|
fn from(_: crate::spawn::Canceled) -> Self {
|
||||||
|
Error::Panic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ResponseError for Error {
|
impl ResponseError for Error {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
match *self {
|
match *self {
|
||||||
|
@ -100,7 +108,7 @@ impl ResponseError for Error {
|
||||||
| Error::Filename
|
| Error::Filename
|
||||||
| Error::FileCount
|
| Error::FileCount
|
||||||
| Error::FileSize => HttpResponse::BadRequest().finish(),
|
| Error::FileSize => HttpResponse::BadRequest().finish(),
|
||||||
Error::MissingMiddleware | Error::TxDropped => {
|
Error::Panic | Error::MissingMiddleware | Error::TxDropped => {
|
||||||
HttpResponse::InternalServerError().finish()
|
HttpResponse::InternalServerError().finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
mod middleware;
|
mod middleware;
|
||||||
|
mod spawn;
|
||||||
mod types;
|
mod types;
|
||||||
mod upload;
|
mod upload;
|
||||||
|
|
||||||
|
@ -84,3 +85,5 @@ pub use self::{
|
||||||
types::{Field, FileMeta, Form, Value},
|
types::{Field, FileMeta, Form, Value},
|
||||||
upload::handle_multipart,
|
upload::handle_multipart,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use self::spawn::spawn;
|
||||||
|
|
23
src/spawn.rs
Normal file
23
src/spawn.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use log::error;
|
||||||
|
use std::future::Future;
|
||||||
|
use tokio::sync::oneshot::channel;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[error("Task panicked")]
|
||||||
|
pub(crate) struct Canceled;
|
||||||
|
|
||||||
|
pub(crate) async fn spawn<F, T>(f: F) -> Result<T, Canceled>
|
||||||
|
where
|
||||||
|
F: Future<Output = T> + 'static,
|
||||||
|
T: 'static,
|
||||||
|
{
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
actix_rt::spawn(async move {
|
||||||
|
if let Err(_) = tx.send(f.await) {
|
||||||
|
error!("rx dropped (this shouldn't happen)");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rx.await.map_err(|_| Canceled)
|
||||||
|
}
|
|
@ -25,7 +25,10 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use futures::stream::StreamExt;
|
use futures::{
|
||||||
|
select,
|
||||||
|
stream::{FuturesUnordered, StreamExt},
|
||||||
|
};
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
@ -207,18 +210,48 @@ async fn handle_stream_field(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle multipart streams from Actix Web
|
/// Handle multipart streams from Actix Web
|
||||||
pub async fn handle_multipart(
|
pub async fn handle_multipart(m: actix_multipart::Multipart, form: Form) -> Result<Value, Error> {
|
||||||
mut m: actix_multipart::Multipart,
|
|
||||||
form: Form,
|
|
||||||
) -> Result<Value, Error> {
|
|
||||||
let mut multipart_form = Vec::new();
|
let mut multipart_form = Vec::new();
|
||||||
let mut file_count: u32 = 0;
|
let mut file_count: u32 = 0;
|
||||||
let mut field_count: u32 = 0;
|
let mut field_count: u32 = 0;
|
||||||
|
|
||||||
while let Some(res) = m.next().await {
|
let mut unordered = FuturesUnordered::new();
|
||||||
let field = res?;
|
|
||||||
let (name_parts, content) = handle_stream_field(field, form.clone()).await?;
|
|
||||||
|
|
||||||
|
let mut m = m.fuse();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
select! {
|
||||||
|
opt = m.next() => {
|
||||||
|
if let Some(res) = opt {
|
||||||
|
let field = res?;
|
||||||
|
|
||||||
|
unordered.push(crate::spawn(handle_stream_field(field, form.clone())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opt = unordered.next() => {
|
||||||
|
if let Some(res) = opt {
|
||||||
|
let (name_parts, content) = res??;
|
||||||
|
|
||||||
|
let (l, r) = count(&content, file_count, field_count, &form)?;
|
||||||
|
file_count = l;
|
||||||
|
field_count = r;
|
||||||
|
|
||||||
|
multipart_form.push((name_parts, content));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
complete => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(consolidate(multipart_form))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count(
|
||||||
|
content: &MultipartContent,
|
||||||
|
mut file_count: u32,
|
||||||
|
mut field_count: u32,
|
||||||
|
form: &Form,
|
||||||
|
) -> Result<(u32, u32), Error> {
|
||||||
match content {
|
match content {
|
||||||
MultipartContent::File(_) => {
|
MultipartContent::File(_) => {
|
||||||
file_count += 1;
|
file_count += 1;
|
||||||
|
@ -234,8 +267,5 @@ pub async fn handle_multipart(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
multipart_form.push((name_parts, content));
|
Ok((file_count, field_count))
|
||||||
}
|
|
||||||
|
|
||||||
Ok(consolidate(multipart_form))
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue