From 3552b293591ffdfadc097eb1acea13010de3395a Mon Sep 17 00:00:00 2001 From: Bevan Hunt Date: Sat, 7 Dec 2019 06:40:31 -0800 Subject: [PATCH] Update Multipart example (#196) * add async/await multipart example * fix * replace multipart * make PR feedback changes * remove port & heroku & update readme * update cargo.toml to be correct * update to alpha 3 --- README.md | 1 + multipart/.gitignore | 3 ++ multipart/Cargo.toml | 22 ++++---- multipart/LICENSE | 21 ++++++++ multipart/README.md | 26 +++------- multipart/client.sh | 11 ---- multipart/example.png | Bin 1190 -> 0 bytes multipart/rust-toolchain | 1 + multipart/src/main.rs | 107 ++++++++++++--------------------------- 9 files changed, 75 insertions(+), 117 deletions(-) create mode 100644 multipart/.gitignore create mode 100644 multipart/LICENSE delete mode 100755 multipart/client.sh delete mode 100644 multipart/example.png create mode 100644 multipart/rust-toolchain diff --git a/README.md b/README.md index 7834467..d5f11df 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ A curated list of examples related to actix. * [kriry.com](http://kriry.com/) : Explore-Interactive net. [source](https://github.com/kriry/waler) * [Roseline](https://github.com/DoumanAsh/roseline.rs) : A personal web site and discord & IRC bot to access simple SQLite database. Demonstrates usage of various actix and actix-web concepts. * [Actix Auth Server](https://hgill.io/posts/auth-microservice-rust-actix-web-diesel-complete-tutorial-part-1/) : Auth web micro-service with rust using actix-web - complete tutorial. See code in [examples/simple-auth-server](https://github.com/actix/examples/tree/master/simple-auth-server) + ## Contribute Welcome to contribute ! diff --git a/multipart/.gitignore b/multipart/.gitignore new file mode 100644 index 0000000..8d2901e --- /dev/null +++ b/multipart/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +/tmp diff --git a/multipart/Cargo.toml b/multipart/Cargo.toml index 00089b0..76e8eab 100644 --- a/multipart/Cargo.toml +++ b/multipart/Cargo.toml @@ -1,17 +1,15 @@ [package] name = "multipart-example" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = ".." +version = "0.3.0" +authors = ["Bevan Hunt "] edition = "2018" - -[[bin]] -name = "multipart" -path = "src/main.rs" +license = "MIT" +description = "Simple file uploader in Actix Web with Async/Await" +keywords = ["actix", "actix-web", "multipart"] +repository = "https://github.com/actix/examples" +readme = "README.md" [dependencies] -actix-web = "1.0.0" -actix-multipart = "0.1.1" - -env_logger = "0.6" -futures = "0.1.25" +futures = "0.3.1" +actix-multipart = "0.2.0-alpha.3" +actix-web = "2.0.0-alpha.3" diff --git a/multipart/LICENSE b/multipart/LICENSE new file mode 100644 index 0000000..5a59c7c --- /dev/null +++ b/multipart/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [2019] [Bevan Hunt] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/multipart/README.md b/multipart/README.md index 5eb57b3..edaf171 100644 --- a/multipart/README.md +++ b/multipart/README.md @@ -1,25 +1,11 @@ -# multipart +# Actix Web File Upload with Async/Await -Multipart's `Getting Started` guide for Actix web +### Run -## Usage +``` open web browser to localhost:3000 and upload file(s) ``` -### server +### Result -```bash -cd examples/multipart -cargo run (or ``cargo watch -x run``) -# Started http server: 127.0.0.1:8080 -``` - -### browser - -- go to ``http://localhost:8080`` -- upload file -- you should see the action reflected in server console - -### client (optional) - -- ``./client.sh`` -- you must see in server console multipart fields +``` file(s) will show up in ./tmp in the same directory as the running process ``` +Note: this is a naive implementation and will panic on any error diff --git a/multipart/client.sh b/multipart/client.sh deleted file mode 100755 index 10d8234..0000000 --- a/multipart/client.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -function SubmitFile () { - curl -X POST \ - -H "Content-Type: multipart/related" \ - --form "data=@example.png;type=image/png" http://localhost:8080 -} - -SubmitFile & SubmitFile & SubmitFile & -SubmitFile & SubmitFile & SubmitFile & -SubmitFile & SubmitFile & SubmitFile diff --git a/multipart/example.png b/multipart/example.png deleted file mode 100644 index 2899104018929d26690630a2c54602e9ead5f35d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1190 zcmaJ=OQ_X#5dO^QanevSsIX8ODvA>~3?)mtSMNo~!XzAADPHZ|5}I>;oIuMKUMv-q zY8DnrQH#JkMg-Pm;i5niB5EU%PAvivi9`rOeAM(myH@_-_u71XuW7fhw;tsCE|5>uev@%`bK4 zwOYN-wqEOc=f=P0x+GI4?ox3Gn;+;EmNEG-}yJc0ZhfJXI76i zUqAo{3IvdV98_R{1?=F00xD3477Q?fIjj&s2*MGC1X7TWtOf}sC`VNv5<+l75rIgE zoT!LFEW}P+q#zYiCoM9N37L}>1t^5VDT)%5Lg|#%u%i+xr>Z_p2*Vl01SVl}reX%O zFgtUxf>l_Zwb;NWY|d63;1CYyC{Az+r*l@Lm`k{vtNPL*3Rjd8ltjsuN*T(c?8>DI zRZ(@-QiGbPxmsyJLo{5YG@&V)u33$XTB7Az)wdI3a6=ivNQ~U5jA1OsZd|4?6;n4Y zGnk2)o0SDD#KJAg5|(1=menLB*=p*7{LW#CFp=DC$jSG$V z3aRjhaYM9HD!oB(K&zz6o7W$Qx{ovyaEgbAR&PIZ@3W6~Zy1_3`{Is|XT3c!_~6LA z_h;@I9=dzo;GHAi-}cv=JLj(Ty;ac0Xuzbu+Lx!~5*pa1&V;fHqbJ$7Nu`Y9X7zFGbHRKb+RpDbVV zz>)91JbCTxTkrk;V(-$z^GkpG=<3+HLwCJ=wR>X!p4oTM*Oy+o^!Ka$t^9ER38T|8br$6F6vU2#-CF>{t17flp3IG5A diff --git a/multipart/rust-toolchain b/multipart/rust-toolchain new file mode 100644 index 0000000..7ff88f6 --- /dev/null +++ b/multipart/rust-toolchain @@ -0,0 +1 @@ +nightly-2019-11-25 diff --git a/multipart/src/main.rs b/multipart/src/main.rs index 791b794..3b902e1 100644 --- a/multipart/src/main.rs +++ b/multipart/src/main.rs @@ -1,67 +1,27 @@ -use std::cell::Cell; -use std::fs; use std::io::Write; +use actix_multipart::Multipart; +use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer}; +use futures::{StreamExt}; -use actix_multipart::{Field, Multipart, MultipartError}; -use actix_web::{error, middleware, web, App, Error, HttpResponse, HttpServer}; -use futures::future::{err, Either}; -use futures::{Future, Stream}; - -pub struct AppState { - pub counter: Cell, -} - -pub fn save_file(field: Field) -> impl Future { - let file_path_string = "upload.png"; - let file = match fs::File::create(file_path_string) { - Ok(file) => file, - Err(e) => return Either::A(err(error::ErrorInternalServerError(e))), - }; - Either::B( - field - .fold((file, 0i64), move |(mut file, mut acc), bytes| { - // fs operations are blocking, we have to execute writes - // on threadpool - web::block(move || { - file.write_all(bytes.as_ref()).map_err(|e| { - println!("file.write_all failed: {:?}", e); - MultipartError::Payload(error::PayloadError::Io(e)) - })?; - acc += bytes.len() as i64; - Ok((file, acc)) - }) - .map_err(|e: error::BlockingError| { - match e { - error::BlockingError::Error(e) => e, - error::BlockingError::Canceled => MultipartError::Incomplete, - } - }) - }) - .map(|(_, acc)| acc) - .map_err(|e| { - println!("save_file failed, {:?}", e); - error::ErrorInternalServerError(e) - }), - ) -} - -pub fn upload( - multipart: Multipart, - counter: web::Data>, -) -> impl Future { - counter.set(counter.get() + 1); - println!("{:?}", counter.get()); - - multipart - .map_err(error::ErrorInternalServerError) - .map(|field| save_file(field).into_stream()) - .flatten() - .collect() - .map(|sizes| HttpResponse::Ok().json(sizes)) - .map_err(|e| { - println!("failed: {}", e); - e - }) +async fn save_file(mut payload: Multipart) -> Result { + // iterate over multipart stream + while let Some(item) = payload.next().await { + let mut field = item?; + let content_type = field.content_disposition().unwrap(); + let filename = content_type.get_filename().unwrap(); + let filepath = format!("./tmp/{}", filename); + let mut f = std::fs::File::create(filepath).unwrap(); + // Field in turn is stream of *Bytes* object + while let Some(chunk) = field.next().await { + let data = chunk.unwrap(); + let mut pos = 0; + while pos < data.len() { + let bytes_written = f.write(&data[pos..])?; + pos += bytes_written; + } + } + } + Ok(HttpResponse::Ok().into()) } fn index() -> HttpResponse { @@ -69,7 +29,7 @@ fn index() -> HttpResponse { Upload Test
- +
@@ -80,18 +40,17 @@ fn index() -> HttpResponse { fn main() -> std::io::Result<()> { std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); - env_logger::init(); - + std::fs::create_dir_all("./tmp").unwrap(); + let ip = "0.0.0.0:3000"; HttpServer::new(|| { - App::new() - .data(Cell::new(0usize)) - .wrap(middleware::Logger::default()) - .service( - web::resource("/") - .route(web::get().to(index)) - .route(web::post().to_async(upload)), - ) + 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")? + .bind(ip)? .run() }