// Example usage with Rocket (https://rocket.rs) | |
// | |
// Direct integration is not provided at this time as it appears the Rocket folks would prefer | |
// to handle multipart requests behind the scenes. | |
#![feature(plugin)] | |
#![plugin(rocket_codegen)] | |
extern crate multipart; | |
extern crate rocket; | |
use multipart::mock::StdoutTee; | |
use multipart::server::Multipart; | |
use multipart::server::save::Entries; | |
use multipart::server::save::SaveResult::*; | |
use rocket::Data; | |
use rocket::http::{ContentType, Status}; | |
use rocket::response::Stream; | |
use rocket::response::status::Custom; | |
use std::io::{self, Cursor, Write}; | |
#[post("/upload", data = "<data>")] | |
// signature requires the request to have a `Content-Type` | |
fn multipart_upload(cont_type: &ContentType, data: Data) -> Result<Stream<Cursor<Vec<u8>>>, Custom<String>> { | |
// this and the next check can be implemented as a request guard but it seems like just | |
// more boilerplate than necessary | |
if !cont_type.is_form_data() { | |
return Err(Custom( | |
Status::BadRequest, | |
"Content-Type not multipart/form-data".into() | |
)); | |
} | |
let (_, boundary) = cont_type.params().find(|&(k, _)| k == "boundary").ok_or_else( | |
|| Custom( | |
Status::BadRequest, | |
"`Content-Type: multipart/form-data` boundary param not provided".into() | |
) | |
)?; | |
match process_upload(boundary, data) { | |
Ok(resp) => Ok(Stream::from(Cursor::new(resp))), | |
Err(err) => Err(Custom(Status::InternalServerError, err.to_string())) | |
} | |
} | |
fn process_upload(boundary: &str, data: Data) -> io::Result<Vec<u8>> { | |
let mut out = Vec::new(); | |
// saves all fields, any field longer than 10kB goes to a temporary directory | |
// Entries could implement FromData though that would give zero control over | |
// how the files are saved; Multipart would be a good impl candidate though | |
match Multipart::with_body(data.open(), boundary).save().temp() { | |
Full(entries) => process_entries(entries, &mut out)?, | |
Partial(partial, reason) => { | |
writeln!(out, "Request partially processed: {:?}", reason)?; | |
if let Some(field) = partial.partial { | |
writeln!(out, "Stopped on field: {:?}", field.source.headers)?; | |
} | |
process_entries(partial.entries, &mut out)? | |
}, | |
Error(e) => return Err(e), | |
} | |
Ok(out) | |
} | |
// having a streaming output would be nice; there's one for returning a `Read` impl | |
// but not one that you can `write()` to | |
fn process_entries(entries: Entries, mut out: &mut Vec<u8>) -> io::Result<()> { | |
{ | |
let stdout = io::stdout(); | |
let tee = StdoutTee::new(&mut out, &stdout); | |
entries.write_debug(tee)?; | |
} | |
writeln!(out, "Entries processed") | |
} | |
fn main() { | |
rocket::ignite().mount("/", routes![multipart_upload]).launch(); | |
} |