diff --git a/Cargo.lock b/Cargo.lock index 5bd8dcc1..cb6e4f45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -222,20 +222,40 @@ dependencies = [ [[package]] name = "actix-multipart" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9edfb0e7663d7fe18c8d5b668c9c1bcf79176b1dcc9d4da9592503209a6bfb0" +checksum = "dee489e3c01eae4d1c35b03c4493f71cb40d93f66b14558feb1b1a807671cc4e" dependencies = [ + "actix-multipart-derive", "actix-utils", "actix-web", "bytes 1.4.0", "derive_more", "futures-core", + "futures-util", "httparse", "local-waker", "log", + "memchr", "mime", - "twoway", + "serde", + "serde_json", + "serde_plain", + "tempfile", + "tokio 1.25.0", +] + +[[package]] +name = "actix-multipart-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ec592f234db8a253cf80531246a4407c8a70530423eea80688a6c5a44a110e7" +dependencies = [ + "darling 0.14.3", + "parse-size", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -816,6 +836,17 @@ dependencies = [ "toml", ] +[[package]] +name = "assert-json-diff" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4259cbe96513d2f1073027a259fc2ca917feb3026a5a8d984e3628e490255cc0" +dependencies = [ + "extend", + "serde", + "serde_json", +] + [[package]] name = "async-channel" version = "1.8.0" @@ -1067,10 +1098,11 @@ dependencies = [ [[package]] name = "aws-config" -version = "0.52.0" +version = "0.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7688e1dfbb9f7804fab0a830820d7e827b8d973906763cf1a855ce4719292f5" +checksum = "3c3d1e2a1f1ab3ac6c4b884e37413eaa03eb9d901e4fc68ee8f5c1d49721680e" dependencies = [ + "aws-credential-types", "aws-http", "aws-sdk-sso", "aws-sdk-sts", @@ -1094,10 +1126,23 @@ dependencies = [ ] [[package]] -name = "aws-endpoint" -version = "0.52.0" +name = "aws-credential-types" +version = "0.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "253d7cd480bfa59a5323390e9e91885a8f06a275e0517d81eeb1070b6aa7d271" +checksum = "bb0696a0523a39a19087747e4dafda0362dc867531e3d72a3f195564c84e5e08" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "tokio 1.25.0", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-endpoint" +version = "0.54.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80a4f935ab6a1919fbfd6102a80c4fccd9ff5f47f94ba154074afe1051903261" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -1109,10 +1154,11 @@ dependencies = [ [[package]] name = "aws-http" -version = "0.52.0" +version = "0.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd1b83859383e46ea8fda633378f9f3f02e6e3a446fd89f0240b5c3662716c9" +checksum = "82976ca4e426ee9ca3ffcf919d9b2c8d14d0cd80d43cc02173737a8f07f28d4d" dependencies = [ + "aws-credential-types", "aws-smithy-http", "aws-smithy-types", "aws-types", @@ -1127,10 +1173,11 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "0.22.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d240ff751efc65099d18f6b0fb80360b31a298cec7b392c511692bec4a6e21" +checksum = "1533be023eeac69668eb718b1c48af7bd5e26305ed770553d2877ab1f7507b68" dependencies = [ + "aws-credential-types", "aws-endpoint", "aws-http", "aws-sig-auth", @@ -1141,6 +1188,7 @@ dependencies = [ "aws-smithy-eventstream", "aws-smithy-http", "aws-smithy-http-tower", + "aws-smithy-json", "aws-smithy-types", "aws-smithy-xml", "aws-types", @@ -1149,17 +1197,22 @@ dependencies = [ "fastrand", "http", "http-body 0.4.5", + "once_cell", + "percent-encoding", + "regex", "tokio-stream", "tower", "tracing", + "url", ] [[package]] name = "aws-sdk-sso" -version = "0.22.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf03342c2b3f52b180f484e60586500765474f2bfc7dcd4ffe893a7a1929db1d" +checksum = "ca0119bacf0c42f587506769390983223ba834e605f049babe514b2bd646dbb2" dependencies = [ + "aws-credential-types", "aws-endpoint", "aws-http", "aws-sig-auth", @@ -1172,16 +1225,18 @@ dependencies = [ "aws-types", "bytes 1.4.0", "http", + "regex", "tokio-stream", "tower", ] [[package]] name = "aws-sdk-sts" -version = "0.22.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa1de4e07ea87a30a317c7b563b3a40fd18a843ad794216dda81672b6e174bce" +checksum = "270b6a33969ebfcb193512fbd5e8ee5306888ad6c6d5d775cdbfb2d50d94de26" dependencies = [ + "aws-credential-types", "aws-endpoint", "aws-http", "aws-sig-auth", @@ -1189,22 +1244,25 @@ dependencies = [ "aws-smithy-client", "aws-smithy-http", "aws-smithy-http-tower", + "aws-smithy-json", "aws-smithy-query", "aws-smithy-types", "aws-smithy-xml", "aws-types", "bytes 1.4.0", "http", + "regex", "tower", "tracing", ] [[package]] name = "aws-sig-auth" -version = "0.52.0" +version = "0.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6126c4ff918e35fb9ae1bf2de71157fad36f0cc6a2b1d0f7197ee711713700fc" +checksum = "660a02a98ab1af83bd8d714afbab2d502ba9b18c49e7e4cddd6bf8837ff778cb" dependencies = [ + "aws-credential-types", "aws-sigv4", "aws-smithy-eventstream", "aws-smithy-http", @@ -1215,9 +1273,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "0.52.0" +version = "0.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c7f88d7395f5411c6eef5889b6cd577ce6b677af461356cbfc20176c26c160" +checksum = "cdaf11005b7444e6cd66f600d09861a3aeb6eb89a0f003c7c9820dbab2d15297" dependencies = [ "aws-smithy-eventstream", "aws-smithy-http", @@ -1236,9 +1294,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "0.52.0" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e6a895d68852dd1564328e63ef1583e5eb307dd2a5ebf35d862a5c402957d5e" +checksum = "63c712a28a4f2f2139759235c08bf98aca99d4fdf1b13c78c5f95613df0a5db9" dependencies = [ "futures-util", "pin-project-lite 0.2.9", @@ -1248,9 +1306,9 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.52.0" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b847d960abc993319d77b52e82971e2bbdce94f6192df42142e14ed5c9c917" +checksum = "a3875fb4b28606a5368a048016a28c15707f2b21238d5b2e4a23198f590e92c4" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -1269,13 +1327,14 @@ dependencies = [ [[package]] name = "aws-smithy-client" -version = "0.52.0" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f505bf793eb3e6d7c166ef1275c27b4b2cd5361173fe950ac8e2cfc08c29a7ef" +checksum = "104ca17f56cde00a10207169697dfe9c6810db339d52fb352707e64875b30a44" dependencies = [ "aws-smithy-async", "aws-smithy-http", "aws-smithy-http-tower", + "aws-smithy-protocol-test", "aws-smithy-types", "bytes 1.4.0", "fastrand", @@ -1285,6 +1344,7 @@ dependencies = [ "hyper-rustls", "lazy_static", "pin-project-lite 0.2.9", + "serde", "tokio 1.25.0", "tower", "tracing", @@ -1292,9 +1352,9 @@ dependencies = [ [[package]] name = "aws-smithy-eventstream" -version = "0.52.0" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751c99da757aecc1408ab6b2d65e9493220a5e7a68bcafa4f07b6fd1bc473f1" +checksum = "ac250d8c0e42af0097a6837ffc5a6fb9f8ba4107bb53124c047c91bc2a58878f" dependencies = [ "aws-smithy-types", "bytes 1.4.0", @@ -1303,9 +1363,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.52.0" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e4b4304b7ea4af1af3e08535100eb7b6459d5a6264b92078bf85176d04ab85" +checksum = "873f316f1833add0d3aa54ed1b0cd252ddd88c792a0cf839886400099971e844" dependencies = [ "aws-smithy-eventstream", "aws-smithy-types", @@ -1326,9 +1386,9 @@ dependencies = [ [[package]] name = "aws-smithy-http-tower" -version = "0.52.0" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86072ecc4dc4faf3e2071144285cfd539263fe7102b701d54fb991eafb04af8" +checksum = "4f38231d3f5dac9ac7976f44e12803add1385119ffca9e5f050d8e980733d164" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -1342,18 +1402,33 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.52.0" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3ddd9275b167bc59e9446469eca56177ec0b51225632f90aaa2cd5f41c940e" +checksum = "4bd83ff2b79e9f729746fcc8ad798676b68fe6ea72986571569a5306a277a182" dependencies = [ "aws-smithy-types", ] [[package]] -name = "aws-smithy-query" -version = "0.52.0" +name = "aws-smithy-protocol-test" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b19d2e0b3ce20e460bad0d0d974238673100edebba6978c2c1aadd925602f7" +checksum = "d4d1c9bcb35ce11055ec128dab2c66a7ed47e2dfff99883e32c21a1ab6d6bee6" +dependencies = [ + "assert-json-diff", + "http", + "pretty_assertions", + "regex", + "roxmltree", + "serde_json", + "thiserror", +] + +[[package]] +name = "aws-smithy-query" +version = "0.54.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2f0445dafe9d2cd50b44339ae3c3ed46549aad8ac696c52ad660b3e7ae8682b" dependencies = [ "aws-smithy-types", "urlencoding", @@ -1361,9 +1436,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "0.52.0" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "987b1e37febb9bd409ca0846e82d35299e572ad8279bc404778caeb5fc05ad56" +checksum = "8161232eda10290f5136610a1eb9de56aceaccd70c963a26a260af20ac24794f" dependencies = [ "base64-simd", "itoa 1.0.5", @@ -1374,19 +1449,20 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.52.0" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ce3791e14eec75ffac851a5a559f1ce6b31843297f42cc8bfba82714a6a5d8" +checksum = "343ffe9a9bb3f542675f4df0e0d5933513d6ad038ca3907ad1767ba690a99684" dependencies = [ "xmlparser", ] [[package]] name = "aws-types" -version = "0.52.0" +version = "0.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c05adca3e2bcf686dd2c47836f216ab52ed7845c177d180c84b08522c1166a3" +checksum = "f8f15b34253b68cde08e39b0627cc6101bcca64351229484b4743392c035d057" dependencies = [ + "aws-credential-types", "aws-smithy-async", "aws-smithy-client", "aws-smithy-http", @@ -1394,7 +1470,6 @@ dependencies = [ "http", "rustc_version 0.4.0", "tracing", - "zeroize", ] [[package]] @@ -1451,11 +1526,12 @@ checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] name = "base64-simd" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "781dd20c3aff0bd194fe7d2a977dd92f21c173891f3a03b677359e5fa457e5d5" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" dependencies = [ - "simd-abstraction", + "outref", + "vsimd", ] [[package]] @@ -2307,6 +2383,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "ctr" version = "0.9.2" @@ -2594,6 +2680,12 @@ dependencies = [ "syn", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.6" @@ -2742,6 +2834,18 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "extend" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47da3a72ec598d9c8937a7ebca8962a5c7a1f28444e38c2b33c771ba3f55f05" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -3575,7 +3679,6 @@ dependencies = [ "rustls-native-certs", "tokio 1.25.0", "tokio-rustls", - "webpki-roots 0.22.6", ] [[package]] @@ -4579,7 +4682,9 @@ version = "1.0.0" dependencies = [ "actix-multipart", "actix-web", + "env_logger", "futures-util", + "log", "sanitize-filename", "uuid 1.3.0", ] @@ -4904,10 +5009,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] -name = "outref" -version = "0.1.0" +name = "output_vt100" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "outref" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" [[package]] name = "parking_lot" @@ -4983,6 +5097,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "parse-size" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944553dd59c802559559161f9816429058b869003836120e262e8caec061b7ae" + [[package]] name = "parse-zoneinfo" version = "0.3.0" @@ -5261,6 +5381,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "pretty_assertions" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" +dependencies = [ + "ctor", + "diff", + "output_vt100", + "yansi", +] + [[package]] name = "prettyplease" version = "0.1.23" @@ -5816,6 +5948,15 @@ dependencies = [ "serde", ] +[[package]] +name = "roxmltree" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" +dependencies = [ + "xmlparser", +] + [[package]] name = "run-in-thread" version = "1.0.0" @@ -6253,6 +6394,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_plain" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6018081315db179d0ce57b1fe4b62a12a0028c9cf9bbef868c9cf477b3c34ae" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -6376,15 +6526,6 @@ dependencies = [ "libc", ] -[[package]] -name = "simd-abstraction" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cadb29c57caadc51ff8346233b5cec1d240b68ce55cf1afc764818791876987" -dependencies = [ - "outref", -] - [[package]] name = "simple-auth-server" version = "1.0.0" @@ -7631,16 +7772,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" -[[package]] -name = "twoway" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47" -dependencies = [ - "memchr", - "unchecked-index", -] - [[package]] name = "twox-hash" version = "1.6.3" @@ -7693,12 +7824,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "unchecked-index" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" - [[package]] name = "unic-char-property" version = "0.9.0" @@ -8020,6 +8145,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "walkdir" version = "2.3.2" @@ -8479,6 +8610,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "yansi-term" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index 76ab05ea..d4993b54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,8 @@ actix-cors = "0.6" actix-files = "0.6" actix-http = "3" actix-identity = "0.5" -actix-multipart = "0.4" +actix-multipart = "0.6" +actix-multipart-derive = "0.6" actix-protobuf = "0.9" actix-session = "0.7" actix-test = "0.1" diff --git a/forms/multipart-s3/Cargo.toml b/forms/multipart-s3/Cargo.toml index bed1e49b..890e96f3 100644 --- a/forms/multipart-s3/Cargo.toml +++ b/forms/multipart-s3/Cargo.toml @@ -8,8 +8,8 @@ actix-multipart.workspace = true actix-web.workspace = true actix-web-lab.workspace = true -aws-config = "0.52" -aws-sdk-s3 = "0.22" +aws-config = "0.54" +aws-sdk-s3 = "0.24" dotenv = "0.15" env_logger.workspace = true diff --git a/forms/multipart-s3/src/client.rs b/forms/multipart-s3/src/client.rs index a6eea7eb..80072b9a 100644 --- a/forms/multipart-s3/src/client.rs +++ b/forms/multipart-s3/src/client.rs @@ -71,14 +71,16 @@ impl Client { async fn upload_and_remove(&self, file: TempFile, key_prefix: &str) -> UploadedFile { let uploaded_file = self.upload(&file, key_prefix).await; - file.delete_from_disk().await; + tokio::fs::remove_file(file.file.path()).await.unwrap(); uploaded_file } async fn upload(&self, file: &TempFile, key_prefix: &str) -> UploadedFile { - let filename = file.name(); - let key = format!("{key_prefix}{}", file.name()); - let s3_url = self.put_object_from_file(file.path(), &key).await; + let filename = file.file_name.as_deref().expect("TODO"); + let key = format!("{key_prefix}{filename}"); + let s3_url = self + .put_object_from_file(file.file.path().to_str().unwrap(), &key) + .await; UploadedFile::new(filename, key, s3_url) } diff --git a/forms/multipart-s3/src/index.html b/forms/multipart-s3/src/index.html index 1bdb47df..28402fe4 100644 --- a/forms/multipart-s3/src/index.html +++ b/forms/multipart-s3/src/index.html @@ -19,10 +19,7 @@ const formData = new FormData(); - const meta = { namespace: $form.namespace.value }; - console.log(meta); - - formData.append("meta", JSON.stringify(meta)); + formData.append("namespace", $form.namespace.value); for (const file in $files.files) { formData.append("file", file); diff --git a/forms/multipart-s3/src/main.rs b/forms/multipart-s3/src/main.rs index 0f923941..626cf930 100644 --- a/forms/multipart-s3/src/main.rs +++ b/forms/multipart-s3/src/main.rs @@ -1,6 +1,6 @@ use std::fs; -use actix_multipart::Multipart; +use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm}; use actix_web::{ body::SizedStream, delete, error, get, middleware::Logger, post, web, App, Error, HttpResponse, HttpServer, Responder, @@ -8,51 +8,41 @@ use actix_web::{ use actix_web_lab::{extract::Path, respond::Html}; use aws_config::meta::region::RegionProviderChain; use dotenv::dotenv; -use serde::{Deserialize, Serialize}; use serde_json::json; mod client; -mod temp_file; mod upload_file; -mod utils; -use self::{client::Client, temp_file::TempFile, upload_file::UploadedFile, utils::split_payload}; +use self::{client::Client, upload_file::UploadedFile}; -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct UploadMeta { - namespace: String, -} +#[derive(Debug, MultipartForm)] +struct UploadForm { + namespace: Text, -impl Default for UploadMeta { - fn default() -> Self { - Self { - namespace: "default".to_owned(), - } - } + #[multipart(rename = "file")] + files: Vec, } #[post("/")] async fn upload_to_s3( s3_client: web::Data, - mut payload: Multipart, + MultipartForm(form): MultipartForm, ) -> Result { - let (data, files) = split_payload(&mut payload).await; - log::info!("bytes = {data:?}"); + let namespace = form.namespace.into_inner(); + let files = form.files; - let upload_meta = serde_json::from_slice::(&data).unwrap_or_default(); - log::info!("converter_struct = {upload_meta:?}"); + log::info!("namespace = {namespace:?}"); log::info!("tmp_files = {files:?}"); // make key prefix (make sure it ends with a forward slash) - let s3_key_prefix = format!("uploads/{}/", upload_meta.namespace); + let s3_key_prefix = format!("uploads/{namespace}/"); - // create tmp file and upload s3 and remove tmp file + // upload temp files to s3 and then remove them let uploaded_files = s3_client.upload_files(files, &s3_key_prefix).await?; Ok(HttpResponse::Ok().json(json!({ "uploadedFiles": uploaded_files, - "meta": upload_meta, + "meta": json!({ "namespace": namespace }), }))) } diff --git a/forms/multipart-s3/src/temp_file.rs b/forms/multipart-s3/src/temp_file.rs deleted file mode 100644 index 78a1bf9d..00000000 --- a/forms/multipart-s3/src/temp_file.rs +++ /dev/null @@ -1,35 +0,0 @@ -use tokio::fs; - -/// Info for a temporary file to be uploaded to S3. -#[derive(Debug, Clone)] -pub struct TempFile { - path: String, - name: String, -} - -impl TempFile { - /// Constructs info container with sanitized file name. - pub fn new(filename: &str) -> TempFile { - let filename = sanitize_filename::sanitize(filename); - - TempFile { - path: format!("./tmp/{filename}"), - name: filename, - } - } - - /// Returns name of temp file. - pub fn name(&self) -> &str { - &self.name - } - - /// Returns path to temp file. - pub fn path(&self) -> &str { - &self.path - } - - /// Deletes temp file from disk. - pub async fn delete_from_disk(self) { - fs::remove_file(&self.path).await.unwrap(); - } -} diff --git a/forms/multipart-s3/src/utils.rs b/forms/multipart-s3/src/utils.rs deleted file mode 100644 index 7462d534..00000000 --- a/forms/multipart-s3/src/utils.rs +++ /dev/null @@ -1,61 +0,0 @@ -use actix_multipart::{Field, Multipart}; -use actix_web::web::{Bytes, BytesMut}; -use futures_util::StreamExt as _; -use tokio::{fs, io::AsyncWriteExt as _}; - -use crate::TempFile; - -/// Returns tuple of `meta` field contents and a list of temp file info to upload. -pub async fn split_payload(payload: &mut Multipart) -> (Bytes, Vec) { - let mut meta = Bytes::new(); - let mut temp_files = vec![]; - - while let Some(item) = payload.next().await { - let mut field = item.expect("split_payload err"); - let cd = field.content_disposition(); - - if matches!(cd.get_name(), Some(name) if name == "meta") { - // if field name is "meta", just collect those bytes in-memory and return them later - meta = collect_meta(&mut field).await; - } else { - match cd.get_filename() { - Some(filename) => { - // if file has a file name, we stream the field contents into a temp file on - // disk so that large uploads do not exhaust memory - - // create file info - let file_info = TempFile::new(filename); - - // create file on disk from file info - let mut file = fs::File::create(file_info.path()).await.unwrap(); - - // stream field contents to file - while let Some(chunk) = field.next().await { - let data = chunk.unwrap(); - file.write_all(&data).await.unwrap(); - } - - // return file info - temp_files.push(file_info); - } - - None => { - log::warn!("field {:?} is not a file", cd.get_name()); - } - } - } - } - - (meta, temp_files) -} - -async fn collect_meta(field: &mut Field) -> Bytes { - let mut buf = BytesMut::new(); - - while let Some(chunk) = field.next().await { - let chunk = chunk.expect("split_payload err chunk"); - buf.extend(chunk); - } - - buf.freeze() -} diff --git a/forms/multipart/Cargo.toml b/forms/multipart/Cargo.toml index 540bd6ea..40f82e40 100644 --- a/forms/multipart/Cargo.toml +++ b/forms/multipart/Cargo.toml @@ -12,6 +12,8 @@ readme = "README.md" actix-multipart.workspace = true actix-web.workspace = true +env_logger.workspace = true futures-util.workspace = true +log.workspace = true sanitize-filename = "0.4" uuid = { version = "1", features = ["v4"] } diff --git a/forms/multipart/src/main.rs b/forms/multipart/src/main.rs index d829e4e1..e5f8e328 100644 --- a/forms/multipart/src/main.rs +++ b/forms/multipart/src/main.rs @@ -1,11 +1,77 @@ use std::io::Write; -use actix_multipart::Multipart; -use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer}; +use actix_multipart::{ + form::{ + tempfile::{TempFile, TempFileConfig}, + text::Text, + MultipartForm, + }, + Multipart, +}; +use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer, Responder}; use futures_util::TryStreamExt as _; use uuid::Uuid; -async fn save_file(mut payload: Multipart) -> Result { +#[derive(Debug, MultipartForm)] +struct UploadForm { + #[multipart(rename = "file")] + files: Vec, +} + +async fn save_files( + MultipartForm(form): MultipartForm, +) -> Result { + for f in form.files { + let path = format!("./tmp/{}", f.file_name.unwrap()); + log::info!("saving to {path}"); + f.file.persist(path).unwrap(); + } + + Ok(HttpResponse::Ok()) +} + +async fn index() -> HttpResponse { + let html = r#" + Upload Test + +
+ + +
+ + "#; + + HttpResponse::Ok().body(html) +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + log::info!("creating temporary upload directory"); + std::fs::create_dir_all("./tmp")?; + + log::info!("starting HTTP server at http://localhost:8080"); + + HttpServer::new(|| { + App::new() + .wrap(middleware::Logger::default()) + .app_data(TempFileConfig::default().directory("./tmp")) + .service( + web::resource("/") + .route(web::get().to(index)) + .route(web::post().to(save_files)), + ) + }) + .bind(("127.0.0.1", 8080))? + .workers(2) + .run() + .await +} + +/// Example of the old manual way of processing multipart forms. +#[allow(unused)] +async fn save_file_manual(mut payload: Multipart) -> Result { // iterate over multipart stream while let Some(mut field) = payload.try_next().await? { // A multipart/form-data stream has to contain `content_disposition` @@ -28,34 +94,3 @@ async fn save_file(mut payload: Multipart) -> Result { Ok(HttpResponse::Ok().into()) } - -async fn index() -> HttpResponse { - let html = r#" - Upload Test - -
- - -
- - "#; - - HttpResponse::Ok().body(html) -} - -#[actix_web::main] -async fn main() -> std::io::Result<()> { - std::env::set_var("RUST_LOG", "info"); - std::fs::create_dir_all("./tmp")?; - - HttpServer::new(|| { - App::new().wrap(middleware::Logger::default()).service( - web::resource("/") - .route(web::get().to(index)) - .route(web::post().to(save_file)), - ) - }) - .bind(("127.0.0.1", 8080))? - .run() - .await -}