| // 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(proc_macro_hygiene, decl_macro)] |
| #![feature(plugin, custom_attribute)] |
| |
| extern crate multipart; |
| #[macro_use] |
| 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(); |
| } |