1
0
mirror of https://github.com/actix/examples synced 2024-11-23 22:41:07 +01:00

update multipart examples to use derive macro

This commit is contained in:
Rob Ede 2023-02-26 21:56:55 +00:00
parent 9b77b033b0
commit 080db60175
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
10 changed files with 309 additions and 241 deletions

289
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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)
}

View File

@ -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);

View File

@ -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<String>,
impl Default for UploadMeta {
fn default() -> Self {
Self {
namespace: "default".to_owned(),
}
}
#[multipart(rename = "file")]
files: Vec<TempFile>,
}
#[post("/")]
async fn upload_to_s3(
s3_client: web::Data<Client>,
mut payload: Multipart,
MultipartForm(form): MultipartForm<UploadForm>,
) -> Result<impl Responder, Error> {
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::<UploadMeta>(&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 }),
})))
}

View File

@ -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();
}
}

View File

@ -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<TempFile>) {
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()
}

View File

@ -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"] }

View File

@ -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<HttpResponse, Error> {
#[derive(Debug, MultipartForm)]
struct UploadForm {
#[multipart(rename = "file")]
files: Vec<TempFile>,
}
async fn save_files(
MultipartForm(form): MultipartForm<UploadForm>,
) -> Result<impl Responder, Error> {
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#"<html>
<head><title>Upload Test</title></head>
<body>
<form target="/" method="post" enctype="multipart/form-data">
<input type="file" multiple name="file"/>
<button type="submit">Submit</button>
</form>
</body>
</html>"#;
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<HttpResponse, Error> {
// 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<HttpResponse, Error> {
Ok(HttpResponse::Ok().into())
}
async fn index() -> HttpResponse {
let html = r#"<html>
<head><title>Upload Test</title></head>
<body>
<form target="/" method="post" enctype="multipart/form-data">
<input type="file" multiple name="file"/>
<button type="submit">Submit</button>
</form>
</body>
</html>"#;
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
}