feat: basic handle of multipart form-data
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
ato 2023-02-15 19:34:58 +01:00
parent e8cdc93ebd
commit 84d7e20baa
7 changed files with 256 additions and 6 deletions

170
Cargo.lock generated
View File

@ -23,6 +23,16 @@ dependencies = [
"memchr",
]
[[package]]
name = "buf_redux"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
dependencies = [
"memchr",
"safemem",
]
[[package]]
name = "bytes"
version = "1.4.0"
@ -50,6 +60,15 @@ dependencies = [
"serde",
]
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -154,6 +173,17 @@ dependencies = [
"slab",
]
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "h2"
version = "0.3.15"
@ -266,6 +296,15 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "itoa"
version = "1.0.5"
@ -327,6 +366,22 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "mime"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "mio"
version = "0.8.5"
@ -360,6 +415,24 @@ dependencies = [
"serde",
]
[[package]]
name = "multipart"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182"
dependencies = [
"buf_redux",
"httparse",
"log",
"mime",
"mime_guess",
"quick-error",
"rand",
"safemem",
"tempfile",
"twoway",
]
[[package]]
name = "num-traits"
version = "0.2.15"
@ -432,6 +505,12 @@ version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.51"
@ -441,6 +520,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.23"
@ -450,6 +535,36 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@ -459,6 +574,15 @@ dependencies = [
"bitflags",
]
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
@ -471,6 +595,12 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]]
name = "safemem"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
[[package]]
name = "scopeguard"
version = "1.1.0"
@ -546,12 +676,28 @@ dependencies = [
"futures",
"hyper",
"matchit",
"mime",
"mlua",
"multipart",
"serde_json",
"tokio",
"url",
]
[[package]]
name = "tempfile"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
dependencies = [
"cfg-if",
"fastrand",
"libc",
"redox_syscall",
"remove_dir_all",
"winapi",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
@ -644,6 +790,24 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "twoway"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
dependencies = [
"memchr",
]
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.10"
@ -676,6 +840,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "want"
version = "0.3.0"

View File

@ -27,6 +27,10 @@ mlua = { version = "0.8", features = [
"async",
"serialize",
] }
mime = { version = "0.3" }
multipart = { version = "0.18", default-features = false, features = [
"server",
], optional = false }
serde_json = { version = "1.0" }
tokio = { version = "1.0", features = ["full"] }
url = { version = "2.3" }

View File

@ -2,7 +2,7 @@ local app = create_app()
app:get("/", function(req, res)
res.body = [[
<html>
<html style="background-color:black;">
<body>
<form action="/submit" method="post">
<input name="hello" value="Hello, World!" />

12
examples/multipart.lua Normal file
View File

@ -0,0 +1,12 @@
local app = create_app()
app:post("/", function(req,res)
for k,v in pairs(req.parts) do
print("name", k)
print("filename", v.filename)
print("content", v.content)
end
return res
end)
app:run(3000)

View File

@ -15,12 +15,14 @@ end)
local app = create_app()
app:get("/:segment", function(req, res)
res.body = { data = req.params.segment }
res.json = { data = req.params.segment }
return res
end)
app:post("/submit", function(req, res)
print(req.body)
for k,v in pairs(req.form) do
print(k,v)
end
return res
end)

View File

@ -3,9 +3,12 @@ use hyper::server::conn::AddrStream;
use hyper::service::Service;
use hyper::{Body, Request, Response, StatusCode};
use matchit::Router;
use mime::Mime;
use mlua::{Error as LuaError, Function, Lua};
use multipart::server::Multipart;
use std::collections::HashMap;
use std::future::Future;
use std::io::{Cursor, Read};
use std::net::SocketAddr;
use std::pin::Pin;
use std::rc::Rc;
@ -13,7 +16,7 @@ use std::task::{Context, Poll};
use tokio::task::spawn_local;
use url::form_urlencoded;
use crate::internal::lua::req::Req;
use crate::internal::lua::req::{Part, Req};
use crate::internal::lua::res::Res;
use super::routers::LuaHandle;
@ -64,26 +67,57 @@ impl Service<Request<Body>> for Svc {
}
let form_content_type = "application/x-www-form-urlencoded";
let multipart_content_type = "multipart/form-data";
let body_bytes = to_bytes(body).await.unwrap();
let mut body_str = String::new();
let mut form_map: HashMap<String, String> = HashMap::new();
let mut parts_map: HashMap<String, Part> = HashMap::new();
if let Some(content_type) = headers_map.get("content-type") {
if content_type.eq_ignore_ascii_case(form_content_type) {
form_map = form_urlencoded::parse(&body_bytes.as_ref())
.into_owned()
.collect::<HashMap<String, String>>();
} else if content_type.starts_with(multipart_content_type) {
let mime = content_type.parse::<Mime>().unwrap();
if let Some(boundary) = mime.get_param("boundary").map(|v| v.to_string()) {
let mut multipart = Multipart::with_body(Cursor::new(body_bytes), boundary);
multipart
.foreach_entry(|mut arg| {
let mut buf = String::new();
let name = arg.headers.name;
arg.data.read_to_string(&mut buf).unwrap();
match arg.headers.filename {
Some(filename) => {
parts_map.insert(
name.to_string(),
Part {
filename,
content: buf,
},
);
}
None => {
form_map.insert(name.to_string(), buf);
}
};
()
})
.unwrap();
}
} else {
body_str = String::from_utf8(body_bytes.to_vec()).unwrap();
}
}
let body_str = String::from_utf8(body_bytes.to_vec()).unwrap();
let req = Req {
body: body_str,
headers: headers_map,
form: form_map,
method: lua_handle_match.value.method.to_string(),
params: params_map,
parts: parts_map,
socket_addr: addr.to_string(),
};

View File

@ -2,6 +2,27 @@ use std::collections::HashMap;
use mlua::{LuaSerdeExt, UserData, UserDataFields};
#[derive(Clone)]
pub struct Part {
pub filename: String,
pub content: String,
}
impl UserData for Part {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_set("filename", |_, this, value| {
this.filename = value;
Ok(())
});
fields.add_field_method_get("filename", |_, this| Ok(this.filename.to_owned()));
fields.add_field_method_set("content", |_, this, value| {
this.content = value;
Ok(())
});
fields.add_field_method_get("content", |_, this| Ok(this.content.to_owned()));
}
}
#[derive(Clone)]
pub struct Req {
pub body: String,
@ -9,6 +30,7 @@ pub struct Req {
pub headers: HashMap<String, String>,
pub method: String,
pub params: HashMap<String, String>,
pub parts: HashMap<String, Part>,
pub socket_addr: String,
}
@ -48,5 +70,11 @@ impl UserData for Req {
Ok(())
});
fields.add_field_method_get("params", |_, this| Ok(this.params.to_owned()));
fields.add_field_method_set("parts", |_, this, value| {
this.parts = value;
Ok(())
});
fields.add_field_method_get("parts", |_, this| Ok(this.parts.to_owned()));
}
}