mirror of
https://github.com/actix/examples
synced 2024-11-27 16:02:57 +01:00
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
This commit is contained in:
parent
1f434406f3
commit
3552b29359
@ -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)
|
* [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.
|
* [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)
|
* [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
|
## Contribute
|
||||||
|
|
||||||
Welcome to contribute !
|
Welcome to contribute !
|
||||||
|
3
multipart/.gitignore
vendored
Normal file
3
multipart/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
/tmp
|
@ -1,17 +1,15 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "multipart-example"
|
name = "multipart-example"
|
||||||
version = "0.1.0"
|
version = "0.3.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Bevan Hunt <bevan@bevanhunt.com>"]
|
||||||
workspace = ".."
|
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
license = "MIT"
|
||||||
[[bin]]
|
description = "Simple file uploader in Actix Web with Async/Await"
|
||||||
name = "multipart"
|
keywords = ["actix", "actix-web", "multipart"]
|
||||||
path = "src/main.rs"
|
repository = "https://github.com/actix/examples"
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "1.0.0"
|
futures = "0.3.1"
|
||||||
actix-multipart = "0.1.1"
|
actix-multipart = "0.2.0-alpha.3"
|
||||||
|
actix-web = "2.0.0-alpha.3"
|
||||||
env_logger = "0.6"
|
|
||||||
futures = "0.1.25"
|
|
||||||
|
21
multipart/LICENSE
Normal file
21
multipart/LICENSE
Normal file
@ -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.
|
@ -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
|
``` file(s) will show up in ./tmp in the same directory as the running process ```
|
||||||
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
|
|
||||||
|
|
||||||
|
Note: this is a naive implementation and will panic on any error
|
||||||
|
@ -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
|
|
Binary file not shown.
1
multipart/rust-toolchain
Normal file
1
multipart/rust-toolchain
Normal file
@ -0,0 +1 @@
|
|||||||
|
nightly-2019-11-25
|
@ -1,67 +1,27 @@
|
|||||||
use std::cell::Cell;
|
|
||||||
use std::fs;
|
|
||||||
use std::io::Write;
|
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};
|
async fn save_file(mut payload: Multipart) -> Result<HttpResponse, Error> {
|
||||||
use actix_web::{error, middleware, web, App, Error, HttpResponse, HttpServer};
|
// iterate over multipart stream
|
||||||
use futures::future::{err, Either};
|
while let Some(item) = payload.next().await {
|
||||||
use futures::{Future, Stream};
|
let mut field = item?;
|
||||||
|
let content_type = field.content_disposition().unwrap();
|
||||||
pub struct AppState {
|
let filename = content_type.get_filename().unwrap();
|
||||||
pub counter: Cell<usize>,
|
let filepath = format!("./tmp/{}", filename);
|
||||||
}
|
let mut f = std::fs::File::create(filepath).unwrap();
|
||||||
|
// Field in turn is stream of *Bytes* object
|
||||||
pub fn save_file(field: Field) -> impl Future<Item = i64, Error = Error> {
|
while let Some(chunk) = field.next().await {
|
||||||
let file_path_string = "upload.png";
|
let data = chunk.unwrap();
|
||||||
let file = match fs::File::create(file_path_string) {
|
let mut pos = 0;
|
||||||
Ok(file) => file,
|
while pos < data.len() {
|
||||||
Err(e) => return Either::A(err(error::ErrorInternalServerError(e))),
|
let bytes_written = f.write(&data[pos..])?;
|
||||||
};
|
pos += bytes_written;
|
||||||
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<MultipartError>| {
|
|
||||||
match e {
|
|
||||||
error::BlockingError::Error(e) => e,
|
|
||||||
error::BlockingError::Canceled => MultipartError::Incomplete,
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
})
|
}
|
||||||
.map(|(_, acc)| acc)
|
Ok(HttpResponse::Ok().into())
|
||||||
.map_err(|e| {
|
|
||||||
println!("save_file failed, {:?}", e);
|
|
||||||
error::ErrorInternalServerError(e)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn upload(
|
|
||||||
multipart: Multipart,
|
|
||||||
counter: web::Data<Cell<usize>>,
|
|
||||||
) -> impl Future<Item = HttpResponse, Error = Error> {
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn index() -> HttpResponse {
|
fn index() -> HttpResponse {
|
||||||
@ -69,7 +29,7 @@ fn index() -> HttpResponse {
|
|||||||
<head><title>Upload Test</title></head>
|
<head><title>Upload Test</title></head>
|
||||||
<body>
|
<body>
|
||||||
<form target="/" method="post" enctype="multipart/form-data">
|
<form target="/" method="post" enctype="multipart/form-data">
|
||||||
<input type="file" name="file"/>
|
<input type="file" multiple name="file"/>
|
||||||
<input type="submit" value="Submit"></button>
|
<input type="submit" value="Submit"></button>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
@ -80,18 +40,17 @@ fn index() -> HttpResponse {
|
|||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
|
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(|| {
|
HttpServer::new(|| {
|
||||||
App::new()
|
App::new()
|
||||||
.data(Cell::new(0usize))
|
|
||||||
.wrap(middleware::Logger::default())
|
.wrap(middleware::Logger::default())
|
||||||
.service(
|
.service(
|
||||||
web::resource("/")
|
web::resource("/")
|
||||||
.route(web::get().to(index))
|
.route(web::get().to(index))
|
||||||
.route(web::post().to_async(upload)),
|
.route(web::post().to(save_file)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.bind("127.0.0.1:8080")?
|
.bind(ip)?
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user