diff --git a/Cargo.lock b/Cargo.lock index 4b19cfd..a799b1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index b219c79..9756a8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/examples/html.lua b/examples/html.lua index 630dd4f..25e52af 100644 --- a/examples/html.lua +++ b/examples/html.lua @@ -2,7 +2,7 @@ local app = create_app() app:get("/", function(req, res) res.body = [[ - +
diff --git a/examples/multipart.lua b/examples/multipart.lua new file mode 100644 index 0000000..c9dde96 --- /dev/null +++ b/examples/multipart.lua @@ -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) diff --git a/examples/server.lua b/examples/server.lua index ab0c8b3..fc80af2 100644 --- a/examples/server.lua +++ b/examples/server.lua @@ -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) diff --git a/src/internal/core/service.rs b/src/internal/core/service.rs index 88eb87f..ac8ae7a 100644 --- a/src/internal/core/service.rs +++ b/src/internal/core/service.rs @@ -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> 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 = HashMap::new(); + let mut parts_map: HashMap = 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::>(); + } else if content_type.starts_with(multipart_content_type) { + let mime = content_type.parse::().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(), }; diff --git a/src/internal/lua/req.rs b/src/internal/lua/req.rs index c89ef46..bf87a51 100644 --- a/src/internal/lua/req.rs +++ b/src/internal/lua/req.rs @@ -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, pub method: String, pub params: HashMap, + pub parts: HashMap, 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())); } }